/* * The Mana Server * Copyright (C) 2004-2011 The Mana World Development Team * * This file is part of The Mana Server. * * The Mana Server 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. * * The Mana Server 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 The Mana Server. If not, see . */ #include #include #include #include #include #include "game-server/map.h" #include "common/defines.h" /** * Stores information used during path finding for each tile of a map. */ class PathInfo { public: PathInfo() : Gcost(0) , Hcost(0) , whichList(0) , parentX(0) , parentY(0) {} 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 */ }; /** * A helper class for finding a path on a map, functor style. */ class FindPath { public: FindPath() : mWidth(0), mOnClosedList(1), mOnOpenList(2) {} Path operator() (int startX, int startY, int destX, int destY, unsigned char walkmask, int maxCost, const Map *map); private: PathInfo *getInfo(int x, int y) { return &mPathInfos.at(x + y * mWidth); } void prepare(const Map *map); int mWidth; std::vector mPathInfos; unsigned mOnClosedList, mOnOpenList; }; static FindPath findPath; /** * A location on a tile map. Used for pathfinding, open list. */ class Location { public: Location(int x, int y, int Fcost): x(x), y(y), Fcost(Fcost) {} /** * Comparison operator. */ bool operator< (const Location &other) const { return Fcost > other.Fcost; } int x, y; int Fcost; /**< Estimation of total path cost */ }; Map::Map(int width, int height, int tileWidth, int tileHeight): mWidth(width), mHeight(height), mTileWidth(tileWidth), mTileHeight(tileHeight), mMetaTiles(width * height) { } Map::~Map() { for (std::vector::iterator it = mMapObjects.begin(); it != mMapObjects.end(); ++it) { delete *it; } } void Map::setSize(int width, int height) { mWidth = width; mHeight = height; mMetaTiles.resize(width * height); } const std::string &Map::getProperty(const std::string &key) const { static std::string empty; std::map::const_iterator i; i = mProperties.find(key); if (i == mProperties.end()) return empty; return i->second; } void Map::blockTile(int x, int y, BlockType type) { if (type == BLOCKTYPE_NONE || !contains(x, y)) return; MetaTile &metaTile = mMetaTiles[x + y * mWidth]; if (metaTile.occupation[type] < UINT_MAX && (++metaTile.occupation[type]) > 0) { switch (type) { case BLOCKTYPE_WALL: metaTile.blockmask |= BLOCKMASK_WALL; break; case BLOCKTYPE_CHARACTER: metaTile.blockmask |= BLOCKMASK_CHARACTER; break; case BLOCKTYPE_MONSTER: metaTile.blockmask |= BLOCKMASK_MONSTER; break; default: // Nothing to do. break; } } } void Map::freeTile(int x, int y, BlockType type) { if (type == BLOCKTYPE_NONE || !contains(x, y)) return; MetaTile &metaTile = mMetaTiles[x + y * mWidth]; assert(metaTile.occupation[type] > 0); if (!(--metaTile.occupation[type])) { switch (type) { case BLOCKTYPE_WALL: metaTile.blockmask &= (BLOCKMASK_WALL xor 0xff); break; case BLOCKTYPE_CHARACTER: metaTile.blockmask &= (BLOCKMASK_CHARACTER xor 0xff); break; case BLOCKTYPE_MONSTER: metaTile.blockmask &= (BLOCKMASK_MONSTER xor 0xff); break; default: // nothing break; } } } bool Map::getWalk(int x, int y, 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); } Path Map::findPath(int startX, int startY, int destX, int destY, unsigned char walkmask, int maxCost) const { return ::findPath(startX, startY, destX, destY, walkmask, maxCost, this); } Path FindPath::operator() (int startX, int startY, int destX, int destY, unsigned char walkmask, int maxCost, const Map *map) { // Basic cost for moving from one tile to another. static int const basicCost = 100; // Path to be built up (empty by default) Path path; // Return when destination not walkable if (!map->getWalk(destX, destY, walkmask)) return path; prepare(map); // Declare open list, a list with open tiles sorted on F cost std::priority_queue openList; // Reset starting tile's G cost to 0 PathInfo *startTile = getInfo(startX, startY); startTile->Gcost = 0; // Add the start point to the open list (F cost irrelevant here) openList.push(Location(startX, startY, 0)); 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, and // add it to the closed list. Location curr = openList.top(); openList.pop(); PathInfo *currInfo = getInfo(curr.x, curr.y); // 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 (currInfo->whichList == mOnClosedList) continue; // Put the current tile on the closed list currInfo->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 int x = curr.x + dx; 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) || !map->contains(x, y)) continue; PathInfo *newTile = getInfo(x, y); // Skip if the tile is on the closed list or is not walkable if (newTile->whichList == mOnClosedList || !map->getWalk(x, y, walkmask)) continue; // When taking a diagonal step, verify that we can skip the // corner. if (dx != 0 && dy != 0) { if (!map->getWalk(curr.x, curr.y + dy, walkmask) || !map->getWalk(curr.x + dx, curr.y, walkmask)) continue; } // Calculate G cost for this route, ~sqrt(2) for moving diagonal int Gcost = currInfo->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; } // Skip if Gcost becomes too much // Warning: probably not entirely accurate if (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 of new tile newTile->Gcost = Gcost; if (x != destX || y != destY) { // Add this tile to the open list newTile->whichList = mOnOpenList; openList.push(Location(x, y, Gcost + newTile->Hcost)); } else { // Target location was found foundPath = true; } } else if (Gcost < newTile->Gcost) { // Found a shorter route. // Update Gcost of the new tile newTile->Gcost = Gcost; // 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, Gcost + newTile->Hcost)); } } } } // 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(Point(pathX, pathY)); // Find out the next parent PathInfo *tile = getInfo(pathX, pathY); pathX = tile->parentX; pathY = tile->parentY; } } return path; } void FindPath::prepare(const Map *map) { // 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) { mOnClosedList += 2; mOnOpenList += 2; } else { // Reset closed and open list IDs and clear the whichList values mOnClosedList = 1; mOnOpenList = 2; for (unsigned i = 0, end = mPathInfos.size(); i < end; ++i) mPathInfos[i].whichList = 0; } // Make sure we have enough room to cover this map with path information const unsigned size = map->getWidth() * map->getHeight(); if (mPathInfos.size() < size) mPathInfos.resize(size); mWidth = map->getWidth(); }