diff options
author | Andrei Karas <akaras@inbox.ru> | 2014-05-11 13:18:07 +0300 |
---|---|---|
committer | Andrei Karas <akaras@inbox.ru> | 2014-05-11 13:26:25 +0300 |
commit | ad42810bb9484f4930252ecc34b91d83f37e3293 (patch) | |
tree | 69f3bcf9b26bbdb08dde7e3a0fa6648fc432cf12 /src/resources/map | |
parent | 116349dac9b9d13d0e57e24e345590a8a0e70210 (diff) | |
download | ManaVerse-ad42810bb9484f4930252ecc34b91d83f37e3293.tar.gz ManaVerse-ad42810bb9484f4930252ecc34b91d83f37e3293.tar.bz2 ManaVerse-ad42810bb9484f4930252ecc34b91d83f37e3293.tar.xz ManaVerse-ad42810bb9484f4930252ecc34b91d83f37e3293.zip |
Move map related files into resources/map.
Diffstat (limited to 'src/resources/map')
-rw-r--r-- | src/resources/map/map.cpp | 1509 | ||||
-rw-r--r-- | src/resources/map/map.h | 540 | ||||
-rw-r--r-- | src/resources/map/mapheights.cpp | 41 | ||||
-rw-r--r-- | src/resources/map/mapheights.h | 48 | ||||
-rw-r--r-- | src/resources/map/maplayer.cpp | 892 | ||||
-rw-r--r-- | src/resources/map/maplayer.h | 328 | ||||
-rw-r--r-- | src/resources/map/properties.h | 135 | ||||
-rw-r--r-- | src/resources/map/tileset.h | 79 | ||||
-rw-r--r-- | src/resources/map/walklayer.cpp | 44 | ||||
-rw-r--r-- | src/resources/map/walklayer.h | 48 |
10 files changed, 3664 insertions, 0 deletions
diff --git a/src/resources/map/map.cpp b/src/resources/map/map.cpp new file mode 100644 index 000000000..c753b0202 --- /dev/null +++ b/src/resources/map/map.cpp @@ -0,0 +1,1509 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#include "resources/map/map.h" + +#include "client.h" +#include "configuration.h" +#include "render/graphics.h" +#include "notifications.h" +#include "notifymanager.h" +#include "simpleanimation.h" + +#include "resources/map/mapheights.h" +#include "resources/map/maplayer.h" +#include "resources/map/tileset.h" +#include "resources/map/walklayer.h" + +#include "being/localplayer.h" + +#include "particle/particle.h" + +#include "resources/ambientlayer.h" +#include "resources/image.h" +#include "resources/resourcemanager.h" +#include "resources/subimage.h" + +#include "utils/delete2.h" +#include "utils/dtor.h" +#include "utils/mkdir.h" +#include "utils/physfstools.h" +#include "utils/timer.h" + +#include <climits> +#include <queue> + +#include <sys/stat.h> + +#include "debug.h" + +/** + * A location on a tile map. Used for pathfinding, open list. + */ +struct Location final +{ + /** + * Constructor. + */ + Location(const int px, const int py, MetaTile *const ptile): + x(px), y(py), tile(ptile) + {} + + /** + * Comparison operator. + */ + bool operator< (const Location &loc) const + { + return tile->Fcost > loc.tile->Fcost; + } + + int x, y; + MetaTile *tile; +}; + +class ActorFunctuator final +{ + public: + bool operator()(const Actor *const a, const Actor *const b) const + { + if (!a || !b) + return false; + return a->getSortPixelY() < b->getSortPixelY(); + } +} actorCompare; + +TileAnimation::TileAnimation(Animation *const ani): + mAffected(), + mAnimation(new SimpleAnimation(ani)), + mLastImage(nullptr) +{ +} + +TileAnimation::~TileAnimation() +{ + delete2(mAnimation); +} + +bool TileAnimation::update(const int ticks) +{ + if (!mAnimation) + return false; + + // update animation + if (!mAnimation->update(ticks)) + return false; + + // exchange images + Image *const img = mAnimation->getCurrentImage(); + if (img != mLastImage) + { + FOR_EACH (TilePairVectorCIter, i, mAffected) + { + if (i->first) + i->first->setTile(i->second, img); + } + mLastImage = img; + } + return true; +} + +Map::Map(const int width, const int height, + const int tileWidth, const int tileHeight) : + Properties(), + mWidth(width), mHeight(height), + mTileWidth(tileWidth), mTileHeight(tileHeight), + mMaxTileHeight(height), + mMetaTiles(new MetaTile[mWidth * mHeight]), + mWalkLayer(nullptr), + mLayers(), + mTilesets(), + mActors(), + mHasWarps(false), + mDebugFlags(MAP_NORMAL), + mOnClosedList(1), + mOnOpenList(2), + mBackgrounds(), + mForegrounds(), + mLastAScrollX(0.0F), + mLastAScrollY(0.0F), + mParticleEffects(), + mMapPortals(), + mTileAnimations(), + mOverlayDetail(config.getIntValue("OverlayDetail")), + mOpacity(config.getFloatValue("guialpha")), +#ifdef USE_OPENGL + mOpenGL(intToRenderType(config.getIntValue("opengl"))), +#else + mOpenGL(RENDER_SOFTWARE), +#endif + mPvp(0), + mTilesetsIndexed(false), + mIndexedTilesets(nullptr), + mIndexedTilesetsSize(0), + mActorFixX(0), + mActorFixY(0), + mVersion(0), + mSpecialLayer(new SpecialLayer(width, height)), + mTempLayer(new SpecialLayer(width, height)), + mObjects(new ObjectsLayer(width, height)), + mFringeLayer(nullptr), + mLastX(-1), + mLastY(-1), + mLastScrollX(-1), + mLastScrollY(-1), + mDrawX(-1), + mDrawY(-1), + mDrawScrollX(-1), + mDrawScrollY(-1), + mMask(1), + mAtlas(nullptr), + mHeights(nullptr), + mRedrawMap(true), + mBeingOpacity(false), + mCustom(false) +{ + const int size = mWidth * mHeight; + for (int i = 0; i < NB_BLOCKTYPES; i++) + { + mOccupation[i] = new unsigned[static_cast<size_t>(size)]; + memset(mOccupation[i], 0, static_cast<size_t>(size) + * sizeof(unsigned)); + } + + config.addListener("OverlayDetail", this); + config.addListener("guialpha", this); + config.addListener("beingopacity", this); + + if (mOpacity != 1.0F) + mBeingOpacity = config.getBoolValue("beingopacity"); + else + mBeingOpacity = false; +} + +Map::~Map() +{ + config.removeListeners(this); + CHECKLISTENERS + + delete [] mMetaTiles; + for (int i = 0; i < NB_BLOCKTYPES; i++) + delete [] mOccupation[i]; + + if (mWalkLayer) + { + mWalkLayer->decRef(); + mWalkLayer = nullptr; + } + mFringeLayer = nullptr; + delete_all(mLayers); + delete_all(mTilesets); + delete_all(mForegrounds); + delete_all(mBackgrounds); + delete_all(mTileAnimations); + delete2(mSpecialLayer); + delete2(mTempLayer); + delete2(mObjects); + delete_all(mMapPortals); + if (mAtlas) + { + mAtlas->decRef(); + mAtlas = nullptr; + } + delete2(mHeights); +} + +void Map::optionChanged(const std::string &value) +{ + if (value == "OverlayDetail") + { + mOverlayDetail = config.getIntValue("OverlayDetail"); + } + else if (value == "guialpha") + { + mOpacity = config.getFloatValue("guialpha"); + if (mOpacity != 1.0F) + mBeingOpacity = config.getBoolValue("beingopacity"); + else + mBeingOpacity = false; + } + else if (value == "beingopacity") + { + if (mOpacity != 1.0F) + mBeingOpacity = config.getBoolValue("beingopacity"); + else + mBeingOpacity = false; + } +} + +void Map::initializeAmbientLayers() +{ + ResourceManager *const resman = ResourceManager::getInstance(); + + // search for "foreground*" or "overlay*" (old term) in map properties + for (int i = 0; /* terminated by a break */; i++) + { + std::string name; + if (hasProperty(std::string("foreground").append( + toString(i)).append("image"))) + { + name = "foreground" + toString(i); + } + else if (hasProperty(std::string("overlay").append( + toString(i)).append("image"))) + { + name = "overlay" + toString(i); + } + else + { + break; // the FOR loop + } + + Image *const img = resman->getImage(getProperty(name + "image")); + if (img) + { + int mask = atoi(getProperty(name + "mask").c_str()); + if (!mask) + mask = 1; + const float parallax = getFloatProperty(name + "parallax"); + mForegrounds.push_back(new AmbientLayer(img, + getFloatProperty(name + "parallaxX", parallax), + getFloatProperty(name + "parallaxY", parallax), + getFloatProperty(name + "posX"), + getFloatProperty(name + "posY"), + getFloatProperty(name + "scrollX"), + getFloatProperty(name + "scrollY"), + getBoolProperty(name + "keepratio"), + mask)); + + // The AmbientLayer takes control over the image. + img->decRef(); + } + } + + // search for "background*" in map properties + for (int i = 0; hasProperty(std::string("background").append( + toString(i)).append("image")); i ++) + { + const std::string name("background" + toString(i)); + Image *const img = resman->getImage(getProperty(name + "image")); + + if (img) + { + int mask = atoi(getProperty(name + "mask").c_str()); + if (!mask) + mask = 1; + + const float parallax = getFloatProperty(name + "parallax"); + mForegrounds.push_back(new AmbientLayer(img, + getFloatProperty(name + "parallaxX", parallax), + getFloatProperty(name + "parallaxY", parallax), + getFloatProperty(name + "posX"), + getFloatProperty(name + "posY"), + getFloatProperty(name + "scrollX"), + getFloatProperty(name + "scrollY"), + getBoolProperty(name + "keepratio"), + mask)); + + // The AmbientLayer takes control over the image. + img->decRef(); + } + } +} + +void Map::addLayer(MapLayer *const layer) +{ + if (layer) + { + mLayers.push_back(layer); + if (layer->isFringeLayer() && !mFringeLayer) + mFringeLayer = layer; + } +} + +void Map::addTileset(Tileset *const tileset) +{ + if (!tileset) + return; + + mTilesets.push_back(tileset); + const int height = tileset->getHeight(); + if (height > mMaxTileHeight) + mMaxTileHeight = height; +} + +void Map::update(const int ticks) +{ + // Update animated tiles + FOR_EACH (TileAnimationMapCIter, iAni, mTileAnimations) + { + TileAnimation *const tileAni = iAni->second; + if (tileAni && tileAni->update(ticks)) + mRedrawMap = true; + } +} + +void Map::draw(Graphics *const graphics, int scrollX, int scrollY) +{ + if (!player_node) + return; + + BLOCK_START("Map::draw") + // Calculate range of tiles which are on-screen + const int endPixelY = graphics->mHeight + scrollY + mTileHeight - 1 + + mMaxTileHeight - mTileHeight; + const int startX = scrollX / mTileWidth - 2; + const int startY = scrollY / mTileHeight; + const int endX = (graphics->mWidth + scrollX + mTileWidth - 1) + / mTileWidth + 1; + const int endY = endPixelY / mTileHeight + 1; + + // Make sure actors are sorted ascending by Y-coordinate + // so that they overlap correctly + BLOCK_START("Map::draw sort") + mActors.sort(actorCompare); + BLOCK_END("Map::draw sort") + + // update scrolling of all ambient layers + updateAmbientLayers(static_cast<float>(scrollX), + static_cast<float>(scrollY)); + + // Draw backgrounds + drawAmbientLayers(graphics, BACKGROUND_LAYERS, mOverlayDetail); + + if (mDebugFlags == MAP_BLACKWHITE && userPalette) + { + graphics->setColor(userPalette->getColorWithAlpha( + UserPalette::WALKABLE_HIGHLIGHT)); + + graphics->fillRectangle(Rect(0, 0, + graphics->mWidth, graphics->mHeight)); + } + +#ifdef USE_OPENGL + int updateFlag = 0; + + if (mOpenGL == RENDER_NORMAL_OPENGL || mOpenGL == RENDER_GLES_OPENGL) + { + if (mLastX != startX || mLastY != startY || mLastScrollX != scrollX + || mLastScrollY != scrollY) + { // player moving + mLastX = startX; + mLastY = startY; + mLastScrollX = scrollX; + mLastScrollY = scrollY; + updateFlag = 2; + } + else if (mRedrawMap || startX != mDrawX || startY != mDrawY || + scrollX != mDrawScrollX || scrollY != mDrawScrollY) + { // player mode to new position + mRedrawMap = false; + mDrawX = startX; + mDrawY = startY; + mDrawScrollX = scrollX; + mDrawScrollY = scrollY; + updateFlag = 1; + } + } +#endif + + if (mDebugFlags == MAP_SPECIAL3 || mDebugFlags == MAP_BLACKWHITE) + { + if (mFringeLayer) + { + mFringeLayer->setSpecialLayer(mSpecialLayer); + mFringeLayer->setTempLayer(mTempLayer); + mFringeLayer->drawFringe(graphics, startX, startY, endX, endY, + scrollX, scrollY, &mActors, mDebugFlags, mActorFixY); + } + } + else + { + bool overFringe = false; + + for (LayersCIter layeri = mLayers.begin(), layeri_end = mLayers.end(); + layeri != layeri_end && !overFringe; ++ layeri) + { + MapLayer *const layer = *layeri; + if (!(layer->mMask & mMask)) + continue; + + if (layer->isFringeLayer()) + { + layer->setSpecialLayer(mSpecialLayer); + layer->setTempLayer(mTempLayer); + if (mDebugFlags == MAP_SPECIAL2) + overFringe = true; + + layer->drawFringe(graphics, startX, startY, endX, endY, + scrollX, scrollY, &mActors, mDebugFlags, mActorFixY); + } + else + { +#ifdef USE_OPENGL + if (mOpenGL == RENDER_NORMAL_OPENGL + || mOpenGL == RENDER_GLES_OPENGL) + { + if (updateFlag) + { + layer->updateOGL(graphics, startX, startY, + endX, endY, scrollX, scrollY, mDebugFlags); + } + + layer->drawOGL(graphics); + } + else +#endif + { + layer->draw(graphics, startX, startY, endX, endY, + scrollX, scrollY, mDebugFlags); + } + } + } + } + + // Don't draw if gui opacity == 1 + if (mBeingOpacity && mOpacity != 1.0F) + { + // Draws beings with a lower opacity to make them visible + // even when covered by a wall or some other elements... + ActorsCIter ai = mActors.begin(); + const ActorsCIter ai_end = mActors.end(); + while (ai != ai_end) + { + if (Actor *const actor = *ai) + { + if (mOpenGL == RENDER_SOFTWARE) + { + const int x = actor->getTileX(); + const int y = actor->getTileY(); + if (x < startX || x > endX || y < startY || y > endY) + { + ++ai; + continue; + } + } + // For now, just draw actors with only one layer. + if (actor->getNumberOfLayers() == 1) + { + actor->setAlpha(0.3F); + actor->draw(graphics, -scrollX, -scrollY); + actor->setAlpha(1.0F); + } + } + ++ai; + } + } + + drawAmbientLayers(graphics, FOREGROUND_LAYERS, mOverlayDetail); + BLOCK_END("Map::draw") +} + +#define fillCollision(collision, color) \ + if (x < endX && mMetaTiles[tilePtr].blockmask & collision)\ + {\ + width = mapTileSize;\ + for (int x2 = tilePtr + 1; x < endX; x2 ++)\ + {\ + if (!(mMetaTiles[x2].blockmask & collision))\ + break;\ + width += mapTileSize;\ + x ++;\ + tilePtr ++;\ + }\ + if (width && userPalette)\ + {\ + graphics->setColor(userPalette->getColorWithAlpha(\ + UserPalette::color));\ + graphics->fillRectangle(Rect(\ + x0 * mTileWidth - scrollX, \ + y * mTileHeight - scrollY, \ + width, mapTileSize));\ + }\ + }\ + +void Map::drawCollision(Graphics *const graphics, + const int scrollX, const int scrollY, + const int debugFlags) const +{ + const int endPixelY = graphics->mHeight + scrollY + mTileHeight - 1; + int startX = scrollX / mTileWidth; + int startY = scrollY / mTileHeight; + int endX = (graphics->mWidth + scrollX + mTileWidth - 1) / mTileWidth; + int endY = endPixelY / mTileHeight; + + if (startX < 0) + startX = 0; + if (startY < 0) + startY = 0; + if (endX > mWidth) + endX = mWidth; + if (endY > mHeight) + endY = mHeight; + + if (debugFlags < MAP_SPECIAL) + { + graphics->setColor(userPalette->getColorWithAlpha(UserPalette::NET)); + graphics->drawNet( + startX * mTileWidth - scrollX, + startY * mTileHeight - scrollY, + endX * mTileWidth - scrollX, + endY * mTileHeight - scrollY, + mapTileSize, mapTileSize); + } + + for (int y = startY; y < endY; y++) + { + const int yWidth = y * mWidth; + int tilePtr = startX + yWidth; + for (int x = startX; x < endX; x++, tilePtr++) + { + int width = 0; + const int x0 = x; + + fillCollision(BLOCKMASK_WALL, COLLISION_HIGHLIGHT); + fillCollision(BLOCKMASK_AIR, AIR_COLLISION_HIGHLIGHT); + fillCollision(BLOCKMASK_WATER, WATER_COLLISION_HIGHLIGHT); + fillCollision(BLOCKMASK_GROUNDTOP, GROUNDTOP_COLLISION_HIGHLIGHT); + } + } +} + +void Map::updateAmbientLayers(const float scrollX, const float scrollY) +{ + BLOCK_START("Map::updateAmbientLayers") + static int lastTick = tick_time; + + if (mLastAScrollX == 0.0F && mLastAScrollY == 0.0F) + { + // First call - initialisation + mLastAScrollX = scrollX; + mLastAScrollY = scrollY; + } + + // Update Overlays + const float dx = scrollX - mLastAScrollX; + const float dy = scrollY - mLastAScrollY; + const int timePassed = get_elapsed_time(lastTick); + + // need check mask to update or not to update + + FOR_EACH (AmbientLayerVectorIter, i, mBackgrounds) + { + AmbientLayer *const layer = *i; + if (layer && (layer->mMask & mMask)) + layer->update(timePassed, dx, dy); + } + + FOR_EACH (AmbientLayerVectorIter, i, mForegrounds) + { + AmbientLayer *const layer = *i; + if (layer && (layer->mMask & mMask)) + layer->update(timePassed, dx, dy); + } + + mLastAScrollX = scrollX; + mLastAScrollY = scrollY; + lastTick = tick_time; + BLOCK_END("Map::updateAmbientLayers") +} + +void Map::drawAmbientLayers(Graphics *const graphics, const LayerType type, + const int detail) +{ + BLOCK_START("Map::drawAmbientLayers") + // Detail 0 = no ambient effects except background image + if (detail <= 0 && type != BACKGROUND_LAYERS) + { + BLOCK_END("Map::drawAmbientLayers") + return; + } + + // find out which layer list to draw + AmbientLayerVector *layers = nullptr; + switch (type) + { + case FOREGROUND_LAYERS: + layers = &mForegrounds; + break; + case BACKGROUND_LAYERS: + layers = &mBackgrounds; + break; + default: + return; + } + + // Draw overlays + FOR_EACHP (AmbientLayerVectorCIter, i, layers) + { + const AmbientLayer *const layer = *i; + // need check mask to draw or not to draw + if (layer && (layer->mMask & mMask)) + (layer)->draw(graphics, graphics->mWidth, graphics->mHeight); + + // Detail 1: only one overlay, higher: all overlays + if (detail == 1) + break; + } + BLOCK_END("Map::drawAmbientLayers") +} + +const Tileset *Map::getTilesetWithGid(const int gid) const +{ + if (gid >= 0 && gid < mIndexedTilesetsSize) + return mIndexedTilesets[gid]; + else + return nullptr; +} + +void Map::blockTile(const int x, const int y, const BlockType type) +{ + if (type == BLOCKTYPE_NONE || !contains(x, y)) + return; + + const int tileNum = x + y * mWidth; + + if (mOccupation[static_cast<size_t>(type)][tileNum] < UINT_MAX && + (++mOccupation[static_cast<size_t>(type)][tileNum]) > 0) + { + switch (type) + { + case BLOCKTYPE_WALL: + mMetaTiles[tileNum].blockmask |= BLOCKMASK_WALL; + break; + case BLOCKTYPE_CHARACTER: + mMetaTiles[tileNum].blockmask |= BLOCKMASK_CHARACTER; + break; + case BLOCKTYPE_MONSTER: + mMetaTiles[tileNum].blockmask |= BLOCKMASK_MONSTER; + break; + case BLOCKTYPE_AIR: + mMetaTiles[tileNum].blockmask |= BLOCKMASK_AIR; + break; + case BLOCKTYPE_WATER: + mMetaTiles[tileNum].blockmask |= BLOCKMASK_WATER; + break; + case BLOCKTYPE_GROUND: + mMetaTiles[tileNum].blockmask |= BLOCKMASK_GROUND; + break; + case BLOCKTYPE_GROUNDTOP: + mMetaTiles[tileNum].blockmask |= BLOCKMASK_GROUNDTOP; + break; + default: + case BLOCKTYPE_NONE: + case NB_BLOCKTYPES: + // Do nothing. + break; + } + } +} + +bool Map::getWalk(const int x, const int y, const unsigned char walkmask) const +{ + // You can't walk outside of the map + if (x < 0 || y < 0 || x >= mWidth || y >= mHeight) + return false; + + // Check if the tile is walkable + return !(mMetaTiles[x + y * mWidth].blockmask & walkmask); +} + +unsigned char Map::getBlockMask(const int x, const int y) const +{ + // You can't walk outside of the map + if (x < 0 || y < 0 || x >= mWidth || y >= mHeight) + return 0; + + // Check if the tile is walkable + return mMetaTiles[x + y * mWidth].blockmask; +} + +void Map::setWalk(const int x, const int y, const bool walkable A_UNUSED) +{ + blockTile(x, y, Map::BLOCKTYPE_GROUNDTOP); +} + +bool Map::contains(const int x, const int y) const +{ + return x >= 0 && y >= 0 && x < mWidth && y < mHeight; +} + +const MetaTile *Map::getMetaTile(const int x, const int y) const +{ + return &mMetaTiles[x + y * mWidth]; +} + +Actors::iterator Map::addActor(Actor *const actor) +{ + mActors.push_front(actor); +// mSpritesUpdated = true; + return mActors.begin(); +} + +void Map::removeActor(const Actors::iterator &iterator) +{ + mActors.erase(iterator); +// mSpritesUpdated = true; +} + +const std::string Map::getMusicFile() const +{ + return getProperty("music"); +} + +const std::string Map::getName() const +{ + if (hasProperty("name")) + return getProperty("name"); + + return getProperty("mapname"); +} + +const std::string Map::getFilename() const +{ + const std::string fileName = getProperty("_filename"); + const size_t lastSlash = fileName.rfind("/") + 1; + return fileName.substr(lastSlash, fileName.rfind(".") - lastSlash); +} + +Path Map::findPath(const int startX, const int startY, + const int destX, const int destY, + const unsigned char walkmask, const int maxCost) +{ + // The basic walking cost of a tile. + static const int basicCost = 100; + const int basicCost2 = 100 * 362 / 256; + const float basicCostF = 100.0 * 362 / 256; + + // Path to be built up (empty by default) + Path path; + + if (startX >= mWidth || startY >= mHeight || startX < 0 || startY < 0) + return path; + + // Return when destination not walkable + if (!getWalk(destX, destY, walkmask)) + return path; + + // Reset starting tile's G cost to 0 + MetaTile *const startTile = &mMetaTiles[startX + startY * mWidth]; + if (!startTile) + return path; + + startTile->Gcost = 0; + + // Declare open list, a list with open tiles sorted on F cost + std::priority_queue<Location> openList; + + // Add the start point to the open list + openList.push(Location(startX, startY, startTile)); + + bool foundPath = false; + + // Keep trying new open tiles until no more tiles to try or target found + while (!openList.empty() && !foundPath) + { + // Take the location with the lowest F cost from the open list. + const Location curr = openList.top(); + openList.pop(); + + const MetaTile *const tile = curr.tile; + + // If the tile is already on the closed list, this means it has already + // been processed with a shorter path to the start point (lower G cost) + if (tile->whichList == mOnClosedList) + continue; + + // Put the current tile on the closed list + curr.tile->whichList = mOnClosedList; + + const int curWidth = curr.y * mWidth; + const int tileGcost = tile->Gcost; + + // Check the adjacent tiles + for (int dy = -1; dy <= 1; dy++) + { + const int y = curr.y + dy; + if (y < 0 || y >= mHeight) + continue; + + const int yWidth = y * mWidth; + const int dy1 = std::abs(y - destY); + + for (int dx = -1; dx <= 1; dx++) + { + // Calculate location of tile to check + const int x = curr.x + dx; + + // Skip if if we're checking the same tile we're leaving from, + // or if the new location falls outside of the map boundaries + if ((dx == 0 && dy == 0) || x < 0 || x >= mWidth) + continue; + + MetaTile *const newTile = &mMetaTiles[x + yWidth]; + + // Skip if the tile is on the closed list or is not walkable + // unless its the destination tile + // +++ here need check block must depend on player abilities. + if (newTile->whichList == mOnClosedList || + ((newTile->blockmask & walkmask) + && !(x == destX && y == destY)) + || (newTile->blockmask & BLOCKMASK_WALL)) + { + continue; + } + + // When taking a diagonal step, verify that we can skip the + // corner. + if (dx != 0 && dy != 0) + { + const MetaTile *const t1 = &mMetaTiles[curr.x + + (curr.y + dy) * mWidth]; + const MetaTile *const t2 = &mMetaTiles[curr.x + + dx + curWidth]; + + // +++ here need check block must depend + // on player abilities. + if (((t1->blockmask | t2->blockmask) & BLOCKMASK_WALL)) + continue; + } + + // Calculate G cost for this route, ~sqrt(2) for moving diagonal + int Gcost = tileGcost + (dx == 0 || dy == 0 + ? basicCost : basicCost2); + + /* Demote an arbitrary direction to speed pathfinding by + adding a defect (TODO: change depending on the desired + visual effect, e.g. a cross-product defect toward + destination). + Important: as long as the total defect along any path is + less than the basicCost, the pathfinder will still find one + of the shortest paths! */ + if (dx == 0 || dy == 0) + { + // Demote horizontal and vertical directions, so that two + // consecutive directions cannot have the same Fcost. + ++Gcost; + } + + // It costs extra to walk through a being (needs to be enough + // to make it more attractive to walk around). +// if (occupied(x, y)) +// { +// Gcost += 3 * basicCost; +// } + + // Skip if Gcost becomes too much + // Warning: probably not entirely accurate + if (maxCost > 0 && Gcost > maxCost * basicCost) + continue; + + if (newTile->whichList != mOnOpenList) + { + // Found a new tile (not on open nor on closed list) + + /* Update Hcost of the new tile. The pathfinder does not + work reliably if the heuristic cost is higher than the + real cost. In particular, using Manhattan distance is + forbidden here. */ + const int dx1 = std::abs(x - destX); + newTile->Hcost = std::abs(dx1 - dy1) * basicCost + + std::min(dx1, dy1) * (basicCostF); + + // Set the current tile as the parent of the new tile + newTile->parentX = curr.x; + newTile->parentY = curr.y; + + // Update Gcost and Fcost of new tile + newTile->Gcost = Gcost; + newTile->Fcost = Gcost + newTile->Hcost; + + if (x != destX || y != destY) + { + // Add this tile to the open list + newTile->whichList = mOnOpenList; + openList.push(Location(x, y, newTile)); + } + else + { + // Target location was found + foundPath = true; + } + } + else if (Gcost < newTile->Gcost) + { + // Found a shorter route. + // Update Gcost and Fcost of the new tile + newTile->Gcost = Gcost; + newTile->Fcost = Gcost + newTile->Hcost; + + // Set the current tile as the parent of the new tile + newTile->parentX = curr.x; + newTile->parentY = curr.y; + + // Add this tile to the open list (it's already + // there, but this instance has a lower F score) + openList.push(Location(x, y, newTile)); + } + } + } + } + + // Two new values to indicate whether a tile is on the open or closed list, + // this way we don't have to clear all the values between each pathfinding. + if (mOnOpenList > UINT_MAX - 2) + { + // We reset the list memebers value. + mOnClosedList = 1; + mOnOpenList = 2; + + // Clean up the metaTiles + const int size = mWidth * mHeight; + for (int i = 0; i < size; ++i) + mMetaTiles[i].whichList = 0; + } + else + { + mOnClosedList += 2; + mOnOpenList += 2; + } + + // If a path has been found, iterate backwards using the parent locations + // to extract it. + if (foundPath) + { + int pathX = destX; + int pathY = destY; + + while (pathX != startX || pathY != startY) + { + // Add the new path node to the start of the path list + path.push_front(Position(pathX, pathY)); + + // Find out the next parent + const MetaTile *const tile = &mMetaTiles[pathX + pathY * mWidth]; + pathX = tile->parentX; + pathY = tile->parentY; + } + } + + return path; +} + +void Map::addParticleEffect(const std::string &effectFile, + const int x, const int y, const int w, const int h) +{ + ParticleEffectData newEffect; + newEffect.file = effectFile; + newEffect.x = x; + newEffect.y = y; + newEffect.w = w; + newEffect.h = h; + mParticleEffects.push_back(newEffect); +} + +void Map::initializeParticleEffects(Particle *const engine) +{ + if (!engine) + return; + + if (config.getBoolValue("particleeffects")) + { + for (std::vector<ParticleEffectData>::const_iterator + i = mParticleEffects.begin(); + i != mParticleEffects.end(); ++i) + { + Particle *const p = engine->addEffect(i->file, i->x, i->y); + if (p && i->w > 0 && i->h > 0) + p->adjustEmitterSize(i->w, i->h); + } + } +} + +void Map::addExtraLayer() +{ + if (!mSpecialLayer) + { + logger->log1("No special layer"); + return; + } + const std::string mapFileName = getUserMapDirectory().append( + "/extralayer.txt"); + logger->log("loading extra layer: " + mapFileName); + struct stat statbuf; + if (!stat(mapFileName.c_str(), &statbuf) && S_ISREG(statbuf.st_mode)) + { + std::ifstream mapFile; + mapFile.open(mapFileName.c_str(), std::ios::in); + if (!mapFile.is_open()) + { + mapFile.close(); + return; + } + char line[201]; + + while (mapFile.getline(line, 200)) + { + std::string buf; + std::string str = line; + if (!str.empty()) + { + std::string x; + std::string y; + std::string type1; + std::string comment; + std::stringstream ss(str); + ss >> x; + ss >> y; + ss >> type1; + ss >> comment; + while (ss >> buf) + comment.append(" ").append(buf); + + const int type = atoi(type1.c_str()); + + if (comment.empty()) + { + if (type < MapItem::ARROW_UP + || type > MapItem::ARROW_RIGHT) + { + comment = "unknown"; + } + } + if (type == MapItem::PORTAL) + { + updatePortalTile(comment, type, atoi(x.c_str()), + atoi(y.c_str()), false); + } + else if (type == MapItem::HOME) + { + updatePortalTile(comment, type, atoi(x.c_str()), + atoi(y.c_str())); + } + else + { + addPortalTile(comment, type, atoi(x.c_str()), + atoi(y.c_str())); + } + } + } + mapFile.close(); + } +} + +void Map::saveExtraLayer() const +{ + if (!mSpecialLayer) + { + logger->log1("No special layer"); + return; + } + const std::string mapFileName = getUserMapDirectory().append( + "/extralayer.txt"); + logger->log("saving extra layer: " + mapFileName); + + if (mkdir_r(getUserMapDirectory().c_str())) + { + logger->log(strprintf("%s doesn't exist and can't be created! " + "Exiting.", getUserMapDirectory().c_str())); + return; + } + + std::ofstream mapFile; + mapFile.open(mapFileName.c_str(), std::ios::binary); + if (!mapFile.is_open()) + { + logger->log1("Unable to open extralayer.txt for writing"); + return; + } + + const int width = mSpecialLayer->mWidth; + const int height = mSpecialLayer->mHeight; + + for (int x = 0; x < width; x ++) + { + for (int y = 0; y < height; y ++) + { + const MapItem *const item = mSpecialLayer->getTile(x, y); + if (item && item->mType != MapItem::EMPTY + && item->mType != MapItem::HOME) + { + mapFile << x << " " << y << " " + << static_cast<int>(item->mType) << " " + << item->mComment << std::endl; + } + } + } + mapFile.close(); +} + +std::string Map::getUserMapDirectory() const +{ + return client->getServerConfigDirectory() + + dirSeparator + getProperty("_realfilename"); +} + +void Map::addRange(const std::string &name, const int type, + const int x, const int y, const int dx, const int dy) +{ + if (!mObjects) + return; + + mObjects->addObject(name, type, x / mapTileSize, y / mapTileSize, + dx / mapTileSize, dy / mapTileSize); +} + +void Map::addPortal(const std::string &name, const int type, + const int x, const int y, const int dx, const int dy) +{ + addPortalTile(name, type, (x / mapTileSize) + (dx / mapTileSize / 2), + (y / mapTileSize) + (dy / mapTileSize / 2)); +} + +void Map::addPortalTile(const std::string &name, const int type, + const int x, const int y) +{ + if (mSpecialLayer) + mSpecialLayer->setTile(x, y, new MapItem(type, name, x, y)); + + mMapPortals.push_back(new MapItem(type, name, x, y)); +} + +void Map::updatePortalTile(const std::string &name, const int type, + const int x, const int y, const bool addNew) +{ + MapItem *item = findPortalXY(x, y); + if (item) + { + item->mComment = name; + item->setType(type); + item->mX = x; + item->mY = y; + if (mSpecialLayer) + { + item = new MapItem(type, name, x, y); + mSpecialLayer->setTile(x, y, item); + } + } + else if (addNew) + { + addPortalTile(name, type, x, y); + } +} + +MapItem *Map::findPortalXY(const int x, const int y) const +{ + FOR_EACH (std::vector<MapItem*>::const_iterator, it, mMapPortals) + { + if (!*it) + continue; + + MapItem *const item = *it; + if (item->mX == x && item->mY == y) + return item; + } + return nullptr; +} + +const TileAnimation *Map::getAnimationForGid(const int gid) const +{ + if (mTileAnimations.empty()) + return nullptr; + + TileAnimationMapCIter i = mTileAnimations.find(gid); + return (i == mTileAnimations.end()) ? nullptr : i->second; +} + +void Map::setPvpMode(const int mode) +{ + const int oldMode = mPvp; + + if (!mode) + mPvp = 0; + else + mPvp |= mode; + + if (mPvp != oldMode && player_node) + { + switch (mPvp) + { + case 0: + NotifyManager::notify(NotifyManager::PVP_OFF_GVG_OFF); + break; + case 1: + NotifyManager::notify(NotifyManager::PVP_ON); + break; + case 2: + NotifyManager::notify(NotifyManager::GVG_ON); + break; + case 3: + NotifyManager::notify(NotifyManager::PVP_ON_GVG_ON); + break; + default: + NotifyManager::notify(NotifyManager::PVP_UNKNOWN); + break; + } + } +} + +std::string Map::getObjectData(const unsigned x, const unsigned y, + const int type) const +{ + if (!mObjects) + return ""; + + MapObjectList *const list = mObjects->getAt(x, y); + if (!list) + return ""; + + std::vector<MapObject>::const_iterator it = list->objects.begin(); + const std::vector<MapObject>::const_iterator it_end = list->objects.end(); + while (it != it_end) + { + if ((*it).type == type) + return (*it).data; + ++ it; + } + + return ""; +} + +void Map::indexTilesets() +{ + if (mTilesetsIndexed) + return; + + mTilesetsIndexed = true; + + const Tileset *s = nullptr; + size_t sSz = 0; + FOR_EACH (Tilesets::const_iterator, it, mTilesets) + { + const size_t sz = (*it)->size(); + if (!s || static_cast<size_t>(s->getFirstGid()) + sSz + < static_cast<size_t>((*it)->getFirstGid()) + sz) + { + s = *it; + sSz = sz; + } + } + if (!s) + { + mIndexedTilesetsSize = 0; + mIndexedTilesets = nullptr; + return; + } + + const int size = static_cast<int>(s->getFirstGid()) + + static_cast<int>(s->size()); + mIndexedTilesetsSize = size; + mIndexedTilesets = new Tileset*[static_cast<size_t>(size)]; + std::fill_n(mIndexedTilesets, size, static_cast<Tileset*>(nullptr)); + + FOR_EACH (Tilesets::const_iterator, it, mTilesets) + { + Tileset *const s2 = *it; + if (s2) + { + const int start = s2->getFirstGid(); + const int end = start + static_cast<int>(s2->size()); + for (int f = start; f < end; f ++) + { + if (f < size) + mIndexedTilesets[f] = s2; + } + } + } +} + +void Map::clearIndexedTilesets() +{ + if (!mTilesetsIndexed) + return; + + mTilesetsIndexed = false; + delete [] mIndexedTilesets; + mIndexedTilesetsSize = 0; +} + +void Map::reduce() +{ +#ifdef USE_SDL2 + return; +#else + if (!mFringeLayer || mOpenGL != RENDER_SOFTWARE || + !config.getBoolValue("enableMapReduce")) + { + return; + } + + int cnt = 0; + for (int x = 0; x < mWidth; x ++) + { + for (int y = 0; y < mHeight; y ++) + { + bool correct(true); + bool dontHaveAlpha(false); + + FOR_EACH (LayersCIter, layeri, mLayers) + { + const MapLayer *const layer = *layeri; + if (x >= layer->mWidth || y >= layer->mHeight) + continue; + + Image *const img = layer->mTiles[x + y * layer->mWidth]; + if (img) + { + if (img->hasAlphaChannel() && img->isAlphaCalculated()) + { + if (!img->isAlphaVisible()) + { + dontHaveAlpha = true; + img->setAlphaVisible(false); + } + } + else if (img->mBounds.w > mapTileSize + || img->mBounds.h > mapTileSize) + { + correct = false; + img->setAlphaVisible(true); + break; + } + else if (!img->isHasAlphaChannel()) + { + dontHaveAlpha = true; + img->setAlphaVisible(false); + } + else if (img->hasAlphaChannel()) + { + const uint8_t *const arr = img->SDLgetAlphaChannel(); + if (!arr) + continue; + + bool bad(false); + bool stop(false); + int width; + const SubImage *const subImg + = dynamic_cast<SubImage*>(img); + if (subImg) + width = subImg->mInternalBounds.w; + else + width = img->mBounds.w; + + for (int f = img->mBounds.x; + f < img->mBounds.x + img->mBounds.w; f ++) + { + for (int d = img->mBounds.y; + d < img->mBounds.y + img->mBounds.h; d ++) + { + const uint8_t chan = arr[f + d * width]; + if (chan != 255) + { + bad = true; + stop = true; + break; + } + } + if (stop) + break; + } + if (!bad) + { + dontHaveAlpha = true; + img->setAlphaVisible(false); + } + else + { + img->setAlphaVisible(true); + } + } + img->setAlphaCalculated(true); + } + } + if (!correct || !dontHaveAlpha) + continue; + + Layers::reverse_iterator ri = mLayers.rbegin(); + while (ri != mLayers.rend()) + { + const MapLayer *const layer = *ri; + if (x >= layer->mWidth || y >= layer->mHeight) + { + ++ ri; + continue; + } + + const Image *img = layer->mTiles[x + y * layer->mWidth]; + if (img && !img->isAlphaVisible()) + { // removing all down tiles + ++ ri; + while (ri != mLayers.rend()) + { + MapLayer *const layer2 = *ri; + const size_t pos = static_cast<size_t>( + x + y * layer2->mWidth); + img = layer2->mTiles[pos]; + if (img) + { + layer2->mTiles[pos] = nullptr; + cnt ++; + } + ++ ri; + } + break; + } + ++ ri; + } + } + } + logger->log("tiles reduced: %d", cnt); +#endif +} + +void Map::redrawMap() +{ + mRedrawMap = true; +} + +void Map::addHeights(MapHeights *const heights) +{ + delete mHeights; + mHeights = heights; +} + +uint8_t Map::getHeightOffset(const int x, const int y) const +{ + if (!mHeights) + return 0; + return mHeights->getHeight(x, y); +} + +void Map::setMask(const int mask) +{ + if (mask != mMask) + mRedrawMap = true; + mMask = mask; +} + +void Map::setMusicFile(const std::string &file) +{ + setProperty("music", file); +} diff --git a/src/resources/map/map.h b/src/resources/map/map.h new file mode 100644 index 000000000..f0712be76 --- /dev/null +++ b/src/resources/map/map.h @@ -0,0 +1,540 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef RESOURCES_MAP_MAP_H +#define RESOURCES_MAP_MAP_H + +#include "position.h" + +#include "being/actor.h" + +#include "resources/map/properties.h" + +#include "listeners/configlistener.h" + +#include "render/renderers.h" + +#include <string> +#include <vector> + +class Animation; +class AmbientLayer; +class Image; +class MapHeights; +class MapItem; +class MapLayer; +class ObjectsLayer; +class Particle; +class Resource; +class SimpleAnimation; +class SpecialLayer; +class Tileset; +class WalkLayer; + +typedef std::vector<Tileset*> Tilesets; +typedef std::vector<MapLayer*> Layers; +typedef Layers::const_iterator LayersCIter; + +typedef std::vector<std::pair<MapLayer*, int> > TilePairVector; +typedef TilePairVector::const_iterator TilePairVectorCIter; + +typedef std::vector<AmbientLayer*> AmbientLayerVector; +typedef AmbientLayerVector::const_iterator AmbientLayerVectorCIter; +typedef AmbientLayerVector::iterator AmbientLayerVectorIter; + +static const int mapTileSize = 32; + +/** + * A meta tile stores additional information about a location on a tile map. + * This is information that doesn't need to be repeated for each tile in each + * layer of the map. + */ +struct MetaTile final +{ + /** + * Constructor. + */ + MetaTile() : Fcost(0), Gcost(0), Hcost(0), whichList(0), + parentX(0), parentY(0), blockmask(0) + {} + + A_DELETE_COPY(MetaTile) + + // Pathfinding members + int Fcost; /**< Estimation of total path cost */ + int Gcost; /**< Cost from start to this location */ + int Hcost; /**< Estimated cost to goal */ + unsigned whichList; /**< No list, open list or closed list */ + int parentX; /**< X coordinate of parent tile */ + int parentY; /**< Y coordinate of parent tile */ + unsigned char blockmask; /**< Blocking properties of this tile */ +}; + +/** + * Animation cycle of a tile image which changes the map accordingly. + */ +class TileAnimation final +{ + public: + explicit TileAnimation(Animation *const ani); + + ~TileAnimation(); + + A_DELETE_COPY(TileAnimation) + + bool update(const int ticks = 1); + + void addAffectedTile(MapLayer *const layer, const int index) + { mAffected.push_back(std::make_pair(layer, index)); } + + private: + TilePairVector mAffected; + SimpleAnimation *mAnimation; + Image *mLastImage; +}; + +typedef std::map<int, TileAnimation*> TileAnimationMap; +typedef TileAnimationMap::const_iterator TileAnimationMapCIter; + +/** + * A tile map. + */ +class Map final : public Properties, public ConfigListener +{ + public: + enum BlockType + { + BLOCKTYPE_NONE = -1, + BLOCKTYPE_WALL, + BLOCKTYPE_CHARACTER, + BLOCKTYPE_MONSTER, + BLOCKTYPE_AIR, + BLOCKTYPE_WATER, + BLOCKTYPE_GROUND, + BLOCKTYPE_GROUNDTOP, + NB_BLOCKTYPES + }; + + enum CollisionTypes + { + COLLISION_EMPTY = 0, // no collision + COLLISION_WALL = 1, // full collison + COLLISION_AIR = 2, // air units can walk + COLLISION_WATER = 3, // water units can walk + COLLISION_GROUNDTOP = 4, // no collision (chair, bed, etc) + COLLISION_MAX = 5 // count index + }; + + enum BlockMask + { + BLOCKMASK_WALL = 0x80, // 1000 0000 + BLOCKMASK_CHARACTER = 0x01, // 0000 0001 + BLOCKMASK_MONSTER = 0x02, // 0000 0010 + BLOCKMASK_AIR = 0x04, // 0000 0100 + BLOCKMASK_WATER = 0x08, // 0000 1000 + BLOCKMASK_GROUND = 0x10, // 0001 0000 + BLOCKMASK_GROUNDTOP = 0x20 // 0010 0000 + }; + + enum DebugType + { + MAP_NORMAL = 0, + MAP_DEBUG = 1, + MAP_SPECIAL = 2, + MAP_SPECIAL2 = 3, + MAP_SPECIAL3 = 4, + MAP_BLACKWHITE = 5 + }; + + /** + * Constructor, taking map and tile size as parameters. + */ + Map(const int width, const int height, + const int tileWidth, const int tileHeight); + + A_DELETE_COPY(Map) + + /** + * Destructor. + */ + ~Map(); + + /** + * Initialize ambient layers. Has to be called after all the properties + * are set. + */ + void initializeAmbientLayers(); + + /** + * Updates animations. Called as needed. + */ + void update(const int ticks = 1); + + /** + * Draws the map to the given graphics output. This method draws all + * layers, actors and overlay effects. + * + * TODO: For efficiency reasons, this method could take into account + * the clipping rectangle set on the Graphics object. However, + * currently the map is always drawn full-screen. + */ + void draw(Graphics *const graphics, int scrollX, int scrollY); + + /** + * Visualizes collision layer for debugging + */ + void drawCollision(Graphics *const graphics, + const int scrollX, const int scrollY, + const int debugFlags) const; + + /** + * Adds a layer to this map. The map takes ownership of the layer. + */ + void addLayer(MapLayer *const layer); + + /** + * Adds a tileset to this map. The map takes ownership of the tileset. + */ + void addTileset(Tileset *const tileset); + + /** + * Finds the tile set that a tile with the given global id is part of. + */ + const Tileset *getTilesetWithGid(const int gid) const A_WARN_UNUSED; + + /** + * Get tile reference. + */ + const MetaTile *getMetaTile(const int x, + const int y) const A_WARN_UNUSED; + + /** + * Marks a tile as occupied. + */ + void blockTile(const int x, const int y, const BlockType type); + + /** + * Gets walkability for a tile with a blocking bitmask. When called + * without walkmask, only blocks against colliding tiles. + */ + bool getWalk(const int x, const int y, + const unsigned char walkmask = BLOCKMASK_WALL + | BLOCKMASK_AIR | BLOCKMASK_WATER) const A_WARN_UNUSED; + + void setWalk(const int x, const int y, const bool walkable); + + unsigned char getBlockMask(const int x, const int y) const; + + /** + * Returns the width of this map in tiles. + */ + int getWidth() const A_WARN_UNUSED + { return mWidth; } + + /** + * Returns the height of this map in tiles. + */ + int getHeight() const A_WARN_UNUSED + { return mHeight; } + + /** + * Returns the tile width of this map. + */ + int getTileWidth() const A_WARN_UNUSED + { return mTileWidth; } + + /** + * Returns the tile height used by this map. + */ + int getTileHeight() const A_WARN_UNUSED + { return mTileHeight; } + + const std::string getMusicFile() const A_WARN_UNUSED; + + void setMusicFile(const std::string &file); + + const std::string getName() const A_WARN_UNUSED; + + /** + * Gives the map id based on filepath (ex: 009-1) + */ + const std::string getFilename() const A_WARN_UNUSED; + + /** + * Find a path from one location to the next. + */ + Path findPath(const int startX, const int startY, + const int destX, const int destY, + const unsigned char walkmask, + const int maxCost = 20) A_WARN_UNUSED; + + /** + * Adds a particle effect + */ + void addParticleEffect(const std::string &effectFile, + const int x, const int y, + const int w = 0, const int h = 0); + + /** + * Initializes all added particle effects + */ + void initializeParticleEffects(Particle *const particleEngine); + + /** + * Adds a tile animation to the map + */ + void addAnimation(const int gid, TileAnimation *const animation) + { mTileAnimations[gid] = animation; } + + void setDebugFlags(const int n) + { mDebugFlags = n; } + + int getDebugFlags() const A_WARN_UNUSED + { return mDebugFlags; } + + void addExtraLayer(); + + void saveExtraLayer() const; + + SpecialLayer *getTempLayer() const A_WARN_UNUSED + { return mTempLayer; } + + SpecialLayer *getSpecialLayer() const A_WARN_UNUSED + { return mSpecialLayer; } + + void setHasWarps(const bool n) + { mHasWarps = n; } + + bool getHasWarps() const A_WARN_UNUSED + { return mHasWarps; } + + std::string getUserMapDirectory() const A_WARN_UNUSED; + + void addPortal(const std::string &name, const int type, + const int x, const int y, const int dx, const int dy); + + void addRange(const std::string &name, const int type, + const int x, const int y, const int dx, const int dy); + + void addPortalTile(const std::string &name, const int type, + const int x, const int y); + + void updatePortalTile(const std::string &name, const int type, + const int x, const int y, + const bool addNew = true); + + const std::vector<MapItem*> &getPortals() const A_WARN_UNUSED + { return mMapPortals; } + + /** + * Gets the tile animation for a specific gid + */ + const TileAnimation *getAnimationForGid(const int gid) + const A_WARN_UNUSED; + + void optionChanged(const std::string &value) override final; + + MapItem *findPortalXY(const int x, const int y) const A_WARN_UNUSED; + + int getActorsCount() const A_WARN_UNUSED + { return static_cast<int>(mActors.size()); } + + void setPvpMode(const int mode); + + const ObjectsLayer* getObjectsLayer() const A_WARN_UNUSED + { return mObjects; } + + std::string getObjectData(const unsigned x, const unsigned y, + const int type) const A_WARN_UNUSED; + + void indexTilesets(); + + void clearIndexedTilesets(); + + void setActorsFix(const int x, const int y) + { mActorFixX = x; mActorFixY = y; } + + int getVersion() const A_WARN_UNUSED + { return mVersion; } + + void setVersion(const int n) + { mVersion = n; } + + void reduce(); + + void redrawMap(); + + bool empty() const A_WARN_UNUSED + { return mLayers.empty(); } + + void setCustom(const bool b) + { mCustom = b; } + + bool isCustom() const A_WARN_UNUSED + { return mCustom; } + + const std::map<int, TileAnimation*> &getTileAnimations() + const A_WARN_UNUSED + { return mTileAnimations; } + + void setAtlas(Resource *const atlas) + { mAtlas = atlas; } + + const MetaTile *getMetaTiles() const + { return mMetaTiles; } + + WalkLayer *getWalkLayer() + { return mWalkLayer; } + + void setWalkLayer(WalkLayer *const layer) + { mWalkLayer = layer; } + + void addHeights(MapHeights *const heights); + + uint8_t getHeightOffset(const int x, const int y) const; + + void setMask(const int mask); + + protected: + friend class Actor; + friend class Minimap; + + /** + * Adds an actor to the map. + */ + Actors::iterator addActor(Actor *const actor); + + /** + * Removes an actor from the map. + */ + void removeActor(const Actors::iterator &iterator); + + private: + enum LayerType + { + FOREGROUND_LAYERS = 0, + BACKGROUND_LAYERS + }; + + /** + * Updates scrolling of ambient layers. Has to be called each game tick. + */ + void updateAmbientLayers(const float scrollX, const float scrollY); + + /** + * Draws the foreground or background layers to the given graphics output. + */ + void drawAmbientLayers(Graphics *const graphics, const LayerType type, + const int detail); + + /** + * Tells whether the given coordinates fall within the map boundaries. + */ + bool contains(const int x, const int y) const A_WARN_UNUSED; + + /** + * Blockmasks for different entities + */ + unsigned *mOccupation[NB_BLOCKTYPES]; + + int mWidth; + int mHeight; + int mTileWidth, mTileHeight; + int mMaxTileHeight; + MetaTile *mMetaTiles; + WalkLayer *mWalkLayer; + Layers mLayers; + Tilesets mTilesets; + Actors mActors; + bool mHasWarps; + + // debug flags + int mDebugFlags; + + // Pathfinding members + unsigned int mOnClosedList; + unsigned int mOnOpenList; + + // Overlay data + AmbientLayerVector mBackgrounds; + AmbientLayerVector mForegrounds; + float mLastAScrollX; + float mLastAScrollY; + + // Particle effect data + struct ParticleEffectData + { + ParticleEffectData() : + file(), + x(0), + y(0), + w(0), + h(0) + { + } + + std::string file; + int x; + int y; + int w; + int h; + }; + std::vector<ParticleEffectData> mParticleEffects; + + std::vector<MapItem*> mMapPortals; + + std::map<int, TileAnimation*> mTileAnimations; + + int mOverlayDetail; + float mOpacity; + RenderType mOpenGL; + int mPvp; + bool mTilesetsIndexed; + Tileset** mIndexedTilesets; + int mIndexedTilesetsSize; + int mActorFixX; + int mActorFixY; + int mVersion; + + SpecialLayer *mSpecialLayer; + SpecialLayer *mTempLayer; + ObjectsLayer *mObjects; + MapLayer *mFringeLayer; + + int mLastX; + int mLastY; + int mLastScrollX; + int mLastScrollY; + + int mDrawX; + int mDrawY; + int mDrawScrollX; + int mDrawScrollY; + int mMask; + Resource *mAtlas; + MapHeights *mHeights; + bool mRedrawMap; + bool mBeingOpacity; + bool mCustom; +}; + +#endif // RESOURCES_MAP_MAP_H diff --git a/src/resources/map/mapheights.cpp b/src/resources/map/mapheights.cpp new file mode 100644 index 000000000..1f93a0a84 --- /dev/null +++ b/src/resources/map/mapheights.cpp @@ -0,0 +1,41 @@ +/* + * The ManaPlus Client + * Copyright (C) 2013-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 <http://www.gnu.org/licenses/>. + */ + +#include "resources/map/mapheights.h" + +#include "debug.h" + +MapHeights::MapHeights(const int width, const int height) : + mWidth(width), + mHeight(height), + mTiles(new uint8_t[mWidth * mHeight]) +{ + memset(mTiles, 0, mWidth * mHeight); +} + +MapHeights::~MapHeights() +{ + delete [] mTiles; +} + +void MapHeights::setHeight(const int x, const int y, const uint8_t height) +{ + mTiles[x + y * mWidth] = height; +} diff --git a/src/resources/map/mapheights.h b/src/resources/map/mapheights.h new file mode 100644 index 000000000..89fc6a5c2 --- /dev/null +++ b/src/resources/map/mapheights.h @@ -0,0 +1,48 @@ +/* + * The ManaPlus Client + * Copyright (C) 2013-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 <http://www.gnu.org/licenses/>. + */ + +#ifndef RESOURCES_MAP_MAPHEIGHTS_H +#define RESOURCES_MAP_MAPHEIGHTS_H + +#include "localconsts.h" + +class MapHeights final +{ + public: + friend class Map; + + MapHeights(const int width, const int height); + + A_DELETE_COPY(MapHeights) + + ~MapHeights(); + + void setHeight(const int x, const int y, const uint8_t height); + + uint8_t getHeight(const int x, const int y) const + { return mTiles[x + y * mWidth]; } + + private: + int mWidth; + int mHeight; + uint8_t *mTiles; +}; + +#endif // RESOURCES_MAP_MAPHEIGHTS_H diff --git a/src/resources/map/maplayer.cpp b/src/resources/map/maplayer.cpp new file mode 100644 index 000000000..dae071565 --- /dev/null +++ b/src/resources/map/maplayer.cpp @@ -0,0 +1,892 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#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 "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<Image*>(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<size_t>(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) + { + MepRowImages *const images = &(*rit)->images; + MepRowImages::const_iterator iit = images->begin(); + const MepRowImages::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<size_t>(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<int, ImageVertexes*> imgSet; + + for (int y = startY; y < endY; y++) + { + const int yWidth = y * mWidth; + const int py0 = y * mapTileSize + dy; + Image **tilePtr = mTiles + static_cast<size_t>(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 MepRowImages *const images = &(*rit)->images; + MepRowImages::const_iterator iit = images->begin(); + const MepRowImages::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<size_t>(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<MapItem*>(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<MapObjectList*>(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]; +} + +MapRowVertexes::~MapRowVertexes() +{ + delete_all(images); + images.clear(); +} diff --git a/src/resources/map/maplayer.h b/src/resources/map/maplayer.h new file mode 100644 index 000000000..2c3fc23f1 --- /dev/null +++ b/src/resources/map/maplayer.h @@ -0,0 +1,328 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef RESOURCES_MAP_MAPLAYER_H +#define RESOURCES_MAP_MAPLAYER_H + +#include "position.h" +#include "main.h" + +#include "listeners/configlistener.h" + +#include "being/actor.h" + +#include <string> +#include <vector> + +class Image; +class MapItem; +class SpecialLayer; +class ImageVertexes; + +typedef std::vector<ImageVertexes*> MepRowImages; + +class MapRowVertexes final +{ + public: + MapRowVertexes() : + images() + { + images.reserve(30); + } + + A_DELETE_COPY(MapRowVertexes) + + ~MapRowVertexes(); + + MepRowImages images; +}; + +class MapObject final +{ + public: + MapObject(const int type0, const std::string &data0) : + type(type0), data(data0) + { + } + + int type; + std::string data; +}; + +class MapObjectList final +{ + public: + MapObjectList() : + objects() + { + } + + A_DELETE_COPY(MapObjectList) + + std::vector<MapObject> objects; +}; + +/** + * A map layer. Stores a grid of tiles and their offset, and implements layer + * rendering. + */ +class MapLayer final: public ConfigListener +{ + public: + enum Type + { + TILES = 0, + COLLISION, + HEIGHTS + }; + + friend class Map; + + /** + * Constructor, taking layer origin, size and whether this layer is the + * fringe layer. The fringe layer is the layer that draws the actors. + * There can be only one fringe layer per map. + */ + MapLayer(const int x, const int y, const int width, const int height, + const bool isFringeLayer, const int mask); + + A_DELETE_COPY(MapLayer) + + /** + * Destructor. + */ + ~MapLayer(); + + /** + * Set tile image, with x and y in layer coordinates. + */ + void setTile(const int x, const int y, Image *const img); + + /** + * Set tile image with x + y * width already known. + */ + void setTile(const int index, Image *const img) + { mTiles[index] = img; } + + /** + * Draws this layer to the given graphics context. The coordinates are + * expected to be in map range and will be translated to local layer + * coordinates and clipped to the layer's dimensions. + * + * The given actors are only drawn when this layer is the fringe + * layer. + */ + void draw(Graphics *const graphics, + int startX, int startY, int endX, int endY, + const int scrollX, const int scrollY, + const int mDebugFlags) const; + + void drawSDL(Graphics *const graphics); + +#ifdef USE_OPENGL + void drawOGL(Graphics *const graphics); + + void updateOGL(const Graphics *const graphics, + int startX, int startY, + int endX, int endY, + const int scrollX, const int scrollY, + const int mDebugFlags); +#endif + + void updateSDL(const Graphics *const graphics, + int startX, int startY, + int endX, int endY, + const int scrollX, const int scrollY, + const int mDebugFlags); + + void drawFringe(Graphics *const graphics, + int startX, int startY, + int endX, int endY, + const int scrollX, const int scrollY, + const Actors *const actors, + const int mDebugFlags, const int yFix) const; + + bool isFringeLayer() const A_WARN_UNUSED + { return mIsFringeLayer; } + + void setSpecialLayer(SpecialLayer *const val) + { mSpecialLayer = val; } + + void setTempLayer(SpecialLayer *const val) + { mTempLayer = val; } + + int getWidth() const A_WARN_UNUSED + { return mWidth; } + + int getHeight() const A_WARN_UNUSED + { return mHeight; } + + void optionChanged(const std::string &value) override final; + + static int getTileDrawWidth(const Image *img, + const int endX, + int &width) A_WARN_UNUSED; + + private: + int mX; + int mY; + int mWidth; + int mHeight; + Image **mTiles; + SpecialLayer *mSpecialLayer; + SpecialLayer *mTempLayer; + typedef std::vector<MapRowVertexes*> MapRows; + MapRows mTempRows; + int mMask; + bool mIsFringeLayer; /**< Whether the actors are drawn. */ + bool mHighlightAttackRange; +}; + +class SpecialLayer final +{ + public: + friend class Map; + friend class MapLayer; + + SpecialLayer(const int width, const int height); + + A_DELETE_COPY(SpecialLayer) + + ~SpecialLayer(); + + void draw(Graphics *const graphics, int startX, int startY, + int endX, int endY, + const int scrollX, const int scrollY) const; + + MapItem* getTile(const int x, const int y) const A_WARN_UNUSED; + + void setTile(const int x, const int y, MapItem *const item); + + void setTile(const int x, const int y, const int type); + + void addRoad(const Path &road); + + void clean() const; + + private: + int mWidth; + int mHeight; + MapItem **mTiles; +}; + +class MapItem final +{ + public: + friend class Map; + friend class MapLayer; + + enum ItemType + { + EMPTY = 0, + HOME = 1, + ROAD = 2, + CROSS = 3, + ARROW_UP = 4, + ARROW_DOWN = 5, + ARROW_LEFT = 6, + ARROW_RIGHT = 7, + PORTAL = 8, + MUSIC = 9, + ATTACK = 10, + PRIORITY = 11, + IGNORE_ = 12, + PICKUP = 13, + NOPICKUP = 14, + SEPARATOR = 15 + }; + + MapItem(); + + explicit MapItem(const int type); + + MapItem(const int type, std::string comment); + + MapItem(const int type, std::string comment, const int x, const int y); + + A_DELETE_COPY(MapItem) + + ~MapItem(); + + int getType() const A_WARN_UNUSED + { return mType; } + + void setType(const int type); + + void setPos(const int x, const int y); + + int getX() const A_WARN_UNUSED + { return mX; } + + int getY() const A_WARN_UNUSED + { return mY; } + + const std::string &getComment() const A_WARN_UNUSED + { return mComment; } + + void setComment(const std::string &comment) + { mComment = comment; } + + const std::string &getName() const A_WARN_UNUSED + { return mName; } + + void setName(const std::string &name) + { mName = name; } + + void draw(Graphics *const graphics, const int x, const int y, + const int dx, const int dy) const; + + private: + Image *mImage; + std::string mComment; + std::string mName; + int mType; + int mX; + int mY; +}; + +class ObjectsLayer final +{ + public: + ObjectsLayer(const unsigned width, const unsigned height); + + A_DELETE_COPY(ObjectsLayer) + + ~ObjectsLayer(); + + void addObject(const std::string &name, const int type, + const unsigned x, const unsigned y, + unsigned dx, unsigned dy); + + MapObjectList *getAt(const unsigned x, + const unsigned y) const A_WARN_UNUSED; + private: + MapObjectList **mTiles; + unsigned mWidth; + unsigned mHeight; +}; + +#endif // RESOURCES_MAP_MAPLAYER_H diff --git a/src/resources/map/properties.h b/src/resources/map/properties.h new file mode 100644 index 000000000..7d3ff6ebf --- /dev/null +++ b/src/resources/map/properties.h @@ -0,0 +1,135 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef RESOURCES_MAP_PROPERTIES_H +#define RESOURCES_MAP_PROPERTIES_H + +#include "localconsts.h" + +#include <map> +#include <sstream> +#include <string> + +/** + * A class holding a set of properties. + */ +class Properties +{ + public: + Properties() : + mProperties() + { + } + + /** + * Destructor. + */ + virtual ~Properties() + { } + + /** + * Get a map property. + * + * @param name The name of the property. + * @param def Default value, empty string by default. + * @return the value of the given property or the given default when it + * doesn't exist. + */ + const std::string getProperty(const std::string &name, + const std::string &def = "") + const A_WARN_UNUSED + { + const PropertyMap::const_iterator i = mProperties.find(name); + return (i != mProperties.end()) ? i->second : def; + } + + /** + * Gets a map property as a float. + * + * @param name The name of the property. + * @param def Default value, 0.0F by default. + * @return the value of the given property or the given default when it + * doesn't exist. + */ + float getFloatProperty(const std::string &name, + const float def = 0.0F) const A_WARN_UNUSED + { + const PropertyMap::const_iterator i = mProperties.find(name); + float ret = def; + if (i != mProperties.end()) + { + std::stringstream ss; + ss.str(i->second); + ss >> ret; + } + return ret; + } + + /** + * Gets a map property as a boolean. + * + * @param name The name of the property. + * @param def Default value, false by default. + * @return the value of the given property or the given default when it + * doesn't exist. + */ + bool getBoolProperty(const std::string &name, + const bool def = false) const A_WARN_UNUSED + { + const PropertyMap::const_iterator i = mProperties.find(name); + bool ret = def; + if (i != mProperties.end()) + { + if (i->second == "true") + ret = true; + if (i->second == "false") + ret = false; + } + return ret; + } + + /** + * Returns whether a certain property is available. + * + * @param name The name of the property. + * @return <code>true</code> when a property is defined, + * <code>false</code> otherwise. + */ + bool hasProperty(const std::string &name) const A_WARN_UNUSED + { return (mProperties.find(name) != mProperties.end()); } + + /** + * Set a map property. + * + * @param name The name of the property. + * @param value The value of the property. + */ + void setProperty(const std::string &name, const std::string &value) + { mProperties[name] = value; } + + + private: + typedef std::map<std::string, std::string> PropertyMap; + PropertyMap mProperties; +}; + +#endif // RESOURCES_MAP_PROPERTIES_H diff --git a/src/resources/map/tileset.h b/src/resources/map/tileset.h new file mode 100644 index 000000000..312896c62 --- /dev/null +++ b/src/resources/map/tileset.h @@ -0,0 +1,79 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef RESOURCES_MAP_TILESET_H +#define RESOURCES_MAP_TILESET_H + +#include "resources/imageset.h" + +#include <map> + +/** + * A tileset, which is basically just an image set but it stores a firstgid. + */ +class Tileset final : public ImageSet +{ + public: + /** + * Constructor. + */ + Tileset(Image *const img, const int w, const int h, const int firstGid, + const int margin, const int spacing): + ImageSet(img, w, h, margin, spacing), + mFirstGid(firstGid), + mProperties() + { + } + + A_DELETE_COPY(Tileset) + + /** + * Returns the first gid. + */ + int getFirstGid() const A_WARN_UNUSED + { return mFirstGid; } + + /** + * Set tileset property. + */ + void setProperties(const std::map<std::string, std::string> &props) + { mProperties = props; } + + /** + * Returns property value. + */ + std::string getProperty(const std::string &name) A_WARN_UNUSED + { + const std::map<std::string, std::string>::const_iterator + it = mProperties.find(name); + if (it == mProperties.end()) + return ""; + return mProperties[name]; + } + + private: + int mFirstGid; + + std::map<std::string, std::string> mProperties; +}; + +#endif // RESOURCES_MAP_TILESET_H diff --git a/src/resources/map/walklayer.cpp b/src/resources/map/walklayer.cpp new file mode 100644 index 000000000..88f2fe8ef --- /dev/null +++ b/src/resources/map/walklayer.cpp @@ -0,0 +1,44 @@ +/* + * The ManaPlus Client + * Copyright (C) 2013-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 <http://www.gnu.org/licenses/>. + */ + +#include "resources/map/walklayer.h" + +#include "debug.h" + +WalkLayer::WalkLayer(const int width, const int height) : + Resource(), + mWidth(width), + mHeight(height), + mTiles(new int[width * height]) +{ + std::fill_n(mTiles, width * height, 0); +} + +WalkLayer::~WalkLayer() +{ + delete [] mTiles; +} + +int WalkLayer::getDataAt(const int x, const int y) const +{ + if (x < 0 || x >= mWidth || y < 0 || y >= mHeight) + return 0; + return mTiles[x + y * mWidth]; +} diff --git a/src/resources/map/walklayer.h b/src/resources/map/walklayer.h new file mode 100644 index 000000000..a55cab434 --- /dev/null +++ b/src/resources/map/walklayer.h @@ -0,0 +1,48 @@ +/* + * The ManaPlus Client + * Copyright (C) 2013-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 <http://www.gnu.org/licenses/>. + */ + +#ifndef RESOURCES_MAP_WALKLAYER_H +#define RESOURCES_MAP_WALKLAYER_H + +#include "resources/resource.h" + +#include "localconsts.h" + +class WalkLayer final : public Resource +{ + public: + WalkLayer(const int width, const int height); + + A_DELETE_COPY(WalkLayer) + + ~WalkLayer(); + + int *getData() + { return mTiles; } + + int getDataAt(const int x, const int y) const; + + private: + int mWidth; + int mHeight; + int *mTiles; +}; + +#endif // RESOURCES_MAP_WALKLAYER_H |