/* * The ManaPlus Client * Copyright (C) 2004-2009 The Mana World Development Team * Copyright (C) 2009-2010 The Mana Developers * Copyright (C) 2011-2020 The ManaPlus Developers * Copyright (C) 2020-2023 The ManaVerse Developers * * This file is part of The ManaPlus Client. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "resources/map/maplayer.h" #include "configuration.h" #include "being/localplayer.h" #include "enums/resources/map/blockmask.h" #include "enums/resources/map/mapitemtype.h" #include "gui/userpalette.h" #ifdef USE_OPENGL #include "utils/foreach.h" #endif // USE_OPENGL #include "render/graphics.h" #include "resources/image/image.h" #include "resources/map/mapitem.h" #include "resources/map/maprowvertexes.h" #include "resources/map/metatile.h" #include "resources/map/speciallayer.h" #include "debug.h" MapLayer::MapLayer(const std::string &name, const int x, const int y, const int width, const int height, const bool fringeLayer, const int mask, const int tileCondition) : mX(x), mY(y), mPixelX(mX * mapTileSize), mPixelY(mY * mapTileSize + mapTileSize), mWidth(width), mHeight(height), mTiles(new TileInfo[mWidth * mHeight]), mDrawLayerFlags(MapType::NORMAL), mSpecialLayer(nullptr), mTempLayer(nullptr), mName(name), mTempRows(), mMask(mask), mTileCondition(tileCondition), mActorsFix(0), mIsFringeLayer(fringeLayer), mHighlightAttackRange(config.getBoolValue("highlightAttackRange")), mSpecialFlag(true) { // std::fill_n(mTiles, mWidth * mHeight, static_cast(nullptr)); config.addListener("highlightAttackRange", this); } MapLayer::~MapLayer() { config.removeListener("highlightAttackRange", this); CHECKLISTENERS delete []mTiles; delete_all(mTempRows); mTempRows.clear(); } void MapLayer::optionChanged(const std::string &value) restrict { if (value == "highlightAttackRange") { mHighlightAttackRange = config.getBoolValue("highlightAttackRange"); } } void MapLayer::draw(Graphics *const graphics, int startX, int startY, int endX, int endY, const int scrollX, const int scrollY) const restrict { 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 = mPixelX - scrollX; const int dy = mPixelY - scrollY; for (int y = startY; y < endY; y++) { const int y32 = y * mapTileSize; const int yWidth = y * mWidth; const int py0 = y32 + dy; int x0 = startX; TileInfo *tilePtr = &mTiles[CAST_SIZE(x0 + yWidth)]; if (tilePtr->isEnabled == false) { if (x0 + tilePtr->nextTile + 1 >= endX) { continue; } x0 += tilePtr->nextTile + 1; tilePtr = &mTiles[CAST_SIZE(x0 + yWidth)]; if (mTiles[x0 + yWidth].isEnabled == false) { continue; } } for (int x = x0; x < endX; x++, tilePtr++) { const int x32 = x * mapTileSize; const Image *const img = tilePtr->image; const int px = x32 + dx; const int py = py0 - img->mBounds.h; if (mSpecialFlag || img->mBounds.h <= mapTileSize) { if (tilePtr->count == 0) { graphics->drawImage(img, px, py); } else { graphics->drawPattern(img, px, py, tilePtr->width, img->mBounds.h); } } const int nextTile = tilePtr->nextTile; x += nextTile; tilePtr += nextTile; } } BLOCK_END("MapLayer::draw") } void MapLayer::drawSDL(Graphics *const graphics) const restrict2 { BLOCK_START("MapLayer::drawSDL") MapRows::const_iterator rit = mTempRows.begin(); const MapRows::const_iterator rit_end = mTempRows.end(); while (rit != rit_end) { MapRowImages *const images = &(*rit)->images; MapRowImages::const_iterator iit = images->begin(); const MapRowImages::const_iterator iit_end = images->end(); while (iit != iit_end) { graphics->drawTileVertexes(*iit); ++ iit; } ++ rit; } BLOCK_END("MapLayer::drawSDL") } #ifdef USE_OPENGL void MapLayer::updateSDL(const Graphics *const graphics, int startX, int startY, int endX, int endY, const int scrollX, const int scrollY) restrict2 { 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 = mPixelX - scrollX; const int dy = mPixelY - scrollY; 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; TileInfo *tilePtr = &mTiles[CAST_SIZE(startX + yWidth)]; for (int x = startX; x < endX; x++, tilePtr++) { if (!tilePtr->isEnabled) continue; Image *const img = (*tilePtr).image; const int px = x * mapTileSize + dx; const int py = py0 - img->mBounds.h; if (mSpecialFlag || 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(Graphics *const graphics, int startX, int startY, int endX, int endY, const int scrollX, const int scrollY) restrict2 { 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 = mPixelX - scrollX; const int dy = mPixelY - scrollY; MapRowVertexes *const row = new MapRowVertexes; mTempRows.push_back(row); Image *lastImage = nullptr; ImageVertexes *imgVert = nullptr; typedef std::map ImageVertexesMap; ImageVertexesMap imgSet; for (int y = startY; y < endY; y++) { const int yWidth = y * mWidth; const int py0 = y * mapTileSize + dy; TileInfo *tilePtr = &mTiles[CAST_SIZE(startX + yWidth)]; for (int x = startX; x < endX; x++, tilePtr++) { if (!tilePtr->isEnabled) continue; Image *const img = (*tilePtr).image; const int px = x * mapTileSize + dx; const int py = py0 - img->mBounds.h; const GLuint imgGlImage = img->mGLImage; if (mSpecialFlag || img->mBounds.h <= mapTileSize) { if ((lastImage == nullptr) || lastImage->mGLImage != imgGlImage) { if (img->mBounds.w > mapTileSize) imgSet.clear(); if (imgSet.find(imgGlImage) != imgSet.end()) { imgVert = imgSet[imgGlImage]; } else { if (lastImage != nullptr) imgSet[lastImage->mGLImage] = imgVert; imgVert = new ImageVertexes; imgVert->ogl.init(); imgVert->image = img; row->images.push_back(imgVert); } } lastImage = img; graphics->calcTileVertexes(imgVert, lastImage, px, py); } } } FOR_EACH (MapRowImages::iterator, it, row->images) { graphics->finalize(*it); } BLOCK_END("MapLayer::updateOGL") } void MapLayer::drawOGL(Graphics *const graphics) const restrict2 { BLOCK_START("MapLayer::drawOGL") MapRows::const_iterator rit = mTempRows.begin(); const MapRows::const_iterator rit_end = mTempRows.end(); while (rit != rit_end) { const MapRowImages *const images = &(*rit)->images; MapRowImages::const_iterator iit = images->begin(); const MapRowImages::const_iterator iit_end = images->end(); while (iit != iit_end) { graphics->drawTileVertexes(*iit); ++ iit; } ++ rit; } BLOCK_END("MapLayer::drawOGL") } #endif // USE_OPENGL void MapLayer::drawSpecialLayer(Graphics *const graphics, const int y, const int startX, const int endX, const int scrollX, const int scrollY) const restrict { const int y32 = y * mapTileSize; const int py1 = y32 - scrollY; int endX1 = endX; int specialWidth = mSpecialLayer->mWidth; int ptr = y * specialWidth; if (endX1 > specialWidth) endX1 = specialWidth; if (endX1 < 0) endX1 = 0; int x0 = startX; const MapItem *item0 = mSpecialLayer->mTiles[ptr + startX]; if ((item0 == nullptr) || item0->mType == MapItemType::EMPTY) { x0 += mSpecialLayer->mCache[ptr + startX] + 1; } for (int x = x0; x < endX1; x++) { const int px1 = x * mapTileSize - scrollX; const MapItem *const item = mSpecialLayer->mTiles[ptr + x]; if (item != nullptr) { item->draw(graphics, px1, py1, mapTileSize, mapTileSize); } x += mSpecialLayer->mCache[ptr + x]; } x0 = startX; specialWidth = mTempLayer->mWidth; ptr = y * specialWidth; endX1 = endX; if (endX1 > specialWidth) endX1 = specialWidth; item0 = mTempLayer->mTiles[ptr + startX]; if ((item0 == nullptr) || item0->mType == MapItemType::EMPTY) { x0 += mTempLayer->mCache[ptr + startX] + 1; } for (int x = x0; x < endX1; x++) { const int px1 = x * mapTileSize - scrollX; const MapItem *const item = mTempLayer->mTiles[ptr + x]; item->draw(graphics, px1, py1, mapTileSize, mapTileSize); x += mTempLayer->mCache[ptr + x]; } } void MapLayer::drawFringe(Graphics *const graphics, int startX, int startY, int endX, int endY, const int scrollX, const int scrollY, const Actors &actors) const restrict { BLOCK_START("MapLayer::drawFringe") if ((localPlayer == nullptr) || (mSpecialLayer == nullptr) || (mTempLayer == nullptr)) { 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 = mPixelX - scrollX; const int dy = mPixelY - scrollY; const int specialHeight = mSpecialLayer->mHeight; const bool flag = mDrawLayerFlags == MapType::SPECIAL3 || mDrawLayerFlags == MapType::SPECIAL4 || mDrawLayerFlags == MapType::BLACKWHITE; const int minEndY = std::min(specialHeight, endY); if (flag) { // flag for (int y = startY; y < minEndY; y ++) { const int y32s = (y + mActorsFix) * mapTileSize; 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") // remove this condition, because it always true if (y < specialHeight) { drawSpecialLayer(graphics, y, startX, endX, scrollX, scrollY); } } for (int y = minEndY; y < endY; y++) { const int y32s = (y + mActorsFix) * mapTileSize; 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") } } else { // !flag for (int y = startY; y < minEndY; y ++) { const int y32 = y * mapTileSize; const int y32s = (y + mActorsFix) * 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") const int py0 = y32 + dy; int x0 = startX; TileInfo *tilePtr = &mTiles[CAST_SIZE(x0 + yWidth)]; if (tilePtr->isEnabled == false) { drawSpecialLayer(graphics, y, 0, std::min(x0 + tilePtr->nextTile + 1, endX), scrollX, scrollY); if (x0 + tilePtr->nextTile + 1 >= endX) { continue; } x0 += tilePtr->nextTile + 1; tilePtr = &mTiles[CAST_SIZE(x0 + yWidth)]; if (mTiles[x0 + yWidth].isEnabled == false) continue; } for (int x = x0; x < endX; x++, tilePtr++) { const int x32 = x * mapTileSize; const Image *const img = tilePtr->image; if (mSpecialFlag || img->mBounds.h <= mapTileSize) { const int px = x32 + dx; const int py = py0 - img->mBounds.h; if (tilePtr->count == 0) { graphics->drawImage(img, px, py); } else { graphics->drawPattern(img, px, py, tilePtr->width, img->mBounds.h); } } const int nextTile = tilePtr->nextTile; // remove this condition, because it always true if (y < specialHeight) { drawSpecialLayer(graphics, y, x, std::min(x + nextTile + 1, endX), scrollX, scrollY); } x += nextTile; tilePtr += nextTile; } } for (int y = minEndY; y < endY; y++) { const int y32 = y * mapTileSize; const int y32s = (y + mActorsFix) * 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") const int py0 = y32 + dy; int x0 = startX; TileInfo *tilePtr = &mTiles[CAST_SIZE(x0 + yWidth)]; if (tilePtr->isEnabled == false) { if (x0 + tilePtr->nextTile + 1 >= endX) continue; x0 += tilePtr->nextTile + 1; tilePtr = &mTiles[CAST_SIZE(x0 + yWidth)]; if (mTiles[x0 + yWidth].isEnabled == false) continue; } for (int x = x0; x < endX; x++, tilePtr++) { const int x32 = x * mapTileSize; const Image *const img = tilePtr->image; const int px = x32 + dx; const int py = py0 - img->mBounds.h; if (mSpecialFlag || img->mBounds.h <= mapTileSize) { const int c = tilePtr->count; if (c == 0) { graphics->drawImage(img, px, py); } else { graphics->drawPattern(img, px, py, tilePtr->width, img->mBounds.h); } } const int nextTile = tilePtr->nextTile; x += nextTile; tilePtr += nextTile; } } } // !flag // Draw any remaining actors if (mDrawLayerFlags != MapType::SPECIAL3 && mDrawLayerFlags != MapType::SPECIAL4) { BLOCK_START("MapLayer::drawFringe drawmobs") while (ai != ai_end) { (*ai)->draw(graphics, -scrollX, -scrollY); ++ai; } BLOCK_END("MapLayer::drawFringe drawmobs") if (mHighlightAttackRange) { const int px = localPlayer->getPixelX() - scrollX - mapTileSize / 2; const int py = localPlayer->getPixelY() - scrollY - mapTileSize; const int attackRange = localPlayer->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 != nullptr) { graphics->setColor(userPalette->getColorWithAlpha( UserColorId::ATTACK_RANGE)); graphics->fillRectangle(Rect(x, y, w, h)); graphics->setColor(userPalette->getColorWithAlpha( UserColorId::ATTACK_RANGE_BORDER)); graphics->drawRectangle(Rect(x, y, w, h)); } } } BLOCK_END("MapLayer::drawFringe") } int MapLayer::getTileDrawWidth(const TileInfo *restrict tilePtr, const int endX, int &restrict width, int &restrict nextTile) { BLOCK_START("MapLayer::getTileDrawWidth") const Image *const img1 = tilePtr->image; int c = 0; width = img1->mBounds.w; for (int x = 1; x < endX; x++) { tilePtr ++; const Image *const img = tilePtr->image; if (img == nullptr || tilePtr->isEnabled == false) { break; } if (img != img1) { nextTile = c; BLOCK_END("MapLayer::getTileDrawWidth") return c; } c ++; width += img->mBounds.w; } int c2 = c; for (int x2 = c2 + 1; x2 < endX; x2++) { if (tilePtr->image != nullptr && tilePtr->isEnabled == true) { break; } c2 ++; tilePtr ++; } nextTile = c2; BLOCK_END("MapLayer::getTileDrawWidth") return c; } int MapLayer::getEmptyTileDrawWidth(const TileInfo *restrict tilePtr, const int endX, int &restrict nextTile) { BLOCK_START("MapLayer::getEmptyTileDrawWidth") int c = 0; for (int x = 1; x < endX; x++) { tilePtr ++; const Image *const img = tilePtr->image; if (img != nullptr && tilePtr->isEnabled == true) break; c ++; } BLOCK_END("MapLayer::getEmptyTileDrawWidth") nextTile = c; return c; } void MapLayer::updateConditionTiles(const MetaTile *const metaTiles, const int width, const int height) restrict { const int width1 = width < mWidth ? width : mWidth; const int height1 = height < mHeight ? height : mHeight; for (int y = mY; y < height1; y ++) { const MetaTile *metaPtr = metaTiles + (y - mY) * width; TileInfo *tilePtr = mTiles + y * mWidth; for (int x = mX; x < width1; x ++, metaPtr ++, tilePtr ++) { if (tilePtr->image != nullptr && (((metaPtr->blockmask & mTileCondition) != 0) || (metaPtr->blockmask == 0 && mTileCondition == BlockMask::GROUND))) { tilePtr->isEnabled = true; } else { tilePtr->isEnabled = false; } } } } void MapLayer::updateCache(const int width, const int height) restrict { const int width1 = width < mWidth ? width : mWidth; const int height1 = height < mHeight ? height : mHeight; for (int y = mY; y < height1; y ++) { for (int x = mX; x < width1; x ++) { TileInfo *tilePtr = mTiles + y * mWidth + x; int nextTile = 0; if (tilePtr->image == nullptr || tilePtr->isEnabled == false) { tilePtr->isEnabled = false; tilePtr->count = getEmptyTileDrawWidth(tilePtr, width1 - x, nextTile); tilePtr->width = 0; } else { int tileWidth = 0; tilePtr->count = getTileDrawWidth(tilePtr, width1 - x, tileWidth, nextTile); tilePtr->width = tileWidth; } tilePtr->nextTile = nextTile; } } } int MapLayer::calcMemoryLocal() const { return static_cast(sizeof(MapLayer) + sizeof(TileInfo) * mWidth * mHeight + sizeof(MapRowVertexes) * mTempRows.capacity()); } int MapLayer::calcMemoryChilds(const int level) const { int sz = 0; if (mSpecialLayer != nullptr) sz += mSpecialLayer->calcMemory(level + 1); if (mTempLayer != nullptr) sz += mTempLayer->calcMemory(level + 1); return sz; }