/*
* Copyright 2010-2016 OpenXcom Developers.
*
* This file is part of OpenXcom.
*
* OpenXcom is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* OpenXcom is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenXcom. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Map.h"
#include "Camera.h"
#include "UnitSprite.h"
#include "Pathfinding.h"
#include "TileEngine.h"
#include "Projectile.h"
#include "Explosion.h"
#include "BattlescapeState.h"
#include "Particle.h"
#include "../Mod/Mod.h"
#include "../Engine/Action.h"
#include "../Engine/SurfaceSet.h"
#include "../Engine/Timer.h"
#include "../Engine/Language.h"
#include "../Engine/Palette.h"
#include "../Engine/Game.h"
#include "../Engine/Screen.h"
#include "../Savegame/SavedBattleGame.h"
#include "../Savegame/Tile.h"
#include "../Savegame/BattleUnit.h"
#include "../Savegame/BattleItem.h"
#include "../Mod/RuleItem.h"
#include "../Mod/RuleInterface.h"
#include "../Mod/MapDataSet.h"
#include "../Mod/MapData.h"
#include "../Mod/Armor.h"
#include "BattlescapeMessage.h"
#include "../Savegame/SavedGame.h"
#include "../Interface/NumberText.h"
#include "../Interface/Text.h"
#include "../fmath.h"
/*
1) Map origin is top corner.
2) X axis goes downright. (width of the map)
3) Y axis goes downleft. (length of the map
4) Z axis goes up (height of the map)
0,0
/\
y+ / \ x+
\ /
\/
*/
namespace OpenXcom
{
/**
* Sets up a map with the specified size and position.
* @param game Pointer to the core game.
* @param width Width in pixels.
* @param height Height in pixels.
* @param x X position in pixels.
* @param y Y position in pixels.
* @param visibleMapHeight Current visible map height.
*/
Map::Map(Game *game, int width, int height, int x, int y, int visibleMapHeight) : InteractiveSurface(width, height, x, y), _game(game), _arrow(0), _selectorX(0), _selectorY(0), _mouseX(0), _mouseY(0), _cursorType(CT_NORMAL), _cursorSize(1), _animFrame(0), _projectile(0), _projectileInFOV(false), _explosionInFOV(false), _launch(false), _visibleMapHeight(visibleMapHeight), _unitDying(false), _smoothingEngaged(false), _flashScreen(false), _projectileSet(0), _showObstacles(false)
{
_iconHeight = _game->getMod()->getInterface("battlescape")->getElement("icons")->h;
_iconWidth = _game->getMod()->getInterface("battlescape")->getElement("icons")->w;
_messageColor = _game->getMod()->getInterface("battlescape")->getElement("messageWindows")->color;
_previewSetting = Options::battleNewPreviewPath;
_smoothCamera = Options::battleSmoothCamera;
if (Options::traceAI)
{
// turn everything on because we want to see the markers.
_previewSetting = PATH_FULL;
}
_save = _game->getSavedGame()->getSavedBattle();
if ((int)(_game->getMod()->getLUTs()->size()) > _save->getDepth())
{
_transparencies = &_game->getMod()->getLUTs()->at(_save->getDepth());
}
_spriteWidth = _game->getMod()->getSurfaceSet("BLANKS.PCK")->getFrame(0)->getWidth();
_spriteHeight = _game->getMod()->getSurfaceSet("BLANKS.PCK")->getFrame(0)->getHeight();
_message = new BattlescapeMessage(320, (visibleMapHeight < 200)? visibleMapHeight : 200, 0, 0);
_message->setX(_game->getScreen()->getDX());
_message->setY((visibleMapHeight - _message->getHeight()) / 2);
_message->setTextColor(_messageColor);
_camera = new Camera(_spriteWidth, _spriteHeight, _save->getMapSizeX(), _save->getMapSizeY(), _save->getMapSizeZ(), this, visibleMapHeight);
_scrollMouseTimer = new Timer(SCROLL_INTERVAL);
_scrollMouseTimer->onTimer((SurfaceHandler)&Map::scrollMouse);
_scrollKeyTimer = new Timer(SCROLL_INTERVAL);
_scrollKeyTimer->onTimer((SurfaceHandler)&Map::scrollKey);
_camera->setScrollTimer(_scrollMouseTimer, _scrollKeyTimer);
_obstacleTimer = new Timer(2500);
_obstacleTimer->stop();
_obstacleTimer->onTimer((SurfaceHandler)&Map::disableObstacles);
_txtAccuracy = new Text(24, 9, 0, 0);
_txtAccuracy->setSmall();
_txtAccuracy->setPalette(_game->getScreen()->getPalette());
_txtAccuracy->setHighContrast(true);
_txtAccuracy->initText(_game->getMod()->getFont("FONT_BIG"), _game->getMod()->getFont("FONT_SMALL"), _game->getLanguage());
}
/**
* Deletes the map.
*/
Map::~Map()
{
delete _scrollMouseTimer;
delete _scrollKeyTimer;
delete _obstacleTimer;
delete _arrow;
delete _message;
delete _camera;
delete _txtAccuracy;
}
/**
* Initializes the map.
*/
void Map::init()
{
// load the tiny arrow into a surface
int f = Palette::blockOffset(1); // yellow
int b = 15; // black
int pixels[81] = { 0, 0, b, b, b, b, b, 0, 0,
0, 0, b, f, f, f, b, 0, 0,
0, 0, b, f, f, f, b, 0, 0,
b, b, b, f, f, f, b, b, b,
b, f, f, f, f, f, f, f, b,
0, b, f, f, f, f, f, b, 0,
0, 0, b, f, f, f, b, 0, 0,
0, 0, 0, b, f, b, 0, 0, 0,
0, 0, 0, 0, b, 0, 0, 0, 0 };
_arrow = new Surface(9, 9);
_arrow->setPalette(this->getPalette());
_arrow->lock();
for (int y = 0; y < 9;++y)
for (int x = 0; x < 9; ++x)
_arrow->setPixel(x, y, pixels[x+(y*9)]);
_arrow->unlock();
_projectile = 0;
if (_save->getDepth() == 0)
{
_projectileSet = _game->getMod()->getSurfaceSet("Projectiles");
}
else
{
_projectileSet = _game->getMod()->getSurfaceSet("UnderwaterProjectiles");
}
}
/**
* Keeps the animation timers running.
*/
void Map::think()
{
_scrollMouseTimer->think(0, this);
_scrollKeyTimer->think(0, this);
_obstacleTimer->think(0, this);
}
/**
* Draws the whole map, part by part.
*/
void Map::draw()
{
if (!_redraw)
{
return;
}
// normally we'd call for a Surface::draw();
// but we don't want to clear the background with colour 0, which is transparent (aka black)
// we use colour 15 because that actually corresponds to the colour we DO want in all variations of the xcom and tftd palettes.
_redraw = false;
clear(Palette::blockOffset(0)+15);
Tile *t;
_projectileInFOV = _save->getDebugMode();
if (_projectile)
{
t = _save->getTile(Position(_projectile->getPosition(0).x/16, _projectile->getPosition(0).y/16, _projectile->getPosition(0).z/24));
if (_save->getSide() == FACTION_PLAYER || (t && t->getVisible()))
{
_projectileInFOV = true;
}
}
_explosionInFOV = _save->getDebugMode();
if (!_explosions.empty())
{
for (std::list<Explosion*>::iterator i = _explosions.begin(); i != _explosions.end(); ++i)
{
t = _save->getTile(Position((*i)->getPosition().x/16, (*i)->getPosition().y/16, (*i)->getPosition().z/24));
if (t && ((*i)->isBig() || t->getVisible()))
{
_explosionInFOV = true;
break;
}
}
}
if ((_save->getSelectedUnit() && _save->getSelectedUnit()->getVisible()) || _unitDying || _save->getSelectedUnit() == 0 || _save->getDebugMode() || _projectileInFOV || _explosionInFOV)
{
drawTerrain(this);
}
else
{
_message->blit(this);
}
}
/**
* Replaces a certain amount of colors in the surface's palette.
* @param colors Pointer to the set of colors.
* @param firstcolor Offset of the first color to replace.
* @param ncolors Amount of colors to replace.
*/
void Map::setPalette(SDL_Color *colors, int firstcolor, int ncolors)
{
Surface::setPalette(colors, firstcolor, ncolors);
for (std::vector<MapDataSet*>::const_iterator i = _save->getMapDataSets()->begin(); i != _save->getMapDataSets()->end(); ++i)
{
(*i)->getSurfaceset()->setPalette(colors, firstcolor, ncolors);
}
_message->setPalette(colors, firstcolor, ncolors);
_message->setBackground(_game->getMod()->getSurface("TAC00.SCR"));
_message->initText(_game->getMod()->getFont("FONT_BIG"), _game->getMod()->getFont("FONT_SMALL"), _game->getLanguage());
_message->setText(_game->getLanguage()->getString("STR_HIDDEN_MOVEMENT"));
}
/**
* Check two positions if have same XY cords
*/
static bool positionHaveSameXY(Position a, Position b)
{
return a.x == b.x && a.y == b.y;
}
/**
* Draw part of unit graphic that overlap current tile.
* @param surface
* @param unitTile
* @param currTile
* @param currTileScreenPosition
* @param shade
* @param obstacleShade unitShade override for no LOF obstacle indicator
* @param topLayer
*/
void Map::drawUnit(Surface *surface, Tile *unitTile, Tile *currTile, Position currTileScreenPosition, int shade, int obstacleShade, bool topLayer)
{
const int tileFoorWidth = 32;
const int tileFoorHeight = 16;
const int tileHeight = 40;
if (!unitTile)
{
return;
}
BattleUnit* bu = unitTile->getUnit();
Position unitOffset;
bool unitFromBelow = false;
if (!bu)
{
Tile *below = _save->getTile(unitTile->getPosition() + Position(0,0,-1));
if (below && unitTile->hasNoFloor(below))
{
bu = below->getUnit();
if (!bu)
{
return;
}
unitFromBelow = true;
}
else
{
return;
}
}
if (!(bu->getVisible() || _save->getDebugMode()))
{
return;
}
unitOffset.x = unitTile->getPosition().x - bu->getPosition().x;
unitOffset.y = unitTile->getPosition().y - bu->getPosition().y;
int part = unitOffset.x + unitOffset.y*2;
Surface *tmpSurface = bu->getCache(part);
if (!tmpSurface)
{
return;
}
bool moving = bu->getStatus() == STATUS_WALKING || bu->getStatus() == STATUS_FLYING;
int bonusWidth = moving ? 0 : tileFoorWidth;
int topMargin = 0;
int bottomMargin = 0;
//if unit is from below then we draw only part that in in tile
if (unitFromBelow)
{
bottomMargin = -tileFoorHeight / 2;
topMargin = tileFoorHeight;
}
else if (topLayer)
{
topMargin = 2 * tileFoorHeight;
}
else
{
Tile *top = _save->getTile(unitTile->getPosition() + Position(0, 0, +1));
if (top && top->hasNoFloor(unitTile))
{
topMargin = -tileFoorHeight / 2;
}
else
{
topMargin = tileFoorHeight;
}
}
GraphSubset mask = GraphSubset(tileFoorWidth + bonusWidth, tileHeight + topMargin + bottomMargin).offset(currTileScreenPosition.x - bonusWidth / 2, currTileScreenPosition.y - topMargin);
if (moving)
{
GraphSubset leftMask = mask.offset(-tileFoorWidth/2, 0);
GraphSubset rightMask = mask.offset(+tileFoorWidth/2, 0);
int direction = bu->getDirection();
Position partCurr = currTile->getPosition();
Position partDest = bu->getDestination() + unitOffset;
Position partLast = bu->getLastPosition() + unitOffset;
bool isTileDestPos = positionHaveSameXY(partDest, partCurr);
bool isTileLastPos = positionHaveSameXY(partLast, partCurr);
//adjusting mask
if (positionHaveSameXY(partLast, partDest))
{
if (currTile == unitTile)
{
//no change
}
else
{
//nothing to draw
return;
}
}
else if (isTileDestPos)
{
//unit is moving to this tile
switch (direction)
{
case 0:
case 1:
mask = GraphSubset::intersection(mask, rightMask);
break;
case 2:
//no change
break;
case 3:
//no change
break;
case 4:
//no change
break;
case 5:
case 6:
mask = GraphSubset::intersection(mask, leftMask);
break;
case 7:
//nothing to draw
return;
}
}
else if (isTileLastPos)
{
//unit is exiting this tile
switch (direction)
{
case 0:
//no change
break;
case 1:
case 2:
mask = GraphSubset::intersection(mask, leftMask);
break;
case 3:
//nothing to draw
return;
case 4:
case 5:
mask = GraphSubset::intersection(mask, rightMask);
break;
case 6:
//no change
break;
case 7:
//no change
break;
}
}
else
{
Position leftPos = partCurr + Position(-1, 0, 0);
Position rightPos = partCurr + Position(0, -1, 0);
if (!topLayer && (partDest.z > partCurr.z || partLast.z > partCurr.z))
{
//unit change layers, it will be drawn by upper layer not lower.
return;
}
else if (
(direction == 1 && (partDest == rightPos || partLast == leftPos)) ||
(direction == 5 && (partDest == leftPos || partLast == rightPos)))
{
mask = GraphSubset(tileFoorWidth, tileHeight + 2 * tileFoorHeight).offset(currTileScreenPosition.x, currTileScreenPosition.y - 2 * tileFoorHeight);
}
else
{
//unit is not moving close to tile
return;
}
}
}
else if (unitTile != currTile)
{
return;
}
Position tileScreenPosition;
_camera->convertMapToScreen(unitTile->getPosition() + Position(0,0, unitFromBelow ? -1 : 0), &tileScreenPosition);
tileScreenPosition += _camera->getMapOffset();
// draw unit
Position offset;
int shadeOffset;
calculateWalkingOffset(bu, &offset, &shadeOffset);
int tileShade = currTile->isDiscovered(2) ? currTile->getShade() : 16;
int unitShade = (tileShade * (16 - shadeOffset) + shade * shadeOffset) / 16;
if (!moving && unitTile->getObstacle(4))
{
unitShade = obstacleShade;
}
tmpSurface->blitNShade(surface, tileScreenPosition.x + offset.x - _spriteWidth / 2, tileScreenPosition.y + offset.y, unitShade, mask);
// draw fire
if (bu->getFire() > 0)
{
int frameNumber = 4 + (_animFrame / 2);
tmpSurface = _game->getMod()->getSurfaceSet("SMOKE.PCK")->getFrame(frameNumber);
tmpSurface->blitNShade(surface, tileScreenPosition.x + offset.x, tileScreenPosition.y + offset.y, 0, mask);
}
if (bu->getBreathFrame() > 0)
{
tmpSurface = _game->getMod()->getSurfaceSet("BREATH-1.PCK")->getFrame(bu->getBreathFrame() - 1);
// lower the bubbles for shorter or kneeling units.
offset.y += (22 - bu->getHeight());
if (tmpSurface)
{
tmpSurface->blitNShade(surface, tileScreenPosition.x + offset.x, tileScreenPosition.y + offset.y - 30, tileShade, mask);
}
}
}
/**
* Draw the terrain.
* Keep this function as optimised as possible. It's big to minimise overhead of function calls.
* @param surface The surface to draw on.
*/
void Map::drawTerrain(Surface *surface)
{
int frameNumber = 0;
Surface *tmpSurface;
Tile *tile;
int beginX = 0, endX = _save->getMapSizeX() - 1;
int beginY = 0, endY = _save->getMapSizeY() - 1;
int beginZ = 0, endZ = _camera->getShowAllLayers()?_save->getMapSizeZ() - 1:_camera->getViewLevel();
Position mapPosition, screenPosition, bulletPositionScreen;
int bulletLowX=16000, bulletLowY=16000, bulletLowZ=16000, bulletHighX=0, bulletHighY=0, bulletHighZ=0;
int dummy;
BattleUnit *unit = 0;
int tileShade, wallShade, tileColor, obstacleShade;
static const int arrowBob[8] = {0,1,2,1,0,1,2,1};
NumberText *_numWaypid = 0;
// if we got bullet, get the highest x and y tiles to draw it on
if (_projectile && _explosions.empty())
{
int part = _projectile->getItem() ? 0 : BULLET_SPRITES-1;
for (int i = 0; i <= part; ++i)
{
if (_projectile->getPosition(1-i).x < bulletLowX)
bulletLowX = _projectile->getPosition(1-i).x;
if (_projectile->getPosition(1-i).y < bulletLowY)
bulletLowY = _projectile->getPosition(1-i).y;
if (_projectile->getPosition(1-i).z < bulletLowZ)
bulletLowZ = _projectile->getPosition(1-i).z;
if (_projectile->getPosition(1-i).x > bulletHighX)
bulletHighX = _projectile->getPosition(1-i).x;
if (_projectile->getPosition(1-i).y > bulletHighY)
bulletHighY = _projectile->getPosition(1-i).y;
if (_projectile->getPosition(1-i).z > bulletHighZ)
bulletHighZ = _projectile->getPosition(1-i).z;
}
// divide by 16 to go from voxel to tile position
bulletLowX = bulletLowX / 16;
bulletLowY = bulletLowY / 16;
bulletLowZ = bulletLowZ / 24;
bulletHighX = bulletHighX / 16;
bulletHighY = bulletHighY / 16;
bulletHighZ = bulletHighZ / 24;
// if the projectile is outside the viewport - center it back on it
_camera->convertVoxelToScreen(_projectile->getPosition(), &bulletPositionScreen);
if (_projectileInFOV)
{
Position newCam = _camera->getMapOffset();
if (newCam.z != bulletHighZ) //switch level
{
newCam.z = bulletHighZ;
if (_projectileInFOV)
{
_camera->setMapOffset(newCam);
_camera->convertVoxelToScreen(_projectile->getPosition(), &bulletPositionScreen);
}
}
if (_smoothCamera)
{
if (_launch)
{
_launch = false;
if ((bulletPositionScreen.x < 1 || bulletPositionScreen.x > surface->getWidth() - 1 ||
bulletPositionScreen.y < 1 || bulletPositionScreen.y > _visibleMapHeight - 1))
{
_camera->centerOnPosition(Position(bulletLowX, bulletLowY, bulletHighZ), false);
_camera->convertVoxelToScreen(_projectile->getPosition(), &bulletPositionScreen);
}
}
if (!_smoothingEngaged)
{
if (bulletPositionScreen.x < 1 || bulletPositionScreen.x > surface->getWidth() - 1 ||
bulletPositionScreen.y < 1 || bulletPositionScreen.y > _visibleMapHeight - 1)
{
_smoothingEngaged = true;
}
}
else
{
_camera->jumpXY(surface->getWidth() / 2 - bulletPositionScreen.x, _visibleMapHeight / 2 - bulletPositionScreen.y);
}
}
else
{
bool enough;
do
{
enough = true;
if (bulletPositionScreen.x < 0)
{
_camera->jumpXY(+surface->getWidth(), 0);
enough = false;
}
else if (bulletPositionScreen.x > surface->getWidth())
{
_camera->jumpXY(-surface->getWidth(), 0);
enough = false;
}
else if (bulletPositionScreen.y < 0)
{
_camera->jumpXY(0, +_visibleMapHeight);
enough = false;
}
else if (bulletPositionScreen.y > _visibleMapHeight)
{
_camera->jumpXY(0, -_visibleMapHeight);
enough = false;
}
_camera->convertVoxelToScreen(_projectile->getPosition(), &bulletPositionScreen);
}
while (!enough);
}
}
}
// get corner map coordinates to give rough boundaries in which tiles to redraw are
_camera->convertScreenToMap(0, 0, &beginX, &dummy);
_camera->convertScreenToMap(surface->getWidth(), 0, &dummy, &beginY);
_camera->convertScreenToMap(surface->getWidth() + _spriteWidth, surface->getHeight() + _spriteHeight, &endX, &dummy);
_camera->convertScreenToMap(0, surface->getHeight() + _spriteHeight, &dummy, &endY);
beginY -= (_camera->getViewLevel() * 2);
beginX -= (_camera->getViewLevel() * 2);
if (beginX < 0)
beginX = 0;
if (beginY < 0)
beginY = 0;
bool pathfinderTurnedOn = _save->getPathfinding()->isPathPreviewed();
if (!_waypoints.empty() || (pathfinderTurnedOn && (_previewSetting & PATH_TU_COST)))
{
_numWaypid = new NumberText(15, 15, 20, 30);
_numWaypid->setPalette(getPalette());
_numWaypid->setColor(pathfinderTurnedOn ? _messageColor + 1 : Palette::blockOffset(1));
}
surface->lock();
for (int itZ = beginZ; itZ <= endZ; itZ++)
{
bool topLayer = itZ == endZ;
for (int itX = beginX; itX <= endX; itX++)
{
for (int itY = beginY; itY <= endY; itY++)
{
mapPosition = Position(itX, itY, itZ);
_camera->convertMapToScreen(mapPosition, &screenPosition);
screenPosition += _camera->getMapOffset();
// only render cells that are inside the surface
if (screenPosition.x > -_spriteWidth && screenPosition.x < surface->getWidth() + _spriteWidth &&
screenPosition.y > -_spriteHeight && screenPosition.y < surface->getHeight() + _spriteHeight )
{
tile = _save->getTile(mapPosition);
if (!tile) continue;
if (tile->isDiscovered(2))
{
tileShade = tile->getShade();
obstacleShade = tileShade;
if (_showObstacles)
{
if (tile->isObstacle())
{
if (tileShade > 7) obstacleShade = 7;
if (tileShade < 2) obstacleShade = 2;
obstacleShade += (arrowBob[_animFrame] * 2 - 2);
}
}
}
else
{
tileShade = 16;
obstacleShade = 16;
unit = 0;
}
tileColor = tile->getMarkerColor();
// Draw floor
tmpSurface = tile->getSprite(O_FLOOR);
if (tmpSurface)
{
if (tile->getObstacle(O_FLOOR))
tmpSurface->blitNShade(surface, screenPosition.x, screenPosition.y - tile->getMapData(O_FLOOR)->getYOffset(), obstacleShade, false);
else
tmpSurface->blitNShade(surface, screenPosition.x, screenPosition.y - tile->getMapData(O_FLOOR)->getYOffset(), tileShade, false);
}
unit = tile->getUnit();
// Draw cursor back
if (_cursorType != CT_NONE && _selectorX > itX - _cursorSize && _selectorY > itY - _cursorSize && _selectorX < itX+1 && _selectorY < itY+1 && !_save->getBattleState()->getMouseOverIcons())
{
if (_camera->getViewLevel() == itZ)
{
if (_cursorType != CT_AIM)
{
if (unit && (unit->getVisible() || _save->getDebugMode()))
frameNumber = (_animFrame % 2); // yellow box
else
frameNumber = 0; // red box
}
else
{
if (unit && (unit->getVisible() || _save->getDebugMode()))
frameNumber = 7 + (_animFrame / 2); // yellow animated crosshairs
else
frameNumber = 6; // red static crosshairs
}
tmpSurface = _game->getMod()->getSurfaceSet("CURSOR.PCK")->getFrame(frameNumber);
tmpSurface->blitNShade(surface, screenPosition.x, screenPosition.y, 0);
}
else if (_camera->getViewLevel() > itZ)
{
frameNumber = 2; // blue box
tmpSurface = _game->getMod()->getSurfaceSet("CURSOR.PCK")->getFrame(frameNumber);
tmpSurface->blitNShade(surface, screenPosition.x, screenPosition.y, 0);
}
}
// special handling for a moving unit in background of tile.
const int backPosSize = 3;
Position backPos[backPosSize] =
{
Position(0, -1, 0),
Position(-1, -1, 0),
Position(-1, 0, 0),
};
for (int b = 0; b < backPosSize; ++b)
{
drawUnit(surface, _save->getTile(mapPosition + backPos[b]), tile, screenPosition, tileShade, obstacleShade, topLayer);
}
// Draw walls
if (!tile->isVoid())
{
// Draw west wall
tmpSurface = tile->getSprite(O_WESTWALL);
if (tmpSurface)
{
if ((tile->getMapData(O_WESTWALL)->isDoor() || tile->getMapData(O_WESTWALL)->isUFODoor())
&& tile->isDiscovered(0))
wallShade = tile->getShade();
else
wallShade = tileShade;
if (tile->getObstacle(O_WESTWALL))
tmpSurface->blitNShade(surface, screenPosition.x, screenPosition.y - tile->getMapData(O_WESTWALL)->getYOffset(), obstacleShade, false);
else
tmpSurface->blitNShade(surface, screenPosition.x, screenPosition.y - tile->getMapData(O_WESTWALL)->getYOffset(), wallShade, false);
}
// Draw north wall
tmpSurface = tile->getSprite(O_NORTHWALL);
if (tmpSurface)
{
if ((tile->getMapData(O_NORTHWALL)->isDoor() || tile->getMapData(O_NORTHWALL)->isUFODoor())
&& tile->isDiscovered(1))
wallShade = tile->getShade();
else
wallShade = tileShade;
if (tile->getObstacle(O_NORTHWALL))
tmpSurface->blitNShade(surface, screenPosition.x, screenPosition.y - tile->getMapData(O_NORTHWALL)->getYOffset(), obstacleShade, tile->getMapData(O_WESTWALL) != 0);
else
tmpSurface->blitNShade(surface, screenPosition.x, screenPosition.y - tile->getMapData(O_NORTHWALL)->getYOffset(), wallShade, tile->getMapData(O_WESTWALL) != 0);
}
// Draw object
if (tile->getMapData(O_OBJECT) && (tile->getMapData(O_OBJECT)->getBigWall() < 6 || tile->getMapData(O_OBJECT)->getBigWall() == 9))
{
tmpSurface = tile->getSprite(O_OBJECT);
if (tmpSurface)
{
if (tile->getObstacle(O_OBJECT))
tmpSurface->blitNShade(surface, screenPosition.x, screenPosition.y - tile->getMapData(O_OBJECT)->getYOffset(), obstacleShade, false);
else
tmpSurface->blitNShade(surface, screenPosition.x, screenPosition.y - tile->getMapData(O_OBJECT)->getYOffset(), tileShade, false);
}
}
// draw an item on top of the floor (if any)
int sprite = tile->getTopItemSprite();
if (sprite != -1)
{
tmpSurface = _game->getMod()->getSurfaceSet("FLOOROB.PCK")->getFrame(sprite);
tmpSurface->blitNShade(surface, screenPosition.x, screenPosition.y + tile->getTerrainLevel(), tileShade, false);
}
}
// check if we got bullet && it is in Field Of View
if (_projectile && _projectileInFOV)
{
tmpSurface = 0;
if (_projectile->getItem())
{
tmpSurface = _projectile->getSprite();
Position voxelPos = _projectile->getPosition();
// draw shadow on the floor
voxelPos.z = _save->getTileEngine()->castedShade(voxelPos);
if (voxelPos.x / 16 >= itX &&
voxelPos.y / 16 >= itY &&
voxelPos.x / 16 <= itX+1 &&
voxelPos.y / 16 <= itY+1 &&
voxelPos.z / 24 == itZ &&
_save->getTileEngine()->isVoxelVisible(voxelPos))
{
_camera->convertVoxelToScreen(voxelPos, &bulletPositionScreen);
tmpSurface->blitNShade(surface, bulletPositionScreen.x - 16, bulletPositionScreen.y - 26, 16);
}
voxelPos = _projectile->getPosition();
// draw thrown object
if (voxelPos.x / 16 >= itX &&
voxelPos.y / 16 >= itY &&
voxelPos.x / 16 <= itX+1 &&
voxelPos.y / 16 <= itY+1 &&
voxelPos.z / 24 == itZ &&
_save->getTileEngine()->isVoxelVisible(voxelPos))
{
_camera->convertVoxelToScreen(voxelPos, &bulletPositionScreen);
tmpSurface->blitNShade(surface, bulletPositionScreen.x - 16, bulletPositionScreen.y - 26, 0);
}
}
else
{
// draw bullet on the correct tile
if (itX >= bulletLowX && itX <= bulletHighX && itY >= bulletLowY && itY <= bulletHighY)
{
int begin = 0;
int end = BULLET_SPRITES;
int direction = 1;
if (_projectile->isReversed())
{
begin = BULLET_SPRITES - 1;
end = -1;
direction = -1;
}
for (int i = begin; i != end; i += direction)
{
tmpSurface = _projectileSet->getFrame(_projectile->getParticle(i));
if (tmpSurface)
{
Position voxelPos = _projectile->getPosition(1-i);
// draw shadow on the floor
voxelPos.z = _save->getTileEngine()->castedShade(voxelPos);
if (voxelPos.x / 16 == itX &&
voxelPos.y / 16 == itY &&
voxelPos.z / 24 == itZ &&
_save->getTileEngine()->isVoxelVisible(voxelPos))
{
_camera->convertVoxelToScreen(voxelPos, &bulletPositionScreen);
bulletPositionScreen.x -= tmpSurface->getWidth() / 2;
bulletPositionScreen.y -= tmpSurface->getHeight() / 2;
tmpSurface->blitNShade(surface, bulletPositionScreen.x, bulletPositionScreen.y, 16);
}
// draw bullet itself
voxelPos = _projectile->getPosition(1-i);
if (voxelPos.x / 16 == itX &&
voxelPos.y / 16 == itY &&
voxelPos.z / 24 == itZ &&
_save->getTileEngine()->isVoxelVisible(voxelPos))
{
_camera->convertVoxelToScreen(voxelPos, &bulletPositionScreen);
bulletPositionScreen.x -= tmpSurface->getWidth() / 2;
bulletPositionScreen.y -= tmpSurface->getHeight() / 2;
tmpSurface->blitNShade(surface, bulletPositionScreen.x, bulletPositionScreen.y, 0);
}
}
}
}
}
}
unit = tile->getUnit();
// Draw soldier from this tile or below
drawUnit(surface, tile, tile, screenPosition, tileShade, obstacleShade, topLayer);
// special handling for a moving unit in forground of tile.
const int frontPosSize = 5;
Position frontPos[frontPosSize] =
{
Position(-1, +1, 0),
Position(0, +1, 0),
Position(+1, +1, 0),
Position(+1, 0, 0),
Position(+1, -1, 0),
};
for (int f = 0; f < frontPosSize; ++f)
{
drawUnit(surface, _save->getTile(mapPosition + frontPos[f]), tile, screenPosition, tileShade, obstacleShade, topLayer);
}
// Draw smoke/fire
if (tile->getSmoke() && tile->isDiscovered(2))
{
frameNumber = 0;
int shade = 0;
if (!tile->getFire())
{
if (_save->getDepth() > 0)
{
frameNumber += Mod::UNDERWATER_SMOKE_OFFSET;
}
else
{
frameNumber += Mod::SMOKE_OFFSET;
}
frameNumber += int(floor((tile->getSmoke() / 6.0) - 0.1)); // see http://www.ufopaedia.org/images/c/cb/Smoke.gif
shade = tileShade;
}
if ((_animFrame / 2) + tile->getAnimationOffset() > 3)
{
frameNumber += ((_animFrame / 2) + tile->getAnimationOffset() - 4);
}
else
{
frameNumber += (_animFrame / 2) + tile->getAnimationOffset();
}
tmpSurface = _game->getMod()->getSurfaceSet("SMOKE.PCK")->getFrame(frameNumber);
tmpSurface->blitNShade(surface, screenPosition.x, screenPosition.y, shade);
}
//draw particle clouds
for (std::list<Particle*>::const_iterator i = tile->getParticleCloud()->begin(); i != tile->getParticleCloud()->end(); ++i)
{
int vaporX = screenPosition.x + (*i)->getX();
int vaporY = screenPosition.y + (*i)->getY();
if ((int)(_transparencies->size()) >= ((*i)->getColor() + 1) * 1024)
{
switch ((*i)->getSize())
{
case 3:
surface->setPixel(vaporX+1, vaporY+1, (*_transparencies)[((*i)->getColor() * 1024) + ((*i)->getOpacity() * 256) + surface->getPixel(vaporX+1, vaporY+1)]);
case 2:
surface->setPixel(vaporX + 1, vaporY, (*_transparencies)[((*i)->getColor() * 1024) + ((*i)->getOpacity() * 256) + surface->getPixel(vaporX + 1, vaporY)]);
case 1:
surface->setPixel(vaporX, vaporY + 1, (*_transparencies)[((*i)->getColor() * 1024) + ((*i)->getOpacity() * 256) + surface->getPixel(vaporX, vaporY + 1)]);
default:
surface->setPixel(vaporX, vaporY, (*_transparencies)[((*i)->getColor() * 1024) + ((*i)->getOpacity() * 256) + surface->getPixel(vaporX, vaporY)]);
break;
}
}
}
// Draw Path Preview
if (tile->getPreview() != -1 && tile->isDiscovered(0) && (_previewSetting & PATH_ARROWS))
{
if (itZ > 0 && tile->hasNoFloor(_save->getTile(tile->getPosition() + Position(0,0,-1))))
{
tmpSurface = _game->getMod()->getSurfaceSet("Pathfinding")->getFrame(11);
if (tmpSurface)
{
tmpSurface->blitNShade(surface, screenPosition.x, screenPosition.y+2, 0, false, tile->getMarkerColor());
}
}
tmpSurface = _game->getMod()->getSurfaceSet("Pathfinding")->getFrame(tile->getPreview());
if (tmpSurface)
{
tmpSurface->blitNShade(surface, screenPosition.x, screenPosition.y + tile->getTerrainLevel(), 0, false, tileColor);
}
}
if (!tile->isVoid())
{
// Draw object
if (tile->getMapData(O_OBJECT) && tile->getMapData(O_OBJECT)->getBigWall() >= 6 && tile->getMapData(O_OBJECT)->getBigWall() != 9)
{
tmpSurface = tile->getSprite(O_OBJECT);
if (tmpSurface)
{
if (tile->getObstacle(O_OBJECT))
tmpSurface->blitNShade(surface, screenPosition.x, screenPosition.y - tile->getMapData(O_OBJECT)->getYOffset(), obstacleShade, false);
else
tmpSurface->blitNShade(surface, screenPosition.x, screenPosition.y - tile->getMapData(O_OBJECT)->getYOffset(), tileShade, false);
}
}
}
// Draw cursor front
if (_cursorType != CT_NONE && _selectorX > itX - _cursorSize && _selectorY > itY - _cursorSize && _selectorX < itX+1 && _selectorY < itY+1 && !_save->getBattleState()->getMouseOverIcons())
{
if (_camera->getViewLevel() == itZ)
{
if (_cursorType != CT_AIM)
{
if (unit && (unit->getVisible() || _save->getDebugMode()))
frameNumber = 3 + (_animFrame % 2); // yellow box
else
frameNumber = 3; // red box
}
else
{
if (unit && (unit->getVisible() || _save->getDebugMode()))
frameNumber = 7 + (_animFrame / 2); // yellow animated crosshairs
else
frameNumber = 6; // red static crosshairs
}
tmpSurface = _game->getMod()->getSurfaceSet("CURSOR.PCK")->getFrame(frameNumber);
tmpSurface->blitNShade(surface, screenPosition.x, screenPosition.y, 0);
// UFO extender accuracy: display adjusted accuracy value on crosshair in real-time.
if (_cursorType == CT_AIM && Options::battleUFOExtenderAccuracy)
{
BattleAction *action = _save->getBattleGame()->getCurrentAction();
RuleItem *weapon = action->weapon->getRules();
int accuracy = action->actor->getFiringAccuracy(action->type, action->weapon);
int distanceSq = _save->getTileEngine()->distanceUnitToPositionSq(action->actor, Position (itX, itY,itZ), false);
int distance = (int)std::ceil(sqrt(float(distanceSq)));
int upperLimit = 200;
int lowerLimit = weapon->getMinRange();
switch (action->type)
{
case BA_AIMEDSHOT:
upperLimit = weapon->getAimRange();
break;
case BA_SNAPSHOT:
upperLimit = weapon->getSnapRange();
break;
case BA_AUTOSHOT:
upperLimit = weapon->getAutoRange();
break;
default:
break;
}
// at this point, let's assume the shot is adjusted and set the text amber.
_txtAccuracy->setColor(Palette::blockOffset(Pathfinding::yellow - 1)-1);
if (distance > upperLimit)
{
accuracy -= (distance - upperLimit) * weapon->getDropoff();
}
else if (distance < lowerLimit)
{
accuracy -= (lowerLimit - distance) * weapon->getDropoff();
}
else
{
// no adjustment made? set it to green.
_txtAccuracy->setColor(Palette::blockOffset(Pathfinding::green - 1)-1);
}
bool outOfRange = distanceSq > weapon->getMaxRangeSq();
// special handling for short ranges and diagonals
if (outOfRange)
{
// special handling for maxRange 1: allow it to target diagonally adjacent tiles (one diagonal move)
if (weapon->getMaxRange() == 1 && distanceSq <= 3)
{
outOfRange = false;
}
// special handling for maxRange 2: allow it to target diagonally adjacent tiles (one diagonal move + one straight move)
else if (weapon->getMaxRange() == 2 && distanceSq <= 6)
{
outOfRange = false;
}
}
// zero accuracy or out of range: set it red.
if (accuracy <= 0 || outOfRange)
{
accuracy = 0;
_txtAccuracy->setColor(Palette::blockOffset(Pathfinding::red - 1)-1);
}
_txtAccuracy->setText(Unicode::formatPercentage(accuracy));
_txtAccuracy->draw();
_txtAccuracy->blitNShade(surface, screenPosition.x, screenPosition.y, 0);
}
}
else if (_camera->getViewLevel() > itZ)
{
frameNumber = 5; // blue box
tmpSurface = _game->getMod()->getSurfaceSet("CURSOR.PCK")->getFrame(frameNumber);
tmpSurface->blitNShade(surface, screenPosition.x, screenPosition.y, 0);
}
if (_cursorType > 2 && _camera->getViewLevel() == itZ)
{
int frame[6] = {0, 0, 0, 11, 13, 15};
tmpSurface = _game->getMod()->getSurfaceSet("CURSOR.PCK")->getFrame(frame[_cursorType] + (_animFrame / 4));
tmpSurface->blitNShade(surface, screenPosition.x, screenPosition.y, 0);
}
}
// Draw waypoints if any on this tile
int waypid = 1;
int waypXOff = 2;
int waypYOff = 2;
for (std::vector<Position>::const_iterator i = _waypoints.begin(); i != _waypoints.end(); ++i)
{
if ((*i) == mapPosition)
{
if (waypXOff == 2 && waypYOff == 2)
{
tmpSurface = _game->getMod()->getSurfaceSet("CURSOR.PCK")->getFrame(7);
tmpSurface->blitNShade(surface, screenPosition.x, screenPosition.y, 0);
}
if (_save->getBattleGame()->getCurrentAction()->type == BA_LAUNCH)
{
_numWaypid->setValue(waypid);
_numWaypid->draw();
_numWaypid->blitNShade(surface, screenPosition.x + waypXOff, screenPosition.y + waypYOff, 0);
waypXOff += waypid > 9 ? 8 : 6;
if (waypXOff >= 26)
{
waypXOff = 2;
waypYOff += 8;
}
}
}
waypid++;
}
}
}
}
}
if (pathfinderTurnedOn)
{
if (_numWaypid)
{
_numWaypid->setBordered(true); // give it a border for the pathfinding display, makes it more visible on snow, etc.
}
for (int itZ = beginZ; itZ <= endZ; itZ++)
{
for (int itX = beginX; itX <= endX; itX++)
{
for (int itY = beginY; itY <= endY; itY++)
{
mapPosition = Position(itX, itY, itZ);
_camera->convertMapToScreen(mapPosition, &screenPosition);
screenPosition += _camera->getMapOffset();
// only render cells that are inside the surface
if (screenPosition.x > -_spriteWidth && screenPosition.x < surface->getWidth() + _spriteWidth &&
screenPosition.y > -_spriteHeight && screenPosition.y < surface->getHeight() + _spriteHeight )
{
tile = _save->getTile(mapPosition);
Tile *tileBelow = _save->getTile(mapPosition - Position(0,0,1));
if (!tile || !tile->isDiscovered(0) || tile->getPreview() == -1)
continue;
int adjustment = -tile->getTerrainLevel();
if (_previewSetting & PATH_ARROWS)
{
if (itZ > 0 && tile->hasNoFloor(tileBelow))
{
tmpSurface = _game->getMod()->getSurfaceSet("Pathfinding")->getFrame(23);
if (tmpSurface)
{
tmpSurface->blitNShade(surface, screenPosition.x, screenPosition.y+2, 0, false, tile->getMarkerColor());
}
}
int overlay = tile->getPreview() + 12;
tmpSurface = _game->getMod()->getSurfaceSet("Pathfinding")->getFrame(overlay);
if (tmpSurface)
{
tmpSurface->blitNShade(surface, screenPosition.x, screenPosition.y - adjustment, 0, false, tile->getMarkerColor());
}
}
if (_previewSetting & PATH_TU_COST && tile->getTUMarker() > -1)
{
int off = tile->getTUMarker() > 9 ? 5 : 3;
if (_save->getSelectedUnit() && _save->getSelectedUnit()->getArmor()->getSize() > 1)
{
adjustment += 1;
if (!(_previewSetting & PATH_ARROWS))
{
adjustment += 7;
}
}
_numWaypid->setValue(tile->getTUMarker());
_numWaypid->draw();
if ( !(_previewSetting & PATH_ARROWS) )
{
_numWaypid->blitNShade(surface, screenPosition.x + 16 - off, screenPosition.y + (29-adjustment), 0, false, tile->getMarkerColor() );
}
else
{
_numWaypid->blitNShade(surface, screenPosition.x + 16 - off, screenPosition.y + (22-adjustment), 0);
}
}
}
}
}
}
if (_numWaypid)
{
_numWaypid->setBordered(false); // make sure we remove the border in case it's being used for missile waypoints.
}
}
unit = (BattleUnit*)_save->getSelectedUnit();
if (unit && (_save->getSide() == FACTION_PLAYER || _save->getDebugMode()) && unit->getPosition().z <= _camera->getViewLevel())
{
_camera->convertMapToScreen(unit->getPosition(), &screenPosition);
screenPosition += _camera->getMapOffset();
Position offset;
calculateWalkingOffset(unit, &offset);
if (unit->getArmor()->getSize() > 1)
{
offset.y += 4;
}
offset.y += 24 - (unit->getHeight() + unit->getFloatHeight());
if (unit->isKneeled())
{
offset.y -= 2;
}
if (this->getCursorType() != CT_NONE)
{
_arrow->blitNShade(surface, screenPosition.x + offset.x + (_spriteWidth / 2) - (_arrow->getWidth() / 2), screenPosition.y + offset.y - _arrow->getHeight() + arrowBob[_animFrame], 0);
}
}
delete _numWaypid;
// check if we got big explosions
if (_explosionInFOV)
{
// big explosions cause the screen to flash as bright as possible before any explosions are actually drawn.
// this causes everything to look like EGA for a single frame.
if (_flashScreen)
{
for (int x = 0, y = 0; x < surface->getWidth() && y < surface->getHeight();)
{
Uint8 pixel = surface->getPixel(x, y);
pixel = (pixel / 16) * 16;
surface->setPixelIterative(&x, &y, pixel);
}
_flashScreen = false;
}
else
{
for (std::list<Explosion*>::const_iterator i = _explosions.begin(); i != _explosions.end(); ++i)
{
_camera->convertVoxelToScreen((*i)->getPosition(), &bulletPositionScreen);
if ((*i)->isBig())
{
if ((*i)->getCurrentFrame() >= 0)
{
tmpSurface = _game->getMod()->getSurfaceSet("X1.PCK")->getFrame((*i)->getCurrentFrame());
tmpSurface->blitNShade(surface, bulletPositionScreen.x - (tmpSurface->getWidth() / 2), bulletPositionScreen.y - (tmpSurface->getHeight() / 2), 0);
}
}
else if ((*i)->isHit())
{
tmpSurface = _game->getMod()->getSurfaceSet("HIT.PCK")->getFrame((*i)->getCurrentFrame());
tmpSurface->blitNShade(surface, bulletPositionScreen.x - 15, bulletPositionScreen.y - 25, 0);
}
else
{
tmpSurface = _game->getMod()->getSurfaceSet("SMOKE.PCK")->getFrame((*i)->getCurrentFrame());
tmpSurface->blitNShade(surface, bulletPositionScreen.x - 15, bulletPositionScreen.y - 15, 0);
}
}
}
}
surface->unlock();
}
/**
* Handles mouse presses on the map.
* @param action Pointer to an action.
* @param state State that the action handlers belong to.
*/
void Map::mousePress(Action *action, State *state)
{
InteractiveSurface::mousePress(action, state);
_camera->mousePress(action, state);
}
/**
* Handles mouse releases on the map.
* @param action Pointer to an action.
* @param state State that the action handlers belong to.
*/
void Map::mouseRelease(Action *action, State *state)
{
InteractiveSurface::mouseRelease(action, state);
_camera->mouseRelease(action, state);
}
/**
* Handles keyboard presses on the map.
* @param action Pointer to an action.
* @param state State that the action handlers belong to.
*/
void Map::keyboardPress(Action *action, State *state)
{
InteractiveSurface::keyboardPress(action, state);
_camera->keyboardPress(action, state);
}
/**
* Handles keyboard releases on the map.
* @param action Pointer to an action.
* @param state State that the action handlers belong to.
*/
void Map::keyboardRelease(Action *action, State *state)
{
InteractiveSurface::keyboardRelease(action, state);
_camera->keyboardRelease(action, state);
}
/**
* Handles mouse over events on the map.
* @param action Pointer to an action.
* @param state State that the action handlers belong to.
*/
void Map::mouseOver(Action *action, State *state)
{
InteractiveSurface::mouseOver(action, state);
_camera->mouseOver(action, state);
_mouseX = (int)action->getAbsoluteXMouse();
_mouseY = (int)action->getAbsoluteYMouse();
setSelectorPosition(_mouseX, _mouseY);
}
/**
* Sets the selector to a certain tile on the map.
* @param mx mouse x position.
* @param my mouse y position.
*/
void Map::setSelectorPosition(int mx, int my)
{
int oldX = _selectorX, oldY = _selectorY;
_camera->convertScreenToMap(mx, my + _spriteHeight/4, &_selectorX, &_selectorY);
if (oldX != _selectorX || oldY != _selectorY)
{
_redraw = true;
}
}
/**
* Handles animating tiles. 8 Frames per animation.
* @param redraw Redraw the battlescape?
*/
void Map::animate(bool redraw)
{
_animFrame++;
if (_animFrame == 8) _animFrame = 0;
// animate tiles
for (int i = 0; i < _save->getMapSizeXYZ(); ++i)
{
_save->getTiles()[i]->animate();
}
// animate certain units (large flying units have a propulsion animation)
for (std::vector<BattleUnit*>::iterator i = _save->getUnits()->begin(); i != _save->getUnits()->end(); ++i)
{
if (_save->getDepth() > 0 && !(*i)->getFloorAbove())
{
(*i)->breathe();
}
if (!(*i)->isOut())
{
if ((*i)->getArmor()->getConstantAnimation())
{
(*i)->setCache(0);
cacheUnit(*i);
}
}
}
if (redraw) _redraw = true;
}
/**
* Draws the rectangle selector.
* @param pos Pointer to a position.
*/
void Map::getSelectorPosition(Position *pos) const
{
pos->x = _selectorX;
pos->y = _selectorY;
pos->z = _camera->getViewLevel();
}
/**
* Calculates the offset of a soldier, when it is walking in the middle of 2 tiles.
* @param unit Pointer to BattleUnit.
* @param offset Pointer to the offset to return the calculation.
*/
void Map::calculateWalkingOffset(BattleUnit *unit, Position *offset, int *shadeOffset)
{
int offsetX[8] = { 1, 1, 1, 0, -1, -1, -1, 0 };
int offsetY[8] = { 1, 0, -1, -1, -1, 0, 1, 1 };
int phase = unit->getWalkingPhase() + unit->getDiagonalWalkingPhase();
int dir = unit->getDirection();
int midphase = 4 + 4 * (dir % 2);
int endphase = 8 + 8 * (dir % 2);
int size = unit->getArmor()->getSize();
if (shadeOffset)
{
*shadeOffset = endphase == 16 ? phase : phase * 2;
}
if (size > 1)
{
if (dir < 1 || dir > 5)
midphase = endphase;
else if (dir == 5)
midphase = 12;
else if (dir == 1)
midphase = 5;
else
midphase = 1;
}
if (unit->getVerticalDirection())
{
midphase = 4;
endphase = 8;
}
else
if ((unit->getStatus() == STATUS_WALKING || unit->getStatus() == STATUS_FLYING))
{
if (phase < midphase)
{
offset->x = phase * 2 * offsetX[dir];
offset->y = - phase * offsetY[dir];
}
else
{
offset->x = (phase - endphase) * 2 * offsetX[dir];
offset->y = - (phase - endphase) * offsetY[dir];
}
}
// If we are walking in between tiles, interpolate it's terrain level.
if (unit->getStatus() == STATUS_WALKING || unit->getStatus() == STATUS_FLYING)
{
if (phase < midphase)
{
int fromLevel = getTerrainLevel(unit->getPosition(), size);
int toLevel = getTerrainLevel(unit->getDestination(), size);
if (unit->getPosition().z > unit->getDestination().z)
{
// going down a level, so toLevel 0 becomes +24, -8 becomes 16
toLevel += 24*(unit->getPosition().z - unit->getDestination().z);
}else if (unit->getPosition().z < unit->getDestination().z)
{
// going up a level, so toLevel 0 becomes -24, -8 becomes -16
toLevel = -24*(unit->getDestination().z - unit->getPosition().z) + abs(toLevel);
}
offset->y += ((fromLevel * (endphase - phase)) / endphase) + ((toLevel * (phase)) / endphase);
}
else
{
// from phase 4 onwards the unit behind the scenes already is on the destination tile
// we have to get it's last position to calculate the correct offset
int fromLevel = getTerrainLevel(unit->getLastPosition(), size);
int toLevel = getTerrainLevel(unit->getDestination(), size);
if (unit->getLastPosition().z > unit->getDestination().z)
{
// going down a level, so fromLevel 0 becomes -24, -8 becomes -32
fromLevel -= 24*(unit->getLastPosition().z - unit->getDestination().z);
}else if (unit->getLastPosition().z < unit->getDestination().z)
{
// going up a level, so fromLevel 0 becomes +24, -8 becomes 16
fromLevel = 24*(unit->getDestination().z - unit->getLastPosition().z) - abs(fromLevel);
}
offset->y += ((fromLevel * (endphase - phase)) / endphase) + ((toLevel * (phase)) / endphase);
}
}
else
{
offset->y += getTerrainLevel(unit->getPosition(), size);
if (_save->getDepth() > 0)
{
unit->setFloorAbove(false);
// make sure this unit isn't obscured by the floor above him, otherwise it looks weird.
if (_camera->getViewLevel() > unit->getPosition().z)
{
for (int z = std::min(_camera->getViewLevel(), _save->getMapSizeZ() - 1); z != unit->getPosition().z; --z)
{
if (!_save->getTile(Position(unit->getPosition().x, unit->getPosition().y, z))->hasNoFloor(0))
{
unit->setFloorAbove(true);
break;
}
}
}
}
}
}
/**
* Terrainlevel goes from 0 to -24. For a larger sized unit, we need to pick the highest terrain level, which is the lowest number...
* @param pos Position.
* @param size Size of the unit we want to get the level from.
* @return terrainlevel.
*/
int Map::getTerrainLevel(const Position& pos, int size) const
{
int lowestlevel = 0;
for (int x = 0; x < size; x++)
{
for (int y = 0; y < size; y++)
{
int l = _save->getTile(pos + Position(x,y,0))->getTerrainLevel();
if (l < lowestlevel)
lowestlevel = l;
}
}
return lowestlevel;
}
/**
* Sets the 3D cursor to selection/aim mode.
* @param type Cursor type.
* @param size Size of cursor.
*/
void Map::setCursorType(CursorType type, int size)
{
_cursorType = type;
if (_cursorType == CT_NORMAL)
_cursorSize = size;
else
_cursorSize = 1;
}
/**
* Gets the cursor type.
* @return cursortype.
*/
CursorType Map::getCursorType() const
{
return _cursorType;
}
/**
* Checks all units for if they need to be redrawn.
*/
void Map::cacheUnits()
{
for (std::vector<BattleUnit*>::iterator i = _save->getUnits()->begin(); i != _save->getUnits()->end(); ++i)
{
cacheUnit(*i);
}
}
/**
* Check if a certain unit needs to be redrawn.
* @param unit Pointer to battleUnit.
*/
void Map::cacheUnit(BattleUnit *unit)
{
UnitSprite *unitSprite = new UnitSprite(_spriteWidth * 2, _spriteHeight, 0, 0, _save->getDepth() != 0);
unitSprite->setPalette(this->getPalette());
int numOfParts = unit->getArmor()->getSize() * unit->getArmor()->getSize();
if (unit->isCacheInvalid())
{
// 1 or 4 iterations, depending on unit size
for (int i = 0; i < numOfParts; i++)
{
Surface *cache = unit->getCache(i);
if (!cache) // no cache created yet
{
cache = new Surface(_spriteWidth * 2, _spriteHeight);
cache->setPalette(this->getPalette());
}
unitSprite->setBattleUnit(unit, i);
unitSprite->setSurfaces(_game->getMod()->getSurfaceSet(unit->getArmor()->getSpriteSheet()),
_game->getMod()->getSurfaceSet("HANDOB.PCK"),
_game->getMod()->getSurfaceSet("HANDOB2.PCK"));
unitSprite->setAnimationFrame(_animFrame);
cache->clear();
unitSprite->blit(cache);
unit->setCache(cache, i);
}
}
delete unitSprite;
}
/**
* Puts a projectile sprite on the map.
* @param projectile Projectile to place.
*/
void Map::setProjectile(Projectile *projectile)
{
_projectile = projectile;
if (projectile && Options::battleSmoothCamera)
{
_launch = true;
}
}
/**
* Gets the current projectile sprite on the map.
* @return Projectile or 0 if there is no projectile sprite on the map.
*/
Projectile *Map::getProjectile() const
{
return _projectile;
}
/**
* Gets a list of explosion sprites on the map.
* @return A list of explosion sprites.
*/
std::list<Explosion*> *Map::getExplosions()
{
return &_explosions;
}
/**
* Gets the pointer to the camera.
* @return Pointer to camera.
*/
Camera *Map::getCamera()
{
return _camera;
}
/**
* Timers only work on surfaces so we have to pass this on to the camera object.
*/
void Map::scrollMouse()
{
_camera->scrollMouse();
}
/**
* Timers only work on surfaces so we have to pass this on to the camera object.
*/
void Map::scrollKey()
{
_camera->scrollKey();
}
/**
* Gets a list of waypoints on the map.
* @return A list of waypoints.
*/
std::vector<Position> *Map::getWaypoints()
{
return &_waypoints;
}
/**
* Sets mouse-buttons' pressed state.
* @param button Index of the button.
* @param pressed The state of the button.
*/
void Map::setButtonsPressed(Uint8 button, bool pressed)
{
setButtonPressed(button, pressed);
}
/**
* Sets the unitDying flag.
* @param flag True if the unit is dying.
*/
void Map::setUnitDying(bool flag)
{
_unitDying = flag;
}
/**
* Updates the selector to the last-known mouse position.
*/
void Map::refreshSelectorPosition()
{
setSelectorPosition(_mouseX, _mouseY);
}
/**
* Special handling for setting the height of the map viewport.
* @param height the new base screen height.
*/
void Map::setHeight(int height)
{
Surface::setHeight(height);
_visibleMapHeight = height - _iconHeight;
_message->setHeight((_visibleMapHeight < 200)? _visibleMapHeight : 200);
_message->setY((_visibleMapHeight - _message->getHeight()) / 2);
}
/**
* Special handling for setting the width of the map viewport.
* @param width the new base screen width.
*/
void Map::setWidth(int width)
{
int dX = width - getWidth();
Surface::setWidth(width);
_message->setX(_message->getX() + dX / 2);
}
/**
* Get the hidden movement screen's vertical position.
* @return the vertical position of the hidden movement window.
*/
int Map::getMessageY() const
{
return _message->getY();
}
/**
* Get the icon height.
*/
int Map::getIconHeight() const
{
return _iconHeight;
}
/**
* Get the icon width.
*/
int Map::getIconWidth() const
{
return _iconWidth;
}
/**
* Returns the angle(left/right balance) of a sound effect,
* based off a map position.
* @param pos the map position to calculate the sound angle from.
* @return the angle of the sound (280 to 440).
*/
int Map::getSoundAngle(const Position& pos) const
{
int midPoint = getWidth() / 2;
Position relativePosition;
_camera->convertMapToScreen(pos, &relativePosition);
// cap the position to the screen edges relative to the center,
// negative values indicating a left-shift, and positive values shifting to the right.
relativePosition.x = Clamp((relativePosition.x + _camera->getMapOffset().x) - midPoint, -midPoint, midPoint);
// convert the relative distance to a relative increment of an 80 degree angle
// we use +- 80 instead of +- 90, so as not to go ALL the way left or right
// which would effectively mute the sound out of one speaker.
// since Mix_SetPosition uses modulo 360, we can't feed it a negative number, so add 360 instead.
return 360 + (relativePosition.x / (midPoint / 80.0));
}
/**
* Reset the camera smoothing bool.
*/
void Map::resetCameraSmoothing()
{
_smoothingEngaged = false;
}
/**
* Set the "explosion flash" bool.
* @param flash should the screen be rendered in EGA this frame?
*/
void Map::setBlastFlash(bool flash)
{
_flashScreen = flash;
}
/**
* Checks if the screen is still being rendered in EGA.
* @return if we are still in EGA mode.
*/
bool Map::getBlastFlash() const
{
return _flashScreen;
}
/**
* Resets obstacle markers.
*/
void Map::resetObstacles(void)
{
for (int z = 0; z < _save->getMapSizeZ(); z++)
for (int y = 0; y < _save->getMapSizeY(); y++)
for (int x = 0; x < _save->getMapSizeX(); x++)
{
Tile *tile = _save->getTile(Position(x, y, z));
if (tile) tile->resetObstacle();
}
_showObstacles = false;
}
/**
* Enables obstacle markers.
*/
void Map::enableObstacles(void)
{
_showObstacles = true;
if (_obstacleTimer)
{
_obstacleTimer->stop();
_obstacleTimer->start();
}
}
/**
* Disables obstacle markers.
*/
void Map::disableObstacles(void)
{
_showObstacles = false;
if (_obstacleTimer)
{
_obstacleTimer->stop();
}
}
}
↑ V595 The 'tile->getMapData(O_WESTWALL)' pointer was utilized before it was verified against nullptr. Check lines: 738, 750.
↑ V595 The '_numWaypid' pointer was utilized before it was verified against nullptr. Check lines: 1089, 1107.
↑ V595 The '_numWaypid' pointer was utilized before it was verified against nullptr. Check lines: 1167, 1174.
↑ V1051 Consider checking for misprints. It's possible that the 'tileBelow' should be checked here.
↑ V547 Expression '_projectileInFOV' is always true.
↑ V522 There might be dereferencing of a potential null pointer '_numWaypid'.
↑ V522 There might be dereferencing of a potential null pointer.
↑ V522 There might be dereferencing of a potential null pointer '_save->getTile(pos + Position(x, y, 0))'.
↑ V525 The code contains the collection of similar blocks. Check items '1', '2', '2' in lines 1072, 1073, 1074.