From e0de76a6f714100df72135476cc9738bc6babf87 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 25 Jan 2014 18:20:17 +0100 Subject: [PATCH] Save/load global map --- apps/openmw/mwbase/windowmanager.hpp | 5 ++ apps/openmw/mwgui/mapwindow.cpp | 17 +++++ apps/openmw/mwgui/mapwindow.hpp | 9 +++ apps/openmw/mwgui/windowmanagerimp.cpp | 10 +++ apps/openmw/mwgui/windowmanagerimp.hpp | 3 + apps/openmw/mwrender/globalmap.cpp | 99 ++++++++++++++++++++++++- apps/openmw/mwrender/globalmap.hpp | 10 ++- apps/openmw/mwstate/statemanagerimp.cpp | 7 ++ components/CMakeLists.txt | 2 +- components/esm/defs.hpp | 1 + components/esm/globalmap.cpp | 26 +++++++ components/esm/globalmap.hpp | 34 +++++++++ 12 files changed, 219 insertions(+), 4 deletions(-) create mode 100644 components/esm/globalmap.cpp create mode 100644 components/esm/globalmap.hpp diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 4d47e7eb7..fa0fe888b 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -33,6 +33,8 @@ namespace OEngine namespace ESM { struct Class; + class ESMReader; + class ESMWriter; } namespace MWWorld @@ -288,6 +290,9 @@ namespace MWBase /// Clear all savegame-specific data virtual void clear() = 0; + + virtual void write (ESM::ESMWriter& writer) = 0; + virtual void readRecord (ESM::ESMReader& reader, int32_t type) = 0; }; } diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index c09b4fea0..9ed3bf80f 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -584,4 +584,21 @@ namespace MWGui MyGUI::Gui::getInstance().destroyWidget(mGlobalMapOverlay->getChildAt(0)); } + void MapWindow::write(ESM::ESMWriter &writer) + { + mGlobalMapRender->write(writer); + } + + void MapWindow::readRecord(ESM::ESMReader &reader, int32_t type) + { + std::vector > exploredCells; + mGlobalMapRender->readRecord(reader, type, exploredCells); + + for (std::vector >::iterator it = exploredCells.begin(); it != exploredCells.end(); ++it) + { + const ESM::Cell* cell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first, it->second); + if (cell && !cell->mName.empty()) + addVisitedLocation(cell->mName, it->first, it->second); + } + } } diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index dec27199a..6ace7dc0f 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -8,6 +8,12 @@ namespace MWRender class GlobalMap; } +namespace ESM +{ + class ESMReader; + class ESMWriter; +} + namespace Loading { class Listener; @@ -95,6 +101,9 @@ namespace MWGui /// Clear all savegame-specific data void clear(); + void write (ESM::ESMWriter& writer); + void readRecord (ESM::ESMReader& reader, int32_t type); + private: void onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); void onMouseDrag(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 8e49d6614..66bd805af 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1386,4 +1386,14 @@ namespace MWGui mMap->clear(); } + void WindowManager::write(ESM::ESMWriter &writer) + { + mMap->write(writer); + } + + void WindowManager::readRecord(ESM::ESMReader &reader, int32_t type) + { + mMap->readRecord(reader, type); + } + } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 68dc947af..bc440d818 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -283,6 +283,9 @@ namespace MWGui /// Clear all savegame-specific data virtual void clear(); + virtual void write (ESM::ESMWriter& writer); + virtual void readRecord (ESM::ESMReader& reader, int32_t type); + private: bool mConsoleOnlyScripts; diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index 522bbb321..6fbcfdc6b 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -12,6 +12,8 @@ #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -60,8 +62,6 @@ namespace MWRender loadingListener->setProgressRange((mMaxX-mMinX+1) * (mMaxY-mMinY+1)); loadingListener->setProgress(0); - mExploredBuffer.resize((mMaxX-mMinX+1) * (mMaxY-mMinY+1) * 4); - //if (!boost::filesystem::exists(mCacheDir + "/GlobalMap.png")) if (1) { @@ -231,4 +231,99 @@ namespace MWRender mOverlayTexture->getBuffer()->blitFromMemory(pb); } + + void GlobalMap::write(ESM::ESMWriter &writer) + { + ESM::GlobalMap map; + map.mBounds.mMinX = mMinX; + map.mBounds.mMaxX = mMaxX; + map.mBounds.mMinY = mMinY; + map.mBounds.mMaxY = mMaxY; + + Ogre::Image image; + mOverlayTexture->convertToImage(image); + Ogre::DataStreamPtr encoded = image.encode("png"); + map.mImageData.resize(encoded->size()); + encoded->read(&map.mImageData[0], encoded->size()); + + writer.startRecord(ESM::REC_GMAP); + map.save(writer); + writer.endRecord(ESM::REC_GMAP); + } + + void GlobalMap::readRecord(ESM::ESMReader &reader, int32_t type, std::vector >& exploredCells) + { + if (type == ESM::REC_GMAP) + { + ESM::GlobalMap map; + map.load(reader); + + const ESM::GlobalMap::Bounds& bounds = map.mBounds; + + if (bounds.mMaxX-bounds.mMinX <= 0) + return; + if (bounds.mMaxY-bounds.mMinY <= 0) + return; + + Ogre::Image image; + Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&map.mImageData[0], map.mImageData.size())); + image.load(stream, "png"); + + int xLength = (bounds.mMaxX-bounds.mMinX+1); + int yLength = (bounds.mMaxY-bounds.mMinY+1); + + // Size of one cell in image space + int cellImageSizeSrc = image.getWidth() / xLength; + if (int(image.getHeight() / yLength) != cellImageSizeSrc) + throw std::runtime_error("cell size must be quadratic"); + + // Determine which cells were explored by reading the image data + for (int x=0; x < xLength; ++x) + { + for (int y=0; y < yLength; ++y) + { + unsigned int imageX = (x) * cellImageSizeSrc; + // NB y + 1, because we want the top left corner, not bottom left where the origin of the cell is + unsigned int imageY = (yLength - (y + 1)) * cellImageSizeSrc; + + assert(imageX < image.getWidth()); + assert(imageY < image.getWidth()); + + if (image.getColourAt(imageX, imageY, 0).a > 0) + exploredCells.push_back(std::make_pair(x+bounds.mMinX,y+bounds.mMinY)); + } + } + + // If cell bounds of the currently loaded content and the loaded savegame do not match, + // we need to resize source/dest boxes to accommodate + // This means nonexisting cells will be dropped silently + + int cellImageSizeDst = 24; + + int leftDiff = (mMinX - bounds.mMinX); + int topDiff = (bounds.mMaxY - mMaxY); + int rightDiff = (bounds.mMaxX - mMaxX); + int bottomDiff = (mMinY - bounds.mMinY); + Ogre::Image::Box srcBox ( std::max(0, leftDiff * cellImageSizeSrc), + std::max(0, topDiff * cellImageSizeSrc), + std::min(image.getWidth(), image.getWidth() - rightDiff * cellImageSizeSrc), + std::min(image.getHeight(), image.getHeight() - bottomDiff * cellImageSizeSrc)); + + Ogre::Image::Box destBox ( std::max(0, -leftDiff * cellImageSizeDst), + std::max(0, -topDiff * cellImageSizeDst), + std::min(mOverlayTexture->getWidth(), mOverlayTexture->getWidth() + rightDiff * cellImageSizeDst), + std::min(mOverlayTexture->getHeight(), mOverlayTexture->getHeight() + bottomDiff * cellImageSizeDst)); + + // Looks like there is no interface for blitting from memory with src/dst boxes. + // So we create a temporary texture for blitting. + Ogre::TexturePtr tex = Ogre::TextureManager::getSingleton().createManual("@temp", + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, image.getWidth(), + image.getHeight(), 0, Ogre::PF_A8B8G8R8); + tex->loadImage(image); + + mOverlayTexture->getBuffer()->blit(tex->getBuffer(), srcBox, destBox); + + Ogre::TextureManager::getSingleton().remove("@temp"); + } + } } diff --git a/apps/openmw/mwrender/globalmap.hpp b/apps/openmw/mwrender/globalmap.hpp index 20f40de99..5fe878cd4 100644 --- a/apps/openmw/mwrender/globalmap.hpp +++ b/apps/openmw/mwrender/globalmap.hpp @@ -10,6 +10,12 @@ namespace Loading class Listener; } +namespace ESM +{ + class ESMWriter; + class ESMReader; +} + namespace MWRender { @@ -34,13 +40,15 @@ namespace MWRender /// Clears the overlay void clear(); + void write (ESM::ESMWriter& writer); + void readRecord (ESM::ESMReader& reader, int32_t type, std::vector >& exploredCells); + private: std::string mCacheDir; std::vector< std::pair > mExploredCells; Ogre::TexturePtr mOverlayTexture; - std::vector mExploredBuffer; int mWidth; int mHeight; diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index e94f790c7..a396d78c5 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -174,6 +174,7 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot +MWBase::Environment::get().getJournal()->countSavedGameRecords() +MWBase::Environment::get().getWorld()->countSavedGameRecords() +MWBase::Environment::get().getScriptManager()->getGlobalScripts().countSavedGameRecords() + + 1 // global map ); writer.save (stream); @@ -185,6 +186,7 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot MWBase::Environment::get().getJournal()->write (writer); MWBase::Environment::get().getWorld()->write (writer); MWBase::Environment::get().getScriptManager()->getGlobalScripts().write (writer); + MWBase::Environment::get().getWindowManager()->write(writer); writer.close(); @@ -243,6 +245,11 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl MWBase::Environment::get().getScriptManager()->getGlobalScripts().readRecord (reader, n.val); break; + case ESM::REC_GMAP: + + MWBase::Environment::get().getWindowManager()->readRecord(reader, n.val); + break; + default: // ignore invalid records diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index d9ab8129d..d73bcaf74 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -40,7 +40,7 @@ add_component_dir (esm loadinfo loadingr loadland loadlevlist loadligh loadlock loadprob loadrepa loadltex loadmgef loadmisc loadnpcc loadnpc loadpgrd loadrace loadregn loadscpt loadskil loadsndg loadsoun loadspel loadsscr loadstat loadweap records aipackage effectlist spelllist variant variantimp loadtes3 cellref filter - savedgame journalentry queststate locals globalscript player objectstate cellid cellstate + savedgame journalentry queststate locals globalscript player objectstate cellid cellstate globalmap ) add_component_dir (misc diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index 74d987df8..40ef7ecb6 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -91,6 +91,7 @@ enum RecNameInts REC_PLAY = 0x59414c50, REC_CSTA = 0x41545343, REC_OBJE = 0x454a424f, + REC_GMAP = 0x50414d47, // format 1 REC_FILT = 0x544C4946 diff --git a/components/esm/globalmap.cpp b/components/esm/globalmap.cpp new file mode 100644 index 000000000..1fa5f907e --- /dev/null +++ b/components/esm/globalmap.cpp @@ -0,0 +1,26 @@ +#include "globalmap.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" +#include "defs.hpp" + +unsigned int ESM::GlobalMap::sRecordId = ESM::REC_GMAP; + +void ESM::GlobalMap::load (ESMReader &esm) +{ + esm.getHNT(mBounds, "BNDS"); + + esm.getSubNameIs("DATA"); + esm.getSubHeader(); + mImageData.resize(esm.getSubSize()); + esm.getExact(&mImageData[0], mImageData.size()); +} + +void ESM::GlobalMap::save (ESMWriter &esm) const +{ + esm.writeHNT("BNDS", mBounds); + + esm.startSubRecord("DATA"); + esm.write(&mImageData[0], mImageData.size()); + esm.endRecord("DATA"); +} diff --git a/components/esm/globalmap.hpp b/components/esm/globalmap.hpp new file mode 100644 index 000000000..5d036c736 --- /dev/null +++ b/components/esm/globalmap.hpp @@ -0,0 +1,34 @@ +#ifndef OPENMW_COMPONENTS_ESM_GLOBALMAP_H +#define OPENMW_COMPONENTS_ESM_GLOBALMAP_H + +#include + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + // format 0, saved games only + + ///< \brief An image containing the explored areas on the global map. + struct GlobalMap + { + static unsigned int sRecordId; + + // The minimum and maximum cell coordinates + struct Bounds + { + int mMinX, mMaxX, mMinY, mMaxY; + }; + + Bounds mBounds; + + std::vector mImageData; + + void load (ESMReader &esm); + void save (ESMWriter &esm) const; + }; + +} + +#endif