1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-01-16 02:47:36 +02:00
vcmi/client/BattleInterface/CBattleInterface.cpp
DjWarmonger 444b59d478 1. Fixed #843
2. Refactoring for battle console hoover texts
3. Clone should work only for creatures of certain tier. However, ALL creature immunities stopped working in the meantime. Looking for an earlier bug.
2012-02-20 08:19:03 +00:00

3202 lines
104 KiB
C++

#include "StdInc.h"
#include "CBattleInterface.h"
#include "../CGameInfo.h"
#include "../../lib/CLodHandler.h"
#include "../UIFramework/SDL_Extensions.h"
#include "../CAdvmapInterface.h"
#include "../CAnimation.h"
#include "../../lib/CObjectHandler.h"
#include "../../lib/CHeroHandler.h"
#include "../CDefHandler.h"
#include "../../lib/CSpellHandler.h"
#include "../CMusicHandler.h"
#include "../CMessage.h"
#include "../../CCallback.h"
#include "../../lib/BattleState.h"
#include "../../lib/CGeneralTextHandler.h"
#include "CCreatureAnimation.h"
#include "../Graphics.h"
#include "../CSpellWindow.h"
#include "../CConfigHandler.h"
#include "../../lib/CondSh.h"
#include "../../lib/NetPacks.h"
#include "../CPlayerInterface.h"
#include "../CCreatureWindow.h"
#include "../CVideoHandler.h"
#include "../../lib/CTownHandler.h"
#include "../../lib/map.h"
#include "CBattleAnimations.h"
#include "CBattleInterfaceClasses.h"
#include "../UIFramework/CCursorHandler.h"
#include "../UIFramework/CGuiHandler.h"
#ifndef __GNUC__
const double M_PI = 3.14159265358979323846;
#else
#define _USE_MATH_DEFINES
#include <cmath>
#endif
#include "../../lib/UnlockGuard.h"
const time_t CBattleInterface::HOVER_ANIM_DELTA = 1;
/*
* CBattleInterface.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
extern SDL_Surface * screen;
CondSh<bool> CBattleInterface::animsAreDisplayed;
struct CMP_stack2
{
inline bool operator ()(const CStack& a, const CStack& b)
{
return (a.Speed())>(b.Speed());
}
} cmpst2 ;
static void transformPalette(SDL_Surface * surf, double rCor, double gCor, double bCor)
{
SDL_Color * colorsToChange = surf->format->palette->colors;
for(int g=0; g<surf->format->palette->ncolors; ++g)
{
if((colorsToChange+g)->b != 132 &&
(colorsToChange+g)->g != 231 &&
(colorsToChange+g)->r != 255) //it's not yellow border
{
(colorsToChange+g)->r = static_cast<double>((colorsToChange+g)->r) * rCor;
(colorsToChange+g)->g = static_cast<double>((colorsToChange+g)->g) * gCor;
(colorsToChange+g)->b = static_cast<double>((colorsToChange+g)->b) * bCor;
}
}
}
//////////////////////
void CBattleInterface::addNewAnim(CBattleAnimation * anim)
{
pendingAnims.push_back( std::make_pair(anim, false) );
animsAreDisplayed.setn(true);
}
CBattleInterface::CBattleInterface(const CCreatureSet * army1, const CCreatureSet * army2, CGHeroInstance *hero1, CGHeroInstance *hero2, const SDL_Rect & myRect, CPlayerInterface * att, CPlayerInterface * defen)
: queue(NULL), attackingHeroInstance(hero1), defendingHeroInstance(hero2), animCount(0),
activeStack(NULL), stackToActivate(NULL), mouseHoveredStack(-1), lastMouseHoveredStackAnimationTime(-1), previouslyHoveredHex(-1),
currentlyHoveredHex(-1), attackingHex(-1), tacticianInterface(NULL), stackCanCastSpell(false), spellDestSelectMode(false), spellSelMode(NO_LOCATION), spellToCast(NULL),
siegeH(NULL), attackerInt(att), defenderInt(defen), curInt(att), animIDhelper(0), bfield(GameConstants::BFIELD_SIZE),
givenCommand(NULL), myTurn(false), resWindow(NULL), moveStarted(false), moveSh(-1), bresult(NULL)
{
OBJ_CONSTRUCTION;
if(!curInt) curInt = LOCPLINT; //may happen when we are defending during network MP game
animsAreDisplayed.setn(false);
pos = myRect;
strongInterest = true;
givenCommand = new CondSh<BattleAction *>(NULL);
if(attackerInt && attackerInt->cb->battleGetTacticDist()) //hotseat -> check tactics for both players (defender may be local human)
tacticianInterface = attackerInt;
else if(defenderInt && defenderInt->cb->battleGetTacticDist())
tacticianInterface = defenderInt;
tacticsMode = tacticianInterface; //if we found interface of player with tactics, then enter tactics mode
//create stack queue
bool embedQueue = screen->h < 700;
queue = new CStackQueue(embedQueue, this);
if(!embedQueue)
{
if(settings["battle"]["showQueue"].Bool())
pos.y += queue->pos.h / 2; //center whole window
queue->moveTo(Point(pos.x, pos.y - queue->pos.h));
// queue->pos.x = pos.x;
// queue->pos.y = pos.y - queue->pos.h;
// pos.h += queue->pos.h;
// center();
}
queue->update();
//preparing siege info
const CGTownInstance * town = curInt->cb->battleGetDefendedTown();
if(town && town->hasFort())
{
siegeH = new SiegeHelper(town, this);
}
curInt->battleInt = this;
//initializing armies
this->army1 = army1;
this->army2 = army2;
std::vector<const CStack*> stacks = curInt->cb->battleGetAllStacks();
BOOST_FOREACH(const CStack *s, stacks)
{
newStack(s);
}
//preparing menu background and terrain
if(siegeH)
{
background = BitmapHandler::loadBitmap( siegeH->getSiegeName(0), false );
ui8 siegeLevel = curInt->cb->battleGetSiegeLevel();
if(siegeLevel >= 2) //citadel or castle
{
//print moat/mlip
SDL_Surface * moat = BitmapHandler::loadBitmap( siegeH->getSiegeName(13) ),
* mlip = BitmapHandler::loadBitmap( siegeH->getSiegeName(14) );
Point moatPos = graphics->wallPositions[siegeH->town->town->typeID][12],
mlipPos = graphics->wallPositions[siegeH->town->town->typeID][13];
if(moat) //eg. tower has no moat
blitAt(moat, moatPos.x,moatPos.y, background);
if(mlip) //eg. tower has no mlip
blitAt(mlip, mlipPos.x, mlipPos.y, background);
SDL_FreeSurface(moat);
SDL_FreeSurface(mlip);
}
}
else
{
std::vector< std::string > & backref = graphics->battleBacks[ curInt->cb->battleGetBattlefieldType() ];
background = BitmapHandler::loadBitmap(backref[ rand() % backref.size()], false );
}
//preparing menu background
//graphics->blueToPlayersAdv(menu, hero1->tempOwner);
//preparing graphics for displaying amounts of creatures
amountNormal = BitmapHandler::loadBitmap("CMNUMWIN.BMP");
CSDL_Ext::alphaTransform(amountNormal);
transformPalette(amountNormal, 0.59, 0.19, 0.93);
amountPositive = BitmapHandler::loadBitmap("CMNUMWIN.BMP");
CSDL_Ext::alphaTransform(amountPositive);
transformPalette(amountPositive, 0.18, 1.00, 0.18);
amountNegative = BitmapHandler::loadBitmap("CMNUMWIN.BMP");
CSDL_Ext::alphaTransform(amountNegative);
transformPalette(amountNegative, 1.00, 0.18, 0.18);
amountEffNeutral = BitmapHandler::loadBitmap("CMNUMWIN.BMP");
CSDL_Ext::alphaTransform(amountEffNeutral);
transformPalette(amountEffNeutral, 1.00, 1.00, 0.18);
////blitting menu background and terrain
// blitAt(background, pos.x, pos.y);
// blitAt(menu, pos.x, 556 + pos.y);
//preparing buttons and console
bOptions = new CAdventureMapButton (CGI->generaltexth->zelp[381].first, CGI->generaltexth->zelp[381].second, boost::bind(&CBattleInterface::bOptionsf,this), 3 + pos.x, 561 + pos.y, "icm003.def", SDLK_o);
bSurrender = new CAdventureMapButton (CGI->generaltexth->zelp[379].first, CGI->generaltexth->zelp[379].second, boost::bind(&CBattleInterface::bSurrenderf,this), 54 + pos.x, 561 + pos.y, "icm001.def", SDLK_s);
bFlee = new CAdventureMapButton (CGI->generaltexth->zelp[380].first, CGI->generaltexth->zelp[380].second, boost::bind(&CBattleInterface::bFleef,this), 105 + pos.x, 561 + pos.y, "icm002.def", SDLK_r);
bFlee->block(!curInt->cb->battleCanFlee());
bSurrender->block(curInt->cb->battleGetSurrenderCost() < 0);
bAutofight = new CAdventureMapButton (CGI->generaltexth->zelp[382].first, CGI->generaltexth->zelp[382].second, boost::bind(&CBattleInterface::bAutofightf,this), 157 + pos.x, 561 + pos.y, "icm004.def", SDLK_a);
bSpell = new CAdventureMapButton (CGI->generaltexth->zelp[385].first, CGI->generaltexth->zelp[385].second, boost::bind(&CBattleInterface::bSpellf,this), 645 + pos.x, 561 + pos.y, "icm005.def", SDLK_c);
bSpell->block(true);
bWait = new CAdventureMapButton (CGI->generaltexth->zelp[386].first, CGI->generaltexth->zelp[386].second, boost::bind(&CBattleInterface::bWaitf,this), 696 + pos.x, 561 + pos.y, "icm006.def", SDLK_w);
bDefence = new CAdventureMapButton (CGI->generaltexth->zelp[387].first, CGI->generaltexth->zelp[387].second, boost::bind(&CBattleInterface::bDefencef,this), 747 + pos.x, 561 + pos.y, "icm007.def", SDLK_d);
bDefence->assignedKeys.insert(SDLK_SPACE);
bConsoleUp = new CAdventureMapButton (std::string(), std::string(), boost::bind(&CBattleInterface::bConsoleUpf,this), 624 + pos.x, 561 + pos.y, "ComSlide.def", SDLK_UP);
bConsoleDown = new CAdventureMapButton (std::string(), std::string(), boost::bind(&CBattleInterface::bConsoleDownf,this), 624 + pos.x, 580 + pos.y, "ComSlide.def", SDLK_DOWN);
bConsoleDown->setOffset(2);
console = new CBattleConsole();
console->pos.x = 211 + pos.x;
console->pos.y = 560 + pos.y;
console->pos.w = 406;
console->pos.h = 38;
if(tacticsMode)
{
btactNext = new CAdventureMapButton(std::string(), std::string(), boost::bind(&CBattleInterface::bTacticNextStack,this), 213 + pos.x, 560 + pos.y, "icm011.def", SDLK_SPACE);
btactEnd = new CAdventureMapButton(std::string(), std::string(), boost::bind(&CBattleInterface::bEndTacticPhase,this), 419 + pos.x, 560 + pos.y, "icm012.def", SDLK_RETURN);
bDefence->block(true);
bWait->block(true);
menu = BitmapHandler::loadBitmap("COPLACBR.BMP");
}
else
{
menu = BitmapHandler::loadBitmap("CBAR.BMP");
btactEnd = btactNext = NULL;
}
graphics->blueToPlayersAdv(menu, curInt->playerID);
//loading hero animations
if(hero1) // attacking hero
{
int type = hero1->type->heroType;
if ( type % 2 ) type--;
if ( hero1->sex ) type++;
attackingHero = new CBattleHero(graphics->battleHeroes[type], 0, 0, false, hero1->tempOwner, hero1->tempOwner == curInt->playerID ? hero1 : NULL, this);
attackingHero->pos = genRect(attackingHero->dh->ourImages[0].bitmap->h, attackingHero->dh->ourImages[0].bitmap->w, pos.x - 43, pos.y - 19);
}
else
{
attackingHero = NULL;
}
if(hero2) // defending hero
{
int type = hero2->type->heroType;
if ( type % 2 ) type--;
if ( hero2->sex ) type++;
defendingHero = new CBattleHero(graphics->battleHeroes[type ], 0, 0, true, hero2->tempOwner, hero2->tempOwner == curInt->playerID ? hero2 : NULL, this);
defendingHero->pos = genRect(defendingHero->dh->ourImages[0].bitmap->h, defendingHero->dh->ourImages[0].bitmap->w, pos.x + 693, pos.y - 19);
}
else
{
defendingHero = NULL;
}
//preparing cells and hexes
cellBorder = BitmapHandler::loadBitmap("CCELLGRD.BMP");
CSDL_Ext::alphaTransform(cellBorder);
cellShade = BitmapHandler::loadBitmap("CCELLSHD.BMP");
CSDL_Ext::alphaTransform(cellShade);
for(int h = 0; h < bfield.size(); ++h)
{
bfield[h].myNumber = h;
int x = 14 + ((h/GameConstants::BFIELD_WIDTH)%2==0 ? 22 : 0) + 44*(h%GameConstants::BFIELD_WIDTH);
int y = 86 + 42 * (h/GameConstants::BFIELD_WIDTH);
bfield[h].pos = genRect(cellShade->h, cellShade->w, x + pos.x, y + pos.y);
bfield[h].accessible = true;
bfield[h].myInterface = this;
}
//locking occupied positions on batlefield
BOOST_FOREACH(const CStack *s, stacks) //stacks gained at top of this function
if(s->position >= 0) //turrets have position < 0
bfield[s->position].accessible = false;
//loading projectiles for units
BOOST_FOREACH(const CStack *s, stacks)
{
int creID = (s->getCreature()->idNumber == 149) ? CGI->creh->factionToTurretCreature[siegeH->town->town->typeID] : s->getCreature()->idNumber; //id of creature whose shots should be loaded
if(s->getCreature()->isShooting() && vstd::contains(CGI->creh->idToProjectile, creID))
{
CDefHandler *&projectile = idToProjectile[s->getCreature()->idNumber];
projectile = CDefHandler::giveDef(CGI->creh->idToProjectile[creID]);
if(projectile->ourImages.size() > 2) //add symmetric images
{
for(int k = projectile->ourImages.size()-2; k > 1; --k)
{
Cimage ci;
ci.bitmap = CSDL_Ext::rotate01(projectile->ourImages[k].bitmap);
ci.groupNumber = 0;
ci.imName = std::string();
projectile->ourImages.push_back(ci);
}
}
for(size_t s = 0; s < projectile->ourImages.size(); ++s) //alpha transforming
{
CSDL_Ext::alphaTransform(projectile->ourImages[s].bitmap);
}
}
}
//preparing graphic with cell borders
cellBorders = CSDL_Ext::newSurface(background->w, background->h, cellBorder);
//copying palette
for(int g=0; g<cellBorder->format->palette->ncolors; ++g) //we assume that cellBorders->format->palette->ncolors == 256
{
cellBorders->format->palette->colors[g] = cellBorder->format->palette->colors[g];
}
//palette copied
for(int i=0; i<GameConstants::BFIELD_HEIGHT; ++i) //rows
{
for(int j=0; j<GameConstants::BFIELD_WIDTH-2; ++j) //columns
{
int x = 58 + (i%2==0 ? 22 : 0) + 44*j;
int y = 86 + 42 * i;
for(int cellX = 0; cellX < cellBorder->w; ++cellX)
{
for(int cellY = 0; cellY < cellBorder->h; ++cellY)
{
if(y+cellY < cellBorders->h && x+cellX < cellBorders->w)
* ((Uint8*)cellBorders->pixels + (y+cellY) * cellBorders->pitch + (x+cellX)) |= * ((Uint8*)cellBorder->pixels + cellY * cellBorder->pitch + cellX);
}
}
}
}
backgroundWithHexes = CSDL_Ext::newSurface(background->w, background->h, screen);
//preparing obstacle defs
std::vector<CObstacleInstance> obst = curInt->cb->battleGetAllObstacles();
for(size_t t = 0; t < obst.size(); ++t)
{
idToObstacle[obst[t].ID] = CDefHandler::giveDef(CGI->heroh->obstacles.find(obst[t].ID)->second.defName);
for(size_t n = 0; n < idToObstacle[obst[t].ID]->ourImages.size(); ++n)
{
SDL_SetColorKey(idToObstacle[obst[t].ID]->ourImages[n].bitmap, SDL_SRCCOLORKEY, SDL_MapRGB(idToObstacle[obst[t].ID]->ourImages[n].bitmap->format,0,255,255));
}
}
for (int i = 0; i < bfield.size(); i++)
{
children.push_back(&bfield[i]);
}
if(tacticsMode)
{
active = 1;
bTacticNextStack();
active = 0;
}
CCS->musich->stopMusic();
int channel = CCS->soundh->playSoundFromSet(CCS->soundh->battleIntroSounds);
CCS->soundh->setCallback(channel, boost::bind(&CMusicHandler::playMusicFromSet, CCS->musich, CCS->musich->battleMusics, -1));
memset(stackCountOutsideHexes, 1, GameConstants::BFIELD_SIZE * sizeof(bool)); //initialize array with trues
}
CBattleInterface::~CBattleInterface()
{
curInt->battleInt = NULL;
givenCommand->cond.notify_all(); //that two lines should make any activeStack waiting thread to finish
if (active) //dirty fix for #485
{
deactivate();
}
SDL_FreeSurface(background);
SDL_FreeSurface(menu);
SDL_FreeSurface(amountNormal);
SDL_FreeSurface(amountNegative);
SDL_FreeSurface(amountPositive);
SDL_FreeSurface(amountEffNeutral);
SDL_FreeSurface(cellBorders);
SDL_FreeSurface(backgroundWithHexes);
delete bOptions;
delete bSurrender;
delete bFlee;
delete bAutofight;
delete bSpell;
delete bWait;
delete bDefence;
delete bConsoleUp;
delete bConsoleDown;
delete console;
delete givenCommand;
delete attackingHero;
delete defendingHero;
delete queue;
SDL_FreeSurface(cellBorder);
SDL_FreeSurface(cellShade);
for(std::map< int, CCreatureAnimation * >::iterator g=creAnims.begin(); g!=creAnims.end(); ++g)
delete g->second;
for(std::map< int, CDefHandler * >::iterator g=idToProjectile.begin(); g!=idToProjectile.end(); ++g)
delete g->second;
for(std::map< int, CDefHandler * >::iterator g=idToObstacle.begin(); g!=idToObstacle.end(); ++g)
delete g->second;
delete siegeH;
//TODO: play AI tracks if battle was during AI turn
//if (!curInt->makingTurn)
//CCS->musich->playMusicFromSet(CCS->musich->aiMusics, -1);
if(adventureInt && adventureInt->selection)
{
int terrain = LOCPLINT->cb->getTile(adventureInt->selection->visitablePos())->tertype;
CCS->musich->playMusic(CCS->musich->terrainMusics[terrain], -1);
}
}
void CBattleInterface::setPrintCellBorders(bool set)
{
Settings cellBorders = settings.write["battle"]["cellBorders"];
cellBorders->Bool() = set;
redrawBackgroundWithHexes(activeStack);
GH.totalRedraw();
}
void CBattleInterface::setPrintStackRange(bool set)
{
Settings stackRange = settings.write["battle"]["stackRange"];
stackRange->Bool() = set;
redrawBackgroundWithHexes(activeStack);
GH.totalRedraw();
}
void CBattleInterface::setPrintMouseShadow(bool set)
{
Settings shadow = settings.write["battle"]["mouseShadow"];
shadow->Bool() = set;
}
void CBattleInterface::activate()
{
activateKeys();
activateMouseMove();
activateRClick();
bOptions->activate();
bSurrender->activate();
bFlee->activate();
bAutofight->activate();
bSpell->activate();
bWait->activate();
bDefence->activate();
for(int b=0; b<GameConstants::BFIELD_SIZE; ++b)
{
bfield[b].activate();
}
if(attackingHero)
attackingHero->activate();
if(defendingHero)
defendingHero->activate();
if(settings["battle"]["showQueue"].Bool())
queue->activate();
if(tacticsMode)
{
btactNext->activate();
btactEnd->activate();
}
else
{
bConsoleUp->activate();
bConsoleDown->activate();
}
LOCPLINT->cingconsole->activate();
}
void CBattleInterface::deactivate()
{
deactivateKeys();
deactivateMouseMove();
deactivateRClick();
bOptions->deactivate();
bSurrender->deactivate();
bFlee->deactivate();
bAutofight->deactivate();
bSpell->deactivate();
bWait->deactivate();
bDefence->deactivate();
for(int b=0; b<GameConstants::BFIELD_SIZE; ++b)
{
bfield[b].deactivate();
}
if(attackingHero)
attackingHero->deactivate();
if(defendingHero)
defendingHero->deactivate();
if(settings["battle"]["showQueue"].Bool())
queue->deactivate();
if(tacticsMode)
{
btactNext->deactivate();
btactEnd->deactivate();
}
else
{
bConsoleUp->deactivate();
bConsoleDown->deactivate();
}
LOCPLINT->cingconsole->deactivate();
}
void CBattleInterface::show(SDL_Surface * to)
{
std::vector<const CStack*> stacks = curInt->cb->battleGetAllStacks(); //used in a few places
++animCount;
if(!to) //"evaluating" to
to = screen;
SDL_Rect buf;
SDL_GetClipRect(to, &buf);
SDL_SetClipRect(to, &pos);
//printing background and hexes
if(activeStack != NULL && creAnims[activeStack->ID]->getType() != 0) //show everything with range
{
blitAt(backgroundWithHexes, pos.x, pos.y, to);
}
else
{
//showing background
blitAt(background, pos.x, pos.y, to);
if(settings["battle"]["cellBorders"].Bool())
{
CSDL_Ext::blit8bppAlphaTo24bpp(cellBorders, NULL, to, &pos);
}
}
//printing hovered cell
for(int b=0; b<GameConstants::BFIELD_SIZE; ++b)
{
if(bfield[b].strictHovered && bfield[b].hovered)
{
if(previouslyHoveredHex == -1) previouslyHoveredHex = b; //something to start with
if(currentlyHoveredHex == -1) currentlyHoveredHex = b; //something to start with
if(currentlyHoveredHex != b) //repair hover info
{
previouslyHoveredHex = currentlyHoveredHex;
currentlyHoveredHex = b;
}
//print shade
if(spellToCast) //when casting spell
{
//calculating spell school level
const CSpell & spToCast = *CGI->spellh->spells[spellToCast->additionalInfo];
ui8 schoolLevel = 0;
if( activeStack->attackerOwned )
{
if(attackingHeroInstance)
schoolLevel = attackingHeroInstance->getSpellSchoolLevel(&spToCast);
}
else
{
if(defendingHeroInstance)
schoolLevel = defendingHeroInstance->getSpellSchoolLevel(&spToCast);
}
//obtaining range and printing it
std::set<ui16> shaded = spToCast.rangeInHexes(b, schoolLevel);
for(std::set<ui16>::iterator it = shaded.begin(); it != shaded.end(); ++it) //for spells with range greater then one hex
{
if(settings["battle"]["mouseShadow"].Bool() && (*it % GameConstants::BFIELD_WIDTH != 0) && (*it % GameConstants::BFIELD_WIDTH != 16))
{
int x = 14 + ((*it/GameConstants::BFIELD_WIDTH)%2==0 ? 22 : 0) + 44*(*it%GameConstants::BFIELD_WIDTH) + pos.x;
int y = 86 + 42 * (*it/GameConstants::BFIELD_WIDTH) + pos.y;
SDL_Rect temp_rect = genRect(cellShade->h, cellShade->w, x, y);
CSDL_Ext::blit8bppAlphaTo24bpp(cellShade, NULL, to, &temp_rect);
}
}
}
else if(settings["battle"]["mouseShadow"].Bool()) //when not casting spell
{//TODO: do not check it every frame
if (activeStack) //highlight all attackable hexes
{
std::set<BattleHex> set = curInt->cb->battleGetAttackedHexes(activeStack, currentlyHoveredHex, attackingHex);
BOOST_FOREACH(BattleHex hex, set)
{
int x = 14 + ((hex/GameConstants::BFIELD_WIDTH)%2==0 ? 22 : 0) + 44*(hex%GameConstants::BFIELD_WIDTH) + pos.x;
int y = 86 + 42 * (hex/GameConstants::BFIELD_WIDTH) + pos.y;
SDL_Rect temp_rect = genRect(cellShade->h, cellShade->w, x, y);
CSDL_Ext::blit8bppAlphaTo24bpp(cellShade, NULL, to, &temp_rect);
}
}
//always highlight pointed hex
int x = 14 + ((b/GameConstants::BFIELD_WIDTH)%2==0 ? 22 : 0) + 44*(b%GameConstants::BFIELD_WIDTH) + pos.x;
int y = 86 + 42 * (b/GameConstants::BFIELD_WIDTH) + pos.y;
SDL_Rect temp_rect = genRect(cellShade->h, cellShade->w, x, y);
CSDL_Ext::blit8bppAlphaTo24bpp(cellShade, NULL, to, &temp_rect);
}
}
}
SDL_SetClipRect(to, &buf); //restoring previous clip_rect
//prevents blitting outside this window
SDL_GetClipRect(to, &buf);
SDL_SetClipRect(to, &pos);
//preparing obstacles to be shown
std::vector<CObstacleInstance> obstacles = curInt->cb->battleGetAllObstacles();
std::multimap<BattleHex, int> hexToObstacle;
for(size_t b = 0; b < obstacles.size(); ++b)
{
BattleHex position = CGI->heroh->obstacles.find(obstacles[b].ID)->second.getMaxBlocked(obstacles[b].pos);
hexToObstacle.insert(std::make_pair(position, b));
}
////showing units //a lot of work...
std::vector<const CStack *> stackAliveByHex[GameConstants::BFIELD_SIZE];
//double loop because dead stacks should be printed first
for (size_t i = 0; i < stacks.size(); i++)
{
const CStack *s = stacks[i];
if(creAnims.find(s->ID) == creAnims.end()) //e.g. for summoned but not yet handled stacks
continue;
if(creAnims[s->ID]->getType() != 5 && s->position >= 0) //don't show turrets here
stackAliveByHex[s->position].push_back(s);
}
std::vector<const CStack *> stackDeadByHex[GameConstants::BFIELD_SIZE];
for (size_t i = 0; i < stacks.size(); i++)
{
const CStack *s = stacks[i];
if(creAnims.find(s->ID) == creAnims.end()) //e.g. for summoned but not yet handled stacks
continue;
if(creAnims[s->ID]->getType() == 5)
stackDeadByHex[s->position].push_back(s);
}
//handle animations
for(std::list<std::pair<CBattleAnimation *, bool> >::iterator it = pendingAnims.begin(); it != pendingAnims.end(); ++it)
{
if(!it->first) //this animation should be deleted
continue;
if(!it->second)
{
it->second = it->first->init();
}
if(it->second && it->first)
it->first->nextFrame();
}
//delete anims
int preSize = pendingAnims.size();
for(std::list<std::pair<CBattleAnimation *, bool> >::iterator it = pendingAnims.begin(); it != pendingAnims.end(); ++it)
{
if(it->first == NULL)
{
pendingAnims.erase(it);
it = pendingAnims.begin();
break;
}
}
if(preSize > 0 && pendingAnims.size() == 0)
{
//action finished, restore the interface
if(!active)
activate();
//activation of next stack
if(pendingAnims.size() == 0 && stackToActivate != NULL)
{
activateStack();
}
//anims ended
animsAreDisplayed.setn(false);
}
for(int b=0; b<GameConstants::BFIELD_SIZE; ++b) //showing dead stacks
{
for(size_t v=0; v<stackDeadByHex[b].size(); ++v)
{
creAnims[stackDeadByHex[b][v]->ID]->nextFrame(to, creAnims[stackDeadByHex[b][v]->ID]->pos.x, creAnims[stackDeadByHex[b][v]->ID]->pos.y, creDir[stackDeadByHex[b][v]->ID], animCount, false); //increment always when moving, never if stack died
}
}
std::vector<const CStack *> flyingStacks; //flying stacks should be displayed later, over other stacks and obstacles
if (!siegeH)
{
for(int b = 0; b < GameConstants::BFIELD_SIZE; ++b) //showing alive stacks
{
showAliveStacks(stackAliveByHex, b, &flyingStacks, to);
showObstacles(&hexToObstacle, obstacles, b, to);
}
}
// Siege drawing
else
{
for (int i = 0; i < 4; i++)
{
// xMin, xMax => go from hex x pos to hex x pos
// yMin, yMax => go from hex y pos to hex y pos
// xMove => 0: left side, 1: right side
// xMoveDir => 0: decrement, 1: increment, alters every second hex line either xMin or xMax depending on xMove
int xMin, xMax, yMin, yMax, xMove, xMoveDir = 0;
switch (i)
{
// display units shown at the upper left side
case 0:
xMin = 0;
yMin = 0;
xMax = 11;
yMax = 4;
xMove = 1;
break;
// display wall/units shown at the upper wall area/right upper side
case 1:
xMin = 12;
yMin = 0;
xMax = 16;
yMax = 4;
xMove = 0;
break;
// display units shown at the lower wall area/right lower side
case 2:
xMin = 10;
yMin = 5;
xMax = 16;
yMax = 10;
xMove = 0;
xMoveDir = 1;
break;
// display units shown at the left lower side
case 3:
xMin = 0;
yMin = 5;
xMax = 9;
yMax = 10;
xMove = 1;
xMoveDir = 1;
break;
}
int runNum = 0;
for (int j = yMin; j <= yMax; j++)
{
if (runNum > 0)
{
if (xMin == xMax)
xMax = xMin = ((runNum % 2) == 0) ? (xMin + (xMoveDir == 0 ? -1 : 1)) : xMin;
else if (xMove == 1)
xMax = ((runNum % 2) == 0) ? (xMax + (xMoveDir == 0 ? -1 : 1)) : xMax;
else if (xMove == 0)
xMin = ((runNum % 2) == 0) ? (xMin + (xMoveDir == 0 ? -1 : 1)) : xMin;
}
for (int k = xMin; k <= xMax; k++)
{
int hex = j * 17 + k;
showAliveStacks(stackAliveByHex, hex, &flyingStacks, to);
showObstacles(&hexToObstacle, obstacles, hex, to);
showPieceOfWall(to, hex, stacks);
}
++runNum;
}
}
}
for(size_t b = 0; b < flyingStacks.size(); ++b) //showing flying stacks
showAliveStack(flyingStacks[b], to);
//units shown
// Show projectiles
projectileShowHelper(to);
//showing spell effects
if(battleEffects.size())
{
for(std::list<BattleEffect>::iterator it = battleEffects.begin(); it!=battleEffects.end(); ++it)
{
SDL_Surface * bitmapToBlit = it->anim->ourImages[(it->frame)%it->anim->ourImages.size()].bitmap;
SDL_Rect temp_rect = genRect(bitmapToBlit->h, bitmapToBlit->w, it->x, it->y);
SDL_BlitSurface(bitmapToBlit, NULL, to, &temp_rect);
}
}
SDL_SetClipRect(to, &buf); //restoring previous clip_rect
//showing menu background and console
blitAt(menu, pos.x, 556 + pos.y, to);
if(tacticsMode)
{
btactNext->showAll(to);
btactEnd->showAll(to);
}
else
{
console->showAll(to);
bConsoleUp->showAll(to);
bConsoleDown->showAll(to);
}
//showing buttons
bOptions->showAll(to);
bSurrender->showAll(to);
bFlee->showAll(to);
bAutofight->showAll(to);
bSpell->showAll(to);
bWait->showAll(to);
bDefence->showAll(to);
//showing window with result of battle
if(resWindow)
{
resWindow->show(to);
}
//showing in-game console
LOCPLINT->cingconsole->show(to);
Rect posWithQueue = Rect(pos.x, pos.y, 800, 600);
if(settings["battle"]["showQueue"].Bool())
{
if(!queue->embedded)
{
posWithQueue.y -= queue->pos.h;
posWithQueue.h += queue->pos.h;
}
//showing queue
if(!bresult)
queue->showAll(to);
else
queue->blitBg(to); //blit only background, stacks are deleted
}
//printing border around interface
if(screen->w != 800 || screen->h !=600)
{
CMessage::drawBorder(curInt->playerID,to,posWithQueue.w + 28, posWithQueue.h + 28, posWithQueue.x-14, posWithQueue.y-15);
}
}
void CBattleInterface::showAliveStacks(std::vector<const CStack *> *aliveStacks, int hex, std::vector<const CStack *> *flyingStacks, SDL_Surface *to)
{
//showing hero animations
if (hex == 0)
if(attackingHero)
attackingHero->show(to);
if (hex == 16)
if(defendingHero)
defendingHero->show(to);
for(size_t v = 0; v < aliveStacks[hex].size(); ++v)
{
const CStack *s = aliveStacks[hex][v];
if(!s->hasBonusOfType(Bonus::FLYING) || creAnims[s->ID]->getType() != 0)
showAliveStack(s, to);
else
flyingStacks->push_back(s);
}
}
void CBattleInterface::showObstacles(std::multimap<BattleHex, int> *hexToObstacle, std::vector<CObstacleInstance> &obstacles, int hex, SDL_Surface *to)
{
std::pair<std::multimap<BattleHex, int>::const_iterator, std::multimap<BattleHex, int>::const_iterator> obstRange =
hexToObstacle->equal_range(hex);
for(std::multimap<BattleHex, int>::const_iterator it = obstRange.first; it != obstRange.second; ++it)
{
CObstacleInstance & curOb = obstacles[it->second];
std::pair<si16, si16> shift = CGI->heroh->obstacles.find(curOb.ID)->second.posShift;
int x = ((curOb.pos/GameConstants::BFIELD_WIDTH)%2==0 ? 22 : 0) + 44*(curOb.pos%GameConstants::BFIELD_WIDTH) + pos.x + shift.first;
int y = 86 + 42 * (curOb.pos/GameConstants::BFIELD_WIDTH) + pos.y + shift.second;
std::vector<Cimage> &images = idToObstacle[curOb.ID]->ourImages; //reference to animation of obstacle
blitAt(images[((animCount+1)/(4/getAnimSpeed()))%images.size()].bitmap, x, y, to);
}
}
void CBattleInterface::keyPressed(const SDL_KeyboardEvent & key)
{
if(key.keysym.sym == SDLK_q && key.state == SDL_PRESSED)
{
if(settings["battle"]["showQueue"].Bool()) //hide queue
hideQueue();
else
showQueue();
}
else if(key.keysym.sym == SDLK_ESCAPE && spellDestSelectMode)
{
endCastingSpell();
}
}
void CBattleInterface::mouseMoved(const SDL_MouseMotionEvent &sEvent)
{
std::string consoleMsg;
if(activeStack && !spellDestSelectMode)
{
int lastMouseHoveredStack = mouseHoveredStack;
bool stackCastsSpell;
mouseHoveredStack = -1;
int myNumber = -1; //number of hovered tile
for(int g = 0; g < GameConstants::BFIELD_SIZE; ++g)
{
if(bfield[g].hovered && bfield[g].strictHovered)
{
myNumber = g;
break;
}
}
if(myNumber == -1)
{
CCS->curh->changeGraphic(1, 6);
if(console->whoSetAlter == 0)
{
console->alterTxt = "";
}
}
else //battlefield hex
{
if(!vstd::contains(occupyableHexes, myNumber) || activeStack->coversPos(myNumber))
{
const CStack *shere = curInt->cb->battleGetStackByPos(myNumber, false);
const CStack *sactive = activeStack;
if(shere)
{
bool ourStack = shere->owner == curInt->playerID;
//determine if creature spell is going to be cast
stackCastsSpell = false;
if (stackCanCastSpell && spellSelMode > STACK_SPELL_CANCELLED) //player did not decide to cancel this spell
{
if ((int)creatureSpellToCast > -1) //use randomized spell (Faerie Dragon), or only avaliable spell (Archangel)
{
if (shere != sactive) //can't cast on itself
{
const CSpell * spell = CGI->spellh->spells[creatureSpellToCast];
if (curInt->cb->battleCanCastThisSpell(spell, BattleHex(myNumber)) == ESpellCastProblem::OK)
{
if ((!spell->isNegative() && ourStack) || (!spell->isPositive() && !ourStack))
{
CCS->curh->changeGraphic(3, 0);
stackCastsSpell = true;
std::string buf = CGI->generaltexth->allTexts[27]; //cast %s on &s
boost::replace_first (buf, "%s", spell->name);
boost::replace_first (buf, "%s", shere->getName());
console->alterTxt = buf;
console->whoSetAlter = 0;
}
}
}
}
else if (ourStack) //must have only random positive spell (genie)
{
if (shere != sactive) //can't cast on itself
{
int spellID = curInt->cb->battleGetRandomStackSpell(shere, CBattleInfoCallback::RANDOM_GENIE);
if (spellID > -1) //can cast any spell on target stack
{
CCS->curh->changeGraphic(3, 0);
stackCastsSpell = true;
std::string buf = CGI->generaltexth->allTexts[301]; //Cast spell on %s
boost::replace_first (buf, "%s", shere->getName().c_str());
console->alterTxt = buf;
console->whoSetAlter = 0;
}
}
}
}
if(ourStack) //our stack
{
if (shere->alive())
{
if (!stackCastsSpell) //use other abilities or display info
{
if(sactive->hasBonusOfType(Bonus::HEALER))
{
//display the possibility to heal this creature
CCS->curh->changeGraphic(1, 17);
}
else
{
//info about creature
CCS->curh->changeGraphic(1,5);
}
//setting console text
consoleMsg += CGI->generaltexth->allTexts[297];
boost::replace_first (consoleMsg, "%s", shere->getName());
console->alterText (consoleMsg);
console->whoSetAlter = 0;
const time_t curTime = time(NULL);
if (shere->ID != lastMouseHoveredStack &&
curTime > lastMouseHoveredStackAnimationTime + HOVER_ANIM_DELTA &&
creAnims[shere->ID]->getType() == CCreatureAnim::HOLDING &&
creAnims[shere->ID]->framesInGroup(CCreatureAnim::MOUSEON) > 0)
{
creAnims[shere->ID]->playOnce(CCreatureAnim::MOUSEON);
lastMouseHoveredStackAnimationTime = curTime;
}
}
} //end of alive
else if (sactive->hasBonusOfType(Bonus::DAEMON_SUMMONING) && sactive->casts)
{
CCS->curh->changeGraphic(3, 0);
}
mouseHoveredStack = shere->ID; //for dead also?
}
//end of our stack
else if (!stackCastsSpell) //if not, then try attack
{
if (curInt->cb->battleCanShoot(activeStack,myNumber)) //we can shoot enemy
{
if(curInt->cb->battleHasDistancePenalty(activeStack, myNumber) ||
curInt->cb->battleHasWallPenalty(activeStack, myNumber))
{
CCS->curh->changeGraphic(1,15);
}
else
{
CCS->curh->changeGraphic(1,3);
}
//calculating estimated dmg
std::pair<ui32, ui32> estimatedDmg = curInt->cb->battleEstimateDamage(sactive, shere);
std::string estDmg;
estDmg += boost::lexical_cast<std::string>(estimatedDmg.first) += " - ";
estDmg += boost::lexical_cast<std::string>(estimatedDmg.second);
//printing
consoleMsg += CGI->generaltexth->allTexts[296];
boost::replace_first (consoleMsg, "%s", shere->getName());
boost::replace_first (consoleMsg, "%d", boost::lexical_cast<std::string>(sactive->shots));
boost::replace_first (consoleMsg, "%s", estDmg);
console->alterText (consoleMsg);
console->whoSetAlter = 0;
}
else if (isTileAttackable(myNumber)) //available enemy (melee attackable)
{
//handle direction of cursor and attackable tile
setBattleCursor(myNumber);
//calculating estimated dmg
std::pair<ui32, ui32> estimatedDmg = curInt->cb->battleEstimateDamage(sactive, shere);
std::string estDmg;
estDmg += boost::lexical_cast<std::string>(estimatedDmg.first) += " - ";
estDmg += boost::lexical_cast<std::string>(estimatedDmg.second);
//printing
consoleMsg += CGI->generaltexth->allTexts[36];
boost::replace_first (consoleMsg, "%s", shere->getName());
boost::replace_first (consoleMsg, "%s", estDmg);
console->alterText (consoleMsg);
console->whoSetAlter = 0;
}
else //unavailable enemy
{
CCS->curh->changeGraphic (1,0);
console->alterTxt = "";
console->whoSetAlter = 0;
}
}
} //end of stack
//TODO: allow aiming for creature spells
else if (sactive && sactive->hasBonusOfType(Bonus::CATAPULT) && isCatapultAttackable(myNumber)) //catapulting
{
CCS->curh->changeGraphic(1,16);
console->alterTxt = "";
console->whoSetAlter = 0;
}
else //empty unavailable tile
{
CCS->curh->changeGraphic(1,0);
console->alterTxt = "";
console->whoSetAlter = 0;
}
}
else //available tile
{
//setting console text and cursor
if (activeStack) //there can be a moment when stack is dead ut next is not yet activated
{
if(activeStack->hasBonusOfType(Bonus::FLYING))
{
CCS->curh->changeGraphic(1,2);
consoleMsg += CGI->generaltexth->allTexts[295]; //Fly %s here
}
else
{
CCS->curh->changeGraphic(1,1);
consoleMsg += CGI->generaltexth->allTexts[294]; //Move %s here
}
boost::replace_first (consoleMsg, "%s", activeStack->getName());
console->alterText(consoleMsg);
console->whoSetAlter = 0;
}
}
}
}
else if (spellDestSelectMode)
{
int myNumber = -1; //number of hovered tile
for(int g=0; g<GameConstants::BFIELD_SIZE; ++g)
{
if(bfield[g].hovered && bfield[g].strictHovered)
{
myNumber = g;
break;
}
}
if(myNumber == -1)
{
CCS->curh->changeGraphic(1, 0);
//setting console text
console->alterTxt = CGI->generaltexth->allTexts[23];
console->whoSetAlter = 0;
}
else
{
//get dead stack if we cast resurrection or animate dead
const CStack * stackUnder = curInt->cb->battleGetStackByPos(myNumber, vstd::contains(CGI->spellh->risingSpells, spellToCast->additionalInfo));
if(stackUnder && spellToCast->additionalInfo == 39 && !stackUnder->hasBonusOfType(Bonus::UNDEAD)) //animate dead can be cast only on undead creatures
stackUnder = NULL;
bool potentialTargetStack; //for cases 1, 2 and 3
switch (spellSelMode)
{
case SpellSelectionType::FRIENDLY_CREATURE:
potentialTargetStack = stackUnder != NULL && curInt->playerID == stackUnder->owner;
break;
case SpellSelectionType::HOSTILE_CREATURE:
potentialTargetStack = stackUnder != NULL && curInt->playerID != stackUnder->owner;
break;
case SpellSelectionType::ANY_CREATURE:
potentialTargetStack = stackUnder != NULL;
break;
}
switch(spellSelMode)
{
case SpellSelectionType::ANY_LOCATION:
CCS->curh->changeGraphic(3, 0);
//setting console text
consoleMsg += CGI->generaltexth->allTexts[26] += CGI->spellh->spells[spellToCast->additionalInfo]->name;
console->alterText (consoleMsg);
console->whoSetAlter = 0;
break;
case SpellSelectionType::FRIENDLY_CREATURE:
case SpellSelectionType::HOSTILE_CREATURE:
case SpellSelectionType::ANY_CREATURE:
if( potentialTargetStack )
{
if (curInt->cb->battleCanCastThisSpell (CGI->spellh->spells[spellToCast->additionalInfo], BattleHex(myNumber)))
CCS->curh->changeGraphic(3, 0);
else
CCS->curh->changeGraphic(1, 0);
//setting console text
consoleMsg += CGI->generaltexth->allTexts[27] += CGI->spellh->spells[spellToCast->additionalInfo]->name;
consoleMsg += stackUnder->getName();
console->alterText (consoleMsg);
console->whoSetAlter = 0;
break;
}
else
{
CCS->curh->changeGraphic(1, 0);
//setting console text
console->alterTxt = CGI->generaltexth->allTexts[23];
console->whoSetAlter = 0;
}
break;
case SpellSelectionType::OBSTACLE: //TODO: implement this case
if ( blockedByObstacle(myNumber) )
{
CCS->curh->changeGraphic(3, 0);
}
else
{
CCS->curh->changeGraphic(1, 0);
}
break;
}
}
}
}
void CBattleInterface::setBattleCursor(const int myNumber)
{
const CClickableHex & hoveredHex = bfield[myNumber];
CCursorHandler *cursor = CCS->curh;
const double subdividingAngle = 2.0*M_PI/6.0; // Divide a hex into six sectors.
const double hexMidX = hoveredHex.pos.x + hoveredHex.pos.w/2;
const double hexMidY = hoveredHex.pos.y + hoveredHex.pos.h/2;
const double cursorHexAngle = M_PI - atan2(hexMidY - cursor->ypos, cursor->xpos - hexMidX) + subdividingAngle/2; //TODO: refactor this nightmare
const double sector = fmod(cursorHexAngle/subdividingAngle, 6.0);
const int zigzagCorrection = !((myNumber/GameConstants::BFIELD_WIDTH)%2); // Off-by-one correction needed to deal with the odd battlefield rows.
std::vector<int> sectorCursor; // From left to bottom left.
sectorCursor.push_back(8);
sectorCursor.push_back(9);
sectorCursor.push_back(10);
sectorCursor.push_back(11);
sectorCursor.push_back(12);
sectorCursor.push_back(7);
const bool doubleWide = activeStack->doubleWide();
bool aboveAttackable = true, belowAttackable = true;
// Exclude directions which cannot be attacked from.
// Check to the left.
if (myNumber%GameConstants::BFIELD_WIDTH <= 1 || !vstd::contains(occupyableHexes, myNumber - 1))
{
sectorCursor[0] = -1;
}
// Check top left, top right as well as above for 2-hex creatures.
if (myNumber/GameConstants::BFIELD_WIDTH == 0)
{
sectorCursor[1] = -1;
sectorCursor[2] = -1;
aboveAttackable = false;
}
else
{
if (doubleWide)
{
bool attackRow[4] = {true, true, true, true};
if (myNumber%GameConstants::BFIELD_WIDTH <= 1 || !vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH - 2 + zigzagCorrection))
attackRow[0] = false;
if (!vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection))
attackRow[1] = false;
if (!vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH + zigzagCorrection))
attackRow[2] = false;
if (myNumber%GameConstants::BFIELD_WIDTH >= GameConstants::BFIELD_WIDTH - 2 || !vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH + 1 + zigzagCorrection))
attackRow[3] = false;
if (!(attackRow[0] && attackRow[1]))
sectorCursor[1] = -1;
if (!(attackRow[1] && attackRow[2]))
aboveAttackable = false;
if (!(attackRow[2] && attackRow[3]))
sectorCursor[2] = -1;
}
else
{
if (!vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection))
sectorCursor[1] = -1;
if (!vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH + zigzagCorrection))
sectorCursor[2] = -1;
}
}
// Check to the right.
if (myNumber%GameConstants::BFIELD_WIDTH >= GameConstants::BFIELD_WIDTH - 2 || !vstd::contains(occupyableHexes, myNumber + 1))
{
sectorCursor[3] = -1;
}
// Check bottom right, bottom left as well as below for 2-hex creatures.
if (myNumber/GameConstants::BFIELD_WIDTH == GameConstants::BFIELD_HEIGHT - 1)
{
sectorCursor[4] = -1;
sectorCursor[5] = -1;
belowAttackable = false;
}
else
{
if (doubleWide)
{
bool attackRow[4] = {true, true, true, true};
if (myNumber%GameConstants::BFIELD_WIDTH <= 1 || !vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH - 2 + zigzagCorrection))
attackRow[0] = false;
if (!vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection))
attackRow[1] = false;
if (!vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH + zigzagCorrection))
attackRow[2] = false;
if (myNumber%GameConstants::BFIELD_WIDTH >= GameConstants::BFIELD_WIDTH - 2 || !vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH + 1 + zigzagCorrection))
attackRow[3] = false;
if (!(attackRow[0] && attackRow[1]))
sectorCursor[5] = -1;
if (!(attackRow[1] && attackRow[2]))
belowAttackable = false;
if (!(attackRow[2] && attackRow[3]))
sectorCursor[4] = -1;
}
else
{
if (!vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH + zigzagCorrection))
sectorCursor[4] = -1;
if (!vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection))
sectorCursor[5] = -1;
}
}
// Determine index from sector.
int cursorIndex;
if (doubleWide)
{
sectorCursor.insert(sectorCursor.begin() + 5, belowAttackable ? 13 : -1);
sectorCursor.insert(sectorCursor.begin() + 2, aboveAttackable ? 14 : -1);
if (sector < 1.5)
cursorIndex = sector;
else if (sector >= 1.5 && sector < 2.5)
cursorIndex = 2;
else if (sector >= 2.5 && sector < 4.5)
cursorIndex = (int) sector + 1;
else if (sector >= 4.5 && sector < 5.5)
cursorIndex = 6;
else
cursorIndex = (int) sector + 2;
}
else
{
cursorIndex = sector;
}
// Find the closest direction attackable, starting with the right one.
// FIXME: Is this really how the original H3 client does it?
int i = 0;
while (sectorCursor[(cursorIndex + i)%sectorCursor.size()] == -1) //Why hast thou forsaken me?
i = i <= 0 ? 1 - i : -i; // 0, 1, -1, 2, -2, 3, -3 etc..
int index = (cursorIndex + i)%sectorCursor.size(); //hopefully we get elements from sectorCursor
cursor->changeGraphic(1, sectorCursor[index]);
switch (index)
{
case 0:
attackingHex = myNumber - 1; //left
break;
case 1:
attackingHex = myNumber - GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection; //top left
break;
case 2:
attackingHex = myNumber - GameConstants::BFIELD_WIDTH + zigzagCorrection; //top right
break;
case 3:
break;
attackingHex = myNumber + 1; //right
case 4:
break;
attackingHex = myNumber + GameConstants::BFIELD_WIDTH + zigzagCorrection; //bottom right
case 5:
attackingHex = myNumber + GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection; //bottom left
break;
}
BattleHex hex(attackingHex);
if (!hex.isValid())
attackingHex = -1;
}
void CBattleInterface::clickRight(tribool down, bool previousState)
{
if(!down && spellDestSelectMode)
{
endCastingSpell();
}
}
void CBattleInterface::bOptionsf()
{
if(spellDestSelectMode) //we are casting a spell
return;
CCS->curh->changeGraphic(0,0);
Rect tempRect = genRect(431, 481, 160, 84);
tempRect += pos.topLeft();
CBattleOptionsWindow * optionsWin = new CBattleOptionsWindow(tempRect, this);
GH.pushInt(optionsWin);
}
void CBattleInterface::bSurrenderf()
{
if(spellDestSelectMode) //we are casting a spell
return;
int cost = curInt->cb->battleGetSurrenderCost();
if(cost >= 0)
{
const CGHeroInstance *opponent = curInt->cb->battleGetFightingHero(1);
std::string enemyHeroName = opponent ? opponent->name : "#ENEMY#"; //TODO: should surrendering without enemy hero be enabled?
std::string surrenderMessage = boost::str(boost::format(CGI->generaltexth->allTexts[32]) % enemyHeroName % cost); //%s states: "I will accept your surrender and grant you and your troops safe passage for the price of %d gold."
curInt->showYesNoDialog(surrenderMessage, std::vector<CComponent*>(), boost::bind(&CBattleInterface::reallySurrender,this), 0, false);
}
}
void CBattleInterface::bFleef()
{
if(spellDestSelectMode) //we are casting a spell
return;
if( curInt->cb->battleCanFlee() )
{
CFunctionList<void()> ony = boost::bind(&CBattleInterface::reallyFlee,this);
curInt->showYesNoDialog(CGI->generaltexth->allTexts[28],std::vector<CComponent*>(), ony, 0, false); //Are you sure you want to retreat?
}
else
{
std::vector<CComponent*> comps;
std::string heroName;
//calculating fleeing hero's name
if(attackingHeroInstance)
if(attackingHeroInstance->tempOwner == curInt->cb->getMyColor())
heroName = attackingHeroInstance->name;
if(defendingHeroInstance)
if(defendingHeroInstance->tempOwner == curInt->cb->getMyColor())
heroName = defendingHeroInstance->name;
//calculating text
char buffer[1000];
sprintf(buffer, CGI->generaltexth->allTexts[340].c_str(), heroName.c_str()); //The Shackles of War are present. %s can not retreat!
//printing message
curInt->showInfoDialog(std::string(buffer), comps);
}
}
void CBattleInterface::reallyFlee()
{
giveCommand(BattleAction::RETREAT,0,0);
CCS->curh->changeGraphic(0, 0);
}
void CBattleInterface::reallySurrender()
{
if(curInt->cb->getResourceAmount(Res::GOLD) < curInt->cb->battleGetSurrenderCost())
{
curInt->showInfoDialog(CGI->generaltexth->allTexts[29]); //You don't have enough gold!
}
else
{
giveCommand(BattleAction::SURRENDER,0,0);
CCS->curh->changeGraphic(0, 0);
}
}
void CBattleInterface::bAutofightf()
{
if(spellDestSelectMode) //we are casting a spell
return;
}
void CBattleInterface::bSpellf()
{
if(spellDestSelectMode) //we are casting a spell
return;
CCS->curh->changeGraphic(0,0);
if ( myTurn && curInt->cb->battleCanCastSpell())
{
const CGHeroInstance * chi = NULL;
if(attackingHeroInstance->tempOwner == curInt->playerID)
chi = attackingHeroInstance;
else
chi = defendingHeroInstance;
CSpellWindow * spellWindow = new CSpellWindow(genRect(595, 620, (screen->w - 620)/2, (screen->h - 595)/2), chi, curInt);
GH.pushInt(spellWindow);
}
}
void CBattleInterface::bWaitf()
{
if(spellDestSelectMode) //we are casting a spell
return;
if(activeStack != NULL)
giveCommand(8,0,activeStack->ID);
}
void CBattleInterface::bDefencef()
{
if(spellDestSelectMode) //we are casting a spell
return;
if(activeStack != NULL)
giveCommand(3,0,activeStack->ID);
}
void CBattleInterface::bConsoleUpf()
{
if(spellDestSelectMode) //we are casting a spell
return;
console->scrollUp();
}
void CBattleInterface::bConsoleDownf()
{
if(spellDestSelectMode) //we are casting a spell
return;
console->scrollDown();
}
void CBattleInterface::newStack(const CStack * stack)
{
Point coords = CClickableHex::getXYUnitAnim(stack->position, stack->owner == attackingHeroInstance->tempOwner, stack, this);
if(stack->position < 0) //turret
{
const CCreature & turretCreature = *CGI->creh->creatures[ CGI->creh->factionToTurretCreature[siegeH->town->town->typeID] ];
creAnims[stack->ID] = new CCreatureAnimation(turretCreature.animDefName);
// Turret positions are read out of the /config/wall_pos.txt
int posID = 0;
switch (stack->position)
{
case -2: // keep creature
posID = 18;
break;
case -3: // bottom creature
posID = 19;
break;
case -4: // upper creature
posID = 20;
break;
}
if (posID != 0)
{
coords.x = graphics->wallPositions[siegeH->town->town->typeID][posID - 1].x + this->pos.x;
coords.y = graphics->wallPositions[siegeH->town->town->typeID][posID - 1].y + this->pos.y;
}
}
else
{
creAnims[stack->ID] = new CCreatureAnimation(stack->getCreature()->animDefName);
}
creAnims[stack->ID]->setType(CCreatureAnim::HOLDING);
creAnims[stack->ID]->pos = Rect(coords.x, coords.y, creAnims[stack->ID]->fullWidth, creAnims[stack->ID]->fullHeight);
creDir[stack->ID] = stack->attackerOwned;
}
void CBattleInterface::stackRemoved(int stackID)
{
delete creAnims[stackID];
creAnims.erase(stackID);
creDir.erase(stackID);
}
void CBattleInterface::stackActivated(const CStack * stack) //TODO: check it all before game state is changed due to abilities
{
//givenCommand = NULL;
stackToActivate = stack;
waitForAnims();
//if(pendingAnims.size() == 0)
if(stackToActivate) //during waiting stack may have gotten activated through show
activateStack();
}
void CBattleInterface::stackMoved(const CStack * stack, std::vector<BattleHex> destHex, int distance)
{
addNewAnim(new CMovementAnimation(this, stack, destHex, distance));
waitForAnims();
}
void CBattleInterface::stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos)
{
for (size_t h = 0; h < attackedInfos.size(); ++h)
{
if (!attackedInfos[h].cloneKilled) //FIXME: play dead animation for cloned creature before it vanishes
addNewAnim(new CDefenceAnimation(attackedInfos[h], this));
if (attackedInfos[h].rebirth)
{
displayEffect(50, attackedInfos[h].defender->position); //TODO: play reverse death animation
CCS->soundh->playSound(soundBase::RESURECT);
}
}
waitForAnims();
int targets = 0, killed = 0, damage = 0;
for(size_t h = 0; h < attackedInfos.size(); ++h)
{
++targets;
killed += attackedInfos[h].killed;
damage += attackedInfos[h].dmg;
}
if (attackedInfos.front().cloneKilled) //FIXME: cloned stack is already removed
return;
if (targets > 1)
printConsoleAttacked(attackedInfos.front().defender, damage, killed, attackedInfos.front().attacker, true); //creatures perish
else
printConsoleAttacked(attackedInfos.front().defender, damage, killed, attackedInfos.front().attacker, false);
for(size_t h = 0; h < attackedInfos.size(); ++h)
{
if (attackedInfos[h].rebirth)
creAnims[attackedInfos[h].defender->ID]->setType(CCreatureAnim::HOLDING);
if (attackedInfos[h].cloneKilled)
stackRemoved(attackedInfos[h].defender->ID);
}
}
void CBattleInterface::stackAttacking( const CStack * attacker, BattleHex dest, const CStack * attacked, bool shooting )
{
if (shooting)
{
addNewAnim(new CShootingAnimation(this, attacker, dest, attacked));
}
else
{
addNewAnim(new CMeleeAttackAnimation(this, attacker, dest, attacked));
}
waitForAnims();
}
void CBattleInterface::newRoundFirst( int round )
{
//handle regeneration
std::vector<const CStack*> stacks = curInt->cb->battleGetStacks(); //gets only alive stacks
// BOOST_FOREACH(const CStack *s, stacks)
// {
// }
waitForAnims();
}
void CBattleInterface::newRound(int number)
{
console->addText(CGI->generaltexth->allTexts[412]);
//unlock spellbook
//bSpell->block(!curInt->cb->battleCanCastSpell());
//don't unlock spellbook - this should be done when we have axctive creature
}
void CBattleInterface::giveCommand(ui8 action, BattleHex tile, ui32 stack, si32 additional)
{
if(!curInt->cb->battleGetStackByID(stack) && action != 1 && action != 4 && action != 5)
{
return;
}
BattleAction * ba = new BattleAction(); //is deleted in CPlayerInterface::activeStack()
ba->side = defendingHeroInstance ? (curInt->playerID == defendingHeroInstance->tempOwner) : false;
ba->actionType = action;
ba->destinationTile = tile;
ba->stackNumber = stack;
ba->additionalInfo = additional;
//some basic validations
switch(action)
{
case BattleAction::WALK_AND_ATTACK:
assert(curInt->cb->battleGetStackByPos(additional)); //stack to attack must exist
case BattleAction::WALK:
case BattleAction::SHOOT:
case BattleAction::CATAPULT:
assert(tile < GameConstants::BFIELD_SIZE);
break;
}
if(!tacticsMode)
{
myTurn = false;
activeStack = NULL;
givenCommand->setn(ba);
}
else
{
curInt->cb->battleMakeTacticAction(ba);
vstd::clear_pointer(ba);
bTacticNextStack();
}
}
bool CBattleInterface::isTileAttackable(const BattleHex & number) const
{
for(size_t b=0; b<occupyableHexes.size(); ++b)
{
if(BattleHex::mutualPosition(occupyableHexes[b], number) != -1 || occupyableHexes[b] == number)
return true;
}
return false;
}
bool CBattleInterface::blockedByObstacle(BattleHex hex) const
{
std::vector<CObstacleInstance> obstacles = curInt->cb->battleGetAllObstacles();
std::set<BattleHex> coveredHexes;
for(size_t b = 0; b < obstacles.size(); ++b)
{
std::vector<BattleHex> blocked = CGI->heroh->obstacles.find(obstacles[b].ID)->second.getBlocked(obstacles[b].pos);
for(size_t w = 0; w < blocked.size(); ++w)
coveredHexes.insert(blocked[w]);
}
return vstd::contains(coveredHexes, hex);
}
bool CBattleInterface::isCatapultAttackable(BattleHex hex) const
{
if(!siegeH)
return false;
int wallUnder = curInt->cb->battleGetWallUnderHex(hex);
if(wallUnder == -1)
return false;
return curInt->cb->battleGetWallState(wallUnder) < 3;
}
const CGHeroInstance * CBattleInterface::getActiveHero()
{
const CStack * attacker = activeStack;
if (!attacker)
{
return NULL;
}
if (attacker->attackerOwned)
{
return attackingHeroInstance;
}
return defendingHeroInstance;
}
void CBattleInterface::hexLclicked(int whichOne)
{
const CStack * actSt = activeStack;
const CStack* dest = curInt->cb->battleGetStackByPos(whichOne, false); //creature at destination tile; -1 if there is no one
if(!actSt)
{
tlog3 << "Hex l-clicked when no active stack!\n";
return;
}
if( ((whichOne%GameConstants::BFIELD_WIDTH)!=0 && (whichOne%GameConstants::BFIELD_WIDTH)!=(GameConstants::BFIELD_WIDTH-1)) //if player is trying to attack enemey unit or move creature stack
|| ((actSt->hasBonusOfType(Bonus::CATAPULT) && !spellDestSelectMode) || dest ) //enemy's first aid tent can stand there and we want to shoot it
)
{
if(!myTurn)
return; //we are not permit to do anything
if(spellDestSelectMode) //TODO: choose target for area creature spell
{
//checking destination
bool allowCasting = true;
bool onlyAlive = vstd::contains(CGI->spellh->risingSpells, spellToCast->additionalInfo); //when casting resurrection or animate dead we should be allow to select dead stack
//TODO: more general handling of dead targets
switch(spellSelMode)
{
case FRIENDLY_CREATURE:
if(!curInt->cb->battleGetStackByPos(whichOne, onlyAlive) || curInt->playerID != dest->owner )
allowCasting = false;
break;
case HOSTILE_CREATURE:
if(!curInt->cb->battleGetStackByPos(whichOne, onlyAlive) || curInt->playerID == dest->owner )
allowCasting = false;
break;
case ANY_CREATURE:
if(!curInt->cb->battleGetStackByPos(whichOne, onlyAlive))
allowCasting = false;
break;
case OBSTACLE:
if(!blockedByObstacle(whichOne))
allowCasting = false;
case TELEPORT: //teleport
const CSpell *s = CGI->spellh->spells[spellToCast->additionalInfo];
ui8 skill = getActiveHero()->getSpellSchoolLevel(s); //skill level
if (!curInt->cb->battleCanTeleportTo(activeStack, whichOne, skill))
{
allowCasting = false;
}
break;
}
//destination checked
if(allowCasting)
{
spellToCast->destinationTile = whichOne;
curInt->cb->battleMakeAction(spellToCast);
endCastingSpell();
}
}
else //we don't aim for spell target area
{
bool walkableTile = false;
bool spellCast = false;
if (dest)
{
bool ourStack = actSt->owner == dest->owner;
//try to cast stack spell first
if (stackCanCastSpell && spellSelMode > STACK_SPELL_CANCELLED) //player did not decide to cancel this spell
{
if ((int)creatureSpellToCast > -1) //use randomized spell (Faerie Dragon), or only avaliable spell (Archangel)
{
const CSpell * spell = CGI->spellh->spells[creatureSpellToCast];
if (curInt->cb->battleCanCastThisSpell(spell, BattleHex(whichOne)) == ESpellCastProblem::OK)
{
if ((!spell->isNegative() && ourStack) || (!spell->isPositive() && !ourStack))
{
giveCommand(BattleAction::MONSTER_SPELL, whichOne, actSt->ID, creatureSpellToCast);
spellCast = true;
}
}
}
else if (ourStack) //must have only random positive spell (genie)
{
if (dest != actSt) //can't cast on itself
{
int spellID = curInt->cb->battleGetRandomStackSpell(dest, CBattleInfoCallback::RANDOM_GENIE);
if (spellID > -1) //can cast any spell on target stack
{
giveCommand(BattleAction::MONSTER_SPELL, whichOne, actSt->ID, spellID); //use randomized spell
spellCast = true;
}
}
}
if (spellCast)
{
creatureSpellToCast = -1;
return; //no further action after cast
}
}
if (dest->alive())
{
if(dest->owner != actSt->owner && curInt->cb->battleCanShoot(activeStack, whichOne)) //shooting
{
CCS->curh->changeGraphic(1, 6); //cursor should be changed
giveCommand (BattleAction::SHOOT, whichOne, activeStack->ID);
}
else if(!ourStack) //attacking
{
const CStack * actStack = activeStack;
int attackFromHex = -1; //hex from which we will attack chosen stack
switch(CCS->curh->number)
{
case 12: //from bottom right
{
bool doubleWide = actStack->doubleWide();
int destHex = whichOne + ( (whichOne/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH+1 ) +
(actStack->attackerOwned && doubleWide ? 1 : 0);
if(vstd::contains(occupyableHexes, destHex))
attackFromHex = destHex;
else if(actStack->attackerOwned) //if we are attacker
{
if(vstd::contains(occupyableHexes, destHex+1))
attackFromHex = destHex+1;
}
else //if we are defender
{
if(vstd::contains(occupyableHexes, destHex-1))
attackFromHex = destHex-1;
}
break;
}
case 7: //from bottom left
{
int destHex = whichOne + ( (whichOne/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH-1 : GameConstants::BFIELD_WIDTH );
if(vstd::contains(occupyableHexes, destHex))
attackFromHex = destHex;
else if(actStack->attackerOwned) //if we are attacker
{
if(vstd::contains(occupyableHexes, destHex+1))
attackFromHex = destHex+1;
}
else //if we are defender
{
if(vstd::contains(occupyableHexes, destHex-1))
attackFromHex = destHex-1;
}
break;
}
case 8: //from left
{
if(actStack->doubleWide() && !actStack->attackerOwned)
{
std::vector<BattleHex> acc = curInt->cb->battleGetAvailableHexes(activeStack, false);
if(vstd::contains(acc, whichOne))
attackFromHex = whichOne - 1;
else
attackFromHex = whichOne - 2;
}
else
{
attackFromHex = whichOne - 1;
}
break;
}
case 9: //from top left
{
int destHex = whichOne - ( (whichOne/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH+1 : GameConstants::BFIELD_WIDTH );
if(vstd::contains(occupyableHexes, destHex))
attackFromHex = destHex;
else if(actStack->attackerOwned) //if we are attacker
{
if(vstd::contains(occupyableHexes, destHex+1))
attackFromHex = destHex+1;
}
else //if we are defender
{
if(vstd::contains(occupyableHexes, destHex-1))
attackFromHex = destHex-1;
}
break;
}
case 10: //from top right
{
bool doubleWide = actStack->doubleWide();
int destHex = whichOne - ( (whichOne/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH-1 ) +
(actStack->attackerOwned && doubleWide ? 1 : 0);
if(vstd::contains(occupyableHexes, destHex))
attackFromHex = destHex;
else if(actStack->attackerOwned) //if we are attacker
{
if(vstd::contains(occupyableHexes, destHex+1))
attackFromHex = destHex+1;
}
else //if we are defender
{
if(vstd::contains(occupyableHexes, destHex-1))
attackFromHex = destHex-1;
}
break;
}
case 11: //from right
{
if(actStack->doubleWide() && actStack->attackerOwned)
{
std::vector<BattleHex> acc = curInt->cb->battleGetAvailableHexes(activeStack, false);
if(vstd::contains(acc, whichOne))
attackFromHex = whichOne + 1;
else
attackFromHex = whichOne + 2;
}
else
{
attackFromHex = whichOne + 1;
}
break;
}
case 13: //from bottom
{
int destHex = whichOne + ( (whichOne/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH+1 );
if(vstd::contains(occupyableHexes, destHex))
attackFromHex = destHex;
else if(attackingHeroInstance->tempOwner == curInt->cb->getMyColor()) //if we are attacker
{
if(vstd::contains(occupyableHexes, destHex+1))
attackFromHex = destHex+1;
}
else //if we are defender
{
if(vstd::contains(occupyableHexes, destHex-1))
attackFromHex = destHex-1;
}
break;
}
case 14: //from top
{
int destHex = whichOne - ( (whichOne/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH-1 );
if(vstd::contains(occupyableHexes, destHex))
attackFromHex = destHex;
else if(attackingHeroInstance->tempOwner == curInt->cb->getMyColor()) //if we are attacker
{
if(vstd::contains(occupyableHexes, destHex+1))
attackFromHex = destHex+1;
}
else //if we are defender
{
if(vstd::contains(occupyableHexes, destHex-1))
attackFromHex = destHex-1;
}
break;
}
}
if(attackFromHex >= 0) //we can be in this line when unreachable creature is L - clicked (as of revision 1308)
{
giveCommand(BattleAction::WALK_AND_ATTACK, attackFromHex, activeStack->ID, whichOne);
CCS->curh->changeGraphic(1, 6); //cursor should be changed
}
}
else if (actSt->hasBonusOfType(Bonus::HEALER) && actSt->owner == dest->owner) //friendly creature we can heal
{ //TODO: spellDestSelectMode > -2 if we don't want to heal but perform some other (?) action
giveCommand(BattleAction::STACK_HEAL, whichOne, activeStack->ID); //command healing
CCS->curh->changeGraphic(1, 6); //cursor should be changed
}
} //stack is not alive
else if (actSt->hasBonusOfType(Bonus::DAEMON_SUMMONING) && actSt->casts &&
actSt->owner == dest->owner && spellSelMode > -2)//friendly body we can (and want) rise
{
giveCommand(BattleAction::DAEMON_SUMMONING, whichOne, activeStack->ID);
CCS->curh->changeGraphic(1, 6); //cursor should be changed
}
else //not a subject of resurrection
walkableTile = true;
}
else
{
walkableTile = true;
}
if (walkableTile) // we can try to move to this tile
{
if(std::find(occupyableHexes.begin(), occupyableHexes.end(), whichOne) != occupyableHexes.end())// and it's in our range
{
CCS->curh->changeGraphic(1, 6); //cursor should be changed
if(activeStack->doubleWide())
{
std::vector<BattleHex> acc = curInt->cb->battleGetAvailableHexes(activeStack, false);
int shiftedDest = whichOne + (activeStack->attackerOwned ? 1 : -1);
if(vstd::contains(acc, whichOne))
giveCommand (BattleAction::WALK ,whichOne, activeStack->ID);
else if(vstd::contains(acc, shiftedDest))
giveCommand (BattleAction::WALK, shiftedDest, activeStack->ID);
}
else
{
giveCommand(BattleAction::WALK, whichOne, activeStack->ID);
}
}
else if(actSt->hasBonusOfType(Bonus::CATAPULT) && isCatapultAttackable(whichOne)) //attacking (catapult)
{
giveCommand(BattleAction::CATAPULT, whichOne, activeStack->ID);
}
}
}
}
}
void CBattleInterface::stackIsCatapulting(const CatapultAttack & ca)
{
for(std::set< std::pair< std::pair< ui8, si16 >, ui8> >::const_iterator it = ca.attackedParts.begin(); it != ca.attackedParts.end(); ++it)
{
const CStack * stack = curInt->cb->battleGetStackByID(ca.attacker);
addNewAnim(new CShootingAnimation(this, stack, it->first.second, NULL, true, it->second));
SDL_FreeSurface(siegeH->walls[it->first.first + 2]);
siegeH->walls[it->first.first + 2] = BitmapHandler::loadBitmap(
siegeH->getSiegeName(it->first.first + 2, curInt->cb->battleGetWallState(it->first.first)) );
}
waitForAnims();
}
void CBattleInterface::battleFinished(const BattleResult& br)
{
bresult = &br;
{
auto unlockPim = vstd::makeUnlockGuard(*LOCPLINT->pim);
animsAreDisplayed.waitUntil(false);
}
displayBattleFinished();
activeStack = NULL;
}
void CBattleInterface::displayBattleFinished()
{
CCS->curh->changeGraphic(0,0);
SDL_Rect temp_rect = genRect(561, 470, (screen->w - 800)/2 + 165, (screen->h - 600)/2 + 19);
resWindow = new CBattleResultWindow(*bresult, temp_rect, this);
GH.pushInt(resWindow);
}
void CBattleInterface::spellCast( const BattleSpellCast * sc )
{
const CSpell &spell = *CGI->spellh->spells[sc->id];
//spell opening battle is cast when no stack is active
if(sc->castedByHero && ( activeStack == NULL || sc->side == !activeStack->attackerOwned) )
bSpell->block(true);
std::vector< std::string > anims; //for magic arrow and ice bolt
if (vstd::contains(CCS->soundh->spellSounds, &spell))
CCS->soundh->playSound(CCS->soundh->spellSounds[&spell]);
switch(sc->id)
{
case Spells::MAGIC_ARROW:
{
//initialization of anims
anims.push_back("C20SPX0.DEF"); anims.push_back("C20SPX1.DEF"); anims.push_back("C20SPX2.DEF"); anims.push_back("C20SPX3.DEF"); anims.push_back("C20SPX4.DEF");
}
case Spells::ICE_BOLT:
{
if(anims.size() == 0) //initialization of anims
{
anims.push_back("C08SPW0.DEF"); anims.push_back("C08SPW1.DEF"); anims.push_back("C08SPW2.DEF"); anims.push_back("C08SPW3.DEF"); anims.push_back("C08SPW4.DEF");
}
} //end of ice bolt only part
{ //common ice bolt and magic arrow part
//initial variables
std::string animToDisplay;
Point srccoord = (sc->side ? Point(770, 60) : Point(30, 60)) + pos;
Point destcoord = CClickableHex::getXYUnitAnim(sc->tile, !sc->side, curInt->cb->battleGetStackByPos(sc->tile), this); //position attacked by arrow
destcoord.x += 250; destcoord.y += 240;
//animation angle
double angle = atan2(static_cast<double>(destcoord.x - srccoord.x), static_cast<double>(destcoord.y - srccoord.y));
bool Vflip = false;
if (angle < 0)
{
Vflip = true;
angle = -angle;
}
//choosing animation by angle
if(angle > 1.50)
animToDisplay = anims[0];
else if(angle > 1.20)
animToDisplay = anims[1];
else if(angle > 0.90)
animToDisplay = anims[2];
else if(angle > 0.60)
animToDisplay = anims[3];
else
animToDisplay = anims[4];
//displaying animation
CDefEssential * animDef = CDefHandler::giveDefEss(animToDisplay);
int steps = sqrt(static_cast<double>((destcoord.x - srccoord.x)*(destcoord.x - srccoord.x) + (destcoord.y - srccoord.y) * (destcoord.y - srccoord.y))) / 40;
if(steps <= 0)
steps = 1;
int dx = (destcoord.x - srccoord.x - animDef->ourImages[0].bitmap->w)/steps, dy = (destcoord.y - srccoord.y - animDef->ourImages[0].bitmap->h)/steps;
delete animDef;
addNewAnim(new CSpellEffectAnimation(this, animToDisplay, srccoord.x, srccoord.y, dx, dy, Vflip));
break; //for 15 and 16 cases
}
case Spells::LIGHTNING_BOLT:
case Spells::TITANS_LIGHTNING_BOLT:
case Spells::THUNDERBOLT:
displayEffect(1, sc->tile);
displayEffect(spell.mainEffectAnim, sc->tile);
break;
case Spells::DISPEL:
case Spells::CURE:
case Spells::RESURRECTION:
case Spells::ANIMATE_DEAD:
case Spells::DISPEL_HELPFUL_SPELLS:
for(std::set<ui32>::const_iterator it = sc->affectedCres.begin(); it != sc->affectedCres.end(); ++it)
{
displayEffect(spell.mainEffectAnim, curInt->cb->battleGetStackByID(*it, false)->position);
}
break;
case Spells::SUMMON_FIRE_ELEMENTAL:
case Spells::SUMMON_EARTH_ELEMENTAL:
case Spells::SUMMON_WATER_ELEMENTAL:
case Spells::SUMMON_AIR_ELEMENTAL:
case Spells::CLONE: //TODO: make it smarter?
addNewAnim(new CDummyAnimation(this, 2));
break;
} //switch(sc->id)
//support for resistance
for(size_t j = 0; j < sc->resisted.size(); ++j)
{
int tile = curInt->cb->battleGetStackByID(sc->resisted[j])->position;
displayEffect(78, tile);
}
//displaying message in console
bool customSpell = false;
bool plural = false; //add singular / plural form of creature text if this is true
int textID = 0;
if(sc->affectedCres.size() == 1)
{
std::string text = CGI->generaltexth->allTexts[195];
if(sc->castedByHero)
{
boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetFightingHero(sc->side)->name);
boost::algorithm::replace_first(text, "%s", CGI->spellh->spells[sc->id]->name); //spell name
boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin(), false)->getCreature()->namePl ); //target
}
else
{
switch(sc->id)
{
case Spells::STONE_GAZE:
customSpell = true;
plural = true;
textID = 558;
break;
case Spells::POISON:
customSpell = true;
plural = true;
textID = 561;
break;
case Spells::BIND:
customSpell = true;
text = CGI->generaltexth->allTexts[560];
boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin(), false)->getCreature()->namePl );
break; //Roots and vines bind the %s to the ground!
case Spells::DISEASE:
customSpell = true;
plural = true;
textID = 553;
break;
case Spells::PARALYZE:
customSpell = true;
plural = true;
textID = 563;
break;
case Spells::AGE:
{
customSpell = true;
if (curInt->cb->battleGetStackByID(*sc->affectedCres.begin())->count > 1)
{
text = CGI->generaltexth->allTexts[552];
boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin())->type->namePl);
}
else
{
text = CGI->generaltexth->allTexts[551];
boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin())->type->nameSing);
}
//The %s shrivel with age, and lose %d hit points."
TBonusListPtr bl = curInt->cb->battleGetStackByID(*sc->affectedCres.begin(), false)->getBonuses(Selector::type(Bonus::STACK_HEALTH));
bl->remove_if(Selector::source(Bonus::SPELL_EFFECT, 75));
boost::algorithm::replace_first(text, "%d", boost::lexical_cast<std::string>(bl->totalValue()/2));
}
break;
case Spells::THUNDERBOLT:
text = CGI->generaltexth->allTexts[367];
boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin())->type->namePl);
console->addText(text);
text = CGI->generaltexth->allTexts[343].substr(1, CGI->generaltexth->allTexts[343].size() - 1); //Does %d points of damage.
boost::algorithm::replace_first(text, "%d", boost::lexical_cast<std::string>(sc->dmgToDisplay)); //no more text afterwards
console->addText(text);
customSpell = true;
text = ""; //yeah, it's a terrible mess
break;
case Spells::DISPEL_HELPFUL_SPELLS:
text = CGI->generaltexth->allTexts[555];
boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin())->type->namePl);
customSpell = true;
break;
case Spells::DEATH_STARE:
customSpell = true;
if (sc->dmgToDisplay)
{
if (sc->dmgToDisplay > 1)
{
text = CGI->generaltexth->allTexts[119]; //%d %s die under the terrible gaze of the %s.
boost::algorithm::replace_first(text, "%d", boost::lexical_cast<std::string>(sc->dmgToDisplay));
boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin(), false)->getCreature()->namePl );
}
else
{
text = CGI->generaltexth->allTexts[118]; //One %s dies under the terrible gaze of the %s.
boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin())->type->nameSing);
}
boost::algorithm::replace_first(text, "%s", CGI->creh->creatures[sc->attackerType]->namePl); //casting stack
}
else
text = "";
break;
default:
text = CGI->generaltexth->allTexts[565]; //The %s casts %s
boost::algorithm::replace_first(text, "%s", CGI->creh->creatures[sc->attackerType]->namePl); //casting stack
}
if (plural)
{
if (curInt->cb->battleGetStackByID(*sc->affectedCres.begin())->count > 1)
{
text = CGI->generaltexth->allTexts[textID + 1];
boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin())->getName());
}
else
{
text = CGI->generaltexth->allTexts[textID];
boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin())->getName());
}
}
}
if (!customSpell && !sc->dmgToDisplay)
boost::algorithm::replace_first(text, "%s", CGI->spellh->spells[sc->id]->name); //simple spell name
if (text.size())
console->addText(text);
}
else
{
std::string text = CGI->generaltexth->allTexts[196];
if(sc->castedByHero)
{
boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetFightingHero(sc->side)->name);
}
else if(sc->attackerType < CGI->creh->creatures.size())
{
boost::algorithm::replace_first(text, "%s", CGI->creh->creatures[sc->attackerType]->namePl); //creature caster
}
else
{
//TODO artifacts that cast spell; scripts some day
boost::algorithm::replace_first(text, "Something", CGI->creh->creatures[sc->attackerType]->namePl); //creature caster
}
boost::algorithm::replace_first(text, "%s", CGI->spellh->spells[sc->id]->name);
console->addText(text);
}
if(sc->dmgToDisplay && !customSpell)
{
std::string dmgInfo = CGI->generaltexth->allTexts[376];
boost::algorithm::replace_first(dmgInfo, "%s", CGI->spellh->spells[sc->id]->name); //simple spell name
boost::algorithm::replace_first(dmgInfo, "%d", boost::lexical_cast<std::string>(sc->dmgToDisplay));
console->addText(dmgInfo); //todo: casualties (?)
}
waitForAnims();
//mana absorption
if (sc->manaGained)
{
Point leftHero = Point(15, 30) + pos;
Point rightHero = Point(755, 30) + pos;
addNewAnim(new CSpellEffectAnimation(this, sc->side ? "SP07_A.DEF" : "SP07_B.DEF", leftHero.x, leftHero.y, 0, 0, false));
addNewAnim(new CSpellEffectAnimation(this, sc->side ? "SP07_B.DEF" : "SP07_A.DEF", rightHero.x, rightHero.y, 0, 0, false));
}
}
void CBattleInterface::battleStacksEffectsSet(const SetStackEffect & sse)
{
int effID = sse.effect.back().sid;
if(effID != -1) //can be -1 for defensive stance effect
{
for(std::vector<ui32>::const_iterator ci = sse.stacks.begin(); ci!=sse.stacks.end(); ++ci)
{
displayEffect(CGI->spellh->spells[effID]->mainEffectAnim, curInt->cb->battleGetStackByID(*ci)->position);
}
}
else if (sse.stacks.size() == 1 && sse.effect.size() == 2)
{
const Bonus & bns = sse.effect.front();
if (bns.source == Bonus::OTHER && bns.type == Bonus::PRIMARY_SKILL)
{
//defensive stance
const CStack * stack = LOCPLINT->cb->battleGetStackByID(*sse.stacks.begin());
int txtid = 120;
if(stack->count != 1)
txtid++; //move to plural text
char txt[4000];
BonusList defenseBonuses = *(stack->getBonuses(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE)));
defenseBonuses.remove_if(Selector::durationType(Bonus::STACK_GETS_TURN)); //remove bonuses gained from defensive stance
int val = stack->Defense() - defenseBonuses.totalValue();
sprintf(txt, CGI->generaltexth->allTexts[txtid].c_str(), (stack->count != 1) ? stack->getCreature()->namePl.c_str() : stack->getCreature()->nameSing.c_str(), val);
console->addText(txt);
}
}
if (activeStack != NULL) //it can be -1 when a creature casts effect
{
redrawBackgroundWithHexes(activeStack);
}
}
void CBattleInterface::castThisSpell(int spellID)
{
BattleAction * ba = new BattleAction;
ba->actionType = BattleAction::HERO_SPELL;
ba->additionalInfo = spellID; //spell number
ba->destinationTile = -1;
ba->stackNumber = (attackingHeroInstance->tempOwner == curInt->playerID) ? -1 : -2;
ba->side = defendingHeroInstance ? (curInt->playerID == defendingHeroInstance->tempOwner) : false;
spellToCast = ba;
spellDestSelectMode = true;
//choosing possible tragets
const CGHeroInstance * castingHero = (attackingHeroInstance->tempOwner == curInt->playerID) ? attackingHeroInstance : defendingHeroInstance;
const CSpell & spell = *CGI->spellh->spells[spellID];
spellSelMode = ANY_LOCATION;
if(spell.getTargetType() == CSpell::CREATURE)
{
spellSelMode = selectionTypeByPositiveness(spell);
}
if(spell.getTargetType() == CSpell::CREATURE_EXPERT_MASSIVE)
{
if(castingHero && castingHero->getSpellSchoolLevel(&spell) < 3)
spellSelMode = selectionTypeByPositiveness(spell);
else
spellSelMode = NO_LOCATION;
}
if(spell.getTargetType() == CSpell::OBSTACLE)
{
spellSelMode = OBSTACLE;
}
if(spell.range[ castingHero->getSpellSchoolLevel(&spell) ] == "X") //spell has no range
{
spellSelMode = NO_LOCATION;
}
if(spell.id == 63) //teleport
{
spellSelMode = TELEPORT;
}
if(spell.range[ castingHero->getSpellSchoolLevel(&spell) ].size() > 1) //spell has many-hex range
{
spellSelMode = ANY_LOCATION;
}
if(spellSelMode == NO_LOCATION) //user does not have to select location
{
spellToCast->destinationTile = -1;
curInt->cb->battleMakeAction(spellToCast);
endCastingSpell();
}
else
{
GH.fakeMouseMove();//update cursor
}
}
void CBattleInterface::displayEffect(ui32 effect, int destTile)
{
addNewAnim(new CSpellEffectAnimation(this, effect, destTile));
}
void CBattleInterface::battleTriggerEffect(const BattleTriggerEffect & bte)
{
const CStack * stack = curInt->cb->battleGetStackByID(bte.stackID);
//don't show animation when no HP is regenerated
switch (bte.effect)
{
case Bonus::HP_REGENERATION:
if( stack->hasBonusOfType(Bonus::HP_REGENERATION) || stack->hasBonusOfType(Bonus::FULL_HP_REGENERATION))
{
displayEffect(74, stack->position);
CCS->soundh->playSound(soundBase::REGENER);
}
break;
case Bonus::MANA_DRAIN:
displayEffect(77, stack->position);
CCS->soundh->playSound(soundBase::MANADRAI);
break;
case Bonus::POISON:
displayEffect(67, stack->position);
CCS->soundh->playSound(soundBase::POISON);
break;
case Bonus::FEAR:
displayEffect(15, stack->position);
CCS->soundh->playSound(soundBase::FEAR);
break;
case Bonus::MORALE:
{
std::string hlp = CGI->generaltexth->allTexts[33];
boost::algorithm::replace_first(hlp,"%s",(stack->getName()));
displayEffect(20,stack->position);
console->addText(hlp);
break;
}
default:
return;
}
//waitForAnims(); //fixme: freezes game :?
}
void CBattleInterface::setAnimSpeed(int set)
{
Settings speed = settings.write["battle"]["animationSpeed"];
speed->Float() = set;
}
int CBattleInterface::getAnimSpeed() const
{
return settings["battle"]["animationSpeed"].Float();
}
void CBattleInterface::activateStack()
{
activeStack = stackToActivate;
stackToActivate = NULL;
const CStack *s = activeStack;
myTurn = true;
if(attackerInt && defenderInt) //hotseat -> need to pick which interface "takes over" as active
curInt = attackerInt->playerID == s->owner ? attackerInt : defenderInt;
queue->update();
redrawBackgroundWithHexes(activeStack);
bWait->block(vstd::contains(s->state, EBattleStackState::WAITING)); //block waiting button if stack has been already waiting
//block cast spell button if hero doesn't have a spellbook
bSpell->block(!curInt->cb->battleCanCastSpell());
bSurrender->block((curInt == attackerInt ? defendingHeroInstance : attackingHeroInstance) == NULL);
bFlee->block(!curInt->cb->battleCanFlee());
bSurrender->block(curInt->cb->battleGetSurrenderCost() < 0);
//set casting flag to true if creature can use it to not check it every time
if (s->casts && s->hasBonus(Selector::type(Bonus::SPELLCASTER) || Selector::type(Bonus::RANDOM_SPELLCASTER)))
{
stackCanCastSpell = true;
creatureSpellToCast = curInt->cb->battleGetRandomStackSpell(s, CBattleInfoCallback::RANDOM_AIMED); //faerie dragon can cast only one spell until their next move
}
else
{
stackCanCastSpell = false;
creatureSpellToCast = -1;
}
GH.fakeMouseMove();
if(!pendingAnims.size() && !active)
activate();
}
double CBattleInterface::getAnimSpeedMultiplier() const
{
switch(getAnimSpeed())
{
case 1:
return 3.5;
case 2:
return 2.2;
case 4:
return 1.0;
default:
return 0.0;
}
}
void CBattleInterface::endCastingSpell()
{
assert(spellDestSelectMode);
delete spellToCast;
spellToCast = NULL;
spellDestSelectMode = false;
CCS->curh->changeGraphic(1, 6);
}
void CBattleInterface::showAliveStack(const CStack *stack, SDL_Surface * to)
{
int ID = stack->ID;
if(creAnims.find(ID) == creAnims.end()) //eg. for summoned but not yet handled stacks
return;
const CCreature *creature = stack->getCreature();
SDL_Rect unitRect = {creAnims[ID]->pos.x, creAnims[ID]->pos.y, int16_t(creAnims[ID]->fullWidth), int16_t(creAnims[ID]->fullHeight)};
int animType = creAnims[ID]->getType();
int affectingSpeed = getAnimSpeed();
if(animType == 1 || animType == 2) //standing stacks should not stand faster :)
affectingSpeed = 2;
bool incrementFrame = (animCount%(4/affectingSpeed)==0) && animType!=5 && animType!=20 && animType!=2;
if (creature->idNumber == 149)
{
// a turret creature has a limited height, so cut it at a certain position; turret creature has no standing anim
unitRect.h = graphics->wallPositions[siegeH->town->town->typeID][20].y;
}
else
{
// standing animation
if(animType == 2)
{
if(standingFrame.find(ID)!=standingFrame.end())
{
incrementFrame = (animCount%(8/affectingSpeed)==0);
if(incrementFrame)
{
++standingFrame[ID];
if(standingFrame[ID] == creAnims[ID]->framesInGroup(CCreatureAnim::HOLDING))
{
standingFrame.erase(standingFrame.find(ID));
}
}
}
else
{
if((rand()%50) == 0)
{
standingFrame.insert(std::make_pair(ID, 0));
}
}
}
}
// As long as the projectile of the shooter-stack is flying incrementFrame should be false
//bool shootingFinished = true;
for (std::list<ProjectileInfo>::iterator it = projectiles.begin(); it != projectiles.end(); ++it)
{
if (it->stackID == ID)
{
//shootingFinished = false;
if (it->animStartDelay == 0)
incrementFrame = false;
}
}
// Increment always when moving, never if stack died
creAnims[ID]->nextFrame(to, unitRect.x, unitRect.y, creDir[ID], animCount, incrementFrame, activeStack && ID==activeStack->ID, ID==mouseHoveredStack, &unitRect);
//printing amount
if(stack->count > 0 //don't print if stack is not alive
&& (!curInt->curAction
|| (curInt->curAction->stackNumber != ID //don't print if stack is currently taking an action
&& (curInt->curAction->actionType != BattleAction::WALK_AND_ATTACK || stack->position != curInt->curAction->additionalInfo) //nor if it's an object of attack
&& (curInt->curAction->destinationTile != stack->position) //nor if it's on destination tile for current action
)
)
&& !stack->hasBonusOfType(Bonus::SIEGE_WEAPON) //and not a war machine...
)
{
const BattleHex nextPos = stack->position + (stack->attackerOwned ? 1 : -1);
const bool edge = stack->position % GameConstants::BFIELD_WIDTH == (stack->attackerOwned ? GameConstants::BFIELD_WIDTH - 2 : 1);
const bool moveInside = !edge && !stackCountOutsideHexes[nextPos];
int xAdd = (stack->attackerOwned ? 220 : 202) +
(stack->doubleWide() ? 44 : 0) * (stack->attackerOwned ? +1 : -1) +
(moveInside ? amountNormal->w + 10 : 0) * (stack->attackerOwned ? -1 : +1);
int yAdd = 260 + ((stack->attackerOwned || moveInside) ? 0 : -15);
//blitting amount background box
SDL_Surface *amountBG = NULL;
TBonusListPtr spellEffects = stack->getSpellBonuses();
if(!spellEffects->size())
{
amountBG = amountNormal;
}
else
{
int pos=0; //determining total positiveness of effects
std::vector<si32> spellIds = stack->activeSpells();
for(std::vector<si32>::const_iterator it = spellIds.begin(); it != spellIds.end(); it++)
{
pos += CGI->spellh->spells[ *it ]->positiveness;
}
if(pos > 0)
{
amountBG = amountPositive;
}
else if(pos < 0)
{
amountBG = amountNegative;
}
else
{
amountBG = amountEffNeutral;
}
}
SDL_Rect temp_rect = genRect(amountNormal->h, amountNormal->w, creAnims[ID]->pos.x + xAdd, creAnims[ID]->pos.y + yAdd);
SDL_BlitSurface(amountBG, NULL, to, &temp_rect);
//blitting amount
CSDL_Ext::printAtMiddle(
makeNumberShort(stack->count),
creAnims[ID]->pos.x + xAdd + 15,
creAnims[ID]->pos.y + yAdd + 5,
FONT_TINY,
Colors::Cornsilk,
to
);
}
}
void CBattleInterface::showPieceOfWall(SDL_Surface * to, int hex, const std::vector<const CStack*> & stacks)
{
if(!siegeH)
return;
using namespace boost::assign;
#ifdef CPP11_USE_INITIALIZERS_LIST
//note - std::list<int> must be specified to avoid type deduction by gcc (may not work in other compilers)
static const std::map<int, std::list<int> > hexToPart = {
{12, std::list<int>{8, 1, 7}}, {45, std::list<int>{12, 6}},
{101, std::list<int>{10}}, {118, std::list<int>{2}},
{165, std::list<int>{11}}, {186, std::list<int>{3}}};
#else
static const std::map<int, std::list<int> > hexToPart = map_list_of<int, std::list<int> >(12, list_of<int>(8)(1)(7))(45, list_of<int>(12)(6))
(101, list_of<int>(10))(118, list_of<int>(2))(165, list_of<int>(11))(186, list_of<int>(3));
#endif
std::map<int, std::list<int> >::const_iterator it = hexToPart.find(hex);
if(it != hexToPart.end())
{
BOOST_FOREACH(int wallNum, it->second)
{
siegeH->printPartOfWall(to, wallNum);
//print creature in turret
int posToSeek = -1;
switch(wallNum)
{
case 3: //bottom turret
posToSeek = -3;
break;
case 8: //upper turret
posToSeek = -4;
break;
case 2: //keep
posToSeek = -2;
break;
}
if(posToSeek != -1)
{
const CStack *turret = NULL;
BOOST_FOREACH(const CStack *s, stacks)
{
if(s->position == posToSeek)
{
turret = s;
break;
}
}
if(turret)
{
showAliveStack(turret, to);
//blitting creature cover
switch(posToSeek)
{
case -3: //bottom turret
siegeH->printPartOfWall(to, 16);
break;
case -4: //upper turret
siegeH->printPartOfWall(to, 17);
break;
case -2: //keep
siegeH->printPartOfWall(to, 15);
break;
}
}
}
}
}
// Damaged wall below gate have to be drawn earlier than a non-damaged wall below gate.
if ((hex == 112 && curInt->cb->battleGetWallState(3) == 3) || (hex == 147 && curInt->cb->battleGetWallState(3) != 3))
siegeH->printPartOfWall(to, 5);
// Damaged bottom wall have to be drawn earlier than a non-damaged bottom wall.
if ((hex == 165 && curInt->cb->battleGetWallState(4) == 3) || (hex == 185 && curInt->cb->battleGetWallState(4) != 3))
siegeH->printPartOfWall(to, 4);
}
void CBattleInterface::redrawBackgroundWithHexes(const CStack * activeStack)
{
attackableHexes.clear();
if (activeStack)
occupyableHexes = curInt->cb->battleGetAvailableHexes(activeStack, true, &attackableHexes);
curInt->cb->battleGetStackCountOutsideHexes(stackCountOutsideHexes);
//preparating background graphic with hexes and shaded hexes
blitAt(background, 0, 0, backgroundWithHexes);
if(settings["battle"]["cellBorders"].Bool())
CSDL_Ext::blit8bppAlphaTo24bpp(cellBorders, NULL, backgroundWithHexes, NULL);
if(settings["battle"]["stackRange"].Bool())
{
std::vector<BattleHex> hexesToShade = occupyableHexes;
hexesToShade.insert(hexesToShade.end(), attackableHexes.begin(), attackableHexes.end());
BOOST_FOREACH(BattleHex hex, hexesToShade)
{
int i = hex.getY(); //row
int j = hex.getX()-1; //column
int x = 58 + (i%2==0 ? 22 : 0) + 44*j;
int y = 86 + 42 * i;
SDL_Rect temp_rect = genRect(cellShade->h, cellShade->w, x, y);
CSDL_Ext::blit8bppAlphaTo24bpp(cellShade, NULL, backgroundWithHexes, &temp_rect);
}
}
}
void CBattleInterface::printConsoleAttacked( const CStack * defender, int dmg, int killed, const CStack * attacker, bool multiple )
{
char tabh[200];
int end = 0;
if (attacker) //ignore if stacks were killed by spell
{
end = sprintf(tabh, CGI->generaltexth->allTexts[attacker->count > 1 ? 377 : 376].c_str(),
(attacker->count > 1 ? attacker->getCreature()->namePl.c_str() : attacker->getCreature()->nameSing.c_str()), dmg);
}
if(killed > 0)
{
if(killed > 1)
{
sprintf(tabh + end, CGI->generaltexth->allTexts[379].c_str(), killed,
multiple ? CGI->generaltexth->allTexts[43].c_str() : defender->getCreature()->namePl.c_str()); // creatures perish
}
else //killed == 1
{
sprintf(tabh + end, CGI->generaltexth->allTexts[378].c_str(),
multiple ? CGI->generaltexth->allTexts[42].c_str() : defender->getCreature()->nameSing.c_str()); // creature perishes
}
}
console->addText(std::string(tabh));
}
void CBattleInterface::projectileShowHelper(SDL_Surface * to)
{
if(to == NULL)
to = screen;
std::list< std::list<ProjectileInfo>::iterator > toBeDeleted;
for(std::list<ProjectileInfo>::iterator it=projectiles.begin(); it!=projectiles.end(); ++it)
{
// Creature have to be in a shooting anim and the anim start delay must be over.
// Otherwise abort to start moving the projectile.
if (it->animStartDelay > 0)
{
if(it->animStartDelay == creAnims[it->stackID]->getAnimationFrame() + 1
&& creAnims[it->stackID]->getType() >= 14 && creAnims[it->stackID]->getType() <= 16)
it->animStartDelay = 0;
else
continue;
}
SDL_Rect dst;
dst.h = idToProjectile[it->creID]->ourImages[it->frameNum].bitmap->h;
dst.w = idToProjectile[it->creID]->ourImages[it->frameNum].bitmap->w;
dst.x = it->x;
dst.y = it->y;
// The equation below calculates the center pos of the canon, but we need the top left pos
// of it for drawing
if (it->catapultInfo)
{
dst.x -= 17.;
dst.y -= 10.;
}
if(it->reverse)
{
SDL_Surface * rev = CSDL_Ext::rotate01(idToProjectile[it->creID]->ourImages[it->frameNum].bitmap);
CSDL_Ext::blit8bppAlphaTo24bpp(rev, NULL, to, &dst);
SDL_FreeSurface(rev);
}
else
{
CSDL_Ext::blit8bppAlphaTo24bpp(idToProjectile[it->creID]->ourImages[it->frameNum].bitmap, NULL, to, &dst);
}
// Update projectile
++it->step;
if(it->step == it->lastStep)
{
toBeDeleted.insert(toBeDeleted.end(), it);
}
else
{
if (it->catapultInfo)
{
// Parabolic shot of the trajectory, as follows: f(x) = ax^2 + bx + c
it->x += it->dx;
it->y = it->catapultInfo->calculateY(it->x - this->pos.x) + this->pos.y;
}
else
{
// Normal projectile, just add the calculated "deltas" to the x and y positions.
it->x += it->dx;
it->y += it->dy;
}
if(it->spin)
{
++(it->frameNum);
it->frameNum %= idToProjectile[it->creID]->ourImages.size();
}
}
}
for(std::list< std::list<ProjectileInfo>::iterator >::iterator it = toBeDeleted.begin(); it!= toBeDeleted.end(); ++it)
{
projectiles.erase(*it);
}
}
void CBattleInterface::endAction(const BattleAction* action)
{
//if((action->actionType==2 || (action->actionType==6 && action->destinationTile!=cb->battleGetPos(action->stackNumber)))) //activating interface when move is finished
// {
// activate();
// }
if(action->actionType == BattleAction::HERO_SPELL)
{
if(action->side)
defendingHero->setPhase(0);
else
attackingHero->setPhase(0);
}
if(action->actionType == BattleAction::WALK && creAnims[action->stackNumber]->getType() != 2) //walk or walk & attack
{
const CStack * stack = curInt->cb->battleGetStackByID(action->stackNumber);
pendingAnims.push_back(std::make_pair(new CMovementEndAnimation(this, stack, action->destinationTile), false));
}
if(action->actionType == BattleAction::CATAPULT) //catapult
{
}
//check if we should reverse stacks
//for some strange reason, it's not enough
// std::set<const CStack *> stacks;
// stacks.insert(LOCPLINT->cb->battleGetStackByID(action->stackNumber));
// stacks.insert(LOCPLINT->cb->battleGetStackByPos(action->destinationTile));
TStacks stacks = curInt->cb->battleGetStacks(CBattleCallback::MINE_AND_ENEMY);
BOOST_FOREACH(const CStack *s, stacks)
{
if(s && creDir[s->ID] != bool(s->attackerOwned) && s->alive())
{
addNewAnim(new CReverseAnimation(this, s, s->position, false));
}
}
queue->update();
if(tacticsMode) //we have activated next stack after sending request that has been just realized -> blockmap due to movement has changed
redrawBackgroundWithHexes(activeStack);
}
void CBattleInterface::hideQueue()
{
Settings showQueue = settings.write["battle"]["showQueue"];
showQueue->Bool() = false;
queue->deactivate();
if(!queue->embedded)
{
moveBy(Point(0, -queue->pos.h / 2));
GH.totalRedraw();
}
}
void CBattleInterface::showQueue()
{
Settings showQueue = settings.write["battle"]["showQueue"];
showQueue->Bool() = true;
queue->activate();
if(!queue->embedded)
{
moveBy(Point(0, +queue->pos.h / 2));
GH.totalRedraw();
}
}
void CBattleInterface::startAction(const BattleAction* action)
{
if(action->actionType == BattleAction::END_TACTIC_PHASE)
{
SDL_FreeSurface(menu);
menu = BitmapHandler::loadBitmap("CBAR.bmp");
graphics->blueToPlayersAdv(menu, curInt->playerID);
bDefence->block(false);
bWait->block(false);
if(active)
{
if(btactEnd && btactNext) //if the other side had tactics, there are no buttons
{
btactEnd->deactivate();
btactNext->deactivate();
bConsoleDown->activate();
bConsoleUp->activate();
}
}
redraw();
return;
}
const CStack *stack = curInt->cb->battleGetStackByID(action->stackNumber);
if(stack)
{
queue->update();
}
else
{
assert(action->actionType == BattleAction::HERO_SPELL); //only cast spell is valid action without acting stack number
}
if(action->actionType == BattleAction::WALK
|| (action->actionType == BattleAction::WALK_AND_ATTACK && action->destinationTile != stack->position))
{
moveStarted = true;
if(creAnims[action->stackNumber]->framesInGroup(CCreatureAnim::MOVE_START))
{
const CStack * stack = curInt->cb->battleGetStackByID(action->stackNumber);
pendingAnims.push_back(std::make_pair(new CMovementStartAnimation(this, stack), false));
}
}
if(active)
deactivate();
char txt[400];
if(action->actionType == BattleAction::HERO_SPELL) //when hero casts spell
{
if(action->side)
defendingHero->setPhase(4);
else
attackingHero->setPhase(4);
return;
}
if(!stack)
{
tlog1<<"Something wrong with stackNumber in actionStarted. Stack number: "<<action->stackNumber<<std::endl;
return;
}
int txtid = 0;
switch(action->actionType)
{
case BattleAction::WAIT:
txtid = 136;
break;
case BattleAction::BAD_MORALE:
txtid = -34; //negative -> no separate singular/plural form
displayEffect(30,stack->position);
break;
}
if(txtid > 0 && stack->count != 1)
txtid++; //move to plural text
else if(txtid < 0)
txtid = -txtid;
if(txtid)
{
sprintf(txt, CGI->generaltexth->allTexts[txtid].c_str(), (stack->count != 1) ? stack->getCreature()->namePl.c_str() : stack->getCreature()->nameSing.c_str(), 0);
console->addText(txt);
}
//displaying special abilities
switch (action->actionType)
{
case BattleAction::STACK_HEAL:
displayEffect(74, action->destinationTile);
CCS->soundh->playSound(soundBase::REGENER);
break;
}
}
void CBattleInterface::waitForAnims()
{
auto unlockPim = vstd::makeUnlockGuard(*LOCPLINT->pim);
animsAreDisplayed.waitWhileTrue();
}
void CBattleInterface::bEndTacticPhase()
{
btactEnd->block(true);
tacticsMode = false;
}
static bool immobile(const CStack *s)
{
return !s->Speed(0, true); //should bound stacks be immobile?
}
void CBattleInterface::bTacticNextStack()
{
TStacks stacksOfMine = tacticianInterface->cb->battleGetStacks(CBattleCallback::ONLY_MINE);
stacksOfMine.erase(std::remove_if(stacksOfMine.begin(), stacksOfMine.end(), &immobile), stacksOfMine.end());
TStacks::iterator it = vstd::find(stacksOfMine, activeStack);
if(it != stacksOfMine.end() && ++it != stacksOfMine.end())
stackActivated(*it);
else
stackActivated(stacksOfMine.front());
}
CBattleInterface::SpellSelectionType CBattleInterface::selectionTypeByPositiveness(const CSpell & spell)
{
switch(spell.positiveness)
{
case CSpell::NEGATIVE :
return HOSTILE_CREATURE;
case CSpell::NEUTRAL:
return ANY_CREATURE;
case CSpell::POSITIVE:
return FRIENDLY_CREATURE;
}
}
std::string CBattleInterface::SiegeHelper::townTypeInfixes[GameConstants::F_NUMBER] = {"CS", "RM", "TW", "IN", "NC", "DN", "ST", "FR", "EL"};
CBattleInterface::SiegeHelper::SiegeHelper(const CGTownInstance *siegeTown, const CBattleInterface * _owner)
: owner(_owner), town(siegeTown)
{
for(int g = 0; g < ARRAY_COUNT(walls); ++g)
{
walls[g] = BitmapHandler::loadBitmap( getSiegeName(g) );
}
}
CBattleInterface::SiegeHelper::~SiegeHelper()
{
for(int g = 0; g < ARRAY_COUNT(walls); ++g)
{
SDL_FreeSurface(walls[g]);
}
}
std::string CBattleInterface::SiegeHelper::getSiegeName(ui16 what, ui16 additInfo) const
{
if(what == 2 || what == 3 || what == 8)
{
if(additInfo == 3) additInfo = 2;
}
char buf[100];
SDL_itoa(additInfo, buf, 10);
std::string addit(buf);
switch(what)
{
case 0: //background
return "SG" + townTypeInfixes[town->town->typeID] + "BACK.BMP";
case 1: //background wall
{
switch(town->town->typeID)
{
case 5: case 4: case 1: case 6:
return "SG" + townTypeInfixes[town->town->typeID] + "TPW1.BMP";
case 0: case 2: case 3: case 7: case 8:
return "SG" + townTypeInfixes[town->town->typeID] + "TPWL.BMP";
default:
return "";
}
}
case 2: //keep
return "SG" + townTypeInfixes[town->town->typeID] + "MAN" + addit + ".BMP";
case 3: //bottom tower
return "SG" + townTypeInfixes[town->town->typeID] + "TW1" + addit + ".BMP";
case 4: //bottom wall
return "SG" + townTypeInfixes[town->town->typeID] + "WA1" + addit + ".BMP";
case 5: //below gate
return "SG" + townTypeInfixes[town->town->typeID] + "WA3" + addit + ".BMP";
case 6: //over gate
return "SG" + townTypeInfixes[town->town->typeID] + "WA4" + addit + ".BMP";
case 7: //upper wall
return "SG" + townTypeInfixes[town->town->typeID] + "WA6" + addit + ".BMP";
case 8: //upper tower
return "SG" + townTypeInfixes[town->town->typeID] + "TW2" + addit + ".BMP";
case 9: //gate
return "SG" + townTypeInfixes[town->town->typeID] + "DRW" + addit + ".BMP";
case 10: //gate arch
return "SG" + townTypeInfixes[town->town->typeID] + "ARCH.BMP";
case 11: //bottom static wall
return "SG" + townTypeInfixes[town->town->typeID] + "WA2.BMP";
case 12: //upper static wall
return "SG" + townTypeInfixes[town->town->typeID] + "WA5.BMP";
case 13: //moat
return "SG" + townTypeInfixes[town->town->typeID] + "MOAT.BMP";
case 14: //mlip
return "SG" + townTypeInfixes[town->town->typeID] + "MLIP.BMP";
case 15: //keep creature cover
return "SG" + townTypeInfixes[town->town->typeID] + "MANC.BMP";
case 16: //bottom turret creature cover
return "SG" + townTypeInfixes[town->town->typeID] + "TW1C.BMP";
case 17: //upper turret creature cover
return "SG" + townTypeInfixes[town->town->typeID] + "TW2C.BMP";
default:
return "";
}
}
/// What: 1. background wall, 2. keep, 3. bottom tower, 4. bottom wall, 5. wall below gate,
/// 6. wall over gate, 7. upper wall, 8. upper tower, 9. gate, 10. gate arch, 11. bottom static wall, 12. upper static wall, 13. moat, 14. mlip,
/// 15. keep turret cover, 16. lower turret cover, 17. upper turret cover
/// Positions are loaded from the config file: /config/wall_pos.txt
void CBattleInterface::SiegeHelper::printPartOfWall(SDL_Surface * to, int what)
{
Point pos = Point(-1, -1);
if (what >= 1 && what <= 17)
{
pos.x = graphics->wallPositions[town->town->typeID][what - 1].x + owner->pos.x;
pos.y = graphics->wallPositions[town->town->typeID][what - 1].y + owner->pos.y;
}
if(pos.x != -1)
{
blitAt(walls[what], pos.x, pos.y, to);
}
}
double CatapultProjectileInfo::calculateY(double x)
{
return (facA * pow(10., -3.)) * pow(x, 2.0) + facB * x + facC;
}