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; for (int y = startY; y < endY; y++) { for (int x = startX; x < endX; x++) { graphics->setColor(gcn::Color(0, 0, 0, 64)); if (debugFlags & MAP_GRID) { graphics->drawRectangle(gcn::Rectangle( x * mTileWidth - scrollX, y * mTileHeight - scrollY, mTileWidth + 1, mTileHeight + 1)); } if (!(debugFlags & MAP_COLLISION_TILES)) continue; if (!getWalk(x, y, BLOCKMASK_WALL)) { graphics->setColor(gcn::Color(0, 0, 200, 64)); graphics->fillRectangle(gcn::Rectangle( x * mTileWidth - scrollX, y * mTileHeight - scrollY, mTileWidth, mTileHeight)); } if (!getWalk(x, y, BLOCKMASK_MONSTER)) { graphics->setColor(gcn::Color(200, 0, 0, 64)); graphics->fillRectangle(gcn::Rectangle( x * mTileWidth - scrollX, y * mTileHeight - scrollY, mTileWidth, mTileHeight)); } if (!getWalk(x, y, BLOCKMASK_CHARACTER)) { graphics->setColor(gcn::Color(0, 200, 0, 64)); graphics->fillRectangle(gcn::Rectangle( x * mTileWidth - scrollX, y * mTileHeight - scrollY, mTileWidth, mTileHeight)); } } } }
void Map::drawCollision(Graphics *graphics, int scrollX, int scrollY) { 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; for (int y = startY; y < endY; y++) { for (int x = startX; x < endX; x++) { graphics->setColor(gcn::Color(0, 0, 0, 64)); graphics->drawRectangle(gcn::Rectangle( x * mTileWidth - scrollX, y * mTileWidth - scrollY, 33, 33)); if (!getWalk(x, y, BLOCKMASK_WALL)) { graphics->setColor(gcn::Color(0, 0, 200, 64)); graphics->fillRectangle(gcn::Rectangle( x * mTileWidth - scrollX, y * mTileWidth - scrollY, 32, 32)); } if (!getWalk(x, y, BLOCKMASK_MONSTER)) { graphics->setColor(gcn::Color(200, 0, 0, 64)); graphics->fillRectangle(gcn::Rectangle( x * mTileWidth - scrollX, y * mTileWidth - scrollY, 32, 32)); } if (!getWalk(x, y, BLOCKMASK_CHARACTER)) { graphics->setColor(gcn::Color(0, 200, 0, 64)); graphics->fillRectangle(gcn::Rectangle( x * mTileWidth - scrollX, y * mTileWidth - scrollY, 32, 32)); } } } }
Position Map::checkNodeOffsets(int radius, unsigned char walkMask, const Position &position) const { // Pre-computing character's position in tiles const int tx = position.x / mTileWidth; const int ty = position.y / mTileHeight; // Pre-computing character's position offsets. int fx = position.x % mTileWidth; int fy = position.y % mTileHeight; // 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 > mTileWidth / 2) radius = mTileWidth / 2; // set a default value if no value returned. if (radius < 1) radius = mTileWidth / 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 = fy = radius; } // Top-right border check if (!getWalk(tx + 1, ty - 1, walkMask) && (fy < radius) && fx > (mTileWidth - radius)) { fx = mTileWidth - radius; fy = radius; } // Bottom-left border check if (!getWalk(tx - 1, ty + 1, walkMask) && fy > (mTileHeight - radius) && fx < radius) { fx = radius; fy = mTileHeight - radius; } // Bottom-right border check if (!getWalk(tx + 1, ty + 1, walkMask) && fy > (mTileHeight - radius) && fx > (mTileWidth - radius)) { fx = mTileWidth - radius; fy = mTileHeight - radius; } // Fix coordinates so that the player does not seem to dig into walls. if (fx > (mTileWidth - radius) && !getWalk(tx + 1, ty, walkMask)) fx = mTileWidth - radius; else if (fx < radius && !getWalk(tx - 1, ty, walkMask)) fx = radius; else if (fy > (mTileHeight - radius) && !getWalk(tx, ty + 1, walkMask)) fy = mTileHeight - radius; else if (fy < radius && !getWalk(tx, ty - 1, walkMask)) fy = radius; return Position(tx * mTileWidth + fx, ty * mTileHeight + fy); }
Path Map::findSimplePath(int startX, int startY, int destX, int destY, unsigned char walkmask) { // Path to be built up (empty by default) Path path; int positionX = startX, positionY = startY; int directionX, directionY; // Checks our path up to 1 tiles, if a blocking tile is found // We go to the last good tile, and break out of the loop while(true) { directionX = destX - positionX; directionY = destY - positionY; if (directionX > 0) directionX = 1; else if(directionX < 0) directionX = -1; if (directionY > 0) directionY = 1; else if(directionY < 0) directionY = -1; positionX += directionX; positionY += directionY; if (getWalk(positionX, positionY, walkmask)) { path.push_back(Position(positionX, positionY)); if ((positionX == destX) && (positionY == destY)) { return path; } } else { return path; } } }
Path Map::findPath(int startX, int startY, int destX, int destY, unsigned char walkmask, int maxCost) { // 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->whichList == mOnClosedList || ((newTile->blockmask & walkmask) && !(x == destX && y == destY))) { 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->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 (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; }