#include "mapwindow.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwworld/player.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwrender/globalmap.hpp" #include "../mwrender/localmap.hpp" #include "confirmationdialog.hpp" #include "tooltips.hpp" #include namespace { const int cellSize = Constants::CellSizeInUnits; constexpr float speed = 1.08f; //the zoom speed, it should be greater than 1 enum LocalMapWidgetDepth { Local_MarkerAboveFogLayer = 0, Local_CompassLayer = 1, Local_FogLayer = 2, Local_MarkerLayer = 3, Local_MapLayer = 4 }; enum GlobalMapWidgetDepth { Global_CompassLayer = 0, Global_MarkerLayer = 1, Global_ExploreOverlayLayer = 2, Global_MapLayer = 3 }; /// @brief A widget that changes its color when hovered. class MarkerWidget final : public MyGUI::Widget { MYGUI_RTTI_DERIVED(MarkerWidget) public: void setNormalColour(const MyGUI::Colour& colour) { mNormalColour = colour; setColour(colour); } void setHoverColour(const MyGUI::Colour& colour) { mHoverColour = colour; } private: MyGUI::Colour mNormalColour; MyGUI::Colour mHoverColour; void onMouseLostFocus(MyGUI::Widget* _new) override { setColour(mNormalColour); } void onMouseSetFocus(MyGUI::Widget* _old) override { setColour(mHoverColour); } }; MyGUI::IntRect createRect(const MyGUI::IntPoint& center, int radius) { return { center.left - radius, center.top + radius, center.left + radius, center.top - radius }; } int getLocalViewingDistance() { if (!Settings::Manager::getBool("allow zooming", "Map")) return Constants::CellGridRadius; if (!Settings::Manager::getBool("distant terrain", "Terrain")) return Constants::CellGridRadius; const int maxLocalViewingDistance = std::max(Settings::Manager::getInt("max local viewing distance", "Map"), Constants::CellGridRadius); const int viewingDistanceInCells = Settings::Manager::getFloat("viewing distance", "Camera") / Constants::CellSizeInUnits; return std::min(maxLocalViewingDistance, viewingDistanceInCells); } } namespace MWGui { void CustomMarkerCollection::addMarker(const ESM::CustomMarker &marker, bool triggerEvent) { mMarkers.insert(std::make_pair(marker.mCell, marker)); if (triggerEvent) eventMarkersChanged(); } void CustomMarkerCollection::deleteMarker(const ESM::CustomMarker &marker) { std::pair range = mMarkers.equal_range(marker.mCell); for (ContainerType::iterator it = range.first; it != range.second; ++it) { if (it->second == marker) { mMarkers.erase(it); eventMarkersChanged(); return; } } throw std::runtime_error("can't find marker to delete"); } void CustomMarkerCollection::updateMarker(const ESM::CustomMarker &marker, const std::string &newNote) { std::pair range = mMarkers.equal_range(marker.mCell); for (ContainerType::iterator it = range.first; it != range.second; ++it) { if (it->second == marker) { it->second.mNote = newNote; eventMarkersChanged(); return; } } throw std::runtime_error("can't find marker to update"); } void CustomMarkerCollection::clear() { mMarkers.clear(); eventMarkersChanged(); } CustomMarkerCollection::ContainerType::const_iterator CustomMarkerCollection::begin() const { return mMarkers.begin(); } CustomMarkerCollection::ContainerType::const_iterator CustomMarkerCollection::end() const { return mMarkers.end(); } CustomMarkerCollection::RangeType CustomMarkerCollection::getMarkers(const ESM::CellId &cellId) const { return mMarkers.equal_range(cellId); } size_t CustomMarkerCollection::size() const { return mMarkers.size(); } // ------------------------------------------------------ LocalMapBase::LocalMapBase(CustomMarkerCollection &markers, MWRender::LocalMap* localMapRender, bool fogOfWarEnabled) : mLocalMapRender(localMapRender) , mCurX(0) , mCurY(0) , mInterior(false) , mLocalMap(nullptr) , mCompass(nullptr) , mChanged(true) , mFogOfWarToggled(true) , mFogOfWarEnabled(fogOfWarEnabled) , mMapWidgetSize(0) , mNumCells(1) , mCellDistance(0) , mCustomMarkers(markers) , mMarkerUpdateTimer(0.0f) , mLastDirectionX(0.0f) , mLastDirectionY(0.0f) , mNeedDoorMarkersUpdate(false) { mCustomMarkers.eventMarkersChanged += MyGUI::newDelegate(this, &LocalMapBase::updateCustomMarkers); } LocalMapBase::~LocalMapBase() { mCustomMarkers.eventMarkersChanged -= MyGUI::newDelegate(this, &LocalMapBase::updateCustomMarkers); } void LocalMapBase::init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass, int cellDistance) { mLocalMap = widget; mCompass = compass; mMapWidgetSize = std::max(1, Settings::Manager::getInt("local map widget size", "Map")); mCellDistance = cellDistance; mNumCells = mCellDistance * 2 + 1; mLocalMap->setCanvasSize(mMapWidgetSize*mNumCells, mMapWidgetSize*mNumCells); mCompass->setDepth(Local_CompassLayer); mCompass->setNeedMouseFocus(false); for (int mx=0; mxcreateWidget("ImageBox", MyGUI::IntCoord(mx*mMapWidgetSize, my*mMapWidgetSize, mMapWidgetSize, mMapWidgetSize), MyGUI::Align::Top | MyGUI::Align::Left); map->setDepth(Local_MapLayer); MyGUI::ImageBox* fog = mLocalMap->createWidget("ImageBox", MyGUI::IntCoord(mx*mMapWidgetSize, my*mMapWidgetSize, mMapWidgetSize, mMapWidgetSize), MyGUI::Align::Top | MyGUI::Align::Left); fog->setDepth(Local_FogLayer); fog->setColour(MyGUI::Colour(0, 0, 0)); map->setNeedMouseFocus(false); fog->setNeedMouseFocus(false); mMaps.emplace_back(map, fog); } } } void LocalMapBase::setCellPrefix(const std::string& prefix) { mPrefix = prefix; mChanged = true; } bool LocalMapBase::toggleFogOfWar() { mFogOfWarToggled = !mFogOfWarToggled; applyFogOfWar(); return mFogOfWarToggled; } void LocalMapBase::applyFogOfWar() { if (!mFogOfWarToggled || !mFogOfWarEnabled) { for (auto& entry : mMaps) { entry.mFogWidget->setImageTexture(""); entry.mFogTexture.reset(); } } redraw(); } MyGUI::IntPoint LocalMapBase::getPosition(int cellX, int cellY, float nX, float nY) const { // normalized cell coordinates auto mapWidgetSize = getWidgetSize(); return MyGUI::IntPoint( std::round(nX * mapWidgetSize + (mCellDistance + (cellX - mCurX)) * mapWidgetSize), std::round(nY * mapWidgetSize + (mCellDistance - (cellY - mCurY)) * mapWidgetSize) ); } MyGUI::IntPoint LocalMapBase::getMarkerPosition(float worldX, float worldY, MarkerUserData& markerPos) const { int cellX, cellY; // normalized cell coordinates float nX,nY; if (!mInterior) { MWBase::Environment::get().getWorld()->positionToIndex(worldX, worldY, cellX, cellY); nX = (worldX - cellSize * cellX) / cellSize; // Image space is -Y up, cells are Y up nY = 1 - (worldY - cellSize * cellY) / cellSize; } else mLocalMapRender->worldToInteriorMapPosition({ worldX, worldY }, nX, nY, cellX, cellY); markerPos.cellX = cellX; markerPos.cellY = cellY; markerPos.nX = nX; markerPos.nY = nY; return getPosition(markerPos.cellX, markerPos.cellY, markerPos.nX, markerPos.nY); } MyGUI::IntCoord LocalMapBase::getMarkerCoordinates(float worldX, float worldY, MarkerUserData& markerPos, size_t markerSize) const { int halfMarkerSize = markerSize / 2; auto position = getMarkerPosition(worldX, worldY, markerPos); return MyGUI::IntCoord(position.left - halfMarkerSize, position.top - halfMarkerSize, markerSize, markerSize); } MyGUI::Widget* LocalMapBase::createDoorMarker(const std::string& name, const MyGUI::VectorString& notes, float x, float y) const { MarkerUserData data(mLocalMapRender); data.notes = notes; data.caption = name; MarkerWidget* markerWidget = mLocalMap->createWidget("MarkerButton", getMarkerCoordinates(x, y, data, 8), MyGUI::Align::Default); markerWidget->setNormalColour(MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=normal}"))); markerWidget->setHoverColour(MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=normal_over}"))); markerWidget->setDepth(Local_MarkerLayer); markerWidget->setNeedMouseFocus(true); // Used by tooltips to not show the tooltip if marker is hidden by fog of war markerWidget->setUserString("ToolTipType", "MapMarker"); markerWidget->setUserData(data); return markerWidget; } void LocalMapBase::centerView() { MyGUI::IntPoint pos = mCompass->getPosition() + MyGUI::IntPoint{ 16, 16 }; MyGUI::IntSize viewsize = mLocalMap->getSize(); MyGUI::IntPoint viewOffset((viewsize.width / 2) - pos.left, (viewsize.height / 2) - pos.top); mLocalMap->setViewOffset(viewOffset); } MyGUI::IntCoord LocalMapBase::getMarkerCoordinates(MyGUI::Widget* widget, size_t markerSize) const { MarkerUserData& markerPos(*widget->getUserData()); auto position = getPosition(markerPos.cellX, markerPos.cellY, markerPos.nX, markerPos.nY); return MyGUI::IntCoord(position.left - markerSize / 2, position.top - markerSize / 2, markerSize, markerSize); } std::vector& LocalMapBase::currentDoorMarkersWidgets() { return mInterior ? mInteriorDoorMarkerWidgets : mExteriorDoorMarkerWidgets; } void LocalMapBase::updateCustomMarkers() { for (MyGUI::Widget* widget : mCustomMarkerWidgets) MyGUI::Gui::getInstance().destroyWidget(widget); mCustomMarkerWidgets.clear(); for (int dX = -mCellDistance; dX <= mCellDistance; ++dX) { for (int dY =-mCellDistance; dY <= mCellDistance; ++dY) { ESM::CellId cellId; cellId.mPaged = !mInterior; cellId.mWorldspace = (mInterior ? mPrefix : ESM::CellId::sDefaultWorldspace); cellId.mIndex.mX = mCurX+dX; cellId.mIndex.mY = mCurY+dY; CustomMarkerCollection::RangeType markers = mCustomMarkers.getMarkers(cellId); for (CustomMarkerCollection::ContainerType::const_iterator it = markers.first; it != markers.second; ++it) { const ESM::CustomMarker& marker = it->second; MarkerUserData markerPos (mLocalMapRender); MarkerWidget* markerWidget = mLocalMap->createWidget("CustomMarkerButton", getMarkerCoordinates(marker.mWorldX, marker.mWorldY, markerPos, 16), MyGUI::Align::Default); markerWidget->setDepth(Local_MarkerAboveFogLayer); markerWidget->setUserString("ToolTipType", "Layout"); markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine"); markerWidget->setUserString("Caption_TextOneLine", MyGUI::TextIterator::toTagsString(marker.mNote)); markerWidget->setNormalColour(MyGUI::Colour(0.6f, 0.6f, 0.6f)); markerWidget->setHoverColour(MyGUI::Colour(1.0f, 1.0f, 1.0f)); markerWidget->setUserData(marker); markerWidget->setNeedMouseFocus(true); customMarkerCreated(markerWidget); mCustomMarkerWidgets.push_back(markerWidget); } } } redraw(); } void LocalMapBase::setActiveCell(const int x, const int y, bool interior) { if (x==mCurX && y==mCurY && mInterior==interior && !mChanged) return; // don't do anything if we're still in the same cell if (!interior && !(x == mCurX && y == mCurY)) { const MyGUI::IntRect intersection = { std::max(x, mCurX) - mCellDistance, std::max(y, mCurY) - mCellDistance, std::min(x, mCurX) + mCellDistance, std::min(y, mCurY) + mCellDistance }; const MyGUI::IntRect activeGrid = createRect({ x, y }, Constants::CellGridRadius); const MyGUI::IntRect currentView = createRect({ x, y }, mCellDistance); mExteriorDoorMarkerWidgets.clear(); for (auto& [coord, doors] : mExteriorDoorsByCell) { if (!mHasALastActiveCell || !currentView.inside({ coord.first, coord.second }) || activeGrid.inside({ coord.first, coord.second })) { mDoorMarkersToRecycle.insert(mDoorMarkersToRecycle.end(), doors.begin(), doors.end()); doors.clear(); } else mExteriorDoorMarkerWidgets.insert(mExteriorDoorMarkerWidgets.end(), doors.begin(), doors.end()); } for (auto& widget : mDoorMarkersToRecycle) widget->setVisible(false); for (auto const& cell : mMaps) { if (mHasALastActiveCell && !intersection.inside({ cell.mCellX, cell.mCellY })) mLocalMapRender->removeExteriorCell(cell.mCellX, cell.mCellY); } } mCurX = x; mCurY = y; mInterior = interior; mChanged = false; for (int mx=0; mxsetRenderItemTexture(nullptr); entry.mFogWidget->setRenderItemTexture(nullptr); entry.mMapTexture.reset(); entry.mFogTexture.reset(); entry.mCellX = x + (mx - mCellDistance); entry.mCellY = y - (my - mCellDistance); } } // Delay the door markers update until scripts have been given a chance to run. // If we don't do this, door markers that should be disabled will still appear on the map. mNeedDoorMarkersUpdate = true; for (MyGUI::Widget* widget : currentDoorMarkersWidgets()) widget->setCoord(getMarkerCoordinates(widget, 8)); if (!mInterior) mHasALastActiveCell = true; updateMagicMarkers(); updateCustomMarkers(); } void LocalMapBase::requestMapRender(const MWWorld::CellStore *cell) { mLocalMapRender->requestMap(cell); } void LocalMapBase::redraw() { // Redraw children in proper order mLocalMap->getParent()->_updateChilds(); } float LocalMapBase::getWidgetSize() const { return mLocalMapZoom * mMapWidgetSize; } void LocalMapBase::setPlayerPos(int cellX, int cellY, const float nx, const float ny) { MyGUI::IntPoint pos = getPosition(cellX, cellY, nx, ny) - MyGUI::IntPoint{ 16, 16 }; if (pos != mCompass->getPosition()) { notifyPlayerUpdate (); mCompass->setPosition(pos); } osg::Vec2f curPos((cellX + nx) * cellSize, (cellY + 1 - ny) * cellSize); if ((curPos - mCurPos).length2() > 0.001) { mCurPos = curPos; centerView(); } } void LocalMapBase::setPlayerDir(const float x, const float y) { if (x == mLastDirectionX && y == mLastDirectionY) return; notifyPlayerUpdate (); MyGUI::ISubWidget* main = mCompass->getSubWidgetMain(); MyGUI::RotatingSkin* rotatingSubskin = main->castType(); rotatingSubskin->setCenter(MyGUI::IntPoint(16,16)); float angle = std::atan2(x,y); rotatingSubskin->setAngle(angle); mLastDirectionX = x; mLastDirectionY = y; } void LocalMapBase::addDetectionMarkers(int type) { std::vector markers; MWBase::World* world = MWBase::Environment::get().getWorld(); world->listDetectedReferences( world->getPlayerPtr(), markers, MWBase::World::DetectionType(type)); if (markers.empty()) return; std::string markerTexture; if (type == MWBase::World::Detect_Creature) { markerTexture = "textures\\detect_animal_icon.dds"; } if (type == MWBase::World::Detect_Key) { markerTexture = "textures\\detect_key_icon.dds"; } if (type == MWBase::World::Detect_Enchantment) { markerTexture = "textures\\detect_enchantment_icon.dds"; } int counter = 0; for (const MWWorld::Ptr& ptr : markers) { const ESM::Position& worldPos = ptr.getRefData().getPosition(); MarkerUserData markerPos (mLocalMapRender); ++counter; MyGUI::ImageBox* markerWidget = mLocalMap->createWidget("ImageBox", getMarkerCoordinates(worldPos.pos[0], worldPos.pos[1], markerPos, 8), MyGUI::Align::Default); markerWidget->setDepth(Local_MarkerAboveFogLayer); markerWidget->setImageTexture(markerTexture); markerWidget->setImageCoord(MyGUI::IntCoord(0,0,8,8)); markerWidget->setNeedMouseFocus(false); markerWidget->setUserData(markerPos); mMagicMarkerWidgets.push_back(markerWidget); } } void LocalMapBase::onFrame(float dt) { if (mNeedDoorMarkersUpdate) { updateDoorMarkers(); mNeedDoorMarkersUpdate = false; } mMarkerUpdateTimer += dt; if (mMarkerUpdateTimer >= 0.25) { mMarkerUpdateTimer = 0; updateMagicMarkers(); } updateRequiredMaps(); } bool widgetCropped(MyGUI::Widget* widget, MyGUI::Widget* cropTo) { MyGUI::IntRect coord = widget->getAbsoluteRect(); MyGUI::IntRect croppedCoord = cropTo->getAbsoluteRect(); if (coord.left < croppedCoord.left && coord.right < croppedCoord.left) return true; if (coord.left > croppedCoord.right && coord.right > croppedCoord.right) return true; if (coord.top < croppedCoord.top && coord.bottom < croppedCoord.top) return true; if (coord.top > croppedCoord.bottom && coord.bottom > croppedCoord.bottom) return true; return false; } void LocalMapBase::updateRequiredMaps() { bool needRedraw = false; for (MapEntry& entry : mMaps) { if (widgetCropped(entry.mMapWidget, mLocalMap)) continue; if (!entry.mMapTexture) { if (!mInterior) requestMapRender(MWBase::Environment::get().getWorld()->getExterior (entry.mCellX, entry.mCellY)); osg::ref_ptr texture = mLocalMapRender->getMapTexture(entry.mCellX, entry.mCellY); if (texture) { entry.mMapTexture.reset(new osgMyGUI::OSGTexture(texture)); entry.mMapWidget->setRenderItemTexture(entry.mMapTexture.get()); entry.mMapWidget->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); needRedraw = true; } else entry.mMapTexture.reset(new osgMyGUI::OSGTexture("", nullptr)); } if (!entry.mFogTexture && mFogOfWarToggled && mFogOfWarEnabled) { osg::ref_ptr tex = mLocalMapRender->getFogOfWarTexture(entry.mCellX, entry.mCellY); if (tex) { entry.mFogTexture.reset(new osgMyGUI::OSGTexture(tex)); entry.mFogWidget->setRenderItemTexture(entry.mFogTexture.get()); entry.mFogWidget->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); } else { entry.mFogWidget->setImageTexture("black"); entry.mFogTexture.reset(new osgMyGUI::OSGTexture("", nullptr)); } needRedraw = true; } } if (needRedraw) redraw(); } void LocalMapBase::updateDoorMarkers() { std::vector doors; MWBase::World* world = MWBase::Environment::get().getWorld(); mDoorMarkersToRecycle.insert(mDoorMarkersToRecycle.end(), mInteriorDoorMarkerWidgets.begin(), mInteriorDoorMarkerWidgets.end()); mInteriorDoorMarkerWidgets.clear(); if (mInterior) { for (MyGUI::Widget* widget : mExteriorDoorMarkerWidgets) widget->setVisible(false); MWWorld::CellStore* cell = world->getInterior (mPrefix); world->getDoorMarkers(cell, doors); } else { for (MapEntry& entry : mMaps) { if (!entry.mMapTexture && !widgetCropped(entry.mMapWidget, mLocalMap)) world->getDoorMarkers(world->getExterior(entry.mCellX, entry.mCellY), doors); } if (doors.empty()) return; } // Create a widget for each marker for (MWBase::World::DoorMarker& marker : doors) { std::vector destNotes; CustomMarkerCollection::RangeType markers = mCustomMarkers.getMarkers(marker.dest); for (CustomMarkerCollection::ContainerType::const_iterator iter = markers.first; iter != markers.second; ++iter) destNotes.push_back(iter->second.mNote); MyGUI::Widget* markerWidget = nullptr; MarkerUserData* data; if (mDoorMarkersToRecycle.empty()) { markerWidget = createDoorMarker(marker.name, destNotes, marker.x, marker.y); data = markerWidget->getUserData(); doorMarkerCreated(markerWidget); } else { markerWidget = (MarkerWidget*)mDoorMarkersToRecycle.back(); mDoorMarkersToRecycle.pop_back(); data = markerWidget->getUserData(); data->notes = destNotes; data->caption = marker.name; markerWidget->setCoord(getMarkerCoordinates(marker.x, marker.y, *data, 8)); markerWidget->setVisible(true); } currentDoorMarkersWidgets().push_back(markerWidget); if (!mInterior) mExteriorDoorsByCell[{data->cellX, data->cellY}].push_back(markerWidget); } for (auto& widget : mDoorMarkersToRecycle) widget->setVisible(false); } void LocalMapBase::updateMagicMarkers() { // clear all previous markers for (MyGUI::Widget* widget : mMagicMarkerWidgets) MyGUI::Gui::getInstance().destroyWidget(widget); mMagicMarkerWidgets.clear(); addDetectionMarkers(MWBase::World::Detect_Creature); addDetectionMarkers(MWBase::World::Detect_Key); addDetectionMarkers(MWBase::World::Detect_Enchantment); // Add marker for the spot marked with Mark magic effect MWWorld::CellStore* markedCell = nullptr; ESM::Position markedPosition; MWBase::Environment::get().getWorld()->getPlayer().getMarkedPosition(markedCell, markedPosition); if (markedCell && markedCell->isExterior() == !mInterior && (!mInterior || Misc::StringUtils::ciEqual(markedCell->getCell()->mName, mPrefix))) { MarkerUserData markerPos (mLocalMapRender); MyGUI::ImageBox* markerWidget = mLocalMap->createWidget("ImageBox", getMarkerCoordinates(markedPosition.pos[0], markedPosition.pos[1], markerPos, 8), MyGUI::Align::Default); markerWidget->setDepth(Local_MarkerAboveFogLayer); markerWidget->setImageTexture("textures\\menu_map_smark.dds"); markerWidget->setNeedMouseFocus(false); markerWidget->setUserData(markerPos); mMagicMarkerWidgets.push_back(markerWidget); } redraw(); } void LocalMapBase::updateLocalMap() { auto mapWidgetSize = getWidgetSize(); mLocalMap->setCanvasSize(mapWidgetSize * mNumCells, mapWidgetSize * mNumCells); const auto size = MyGUI::IntSize(std::ceil(mapWidgetSize), std::ceil(mapWidgetSize)); for (auto& entry : mMaps) { const auto position = getPosition(entry.mCellX, entry.mCellY, 0, 0); entry.mMapWidget->setCoord({ position, size }); entry.mFogWidget->setCoord({ position, size }); } MarkerUserData markerPos(mLocalMapRender); for (MyGUI::Widget* widget : currentDoorMarkersWidgets()) widget->setCoord(getMarkerCoordinates(widget, 8)); for (MyGUI::Widget* widget : mCustomMarkerWidgets) { const auto& marker = *widget->getUserData(); widget->setCoord(getMarkerCoordinates(marker.mWorldX, marker.mWorldY, markerPos, 16)); } for (MyGUI::Widget* widget : mMagicMarkerWidgets) widget->setCoord(getMarkerCoordinates(widget, 8)); } // ------------------------------------------------------------------------------------------ MapWindow::MapWindow(CustomMarkerCollection &customMarkers, DragAndDrop* drag, MWRender::LocalMap* localMapRender, SceneUtil::WorkQueue* workQueue) : WindowPinnableBase("openmw_map_window.layout") , LocalMapBase(customMarkers, localMapRender) , NoDrop(drag, mMainWidget) , mGlobalMap(nullptr) , mGlobalMapImage(nullptr) , mGlobalMapOverlay(nullptr) , mGlobal(Settings::Manager::getBool("global", "Map")) , mEventBoxGlobal(nullptr) , mEventBoxLocal(nullptr) , mGlobalMapRender(new MWRender::GlobalMap(localMapRender->getRoot(), workQueue)) , mEditNoteDialog() { static bool registered = false; if (!registered) { MyGUI::FactoryManager::getInstance().registerFactory("Widget"); registered = true; } mEditNoteDialog.setVisible(false); mEditNoteDialog.eventOkClicked += MyGUI::newDelegate(this, &MapWindow::onNoteEditOk); mEditNoteDialog.eventDeleteClicked += MyGUI::newDelegate(this, &MapWindow::onNoteEditDelete); setCoord(500,0,320,300); getWidget(mLocalMap, "LocalMap"); getWidget(mGlobalMap, "GlobalMap"); getWidget(mGlobalMapImage, "GlobalMapImage"); getWidget(mGlobalMapOverlay, "GlobalMapOverlay"); getWidget(mPlayerArrowLocal, "CompassLocal"); getWidget(mPlayerArrowGlobal, "CompassGlobal"); mPlayerArrowGlobal->setDepth(Global_CompassLayer); mPlayerArrowGlobal->setNeedMouseFocus(false); mGlobalMapImage->setDepth(Global_MapLayer); mGlobalMapOverlay->setDepth(Global_ExploreOverlayLayer); mLastScrollWindowCoordinates = mLocalMap->getCoord(); mLocalMap->eventChangeCoord += MyGUI::newDelegate(this, &MapWindow::onChangeScrollWindowCoord); mGlobalMap->setVisible (false); getWidget(mButton, "WorldButton"); mButton->eventMouseButtonClick += MyGUI::newDelegate(this, &MapWindow::onWorldButtonClicked); mButton->setCaptionWithReplacing( mGlobal ? "#{sLocal}" : "#{sWorld}"); getWidget(mEventBoxGlobal, "EventBoxGlobal"); mEventBoxGlobal->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); mEventBoxGlobal->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); const bool allowZooming = Settings::Manager::getBool("allow zooming", "Map"); if(allowZooming) mEventBoxGlobal->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed); mEventBoxGlobal->setDepth(Global_ExploreOverlayLayer); getWidget(mEventBoxLocal, "EventBoxLocal"); mEventBoxLocal->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); mEventBoxLocal->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); mEventBoxLocal->eventMouseButtonDoubleClick += MyGUI::newDelegate(this, &MapWindow::onMapDoubleClicked); if (allowZooming) mEventBoxLocal->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed); LocalMapBase::init(mLocalMap, mPlayerArrowLocal, getLocalViewingDistance()); mGlobalMap->setVisible(mGlobal); mLocalMap->setVisible(!mGlobal); } void MapWindow::onNoteEditOk() { if (mEditNoteDialog.getDeleteButtonShown()) mCustomMarkers.updateMarker(mEditingMarker, mEditNoteDialog.getText()); else { mEditingMarker.mNote = mEditNoteDialog.getText(); mCustomMarkers.addMarker(mEditingMarker); } mEditNoteDialog.setVisible(false); } void MapWindow::onNoteEditDelete() { ConfirmationDialog* confirmation = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); confirmation->askForConfirmation("#{sDeleteNote}"); confirmation->eventCancelClicked.clear(); confirmation->eventOkClicked.clear(); confirmation->eventOkClicked += MyGUI::newDelegate(this, &MapWindow::onNoteEditDeleteConfirm); } void MapWindow::onNoteEditDeleteConfirm() { mCustomMarkers.deleteMarker(mEditingMarker); mEditNoteDialog.setVisible(false); } void MapWindow::onCustomMarkerDoubleClicked(MyGUI::Widget *sender) { mEditingMarker = *sender->getUserData(); mEditNoteDialog.setText(mEditingMarker.mNote); mEditNoteDialog.showDeleteButton(true); mEditNoteDialog.setVisible(true); } void MapWindow::onMapDoubleClicked(MyGUI::Widget *sender) { MyGUI::IntPoint clickedPos = MyGUI::InputManager::getInstance().getMousePosition(); MyGUI::IntPoint widgetPos = clickedPos - mEventBoxLocal->getAbsolutePosition(); auto mapWidgetSize = getWidgetSize(); int x = int(widgetPos.left/float(mapWidgetSize))-mCellDistance; int y = (int(widgetPos.top/float(mapWidgetSize))-mCellDistance)*-1; float nX = widgetPos.left/float(mapWidgetSize) - int(widgetPos.left/float(mapWidgetSize)); float nY = widgetPos.top/float(mapWidgetSize) - int(widgetPos.top/float(mapWidgetSize)); x += mCurX; y += mCurY; osg::Vec2f worldPos; if (mInterior) { worldPos = mLocalMapRender->interiorMapToWorldPosition(nX, nY, x, y); } else { worldPos.x() = (x + nX) * cellSize; worldPos.y() = (y + (1.0f-nY)) * cellSize; } mEditingMarker.mWorldX = worldPos.x(); mEditingMarker.mWorldY = worldPos.y(); mEditingMarker.mCell.mPaged = !mInterior; if (mInterior) mEditingMarker.mCell.mWorldspace = LocalMapBase::mPrefix; else { mEditingMarker.mCell.mWorldspace = ESM::CellId::sDefaultWorldspace; mEditingMarker.mCell.mIndex.mX = x; mEditingMarker.mCell.mIndex.mY = y; } mEditNoteDialog.setVisible(true); mEditNoteDialog.showDeleteButton(false); mEditNoteDialog.setText(""); } void MapWindow::onMapZoomed(MyGUI::Widget* sender, int rel) { const static int localWidgetSize = Settings::Manager::getInt("local map widget size", "Map"); const static int globalCellSize = Settings::Manager::getInt("global map cell size", "Map"); const bool zoomOut = rel < 0; const bool zoomIn = !zoomOut; const double speedDiff = zoomOut ? 1.0 / speed : speed; const float localMapSizeInUnits = localWidgetSize * mNumCells; const float currentMinLocalMapZoom = std::max({ (float(globalCellSize) * 4.f) / float(localWidgetSize), float(mLocalMap->getWidth()) / localMapSizeInUnits, float(mLocalMap->getHeight()) / localMapSizeInUnits }); if (mGlobal) { const float currentGlobalZoom = mGlobalMapZoom; const float currentMinGlobalMapZoom = std::min( float(mGlobalMap->getWidth()) / float(mGlobalMapRender->getWidth()), float(mGlobalMap->getHeight()) / float(mGlobalMapRender->getHeight()) ); mGlobalMapZoom *= speedDiff; if (zoomIn && mGlobalMapZoom > 4.f) { mGlobalMapZoom = currentGlobalZoom; mLocalMapZoom = currentMinLocalMapZoom; onWorldButtonClicked(nullptr); updateLocalMap(); return; //the zoom in is too big } if (zoomOut && mGlobalMapZoom < currentMinGlobalMapZoom) { mGlobalMapZoom = currentGlobalZoom; return; //the zoom out is too big, we have reach the borders of the widget } } else { auto const currentLocalZoom = mLocalMapZoom; mLocalMapZoom *= speedDiff; if (zoomIn && mLocalMapZoom > 4.0f) { mLocalMapZoom = currentLocalZoom; return; //the zoom in is too big } if (zoomOut && mLocalMapZoom < currentMinLocalMapZoom) { mLocalMapZoom = currentLocalZoom; float zoomRatio = 4.f/ mGlobalMapZoom; mGlobalMapZoom = 4.f; onWorldButtonClicked(nullptr); zoomOnCursor(zoomRatio); return; //the zoom out is too big, we switch to the global map } if (zoomOut) mNeedDoorMarkersUpdate = true; } zoomOnCursor(speedDiff); } void MapWindow::zoomOnCursor(float speedDiff) { auto map = mGlobal ? mGlobalMap : mLocalMap; auto cursor = MyGUI::InputManager::getInstance().getMousePosition() - map->getAbsolutePosition(); auto centerView = map->getViewOffset() - cursor; mGlobal? updateGlobalMap() : updateLocalMap(); map->setViewOffset(MyGUI::IntPoint( std::round(centerView.left * speedDiff) + cursor.left, std::round(centerView.top * speedDiff) + cursor.top )); } void MapWindow::updateGlobalMap() { resizeGlobalMap(); float x = mCurPos.x(), y = mCurPos.y(); if (mInterior) { auto pos = MWBase::Environment::get().getWorld()->getPlayer().getLastKnownExteriorPosition(); x = pos.x(); y = pos.y(); } setGlobalMapPlayerPosition(x, y); for (auto& [marker, col] : mGlobalMapMarkers) { marker.widget->setCoord(createMarkerCoords(marker.position.x(), marker.position.y(), col.size())); marker.widget->setVisible(marker.widget->getHeight() >= 6); } } void MapWindow::onChangeScrollWindowCoord(MyGUI::Widget* sender) { MyGUI::IntCoord currentCoordinates = sender->getCoord(); MyGUI::IntPoint currentViewPortCenter = MyGUI::IntPoint(currentCoordinates.width / 2, currentCoordinates.height / 2); MyGUI::IntPoint lastViewPortCenter = MyGUI::IntPoint(mLastScrollWindowCoordinates.width / 2, mLastScrollWindowCoordinates.height / 2); MyGUI::IntPoint viewPortCenterDiff = currentViewPortCenter - lastViewPortCenter; mLocalMap->setViewOffset(mLocalMap->getViewOffset() + viewPortCenterDiff); mGlobalMap->setViewOffset(mGlobalMap->getViewOffset() + viewPortCenterDiff); mLastScrollWindowCoordinates = currentCoordinates; } void MapWindow::setVisible(bool visible) { WindowBase::setVisible(visible); mButton->setVisible(visible && MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_None); } void MapWindow::renderGlobalMap() { mGlobalMapRender->render(); resizeGlobalMap(); } MapWindow::~MapWindow() { delete mGlobalMapRender; } void MapWindow::setCellName(const std::string& cellName) { setTitle("#{sCell=" + cellName + "}"); } MyGUI::IntCoord MapWindow::createMarkerCoords(float x, float y, float agregatedWeight) const { float worldX, worldY; worldPosToGlobalMapImageSpace((x + 0.5f) * Constants::CellSizeInUnits, (y + 0.5f)* Constants::CellSizeInUnits, worldX, worldY); const float markerSize = getMarkerSize(agregatedWeight); const float halfMarkerSize = markerSize / 2.0f; return MyGUI::IntCoord( static_cast(worldX - halfMarkerSize), static_cast(worldY - halfMarkerSize), markerSize, markerSize); } MyGUI::Widget* MapWindow::createMarker(const std::string& name, float x, float y, float agregatedWeight) { MyGUI::Widget* markerWidget = mGlobalMap->createWidget("MarkerButton", createMarkerCoords(x, y, agregatedWeight), MyGUI::Align::Default); markerWidget->setVisible(markerWidget->getHeight() >= 6.0); markerWidget->setUserString("Caption_TextOneLine", "#{sCell=" + name + "}"); setGlobalMapMarkerTooltip(markerWidget, x, y); markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine"); markerWidget->setNeedMouseFocus(true); markerWidget->setColour(MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=normal}"))); markerWidget->setDepth(Global_MarkerLayer); markerWidget->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); markerWidget->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed); markerWidget->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); return markerWidget; } void MapWindow::addVisitedLocation(const std::string& name, int x, int y) { CellId cell; cell.first = x; cell.second = y; if (mMarkers.insert(cell).second) { MapMarkerType mapMarkerWidget = { osg::Vec2f(x, y), createMarker(name, x, y, 0) }; mGlobalMapMarkers.emplace(mapMarkerWidget, std::vector()); std::string name_ = name.substr(0, name.find(',')); auto& entry = mGlobalMapMarkersByName[name_]; if (!entry.widget) { entry = { osg::Vec2f(x, y), entry.widget }; //update the coords entry.widget = createMarker(name_, entry.position.x(), entry.position.y(), 1); mGlobalMapMarkers.emplace(entry, std::vector{ entry }); } else { auto it = mGlobalMapMarkers.find(entry); auto& marker = const_cast(it->first); auto& elements = it->second; elements.emplace_back(mapMarkerWidget); //we compute the barycenter of the entry elements => it will be the place on the world map for the agregated widget marker.position = std::accumulate(elements.begin(), elements.end(), osg::Vec2f(0.f, 0.f), [](const auto& left, const auto& right) { return left + right.position; }) / float(elements.size()); marker.widget->setCoord(createMarkerCoords(marker.position.x(), marker.position.y(), elements.size())); marker.widget->setVisible(marker.widget->getHeight() >= 6); } } } void MapWindow::cellExplored(int x, int y) { mGlobalMapRender->cleanupCameras(); mGlobalMapRender->exploreCell(x, y, mLocalMapRender->getMapTexture(x, y)); } void MapWindow::onFrame(float dt) { LocalMapBase::onFrame(dt); NoDrop::onFrame(dt); } void MapWindow::setGlobalMapMarkerTooltip(MyGUI::Widget* markerWidget, int x, int y) { ESM::CellId cellId; cellId.mIndex.mX = x; cellId.mIndex.mY = y; cellId.mWorldspace = ESM::CellId::sDefaultWorldspace; cellId.mPaged = true; CustomMarkerCollection::RangeType markers = mCustomMarkers.getMarkers(cellId); std::vector destNotes; for (CustomMarkerCollection::ContainerType::const_iterator it = markers.first; it != markers.second; ++it) destNotes.push_back(it->second.mNote); if (!destNotes.empty()) { MarkerUserData data (nullptr); data.notes = destNotes; data.caption = markerWidget->getUserString("Caption_TextOneLine"); markerWidget->setUserData(data); markerWidget->setUserString("ToolTipType", "MapMarker"); } else { markerWidget->setUserString("ToolTipType", "Layout"); } } float MapWindow::getMarkerSize(size_t agregatedWeight) const { float markerSize = 12.f * mGlobalMapZoom; if (mGlobalMapZoom < 1) return markerSize * std::sqrt(agregatedWeight); //we want to see agregated object return agregatedWeight ? 0 : markerSize; //we want to see only original markers (i.e. non agregated) } void MapWindow::resizeGlobalMap() { mGlobalMap->setCanvasSize(mGlobalMapRender->getWidth() * mGlobalMapZoom, mGlobalMapRender->getHeight() * mGlobalMapZoom); mGlobalMapImage->setSize(mGlobalMapRender->getWidth() * mGlobalMapZoom, mGlobalMapRender->getHeight() * mGlobalMapZoom); } void MapWindow::worldPosToGlobalMapImageSpace(float x, float y, float& imageX, float& imageY) const { mGlobalMapRender->worldPosToImageSpace(x, y, imageX, imageY); imageX *= mGlobalMapZoom; imageY *= mGlobalMapZoom; } void MapWindow::updateCustomMarkers() { LocalMapBase::updateCustomMarkers(); for (auto& [widgetPair, ignore]: mGlobalMapMarkers) setGlobalMapMarkerTooltip(widgetPair.widget, widgetPair.position.x(), widgetPair.position.y()); } void MapWindow::onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) { if (_id!=MyGUI::MouseButton::Left) return; mLastDragPos = MyGUI::IntPoint(_left, _top); } void MapWindow::onMouseDrag(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) { if (_id!=MyGUI::MouseButton::Left) return; MyGUI::IntPoint diff = MyGUI::IntPoint(_left, _top) - mLastDragPos; if (!mGlobal) { mNeedDoorMarkersUpdate = true; mLocalMap->setViewOffset( mLocalMap->getViewOffset() + diff ); } else mGlobalMap->setViewOffset( mGlobalMap->getViewOffset() + diff ); mLastDragPos = MyGUI::IntPoint(_left, _top); } void MapWindow::onWorldButtonClicked(MyGUI::Widget* _sender) { mGlobal = !mGlobal; mGlobalMap->setVisible(mGlobal); mLocalMap->setVisible(!mGlobal); Settings::Manager::setBool("global", "Map", mGlobal); mButton->setCaptionWithReplacing( mGlobal ? "#{sLocal}" : "#{sWorld}"); } void MapWindow::onPinToggled() { Settings::Manager::setBool("map pin", "Windows", mPinned); MWBase::Environment::get().getWindowManager()->setMinimapVisibility(!mPinned); } void MapWindow::onTitleDoubleClicked() { if (MyGUI::InputManager::getInstance().isShiftPressed()) MWBase::Environment::get().getWindowManager()->toggleMaximized(this); else if (!mPinned) MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Map); } void MapWindow::onOpen() { ensureGlobalMapLoaded(); globalMapUpdatePlayer(); } void MapWindow::globalMapUpdatePlayer () { // For interiors, position is set by WindowManager via setGlobalMapPlayerPosition if (MWBase::Environment::get().getWorld ()->isCellExterior ()) { osg::Vec3f pos = MWBase::Environment::get().getWorld ()->getPlayerPtr().getRefData().getPosition().asVec3(); setGlobalMapPlayerPosition(pos.x(), pos.y()); } } void MapWindow::notifyPlayerUpdate () { globalMapUpdatePlayer (); setGlobalMapPlayerDir(mLastDirectionX, mLastDirectionY); } void MapWindow::centerView() { LocalMapBase::centerView(); // set the view offset so that player is in the center MyGUI::IntSize viewsize = mGlobalMap->getSize(); MyGUI::IntPoint pos = mPlayerArrowGlobal->getPosition() + MyGUI::IntPoint{ 16,16 }; MyGUI::IntPoint viewoffs(static_cast(viewsize.width * 0.5f - pos.left), static_cast(viewsize.height * 0.5f - pos.top)); mGlobalMap->setViewOffset(viewoffs); } void MapWindow::setGlobalMapPlayerPosition(float worldX, float worldY) { float x, y; worldPosToGlobalMapImageSpace(worldX, worldY, x, y); mPlayerArrowGlobal->setPosition(MyGUI::IntPoint(static_cast(x - 16), static_cast(y - 16))); } void MapWindow::setGlobalMapPlayerDir(const float x, const float y) { MyGUI::ISubWidget* main = mPlayerArrowGlobal->getSubWidgetMain(); MyGUI::RotatingSkin* rotatingSubskin = main->castType(); rotatingSubskin->setCenter(MyGUI::IntPoint(16,16)); float angle = std::atan2(x,y); rotatingSubskin->setAngle(angle); } void MapWindow::ensureGlobalMapLoaded() { if (!mGlobalMapTexture.get()) { mGlobalMapTexture.reset(new osgMyGUI::OSGTexture(mGlobalMapRender->getBaseTexture())); mGlobalMapImage->setRenderItemTexture(mGlobalMapTexture.get()); mGlobalMapImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); mGlobalMapOverlayTexture.reset(new osgMyGUI::OSGTexture(mGlobalMapRender->getOverlayTexture())); mGlobalMapOverlay->setRenderItemTexture(mGlobalMapOverlayTexture.get()); mGlobalMapOverlay->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); // Redraw children in proper order mGlobalMap->getParent()->_updateChilds(); } } void MapWindow::clear() { mMarkers.clear(); mGlobalMapRender->clear(); mChanged = true; for (auto& widgetPair : mGlobalMapMarkers) MyGUI::Gui::getInstance().destroyWidget(widgetPair.first.widget); mGlobalMapMarkers.clear(); mGlobalMapMarkersByName.clear(); } void MapWindow::write(ESM::ESMWriter &writer, Loading::Listener& progress) { ESM::GlobalMap map; mGlobalMapRender->write(map); map.mMarkers = mMarkers; writer.startRecord(ESM::REC_GMAP); map.save(writer); writer.endRecord(ESM::REC_GMAP); } void MapWindow::readRecord(ESM::ESMReader &reader, uint32_t type) { if (type == ESM::REC_GMAP) { ESM::GlobalMap map; map.load(reader); mGlobalMapRender->read(map); for (const ESM::GlobalMap::CellId& cellId : map.mMarkers) { const ESM::Cell* cell = MWBase::Environment::get().getWorld()->getStore().get().search(cellId.first, cellId.second); if (cell && !cell->mName.empty()) addVisitedLocation(cell->mName, cellId.first, cellId.second); } } } void MapWindow::setAlpha(float alpha) { NoDrop::setAlpha(alpha); // can't allow showing map with partial transparency, as the fog of war will also go transparent // and reveal parts of the map you shouldn't be able to see for (MapEntry& entry : mMaps) entry.mMapWidget->setVisible(alpha == 1); } void MapWindow::customMarkerCreated(MyGUI::Widget *marker) { marker->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); marker->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); marker->eventMouseButtonDoubleClick += MyGUI::newDelegate(this, &MapWindow::onCustomMarkerDoubleClicked); marker->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed); } void MapWindow::doorMarkerCreated(MyGUI::Widget *marker) { marker->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); marker->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); marker->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed); } // ------------------------------------------------------------------- EditNoteDialog::EditNoteDialog() : WindowModal("openmw_edit_note.layout") { getWidget(mOkButton, "OkButton"); getWidget(mCancelButton, "CancelButton"); getWidget(mDeleteButton, "DeleteButton"); getWidget(mTextEdit, "TextEdit"); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditNoteDialog::onCancelButtonClicked); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditNoteDialog::onOkButtonClicked); mDeleteButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditNoteDialog::onDeleteButtonClicked); } void EditNoteDialog::showDeleteButton(bool show) { mDeleteButton->setVisible(show); } bool EditNoteDialog::getDeleteButtonShown() { return mDeleteButton->getVisible(); } void EditNoteDialog::setText(const std::string &text) { mTextEdit->setCaption(MyGUI::TextIterator::toTagsString(text)); } std::string EditNoteDialog::getText() { return MyGUI::TextIterator::getOnlyText(mTextEdit->getCaption()); } void EditNoteDialog::onOpen() { WindowModal::onOpen(); center(); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mTextEdit); } void EditNoteDialog::onCancelButtonClicked(MyGUI::Widget *sender) { setVisible(false); } void EditNoteDialog::onOkButtonClicked(MyGUI::Widget *sender) { eventOkClicked(); } void EditNoteDialog::onDeleteButtonClicked(MyGUI::Widget *sender) { eventDeleteClicked(); } bool LocalMapBase::MarkerUserData::isPositionExplored() const { if (!mLocalMapRender) return true; return mLocalMapRender->isPositionExplored(nX, nY, cellX, cellY); } }