/*
* The ManaPlus Client
* Copyright (C) 2004-2009 The Mana World Development Team
* Copyright (C) 2009-2010 The Mana Developers
* Copyright (C) 2011-2014 The ManaPlus Developers
*
* This file is part of The ManaPlus Client.
*
* This program 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 2 of the License, or
* any later version.
*
* This program 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 this program. If not, see .
*/
#include "resources/map/maplayer.h"
#include "configuration.h"
#include "graphicsvertexes.h"
#ifndef USE_OPENGL
#include "render/graphics.h"
#endif
#include "being/localplayer.h"
#include "render/graphics.h"
#include "resources/image.h"
#include "resources/resourcemanager.h"
#include "resources/map/maprowvertexes.h"
#include "gui/font.h"
#include "gui/gui.h"
#include "utils/delete2.h"
#include "utils/dtor.h"
#include "debug.h"
MapLayer::MapLayer(const int x, const int y, const int width, const int height,
const bool fringeLayer, const int mask):
mX(x),
mY(y),
mWidth(width),
mHeight(height),
mTiles(new Image*[mWidth * mHeight]),
mSpecialLayer(nullptr),
mTempLayer(nullptr),
mTempRows(),
mMask(mask),
mIsFringeLayer(fringeLayer),
mHighlightAttackRange(config.getBoolValue("highlightAttackRange"))
{
std::fill_n(mTiles, mWidth * mHeight, static_cast(nullptr));
config.addListener("highlightAttackRange", this);
}
MapLayer::~MapLayer()
{
config.removeListener("highlightAttackRange", this);
CHECKLISTENERS
delete [] mTiles;
delete_all(mTempRows);
mTempRows.clear();
}
void MapLayer::optionChanged(const std::string &value)
{
if (value == "highlightAttackRange")
{
mHighlightAttackRange =
config.getBoolValue("highlightAttackRange");
}
}
void MapLayer::setTile(const int x, const int y, Image *const img)
{
mTiles[x + y * mWidth] = img;
}
void MapLayer::draw(Graphics *const graphics,
int startX, int startY, int endX, int endY,
const int scrollX, const int scrollY,
const int debugFlags) const
{
if (!player_node)
return;
BLOCK_START("MapLayer::draw")
startX -= mX;
startY -= mY;
endX -= mX;
endY -= mY;
if (startX < 0)
startX = 0;
if (startY < 0)
startY = 0;
if (endX > mWidth)
endX = mWidth;
if (endY > mHeight)
endY = mHeight;
const int dx = (mX * mapTileSize) - scrollX;
const int dy = (mY * mapTileSize) - scrollY + mapTileSize;
const bool flag = (debugFlags != Map::MAP_SPECIAL
&& debugFlags != Map::MAP_SPECIAL2);
for (int y = startY; y < endY; y++)
{
const int y32 = y * mapTileSize;
const int yWidth = y * mWidth;
const int py0 = y32 + dy;
Image **tilePtr = mTiles + static_cast(startX + yWidth);
for (int x = startX; x < endX; x++, tilePtr++)
{
const int x32 = x * mapTileSize;
int c = 0;
const Image *const img = *tilePtr;
if (img)
{
const int px = x32 + dx;
const int py = py0 - img->mBounds.h;
if (flag || img->mBounds.h <= mapTileSize)
{
int width = 0;
// here need not draw over player position
c = getTileDrawWidth(img, endX - x, width);
if (!c)
{
graphics->drawImage(img, px, py);
}
else
{
graphics->drawPattern(img, px, py,
width, img->mBounds.h);
}
}
}
x += c;
}
}
BLOCK_END("MapLayer::draw")
}
void MapLayer::drawSDL(Graphics *const graphics)
{
BLOCK_START("MapLayer::drawSDL")
MapRows::const_iterator rit = mTempRows.begin();
const MapRows::const_iterator rit_end = mTempRows.end();
while (rit != rit_end)
{
MapRowImages *const images = &(*rit)->images;
MapRowImages::const_iterator iit = images->begin();
const MapRowImages::const_iterator iit_end = images->end();
while (iit != iit_end)
{
graphics->drawTileVertexes(*iit);
++ iit;
}
++ rit;
}
BLOCK_END("MapLayer::drawSDL")
}
#ifdef USE_OPENGL
void MapLayer::updateSDL(const Graphics *const graphics,
int startX, int startY,
int endX, int endY,
const int scrollX, const int scrollY,
const int debugFlags)
{
BLOCK_START("MapLayer::updateSDL")
delete_all(mTempRows);
mTempRows.clear();
startX -= mX;
startY -= mY;
endX -= mX;
endY -= mY;
if (startX < 0)
startX = 0;
if (startY < 0)
startY = 0;
if (endX > mWidth)
endX = mWidth;
if (endY > mHeight)
endY = mHeight;
const int dx = (mX * mapTileSize) - scrollX;
const int dy = (mY * mapTileSize) - scrollY + mapTileSize;
const bool flag = (debugFlags != Map::MAP_SPECIAL
&& debugFlags != Map::MAP_SPECIAL2);
for (int y = startY; y < endY; y++)
{
MapRowVertexes *const row = new MapRowVertexes();
mTempRows.push_back(row);
const Image *lastImage = nullptr;
ImageVertexes *imgVert = nullptr;
const int yWidth = y * mWidth;
const int py0 = y * mapTileSize + dy;
Image **tilePtr = mTiles + static_cast(startX + yWidth);
for (int x = startX; x < endX; x++, tilePtr++)
{
Image *const img = *tilePtr;
if (img)
{
const int px = x * mapTileSize + dx;
const int py = py0 - img->mBounds.h;
if (flag || img->mBounds.h <= mapTileSize)
{
if (lastImage != img)
{
imgVert = new ImageVertexes();
imgVert->image = img;
row->images.push_back(imgVert);
lastImage = img;
}
graphics->calcTileSDL(imgVert, px, py);
}
}
}
}
BLOCK_END("MapLayer::updateSDL")
}
void MapLayer::updateOGL(const Graphics *const graphics,
int startX, int startY,
int endX, int endY,
const int scrollX, const int scrollY,
const int debugFlags)
{
BLOCK_START("MapLayer::updateOGL")
delete_all(mTempRows);
mTempRows.clear();
startX -= mX;
startY -= mY;
endX -= mX;
endY -= mY;
if (startX < 0)
startX = 0;
if (startY < 0)
startY = 0;
if (endX > mWidth)
endX = mWidth;
if (endY > mHeight)
endY = mHeight;
const int dx = (mX * mapTileSize) - scrollX;
const int dy = (mY * mapTileSize) - scrollY + mapTileSize;
const bool flag = (debugFlags != Map::MAP_SPECIAL
&& debugFlags != Map::MAP_SPECIAL2);
MapRowVertexes *const row = new MapRowVertexes();
mTempRows.push_back(row);
Image *lastImage = nullptr;
ImageVertexes *imgVert = nullptr;
std::map imgSet;
for (int y = startY; y < endY; y++)
{
const int yWidth = y * mWidth;
const int py0 = y * mapTileSize + dy;
Image **tilePtr = mTiles + static_cast(startX + yWidth);
for (int x = startX; x < endX; x++, tilePtr++)
{
Image *const img = *tilePtr;
if (img)
{
const int px = x * mapTileSize + dx;
const int py = py0 - img->mBounds.h;
const GLuint imgGlImage = img->mGLImage;
if (flag || img->mBounds.h <= mapTileSize)
{
if (!lastImage || lastImage->mGLImage != imgGlImage)
{
if (img->mBounds.w > mapTileSize)
imgSet.clear();
if (imgSet.find(imgGlImage) != imgSet.end())
{
imgVert = imgSet[imgGlImage];
}
else
{
if (lastImage)
imgSet[lastImage->mGLImage] = imgVert;
imgVert = new ImageVertexes();
imgVert->ogl.init();
imgVert->image = img;
row->images.push_back(imgVert);
}
}
lastImage = img;
// if (imgVert->image->mGLImage != lastImage->mGLImage)
// logger->log("wrong image draw");
graphics->calcTileVertexes(imgVert, lastImage, px, py);
}
}
}
}
BLOCK_END("MapLayer::updateOGL")
}
void MapLayer::drawOGL(Graphics *const graphics)
{
BLOCK_START("MapLayer::drawOGL")
MapRows::const_iterator rit = mTempRows.begin();
const MapRows::const_iterator rit_end = mTempRows.end();
// int k = 0;
while (rit != rit_end)
{
const MapRowImages *const images = &(*rit)->images;
MapRowImages::const_iterator iit = images->begin();
const MapRowImages::const_iterator iit_end = images->end();
while (iit != iit_end)
{
graphics->drawTileVertexes(*iit);
++ iit;
// k ++;
}
++ rit;
}
BLOCK_END("MapLayer::drawOGL")
// logger->log("draws: %d", k);
}
#endif
void MapLayer::drawFringe(Graphics *const graphics, int startX, int startY,
int endX, int endY,
const int scrollX, const int scrollY,
const Actors *const actors,
const int debugFlags, const int yFix) const
{
BLOCK_START("MapLayer::drawFringe")
if (!player_node || !mSpecialLayer || !mTempLayer)
{
BLOCK_END("MapLayer::drawFringe")
return;
}
startX -= mX;
startY -= mY;
endX -= mX;
endY -= mY;
if (startX < 0)
startX = 0;
if (startY < 0)
startY = 0;
if (endX > mWidth)
endX = mWidth;
if (endY > mHeight)
endY = mHeight;
ActorsCIter ai = actors->begin();
const ActorsCIter ai_end = actors->end();
const int dx = (mX * mapTileSize) - scrollX;
const int dy = (mY * mapTileSize) - scrollY + mapTileSize;
const int specialWidth = mSpecialLayer->mWidth;
const int specialHeight = mSpecialLayer->mHeight;
for (int y = startY; y < endY; y++)
{
const int y32 = y * mapTileSize;
const int y32s = (y + yFix) * mapTileSize;
const int yWidth = y * mWidth;
BLOCK_START("MapLayer::drawFringe drawmobs")
// If drawing the fringe layer, make sure all actors above this row of
// tiles have been drawn
while (ai != ai_end && (*ai)->getSortPixelY() <= y32s)
{
(*ai)->draw(graphics, -scrollX, -scrollY);
++ ai;
}
BLOCK_END("MapLayer::drawFringe drawmobs")
if (debugFlags == Map::MAP_SPECIAL3
|| debugFlags == Map::MAP_BLACKWHITE)
{
if (y < specialHeight)
{
const int ptr = y * specialWidth;
const int py1 = y32 - scrollY;
int endX1 = endX;
if (endX1 > specialWidth)
endX1 = specialWidth;
if (endX1 < 0)
endX1 = 0;
for (int x = startX; x < endX1; x++)
{
const int px1 = x * mapTileSize - scrollX;
const MapItem *item = mSpecialLayer->mTiles[ptr + x];
if (item)
{
item->draw(graphics, px1, py1,
mapTileSize, mapTileSize);
}
item = mTempLayer->mTiles[ptr + x];
if (item)
{
item->draw(graphics, px1, py1,
mapTileSize, mapTileSize);
}
}
}
}
else
{
const int py0 = y32 + dy;
const int py1 = y32 - scrollY;
Image **tilePtr = mTiles + static_cast(startX + yWidth);
for (int x = startX; x < endX; x++, tilePtr++)
{
const int x32 = x * mapTileSize;
const int px1 = x32 - scrollX;
int c = 0;
const Image *const img = *tilePtr;
if (img)
{
const int px = x32 + dx;
const int py = py0 - img->mBounds.h;
if ((debugFlags != Map::MAP_SPECIAL
&& debugFlags != Map::MAP_SPECIAL2)
|| img->mBounds.h <= mapTileSize)
{
int width = 0;
// here need not draw over player position
c = getTileDrawWidth(img, endX - x, width);
if (!c)
{
graphics->drawImage(img, px, py);
}
else
{
graphics->drawPattern(img, px, py,
width, img->mBounds.h);
}
}
}
if (y < specialHeight)
{
int c1 = c;
if (c1 + x + 1 > specialWidth)
c1 = specialWidth - x - 1;
if (c1 < 0)
c1 = 0;
const int ptr = y * specialWidth + x;
for (int x1 = 0; x1 < c1 + 1; x1 ++)
{
const MapItem *const item1
= mSpecialLayer->mTiles[ptr + x1];
const MapItem *const item2
= mTempLayer->mTiles[ptr + x1];
if (item1 || item2)
{
const int px2 = px1 + (x1 * mapTileSize);
if (item1 && item1->mType != MapItem::EMPTY)
{
item1->draw(graphics, px2, py1,
mapTileSize, mapTileSize);
}
if (item2 && item2->mType != MapItem::EMPTY)
{
item2->draw(graphics, px2, py1,
mapTileSize, mapTileSize);
}
}
}
}
x += c;
}
}
}
// Draw any remaining actors
if (debugFlags != Map::MAP_SPECIAL3)
{
BLOCK_START("MapLayer::drawFringe drawmobs")
while (ai != ai_end)
{
(*ai)->draw(graphics, -scrollX, -scrollY);
++ai;
}
BLOCK_END("MapLayer::drawFringe drawmobs")
if (mHighlightAttackRange && player_node)
{
const int px = player_node->getPixelX()
- scrollX - mapTileSize / 2;
const int py = player_node->getPixelY() - scrollY - mapTileSize;
const int attackRange = player_node->getAttackRange()
* mapTileSize;
int x = px - attackRange;
int y = py - attackRange;
int w = 2 * attackRange + mapTileSize;
int h = w;
if (attackRange <= mapTileSize)
{
x -= mapTileSize / 2;
y -= mapTileSize / 2;
w += mapTileSize;
h += mapTileSize;
}
if (userPalette)
{
graphics->setColor(userPalette->getColorWithAlpha(
UserPalette::ATTACK_RANGE));
graphics->fillRectangle(Rect(x, y, w, h));
graphics->setColor(userPalette->getColorWithAlpha(
UserPalette::ATTACK_RANGE_BORDER));
graphics->drawRectangle(Rect(x, y, w, h));
}
}
}
BLOCK_END("MapLayer::drawFringe")
}
int MapLayer::getTileDrawWidth(const Image *img,
const int endX,
int &width)
{
BLOCK_START("MapLayer::getTileDrawWidth")
const Image *const img1 = img;
int c = 0;
if (!img1)
{
width = 0;
BLOCK_END("MapLayer::getTileDrawWidth")
return c;
}
width = img1->mBounds.w;
for (int x = 1; x < endX; x++)
{
img ++;
if (img != img1)
break;
c ++;
if (img)
width += img->mBounds.w;
}
BLOCK_END("MapLayer::getTileDrawWidth")
return c;
}
SpecialLayer::SpecialLayer(const int width, const int height) :
mWidth(width),
mHeight(height),
mTiles(new MapItem*[mWidth * mHeight])
{
std::fill_n(mTiles, mWidth * mHeight, static_cast(nullptr));
}
SpecialLayer::~SpecialLayer()
{
for (int f = 0; f < mWidth * mHeight; f ++)
delete2(mTiles[f])
delete [] mTiles;
}
MapItem* SpecialLayer::getTile(const int x, const int y) const
{
if (x < 0 || x >= mWidth ||
y < 0 || y >= mHeight)
{
return nullptr;
}
return mTiles[x + y * mWidth];
}
void SpecialLayer::setTile(const int x, const int y, MapItem *const item)
{
if (x < 0 || x >= mWidth ||
y < 0 || y >= mHeight)
{
return;
}
const int idx = x + y * mWidth;
delete mTiles[idx];
if (item)
item->setPos(x, y);
mTiles[idx] = item;
}
void SpecialLayer::setTile(const int x, const int y, const int type)
{
if (x < 0 || x >= mWidth ||
y < 0 || y >= mHeight)
{
return;
}
const int idx = x + y * mWidth;
MapItem *const tile = mTiles[idx];
if (tile)
{
tile->setType(type);
tile->setPos(x, y);
}
else
{
mTiles[idx] = new MapItem(type);
mTiles[idx]->setPos(x, y);
}
}
void SpecialLayer::addRoad(const Path &road)
{
FOR_EACH (Path::const_iterator, i, road)
{
const Position &pos = (*i);
MapItem *const item = getTile(pos.x, pos.y);
if (!item)
setTile(pos.x, pos.y, new MapItem(MapItem::ROAD));
else
item->setType(MapItem::ROAD);
}
}
void SpecialLayer::clean() const
{
if (!mTiles)
return;
for (int f = 0; f < mWidth * mHeight; f ++)
{
MapItem *const item = mTiles[f];
if (item)
item->setType(MapItem::EMPTY);
}
}
void SpecialLayer::draw(Graphics *const graphics, int startX, int startY,
int endX, int endY,
const int scrollX, const int scrollY) const
{
BLOCK_START("SpecialLayer::draw")
if (startX < 0)
startX = 0;
if (startY < 0)
startY = 0;
if (endX > mWidth)
endX = mWidth;
if (endY > mHeight)
endY = mHeight;
for (int y = startY; y < endY; y ++)
{
const int py = y * mapTileSize - scrollY;
const int y2 = y * mWidth;
for (int x = startX; x < endX; x ++)
{
const MapItem *const item = mTiles[x + y2];
if (item)
{
item->draw(graphics, x * mapTileSize - scrollX, py,
mapTileSize, mapTileSize);
}
}
}
BLOCK_END("SpecialLayer::draw")
}
MapItem::MapItem():
mImage(nullptr),
mComment(),
mName(),
mType(EMPTY),
mX(-1),
mY(-1)
{
setType(EMPTY);
}
MapItem::MapItem(const int type):
mImage(nullptr),
mComment(),
mName(),
mType(type),
mX(-1),
mY(-1)
{
setType(type);
}
MapItem::MapItem(const int type, std::string comment):
mImage(nullptr),
mComment(comment),
mName(),
mType(type),
mX(-1),
mY(-1)
{
setType(type);
}
MapItem::MapItem(const int type, std::string comment,
const int x, const int y):
mImage(nullptr),
mComment(comment),
mName(),
mType(type),
mX(x),
mY(y)
{
setType(type);
}
MapItem::~MapItem()
{
if (mImage)
{
mImage->decRef();
mImage = nullptr;
}
}
void MapItem::setType(const int type)
{
std::string name;
mType = type;
if (mImage)
mImage->decRef();
switch (type)
{
case ARROW_UP:
name = "graphics/sprites/arrow_up.png";
break;
case ARROW_DOWN:
name = "graphics/sprites/arrow_down.png";
break;
case ARROW_LEFT:
name = "graphics/sprites/arrow_left.png";
break;
case ARROW_RIGHT:
name = "graphics/sprites/arrow_right.png";
break;
default:
break;
}
if (!name.empty())
{
ResourceManager *const resman = ResourceManager::getInstance();
mImage = resman->getImage(name);
}
else
{
mImage = nullptr;
}
}
void MapItem::setPos(const int x, const int y)
{
mX = x;
mY = y;
}
void MapItem::draw(Graphics *const graphics, const int x, const int y,
const int dx, const int dy) const
{
BLOCK_START("MapItem::draw")
if (mImage)
graphics->drawImage(mImage, x, y);
switch (mType)
{
case ROAD:
case CROSS:
graphics->setColor(userPalette->getColorWithAlpha(
UserPalette::ROAD_POINT));
graphics->fillRectangle(Rect(x + dx / 3, y + dy / 3,
dx / 3, dy / 3));
break;
case HOME:
{
graphics->setColor(userPalette->getColorWithAlpha(
UserPalette::HOME_PLACE));
graphics->fillRectangle(Rect(x, y, dx, dy));
graphics->setColor(userPalette->getColorWithAlpha(
UserPalette::HOME_PLACE_BORDER));
graphics->drawRectangle(Rect(x, y, dx, dy));
break;
}
default:
break;
}
if (!mName.empty() && mType != PORTAL && mType != EMPTY)
{
Font *const font = gui->getFont();
if (font)
{
graphics->setColor(userPalette->getColor(UserPalette::BEING));
font->drawString(graphics, mName, x, y);
}
}
BLOCK_END("MapItem::draw")
}
ObjectsLayer::ObjectsLayer(const unsigned width, const unsigned height) :
mTiles(new MapObjectList*[width * height]),
mWidth(width),
mHeight(height)
{
std::fill_n(mTiles, width * height, static_cast(nullptr));
}
ObjectsLayer::~ObjectsLayer()
{
const unsigned size = mWidth * mHeight;
for (unsigned f = 0; f < size; f ++)
delete mTiles[f];
delete [] mTiles;
mTiles = nullptr;
}
void ObjectsLayer::addObject(const std::string &name, const int type,
const unsigned x, const unsigned y,
unsigned dx, unsigned dy)
{
if (!mTiles)
return;
if (x + dx > mWidth)
dx = mWidth - x;
if (y + dy > mHeight)
dy = mHeight - y;
for (unsigned y1 = y; y1 < y + dy; y1 ++)
{
const unsigned idx1 = x + y1 * mWidth;
const unsigned idx2 = idx1 + dx;
for (unsigned i = idx1; i < idx2; i ++)
{
if (!mTiles[i])
mTiles[i] = new MapObjectList();
mTiles[i]->objects.push_back(MapObject(type, name));
}
}
}
MapObjectList *ObjectsLayer::getAt(const unsigned x, const unsigned y) const
{
if (x >= mWidth || y >= mHeight)
return nullptr;
return mTiles[x + y * mWidth];
}