summaryrefslogtreecommitdiff
path: root/src/map.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/map.cpp')
-rw-r--r--src/map.cpp1654
1 files changed, 1654 insertions, 0 deletions
diff --git a/src/map.cpp b/src/map.cpp
new file mode 100644
index 000000000..5dd933f0b
--- /dev/null
+++ b/src/map.cpp
@@ -0,0 +1,1654 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana 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 "map.h"
+
+#include "actorspritemanager.h"
+#include "client.h"
+#include "configuration.h"
+#include "graphics.h"
+#include "log.h"
+#include "particle.h"
+#include "simpleanimation.h"
+#include "tileset.h"
+#include "localplayer.h"
+
+#include "resources/ambientlayer.h"
+#include "resources/image.h"
+#include "resources/resourcemanager.h"
+
+#include "gui/gui.h"
+#include "gui/palette.h"
+#include "gui/truetypefont.h"
+
+#include "gui/widgets/chattab.h"
+
+#include "utils/dtor.h"
+#include "utils/mkdir.h"
+#include "utils/stringutils.h"
+
+#include <queue>
+
+#include <sys/stat.h>
+
+bool actorCompare(const Actor *a, const Actor *b);
+
+/**
+ * A location on a tile map. Used for pathfinding, open list.
+ */
+struct Location
+{
+ /**
+ * Constructor.
+ */
+ Location(int px, int py, MetaTile *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;
+};
+
+TileAnimation::TileAnimation(Animation *ani):
+ mLastImage(NULL)
+{
+ mAnimation = new SimpleAnimation(ani);
+}
+
+TileAnimation::~TileAnimation()
+{
+ delete mAnimation;
+ mAnimation = 0;
+}
+
+void TileAnimation::update(int ticks)
+{
+ if (!mAnimation)
+ return;
+
+ // update animation
+ mAnimation->update(ticks);
+
+ // exchange images
+ Image *img = mAnimation->getCurrentImage();
+ if (img != mLastImage)
+ {
+ for (std::list<std::pair<MapLayer*, int> >::iterator i =
+ mAffected.begin(); i != mAffected.end(); i++)
+ {
+ i->first->setTile(i->second, img);
+ }
+ mLastImage = img;
+ }
+}
+
+MapLayer::MapLayer(int x, int y, int width, int height, bool isFringeLayer):
+ mX(x), mY(y),
+ mWidth(width), mHeight(height),
+ mIsFringeLayer(isFringeLayer),
+ mHighlightAttackRange(config.getBoolValue("highlightAttackRange"))
+{
+ const int size = mWidth * mHeight;
+ mTiles = new Image*[size];
+
+ std::fill_n(mTiles, size, (Image*) 0);
+
+ config.addListener("highlightAttackRange", this);
+}
+
+MapLayer::~MapLayer()
+{
+ config.removeListener("highlightAttackRange", this);
+ delete[] mTiles;
+}
+
+void MapLayer::optionChanged(const std::string &value)
+{
+ if (value == "highlightAttackRange")
+ {
+ mHighlightAttackRange =
+ config.getBoolValue("highlightAttackRange");
+ }
+}
+
+void MapLayer::setTile(int x, int y, Image *img)
+{
+ setTile(x + y * mWidth, img);
+}
+
+Image* MapLayer::getTile(int x, int y) const
+{
+ return mTiles[x + y * mWidth];
+}
+
+void MapLayer::draw(Graphics *graphics, int startX, int startY,
+ int endX, int endY, int scrollX, int scrollY,
+ const Actors &actors, int debugFlags) const
+{
+ if (!player_node)
+ 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;
+
+ Actors::const_iterator ai = actors.begin();
+
+ const int dx = (mX * 32) - scrollX;
+ const int dy = (mY * 32) - scrollY + 32;
+
+ int specialWidth = 0;
+ int specialHeight = 0;
+
+ const bool extraDraw =
+ mIsFringeLayer && mSpecialLayer && mTempLayer;
+
+ if (mIsFringeLayer)
+ {
+ if (mSpecialLayer)
+ {
+ specialWidth = mSpecialLayer->mWidth;
+ specialHeight = mSpecialLayer->mHeight;
+ }
+ }
+
+ for (int y = startY; y < endY; y++)
+ {
+ const int y32 = y * 32;
+ const int yWidth = y * mWidth;
+
+ // If drawing the fringe layer, make sure all actors above this row of
+ // tiles have been drawn
+ if (mIsFringeLayer)
+ {
+ while (ai != actors.end() && (*ai)->getPixelY() <= y32)
+ {
+ (*ai)->draw(graphics, -scrollX, -scrollY);
+ ai++;
+ }
+ }
+
+ if (debugFlags == Map::MAP_SPECIAL3
+ || debugFlags == Map::MAP_BLACKWHITE)
+ {
+ if (extraDraw && y < specialHeight)
+ {
+ //x + y * mWidth
+ 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 * 32 - scrollX;
+
+ MapItem *item = mSpecialLayer->mTiles[ptr + x];
+ if (item)
+ item->draw(graphics, px1, py1, 32, 32);
+
+ item = mTempLayer->mTiles[ptr + x];
+ if (item)
+ item->draw(graphics, px1, py1, 32, 32);
+ }
+ }
+ }
+ else
+ {
+ const int py0 = y32 + dy;
+ const int py1 = y32 - scrollY;
+
+ for (int x = startX; x < endX; x++)
+ {
+ const int px1 = x * 32 - scrollX;
+ const int tilePtr = x + yWidth;
+ int c = 0;
+ Image *img = mTiles[tilePtr];
+ if (img)
+ {
+ const int px = (x * 32) + dx;
+ const int py = py0 - img->getHeight();
+ if ((debugFlags != Map::MAP_SPECIAL
+ && debugFlags != Map::MAP_SPECIAL2)
+ || img->getHeight() <= 32)
+ {
+ int width = 0;
+ c = getTileDrawWidth(tilePtr, endX - x, width);
+
+ if (!c)
+ {
+ graphics->drawImage(img, px, py);
+ }
+ else
+ {
+ graphics->drawImagePattern(img, px, py,
+ width, img->getHeight());
+ }
+ }
+ }
+
+ if (extraDraw && y < specialHeight)
+ {
+ int c1 = c;
+ if (c1 + x + 1 > specialWidth)
+ c1 = specialWidth - x - 1;
+ if (c1 < 0)
+ c1 = 0;
+
+ int ptr = y * specialWidth + x;
+
+ for (int x1 = 0; x1 < c1 + 1; x1 ++)
+ {
+ MapItem *item1 = mSpecialLayer->mTiles[ptr + x1];
+ MapItem *item2 = mTempLayer->mTiles[ptr + x1];
+ if (item1 || item2)
+ {
+ const int px2 = px1 + (x1 * 32);
+ if (item1 && item1->mType != MapItem::EMPTY)
+ item1->draw(graphics, px2, py1, 32, 32);
+
+ if (item2 && item2->mType != MapItem::EMPTY)
+ item2->draw(graphics, px2, py1, 32, 32);
+ }
+ }
+ }
+ x += c;
+ }
+ }
+ }
+
+ // Draw any remaining actors
+ if (mIsFringeLayer && debugFlags != Map::MAP_SPECIAL3)
+ {
+ while (ai != actors.end())
+ {
+ (*ai)->draw(graphics, -scrollX, -scrollY);
+ ai++;
+ }
+ if (mHighlightAttackRange && player_node)
+ {
+ const int px = player_node->getPixelX() - scrollX - 16;
+ const int py = player_node->getPixelY() - scrollY - 32;
+ const int attackRange = player_node->getAttackRange() * 32;
+
+ int x = px - attackRange;
+ int y = py - attackRange;
+ int w = 2 * attackRange + 32;
+ int h = w;
+ if (attackRange <= 32)
+ {
+ x -= 16;
+ y -= 16;
+ w += 32;
+ h += 32;
+ }
+
+ if (userPalette)
+ {
+ graphics->setColor(userPalette->getColorWithAlpha(
+ UserPalette::ATTACK_RANGE));
+
+ graphics->fillRectangle(gcn::Rectangle(
+ x, y,
+ w, h));
+
+ graphics->setColor(userPalette->getColorWithAlpha(
+ UserPalette::ATTACK_RANGE_BORDER));
+
+ graphics->drawRectangle(gcn::Rectangle(
+ x, y,
+ w, h));
+ }
+ }
+ }
+}
+
+int MapLayer::getTileDrawWidth(int tilePtr, int endX, int &width) const
+{
+ Image *img1 = mTiles[tilePtr];
+ int c = 0;
+ if (!img1)
+ {
+ width = 0;
+ return c;
+ }
+ width = img1->getWidth();
+ for (int x = 1; x < endX; x++)
+ {
+ tilePtr ++;
+ Image *img = mTiles[tilePtr];
+ if (img != img1)
+ break;
+ c ++;
+ if (img)
+ width += img->getWidth();
+ }
+ return c;
+}
+
+Map::Map(int width, int height, int tileWidth, int tileHeight):
+ mWidth(width), mHeight(height),
+ mTileWidth(tileWidth), mTileHeight(tileHeight),
+ mMaxTileHeight(height),
+ mHasWarps(false),
+ mDebugFlags(MAP_NORMAL),
+ mOnClosedList(1), mOnOpenList(2),
+ mLastScrollX(0.0f), mLastScrollY(0.0f),
+ mOverlayDetail(config.getIntValue("OverlayDetail")),
+ mOpacity(config.getFloatValue("guialpha")),
+ mPvp(0)
+// mSpritesUpdated(true)
+{
+ const int size = mWidth * mHeight;
+
+ mDebugFlags = 0;
+ mMetaTiles = new MetaTile[size];
+ for (int i = 0; i < NB_BLOCKTYPES; i++)
+ {
+ mOccupation[i] = new int[size];
+ memset(mOccupation[i], 0, size * sizeof(int));
+ }
+ mSpecialLayer = new SpecialLayer(width, height);
+ mTempLayer = new SpecialLayer(width, height, true);
+ config.addListener("OverlayDetail", this);
+ config.addListener("guialpha", this);
+
+#ifdef USE_OPENGL
+ mOpenGL = config.getIntValue("opengl");
+#else
+ mOpenGL = 0;
+#endif
+}
+
+Map::~Map()
+{
+ config.removeListener("OverlayDetail", this);
+ config.removeListener("guialpha", this);
+
+ // delete metadata, layers, tilesets and overlays
+ delete[] mMetaTiles;
+ for (int i = 0; i < NB_BLOCKTYPES; i++)
+ delete[] mOccupation[i];
+
+ delete_all(mLayers);
+ delete_all(mTilesets);
+ delete_all(mForegrounds);
+ delete_all(mBackgrounds);
+ delete_all(mTileAnimations);
+ delete mSpecialLayer;
+ mSpecialLayer = 0;
+ delete mTempLayer;
+ mTempLayer = 0;
+ delete_all(mMapPortals);
+}
+
+void Map::optionChanged(const std::string &value)
+{
+ if (value == "OverlayDetail")
+ mOverlayDetail = config.getIntValue("OverlayDetail");
+ else if (value == "guialpha")
+ mOpacity = config.getFloatValue("guialpha");
+}
+
+void Map::initializeAmbientLayers()
+{
+ ResourceManager *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("foreground" + toString(i) + "image"))
+ name = "foreground" + toString(i);
+ else if (hasProperty("overlay" + toString(i) + "image"))
+ name = "overlay" + toString(i);
+ else
+ break; // the FOR loop
+
+ Image *img = resman->getImage(getProperty(name + "image"));
+ const float speedX = getFloatProperty(name + "scrollX");
+ const float speedY = getFloatProperty(name + "scrollY");
+ const float parallax = getFloatProperty(name + "parallax");
+ const bool keepRatio = getBoolProperty(name + "keepratio");
+
+ if (img)
+ {
+ mForegrounds.push_back(
+ new AmbientLayer(img, parallax, speedX, speedY, keepRatio));
+
+ // The AmbientLayer takes control over the image.
+ img->decRef();
+ }
+ }
+
+
+ // search for "background*" in map properties
+ for (int i = 0;
+ hasProperty("background" + toString(i) + "image");
+ i++)
+ {
+ const std::string name = "background" + toString(i);
+
+ Image *img = resman->getImage(getProperty(name + "image"));
+ const float speedX = getFloatProperty(name + "scrollX");
+ const float speedY = getFloatProperty(name + "scrollY");
+ const float parallax = getFloatProperty(name + "parallax");
+ const bool keepRatio = getBoolProperty(name + "keepratio");
+
+ if (img)
+ {
+ mBackgrounds.push_back(
+ new AmbientLayer(img, parallax, speedX, speedY, keepRatio));
+
+ // The AmbientLayer takes control over the image.
+ img->decRef();
+ }
+ }
+}
+
+void Map::addLayer(MapLayer *layer)
+{
+ mLayers.push_back(layer);
+}
+
+void Map::addTileset(Tileset *tileset)
+{
+ if (!tileset)
+ return;
+
+ mTilesets.push_back(tileset);
+
+ if (tileset->getHeight() > mMaxTileHeight)
+ mMaxTileHeight = tileset->getHeight();
+}
+
+bool actorCompare(const Actor *a, const Actor *b)
+{
+ if (!a || !b)
+ return false;
+
+ return a->getPixelY() < b->getPixelY();
+}
+
+void Map::update(int ticks)
+{
+ // Update animated tiles
+ for (std::map<int, TileAnimation*>::iterator
+ iAni = mTileAnimations.begin();
+ iAni != mTileAnimations.end(); iAni++)
+ {
+ iAni->second->update(ticks);
+ }
+}
+
+void Map::draw(Graphics *graphics, int scrollX, int scrollY)
+{
+ // Calculate range of tiles which are on-screen
+ int endPixelY = graphics->getHeight() + scrollY + mTileHeight - 1;
+ endPixelY += mMaxTileHeight - mTileHeight;
+ int startX = scrollX / mTileWidth;
+ int startY = scrollY / mTileHeight;
+ int endX = (graphics->getWidth() + scrollX + mTileWidth - 1) / mTileWidth;
+ int endY = endPixelY / mTileHeight;
+
+ // Make sure actors are sorted ascending by Y-coordinate
+ // so that they overlap correctly
+// if (mSpritesUpdated)
+// {
+ mActors.sort(actorCompare);
+// mSpritesUpdated = false;
+// }
+
+ // 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)
+ {
+ graphics->setColor(userPalette->getColorWithAlpha(
+ UserPalette::WALKABLE_HIGHLIGHT));
+
+ graphics->fillRectangle(gcn::Rectangle(0, 0,
+ graphics->getWidth(), graphics->getHeight()));
+ }
+
+ // draw the game world
+ Layers::const_iterator layeri = mLayers.begin();
+
+ bool overFringe = false;
+
+ if (mDebugFlags == MAP_SPECIAL3 || mDebugFlags == MAP_BLACKWHITE)
+ {
+ for (; layeri != mLayers.end(); ++layeri)
+ {
+ if ((*layeri)->isFringeLayer())
+ {
+ (*layeri)->setSpecialLayer(mSpecialLayer);
+ (*layeri)->setTempLayer(mTempLayer);
+ (*layeri)->draw(graphics,
+ startX, startY, endX, endY,
+ scrollX, scrollY,
+ mActors, mDebugFlags);
+ break;
+ }
+ }
+ }
+ else
+ {
+ for (; layeri != mLayers.end() && !overFringe; ++layeri)
+ {
+ if ((*layeri)->isFringeLayer())
+ {
+ (*layeri)->setSpecialLayer(mSpecialLayer);
+ (*layeri)->setTempLayer(mTempLayer);
+ if (mDebugFlags == MAP_SPECIAL2)
+ overFringe = true;
+ }
+
+ (*layeri)->draw(graphics,
+ startX, startY, endX, endY,
+ scrollX, scrollY,
+ mActors, mDebugFlags);
+ }
+ }
+
+ // Dont draw if gui opacity == 1
+ if (mOpacity != 1.0f)
+ {
+ // Draws beings with a lower opacity to make them visible
+ // even when covered by a wall or some other elements...
+ Actors::const_iterator ai = mActors.begin();
+ while (ai != mActors.end())
+ {
+ if (Actor *actor = *ai)
+ {
+ if (!mOpenGL && (actor->getTileX() < startX
+ || actor->getTileX() > endX || actor->getTileY() < startY
+ || actor->getTileY() > 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);
+}
+
+void Map::drawCollision(Graphics *graphics, int scrollX, int scrollY,
+ int debugFlags)
+{
+ int endPixelY = graphics->getHeight() + scrollY + mTileHeight - 1;
+ int startX = scrollX / mTileWidth;
+ int startY = scrollY / mTileHeight;
+ int endX = (graphics->getWidth() + 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(gcn::Color(0, 0, 0, 64));
+ graphics->drawNet(
+ startX * mTileWidth - scrollX,
+ startY * mTileHeight - scrollY,
+ endX * mTileWidth - scrollX,
+ endY * mTileHeight - scrollY,
+ 32, 32);
+ }
+
+ for (int y = startY; y < endY; y++)
+ {
+ for (int x = startX; x < endX; x++)
+ {
+ int width = 0;
+ int x0 = x;
+
+ if (!getWalk(x, y, BLOCKMASK_WALL))
+ {
+ width = 32;
+ for (int x2 = x + 1; x < endX; x2 ++)
+ {
+ if (getWalk(x2, y, BLOCKMASK_WALL))
+ break;
+ width += 32;
+ x ++;
+ }
+ }
+
+ if (width && userPalette)
+ {
+ graphics->setColor(userPalette->getColorWithAlpha(
+ UserPalette::COLLISION_HIGHLIGHT));
+
+ graphics->fillRectangle(gcn::Rectangle(
+ x0 * mTileWidth - scrollX,
+ y * mTileHeight - scrollY,
+ width, 32));
+ }
+ }
+ }
+}
+
+void Map::updateAmbientLayers(float scrollX, float scrollY)
+{
+ static int lastTick = tick_time; // static = only initialized at first call
+
+ if (mLastScrollX == 0.0f && mLastScrollY == 0.0f)
+ {
+ // First call - initialisation
+ mLastScrollX = scrollX;
+ mLastScrollY = scrollY;
+ }
+
+ // Update Overlays
+ float dx = scrollX - mLastScrollX;
+ float dy = scrollY - mLastScrollY;
+ int timePassed = get_elapsed_time(lastTick);
+
+ std::list<AmbientLayer*>::iterator i;
+ for (i = mBackgrounds.begin(); i != mBackgrounds.end(); i++)
+ (*i)->update(timePassed, dx, dy);
+
+ for (i = mForegrounds.begin(); i != mForegrounds.end(); i++)
+ (*i)->update(timePassed, dx, dy);
+
+ mLastScrollX = scrollX;
+ mLastScrollY = scrollY;
+ lastTick = tick_time;
+}
+
+void Map::drawAmbientLayers(Graphics *graphics, LayerType type,
+ int detail)
+{
+ // Detail 0 = no ambient effects except background image
+ if (detail <= 0 && type != BACKGROUND_LAYERS)
+ return;
+
+ // find out which layer list to draw
+ std::list<AmbientLayer*> *layers;
+ switch (type)
+ {
+ case FOREGROUND_LAYERS:
+ layers = &mForegrounds;
+ break;
+ case BACKGROUND_LAYERS:
+ layers = &mBackgrounds;
+ break;
+ default:
+ // New type of ambient layers added here without adding it
+ // to Map::drawAmbientLayers.
+ assert(false);
+ break;
+ }
+
+ // Draw overlays
+ for (std::list<AmbientLayer*>::iterator i = layers->begin();
+ i != layers->end(); i++)
+ {
+ (*i)->draw(graphics, graphics->getWidth(), graphics->getHeight());
+
+ // Detail 1: only one overlay, higher: all overlays
+ if (detail == 1)
+ break;
+ }
+}
+
+Tileset *Map::getTilesetWithGid(int gid) const
+{
+ Tileset *s = NULL;
+ for (Tilesets::const_iterator it = mTilesets.begin(),
+ it_end = mTilesets.end(); it < it_end && (*it)->getFirstGid() <= gid;
+ it++)
+ {
+ s = *it;
+ }
+
+ return s;
+}
+
+void Map::blockTile(int x, int y, BlockType type)
+{
+ if (type == BLOCKTYPE_NONE || !contains(x, y))
+ return;
+
+ const int tileNum = x + y * mWidth;
+
+ if ((++mOccupation[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;
+ default:
+ // Do nothing.
+ break;
+ }
+ }
+}
+
+bool Map::getWalk(int x, int y, unsigned char walkmask) const
+{
+ // You can't walk outside of the map
+ if (!contains(x, y))
+ return false;
+
+ // Check if the tile is walkable
+ return !(mMetaTiles[x + y * mWidth].blockmask & walkmask);
+}
+
+bool Map::occupied(int x, int y) const
+{
+ const ActorSprites &actors = actorSpriteManager->getAll();
+ ActorSpritesConstIterator it, it_end;
+ for (it = actors.begin(), it_end = actors.end(); it != it_end; it++)
+ {
+ const ActorSprite *actor = *it;
+
+//+++ if (actor->getTileX() == x && actor->getTileY() == y
+// && being->getSubType() != 45)
+ if (actor->getTileX() == x && actor->getTileY() == y &&
+ actor->getType() != ActorSprite::FLOOR_ITEM)
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool Map::contains(int x, int y) const
+{
+ return x >= 0 && y >= 0 && x < mWidth && y < mHeight;
+}
+
+MetaTile *Map::getMetaTile(int x, int y) const
+{
+ return &mMetaTiles[x + y * mWidth];
+}
+
+Actors::iterator Map::addActor(Actor *actor)
+{
+ mActors.push_front(actor);
+// mSpritesUpdated = true;
+ return mActors.begin();
+}
+
+void Map::removeActor(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
+{
+ std::string fileName = getProperty("_filename");
+ int lastSlash = static_cast<int>(fileName.rfind("/")) + 1;
+ int lastDot = static_cast<int>(fileName.rfind("."));
+
+ return fileName.substr(lastSlash, lastDot - lastSlash);
+}
+
+Position Map::checkNodeOffsets(int radius, unsigned char walkMask,
+ const Position &position) const
+{
+ // Pre-computing character's position in tiles
+ const int tx = position.x / 32;
+ const int ty = position.y / 32;
+
+ // Pre-computing character's position offsets.
+ int fx = position.x % 32;
+ int fy = position.y % 32;
+
+ // Compute the being radius:
+ // FIXME: Hande beings with more than 1/2 tile radius by not letting them
+ // go or spawn in too narrow places. The server will have to be aware
+ // of being's radius value (in tiles) to handle this gracefully.
+ if (radius > 32 / 2)
+ radius = 32 / 2;
+ // set a default value if no value returned.
+ if (radius < 1)
+ radius = 32 / 3;
+
+ // We check diagonal first as they are more restrictive.
+ // Top-left border check
+ if (!getWalk(tx - 1, ty - 1, walkMask)
+ && fy < radius && fx < radius)
+ {
+ fx = radius;
+ fy = radius;
+ }
+ // Top-right border check
+ if (!getWalk(tx + 1, ty - 1, walkMask)
+ && (fy < radius) && fx > (32 - radius))
+ {
+ fx = 32 - radius;
+ fy = radius;
+ }
+ // Bottom-left border check
+ if (!getWalk(tx - 1, ty + 1, walkMask)
+ && fy > (32 - radius) && fx < radius)
+ {
+ fx = radius;
+ fy = 32 - radius;
+ }
+ // Bottom-right border check
+ if (!getWalk(tx + 1, ty + 1, walkMask)
+ && fy > (32 - radius) && fx > (32 - radius))
+ {
+ fx = 32 - radius;
+ fy = fx;
+ }
+
+ // Fix coordinates so that the player does not seem to dig into walls.
+ if (fx > (32 - radius) && !getWalk(tx + 1, ty, walkMask))
+ fx = 32 - radius;
+ else if (fx < radius && !getWalk(tx - 1, ty, walkMask))
+ fx = radius;
+ else if (fy > (32 - radius) && !getWalk(tx, ty + 1, walkMask))
+ fy = 32 - radius;
+ else if (fy < radius && !getWalk(tx, ty - 1, walkMask))
+ fy = radius;
+
+ return Position(tx * 32 + fx, ty * 32 + fy);
+}
+
+Path Map::findPixelPath(int startPixelX, int startPixelY, int endPixelX,
+ int endPixelY,
+ int radius, unsigned char walkMask, int maxCost)
+{
+ Path myPath = findPath(startPixelX / 32, startPixelY / 32,
+ endPixelX / 32, endPixelY / 32, walkMask, maxCost);
+
+ // Don't compute empty coordinates.
+ if (myPath.empty())
+ return myPath;
+
+ // Find the starting offset
+ float startOffsetX = static_cast<float>(startPixelX % 32);
+ float startOffsetY = static_cast<float>(startPixelY % 32);
+
+ // Find the ending offset
+ float endOffsetX = static_cast<float>(endPixelX % 32);
+ float endOffsetY = static_cast<float>(endPixelY % 32);
+
+ // Find the distance, and divide it by the number of steps
+ int changeX = (int)((endOffsetX - startOffsetX)
+ / static_cast<float>(myPath.size()));
+ int changeY = (int)((endOffsetY - startOffsetY)
+ / static_cast<float>(myPath.size()));
+
+ // Convert the map path to pixels over tiles
+ // And add interpolation between the starting and ending offsets
+ Path::iterator it = myPath.begin();
+ int i = 0;
+ while (it != myPath.end())
+ {
+ // A position that is valid on the start and end tile is not
+ // necessarily valid on all the tiles in between, so check the offsets.
+ *it = checkNodeOffsets(radius, walkMask,
+ it->x * 32 + startOffsetX + static_cast<float>(changeX * i),
+ it->y * 32 + startOffsetY + static_cast<float>(changeY * i));
+ i++;
+ it++;
+ }
+
+ // Remove the last path node, as it's more clever to go to the destination.
+ // It also permit to avoid zigzag at the end of the path,
+ // especially with mouse.
+ Position destination = checkNodeOffsets(radius, walkMask,
+ endPixelX, endPixelY);
+ myPath.pop_back();
+ myPath.push_back(destination);
+
+ return myPath;
+}
+
+Path Map::findPath(int startX, int startY, int destX, int destY,
+ unsigned char walkmask, int maxCost)
+{
+ static int const basicCost = 100;
+
+ // Path to be built up (empty by default)
+ Path path;
+
+ // Declare open list, a list with open tiles sorted on F cost
+ std::priority_queue<Location> openList;
+
+ // Return when destination not walkable
+ if (!getWalk(destX, destY, walkmask))
+ return path;
+
+ // Reset starting tile's G cost to 0
+ MetaTile *startTile = getMetaTile(startX, startY);
+ startTile->Gcost = 0;
+
+ // 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.
+ Location curr = openList.top();
+ openList.pop();
+
+ // 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 (curr.tile->whichList == mOnClosedList)
+ continue;
+
+ // Put the current tile on the closed list
+ curr.tile->whichList = mOnClosedList;
+
+ // Check the adjacent tiles
+ for (int dy = -1; dy <= 1; dy++)
+ {
+ for (int dx = -1; dx <= 1; dx++)
+ {
+ // Calculate location of tile to check
+ const int x = curr.x + dx;
+ const int y = curr.y + dy;
+
+ // 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) || !contains(x, y))
+ continue;
+
+ MetaTile *newTile = getMetaTile(x, y);
+
+ // Skip if the tile is on the closed list or is not walkable
+ // unless its the destination tile
+ if (!newTile || 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)
+ {
+ MetaTile *t1 = getMetaTile(curr.x, curr.y + dy);
+ MetaTile *t2 = getMetaTile(curr.x + dx, curr.y);
+
+ if (!t1 || !t2 || ((t1->blockmask | t2->blockmask)
+ & BLOCKMASK_WALL))
+ {
+ continue;
+ }
+ }
+
+ // Calculate G cost for this route, ~sqrt(2) for moving diagonal
+ int Gcost = curr.tile->Gcost +
+ (dx == 0 || dy == 0 ? basicCost : basicCost * 362 / 256);
+
+ /* 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. */
+ int dx = std::abs(x - destX), dy = std::abs(y - destY);
+ newTile->Hcost = std::abs(dx - dy) * basicCost +
+ std::min(dx, dy) * (basicCost * 362 / 256);
+
+ // 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.
+ 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
+ MetaTile *tile = getMetaTile(pathX, pathY);
+ pathX = tile->parentX;
+ pathY = tile->parentY;
+ }
+ }
+
+ return path;
+}
+
+void Map::addParticleEffect(const std::string &effectFile,
+ int x, int y, int w, int h)
+{
+ ParticleEffectData newEffect;
+ newEffect.file = effectFile;
+ newEffect.x = x;
+ newEffect.y = y;
+ newEffect.w = w;
+ newEffect.h = h;
+ particleEffects.push_back(newEffect);
+}
+
+void Map::initializeParticleEffects(Particle *particleEngine)
+{
+ if (!particleEngine)
+ return;
+
+ Particle *p;
+
+ if (config.getBoolValue("particleeffects"))
+ {
+ for (std::list<ParticleEffectData>::iterator
+ i = particleEffects.begin();
+ i != particleEffects.end(); i++)
+ {
+ p = particleEngine->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;
+ }
+ std::string mapFileName = getUserMapDirectory() + "/extralayer.txt";
+ logger->log("try load 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);
+ char line[201];
+ std::string buf;
+ 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;
+ std::vector<int> tokens;
+ ss >> comment;
+ while (ss >> buf)
+ comment += " " + buf;
+
+ int type = atoi(type1.c_str());
+/*
+ MapItem *item = new MapItem(atoi(type.c_str()), comment);
+ int x1 = atoi(x.c_str());
+ int y1 = atoi(y.c_str());
+ mSpecialLayer->setTile(x1, y1, item);
+*/
+ 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()
+{
+ if (!mSpecialLayer)
+ {
+ logger->log1("No special layer");
+ return;
+ }
+ std::string mapFileName = getUserMapDirectory() + "/extralayer.txt";
+ logger->log("try save 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;
+ }
+
+ int width = mSpecialLayer->mWidth;
+ int height = mSpecialLayer->mHeight;
+
+ for (int x = 0; x < width; x ++)
+ {
+ for (int y = 0; y < height; y ++)
+ {
+ MapItem *item = mSpecialLayer->getTile(x, y);
+ if (item && item->mType != MapItem::EMPTY
+ && item->mType != MapItem::HOME)
+ {
+ mapFile << x << " " << y << " " << (int)item->mType
+ << " " << item->mComment << std::endl;
+ }
+ }
+ }
+ mapFile.close();
+}
+
+std::string Map::getUserMapDirectory() const
+{
+ return Client::getServerConfigDirectory() + "/" + getProperty("_filename");
+}
+
+void Map::addPortal(const std::string &name, int type,
+ int x, int y, int dx, int dy)
+{
+ addPortalTile(name, type, (x / 32) + (dx / 64), (y / 32) + (dy / 64));
+}
+
+void Map::addPortalTile(const std::string &name, int type, int x, int y)
+{
+ MapItem *item = new MapItem(type, name, x, y);
+ if (mSpecialLayer)
+ mSpecialLayer->setTile(x, y, item);
+
+ item = new MapItem(type, name, x, y);
+ mMapPortals.push_back(item);
+}
+
+void Map::updatePortalTile(const std::string &name, int type,
+ int x, int y, 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(int x, int y)
+{
+ std::list<MapItem*>::iterator it;
+ std::list<MapItem*>::iterator it_end;
+
+ for (it = mMapPortals.begin(), it_end = mMapPortals.end();
+ it != it_end; it++)
+ {
+ MapItem *item = *it;
+ if (item->mX == x && item->mY == y)
+ return item;
+ }
+ return 0;
+}
+
+TileAnimation *Map::getAnimationForGid(int gid) const
+{
+ std::map<int, TileAnimation*>::const_iterator
+ i = mTileAnimations.find(gid);
+ return (i == mTileAnimations.end()) ? NULL : i->second;
+}
+
+void Map::setPvpMode(int mode)
+{
+ int oldMode = mPvp;
+
+ if (!mode)
+ mPvp = 0;
+ else
+ mPvp |= mode;
+
+ if (mPvp != oldMode && player_node)
+ {
+ switch (mPvp)
+ {
+ case 0:
+ player_node->setSpeech("pvp off, gvg off", SPEECH_TIME);
+ break;
+ case 1:
+ player_node->setSpeech("pvp on", SPEECH_TIME);
+ break;
+ case 2:
+ player_node->setSpeech("gvg on", SPEECH_TIME);
+ break;
+ case 3:
+ player_node->setSpeech("pvp on, gvg on", SPEECH_TIME);
+ break;
+ default:
+ player_node->setSpeech("unknown pvp", SPEECH_TIME);
+ break;
+ }
+ }
+}
+
+SpecialLayer::SpecialLayer(int width, int height, bool drawSprites):
+ mWidth(width), mHeight(height)
+{
+ const int size = mWidth * mHeight;
+ mTiles = new MapItem*[size];
+ std::fill_n(mTiles, size, (MapItem*) 0);
+ mDrawSprites = drawSprites;
+}
+
+SpecialLayer::~SpecialLayer()
+{
+ for (int f = 0; f < mWidth * mHeight; f ++)
+ {
+ delete mTiles[f];
+ mTiles[f] = 0;
+ }
+ delete[] mTiles;
+}
+
+MapItem* SpecialLayer::getTile(int x, int y) const
+{
+ if (x < 0 || x >= mWidth ||
+ y < 0 || y >= mHeight)
+ {
+ return 0;
+ }
+ return mTiles[x + y * mWidth];
+}
+
+void SpecialLayer::setTile(int x, int y, MapItem *item)
+{
+ if (x < 0 || x >= mWidth ||
+ y < 0 || y >= mHeight)
+ {
+ return;
+ }
+
+ int idx = x + y * mWidth;
+ delete mTiles[idx];
+ if (item)
+ item->setPos(x, y);
+ mTiles[idx] = item;
+}
+
+void SpecialLayer::setTile(int x, int y, int type)
+{
+ if (x < 0 || x >= mWidth ||
+ y < 0 || y >= mHeight)
+ {
+ return;
+ }
+
+ int idx = x + y * mWidth;
+ if (mTiles[idx])
+ {
+ mTiles[idx]->setType(type);
+ }
+ else
+ {
+ delete mTiles[idx];
+ mTiles[idx] = new MapItem(type);
+ }
+ mTiles[idx]->setPos(x, y);
+}
+
+void SpecialLayer::addRoad(Path road)
+{
+ for (Path::const_iterator i = road.begin(), i_end = road.end();
+ i != i_end; ++i)
+ {
+ Position pos = (*i);
+ MapItem *item = getTile(pos.x, pos.y);
+ if (!item)
+ {
+ item = new MapItem(MapItem::ROAD);
+ setTile(pos.x, pos.y, item);
+ }
+ else
+ {
+ item->setType(MapItem::ROAD);
+ }
+ }
+}
+
+void SpecialLayer::clean()
+{
+ for (int f = 0; f < mWidth * mHeight; f ++)
+ {
+ MapItem *item = mTiles[f];
+ if (item)
+ item->setType(MapItem::EMPTY);
+ }
+}
+
+void SpecialLayer::draw(Graphics *graphics, int startX, int startY,
+ int endX, int endY, int scrollX, int scrollY)
+{
+ if (startX < 0)
+ startX = 0;
+ if (startY < 0)
+ startY = 0;
+ if (endX > mWidth)
+ endX = mWidth;
+ if (endY > mHeight)
+ endY = mHeight;
+
+// MapSprites::const_iterator si = sprites.begin();
+
+ for (int y = startY; y < endY; y++)
+ {
+ for (int x = startX; x < endX; x++)
+ itemDraw(graphics, x, y, scrollX, scrollY);
+ }
+}
+
+void SpecialLayer::itemDraw(Graphics *graphics, int x, int y,
+ int scrollX, int scrollY)
+{
+ MapItem *item = getTile(x, y);
+ if (item)
+ {
+ const int px = x * 32 - scrollX;
+ const int py = y * 32 - scrollY;
+ item->draw(graphics, px, py, 32, 32);
+ }
+}
+
+
+MapItem::MapItem():
+ mImage(0), mComment(""), mName(""), mX(-1), mY(-1)
+{
+ setType(EMPTY);
+}
+
+MapItem::MapItem(int type):
+ mImage(0), mComment(""), mName(""), mX(-1), mY(-1)
+{
+ setType(type);
+}
+
+MapItem::MapItem(int type, std::string comment):
+ mImage(0), mComment(comment), mName(""), mX(-1), mY(-1)
+{
+ setType(type);
+}
+
+MapItem::MapItem(int type, std::string comment, int x, int y):
+ mImage(0), mComment(comment), mName(""), mX(x), mY(y)
+{
+ setType(type);
+}
+
+MapItem::~MapItem()
+{
+ if (mImage)
+ mImage->decRef();
+}
+
+void MapItem::setType(int type)
+{
+ std::string name = "";
+ mType = type;
+ if (mImage)
+ mImage->decRef();
+
+ switch (type)
+ {
+ case ARROW_UP:
+ name = "graphics/sprites/arrow_up.gif";
+ break;
+ case ARROW_DOWN:
+ name = "graphics/sprites/arrow_down.gif";
+ break;
+ case ARROW_LEFT:
+ name = "graphics/sprites/arrow_left.gif";
+ break;
+ case ARROW_RIGHT:
+ name = "graphics/sprites/arrow_right.gif";
+ break;
+ default:
+ break;
+ }
+
+ if (name != "")
+ {
+ ResourceManager *resman = ResourceManager::getInstance();
+ mImage = resman->getImage(name);
+ }
+ else
+ {
+ mImage = 0;
+ }
+}
+
+void MapItem::setPos(int x, int y)
+{
+ mX = x;
+ mY = y;
+}
+
+void MapItem::draw(Graphics *graphics, int x, int y, int dx, int dy)
+{
+ if (mImage)
+ graphics->drawImage(mImage, x, y);
+
+ switch(mType)
+ {
+ case ROAD:
+ case CROSS:
+ graphics->setColor(userPalette->getColorWithAlpha(
+ UserPalette::ROAD_POINT));
+ graphics->fillRectangle(gcn::Rectangle(x + dx / 3, y + dy / 3,
+ dx / 3, dy / 3));
+ break;
+ case HOME:
+ {
+ graphics->setColor(userPalette->getColorWithAlpha(
+ UserPalette::HOME_PLACE));
+ graphics->fillRectangle(gcn::Rectangle(
+ x, y,
+ dx, dy));
+ graphics->setColor(userPalette->getColorWithAlpha(
+ UserPalette::HOME_PLACE_BORDER));
+ graphics->drawRectangle(gcn::Rectangle(
+ x, y,
+ dx, dy));
+ break;
+ }
+ default:
+ break;
+ }
+ if (!mName.empty() && mType != PORTAL && mType != EMPTY)
+ {
+ gcn::Font *font = gui->getFont();
+ if (font)
+ {
+ graphics->setColor(userPalette->getColor(UserPalette::BEING));
+ font->drawString(graphics, mName, x, y);
+ }
+ }
+}
+