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 4d47e7eb7a..fa0fe888ba 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 c09b4fea09..9ed3bf80ff 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 dec27199af..6ace7dc0fd 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 8e49d66142..66bd805af7 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 68dc947af6..bc440d8182 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 522bbb321b..6fbcfdc6b5 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 20f40de99f..5fe878cd45 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 e94f790c72..a396d78c5b 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 d9ab8129dd..d73bcaf74f 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 74d987df85..40ef7ecb65 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 0000000000..1fa5f907e0 --- /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 0000000000..5d036c736f --- /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