/* * 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 "gui/windows/minimap.h" #include "actormanager.h" #include "configuration.h" #include "party.h" #include "settings.h" #include "being/localplayer.h" #include "enums/resources/map/blockmask.h" #include "fs/virtfs/fs.h" #include "gui/popupmanager.h" #include "gui/viewport.h" #include "gui/userpalette.h" #include "gui/popups/popupmenu.h" #include "gui/popups/textpopup.h" #include "gui/windows/setupwindow.h" #include "resources/imagehelper.h" #include "resources/image/image.h" #include "resources/map/map.h" #include "resources/map/metatile.h" #include "resources/loaders/imageloader.h" #include "utils/gettext.h" #include "utils/foreach.h" #include "utils/sdlcheckutils.h" #include "utils/stdmove.h" #include "debug.h" Minimap *minimap = nullptr; bool Minimap::mShow = true; Minimap::Minimap() : // TRANSLATORS: mini map window name Window(_("Map"), Modal_false, nullptr, "map.xml"), mWidthProportion(0.5), mHeightProportion(0.5), mMapImage(nullptr), mMapOriginX(0), mMapOriginY(0), mCustomMapImage(false), mAutoResize(config.getBoolValue("autoresizeminimaps")) { setWindowName("Minimap"); mShow = config.getValueBool(getWindowName() + "Show", true); config.addListener("autoresizeminimaps", this); setDefaultSize(5, 25, 100, 100); // set this to false as the minimap window size is changed // depending on the map size setResizable(true); if (setupWindow != nullptr) setupWindow->registerWindowForReset(this); setDefaultVisible(true); setSaveVisible(true); setStickyButton(true); setSticky(false); loadWindowState(); setVisible(fromBool(mShow, Visible), isSticky()); enableVisibleSound(true); } Minimap::~Minimap() { config.setValue(getWindowName() + "Show", mShow); config.removeListeners(this); CHECKLISTENERS deleteMapImage(); } void Minimap::deleteMapImage() { if (mMapImage != nullptr) { if (mCustomMapImage) delete mMapImage; else mMapImage->decRef(); mMapImage = nullptr; } } void Minimap::setMap(const Map *const map) { BLOCK_START("Minimap::setMap") std::string caption; if (map != nullptr) caption = map->getName(); if (caption.empty()) { // TRANSLATORS: mini map window name caption = _("Map"); } setCaption(caption); deleteMapImage(); if (map != nullptr) { if (config.getBoolValue("showExtMinimaps")) { SDL_Surface *const surface = MSDL_CreateRGBSurface(SDL_SWSURFACE, map->getWidth(), map->getHeight(), 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000); if (surface == nullptr) { if (!isSticky()) setVisible(Visible_false); BLOCK_END("Minimap::setMap") return; } // I'm not sure if the locks are necessary since it's a SWSURFACE SDL_LockSurface(surface); int* data = static_cast(surface->pixels); if (data == nullptr) { if (!isSticky()) setVisible(Visible_false); BLOCK_END("Minimap::setMap") return; } const int size = surface->h * surface->w; const int mask = (BlockMask::WALL | BlockMask::AIR | BlockMask::WATER | BlockMask::PLAYERWALL); for (int ptr = 0; ptr < size; ptr ++) { *(data ++) = (map->mMetaTiles[ptr].blockmask & mask) != 0 ? 0x0 : 0x00ffffff; } SDL_UnlockSurface(surface); mMapImage = imageHelper->loadSurface(surface); mMapImage->setAlpha(settings.guiAlpha); mCustomMapImage = true; MSDL_FreeSurface(surface); } else { std::string tempname = pathJoin(paths.getStringValue("minimaps"), map->getFilename()).append(".png"); std::string minimapName = map->getProperty("minimap", std::string()); if (minimapName.empty() && VirtFs::exists(tempname)) minimapName = tempname; if (minimapName.empty()) { tempname = pathJoin("graphics/minimaps", map->getFilename()).append(".png"); if (VirtFs::exists(tempname)) minimapName = STD_MOVE(tempname); } if (!minimapName.empty()) mMapImage = Loader::getImage(minimapName); else mMapImage = nullptr; mCustomMapImage = false; } } if ((mMapImage != nullptr) && (map != nullptr)) { const int width = mMapImage->mBounds.w + 2 * getPadding(); const int height = mMapImage->mBounds.h + getTitleBarHeight() + getPadding(); const int mapWidth = mMapImage->mBounds.w < 100 ? width : 100; const int mapHeight = mMapImage->mBounds.h < 100 ? height : 100; const int minWidth = mapWidth > 310 ? 310 : mapWidth; const int minHeight = mapHeight > 220 ? 220 : mapHeight; setMinWidth(minWidth); setMinHeight(minHeight); mWidthProportion = static_cast( mMapImage->mBounds.w) / static_cast(map->getWidth()); mHeightProportion = static_cast( mMapImage->mBounds.h) / static_cast(map->getHeight()); setMaxWidth(width); setMaxHeight(height); if (mAutoResize) { setWidth(width); setHeight(height); } const Rect &rect = mDimension; setDefaultSize(rect.x, rect.y, rect.width, rect.height); resetToDefaultSize(); if (mShow) setVisible(Visible_true); } else { if (!isSticky()) setVisible(Visible_false); } BLOCK_END("Minimap::setMap") } void Minimap::toggle() { setVisible(fromBool(!isWindowVisible(), Visible), isSticky()); mShow = isWindowVisible(); } void Minimap::draw(Graphics *const graphics) { BLOCK_START("Minimap::draw") Window::draw(graphics); draw2(graphics); } void Minimap::safeDraw(Graphics *const graphics) { BLOCK_START("Minimap::draw") Window::safeDraw(graphics); draw2(graphics); } void Minimap::draw2(Graphics *const graphics) { if (userPalette == nullptr || localPlayer == nullptr || viewport == nullptr) { BLOCK_END("Minimap::draw") return; } const Rect a = getChildrenArea(); graphics->pushClipArea(a); if (actorManager == nullptr) { BLOCK_END("Minimap::draw") return; } mMapOriginX = 0; mMapOriginY = 0; if (mMapImage != nullptr) { const SDL_Rect &rect = mMapImage->mBounds; const int w = rect.w; const int h = rect.h; if (w > a.width || h > a.height) { mMapOriginX = (a.width / 2) - (localPlayer->mPixelX + viewport->getCameraRelativeX() * mWidthProportion) / 32; mMapOriginY = (a.height / 2) - (localPlayer->mPixelY + viewport->getCameraRelativeY() * mHeightProportion) / 32; const int minOriginX = a.width - w; const int minOriginY = a.height - h; if (mMapOriginX < minOriginX) mMapOriginX = minOriginX; if (mMapOriginY < minOriginY) mMapOriginY = minOriginY; if (mMapOriginX > 0) mMapOriginX = 0; if (mMapOriginY > 0) mMapOriginY = 0; } graphics->drawImage(mMapImage, mMapOriginX, mMapOriginY); } const ActorSprites &actors = actorManager->getAll(); FOR_EACH (ActorSpritesConstIterator, it, actors) { if (((*it) == nullptr) || (*it)->getType() == ActorType::FloorItem) continue; const Being *const being = static_cast(*it); if (being == nullptr) continue; int dotSize = 2; UserColorIdT type = UserColorId::PC; if (being == localPlayer) { type = UserColorId::SELF; dotSize = 3; } else if (being->isGM()) { type = UserColorId::GM; } else if (being->getGuild() == localPlayer->getGuild() || being->getGuildName() == localPlayer->getGuildName()) { type = UserColorId::GUILD; } else { switch (being->getType()) { case ActorType::Monster: type = UserColorId::MONSTER; break; case ActorType::Npc: type = UserColorId::NPC; break; case ActorType::Portal: type = UserColorId::PORTAL_HIGHLIGHT; break; case ActorType::Pet: type = UserColorId::PET; break; case ActorType::Mercenary: type = UserColorId::MERCENARY; break; case ActorType::Homunculus: type = UserColorId::HOMUNCULUS; break; case ActorType::SkillUnit: type = UserColorId::SKILLUNIT; break; case ActorType::Avatar: case ActorType::Unknown: case ActorType::Player: case ActorType::FloorItem: case ActorType::Elemental: default: continue; } } if (userPalette != nullptr) graphics->setColor(userPalette->getColor(type, 255U)); const int offsetHeight = CAST_S32(static_cast( dotSize - 1) * mHeightProportion); const int offsetWidth = CAST_S32(static_cast( dotSize - 1) * mWidthProportion); graphics->fillRectangle(Rect( (being->mPixelX * mWidthProportion) / 32 + mMapOriginX - offsetWidth, (being->mPixelY * mHeightProportion) / 32 + mMapOriginY - offsetHeight, dotSize, dotSize)); } if (localPlayer->isInParty()) { const Party *const party = localPlayer->getParty(); if (party != nullptr) { const PartyMember *const m = party->getMember( localPlayer->getName()); const Party::MemberList *const members = party->getMembers(); if (m != nullptr) { const std::string curMap = m->getMap(); Party::MemberList::const_iterator it = members->begin(); const Party::MemberList::const_iterator it_end = members->end(); while (it != it_end) { const PartyMember *const member = *it; if ((member != nullptr) && member->getMap() == curMap && member->getOnline() && member != m) { if (userPalette != nullptr) { graphics->setColor(userPalette->getColor( UserColorId::PARTY, 255U)); } const int offsetHeight = CAST_S32( mHeightProportion); const int offsetWidth = CAST_S32( mWidthProportion); graphics->fillRectangle(Rect( CAST_S32(member->getX() * mWidthProportion) + mMapOriginX - offsetWidth, CAST_S32(member->getY() * mHeightProportion) + mMapOriginY - offsetHeight, 2, 2)); } ++ it; } } } } const int gw = graphics->getWidth(); const int gh = graphics->getHeight(); int x = (localPlayer->mPixelX - (gw / 2) + viewport->getCameraRelativeX()) * mWidthProportion / 32 + mMapOriginX; int y = (localPlayer->mPixelY - (gh / 2) + viewport->getCameraRelativeY()) * mHeightProportion / 32 + mMapOriginY; const int w = CAST_S32(static_cast( gw) * mWidthProportion / 32); const int h = CAST_S32(static_cast( gh) * mHeightProportion / 32); if (w <= a.width) { if (x < 0 && (w != 0)) x = 0; if (x + w > a.width) x = a.width - w; } if (h <= a.height) { if (y < 0 && (h != 0)) y = 0; if (y + h > a.height) y = a.height - h; } graphics->setColor(userPalette->getColor(UserColorId::PC, 255U)); graphics->drawRectangle(Rect(x, y, w, h)); graphics->popClipArea(); BLOCK_END("Minimap::draw") } void Minimap::mousePressed(MouseEvent &event) { if (event.getButton() == MouseButton::RIGHT) return; Window::mousePressed(event); } void Minimap::mouseReleased(MouseEvent &event) { Window::mouseReleased(event); if ((localPlayer == nullptr) || (popupManager == nullptr)) return; if (event.getButton() == MouseButton::LEFT) { int x = event.getX(); int y = event.getY(); screenToMap(x, y); localPlayer->navigateTo(x, y); } else if (event.getButton() == MouseButton::RIGHT) { int x = event.getX(); int y = event.getY(); screenToMap(x, y); popupMenu->showMapPopup(viewport->mMouseX, viewport->mMouseY, x, y, true); } } void Minimap::mouseMoved(MouseEvent &event) { Window::mouseMoved(event); const int x = event.getX(); const int y = event.getY(); const Rect &rect = mDimension; textPopup->show(x + rect.x, y + rect.y, mCaption); } void Minimap::mouseExited(MouseEvent &event) { Window::mouseExited(event); textPopup->hide(); } void Minimap::screenToMap(int &x, int &y) { const Rect a = getChildrenArea(); x = (x - a.x - mMapOriginX + mWidthProportion) / mWidthProportion; y = (y - a.y - mMapOriginY + mHeightProportion) / mHeightProportion; } void Minimap::optionChanged(const std::string &name) { if (name == "autoresizeminimaps") mAutoResize = config.getBoolValue("autoresizeminimaps"); }