diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index b86923a1d..a02dbf1d6 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -212,18 +212,31 @@ void OMW::Engine::setCell (const std::string& cellName) // Set master file (esm) // - If the given name does not have an extension, ".esm" is added automatically -// - Currently OpenMW only supports one master at the same time. void OMW::Engine::addMaster (const std::string& master) { - assert (mMaster.empty()); - mMaster = master; - + mMaster.push_back(master); + std::string &str = mMaster.back(); + // Append .esm if not already there - std::string::size_type sep = mMaster.find_last_of ("."); + std::string::size_type sep = str.find_last_of ("."); + if (sep == std::string::npos) + { + str += ".esm"; + } +} + +// Add plugin file (esp) +void OMW::Engine::addPlugin (const std::string& plugin) +{ + mPlugins.push_back(plugin); + std::string &str = mPlugins.back(); + + // Append .esp if not already there + std::string::size_type sep = str.find_last_of ("."); if (sep == std::string::npos) { - mMaster += ".esm"; + str += ".esp"; } } @@ -331,8 +344,8 @@ void OMW::Engine::go() MWGui::CursorReplace replacer; // Create the world - mEnvironment.setWorld (new MWWorld::World (*mOgre, mFileCollections, mMaster, - mResDir, mCfgMgr.getCachePath(), mNewGame, mEncoding, mFallbackMap)); + mEnvironment.setWorld( new MWWorld::World (*mOgre, mFileCollections, mMaster, mPlugins, + mResDir, mCfgMgr.getCachePath(), mNewGame, mEncoding, mFallbackMap) ); // Create window manager - this manages all the MW-specific GUI windows MWScript::registerExtensions (mExtensions); diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 57402c91e..7fd27b36b 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -64,7 +64,8 @@ namespace OMW boost::filesystem::path mResDir; OEngine::Render::OgreRenderer *mOgre; std::string mCellName; - std::string mMaster; + std::vector mMaster; + std::vector mPlugins; int mFpsLevel; bool mDebug; bool mVerboseScripts; @@ -122,9 +123,12 @@ namespace OMW /// Set master file (esm) /// - If the given name does not have an extension, ".esm" is added automatically - /// - Currently OpenMW only supports one master at the same time. void addMaster(const std::string& master); + /// Same as "addMaster", but for plugin files (esp) + /// - If the given name does not have an extension, ".esp" is added automatically + void addPlugin(const std::string& plugin); + /// Enable fps counter void showFPS(int level); diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 5c2ba2f80..c2b8d7559 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -224,18 +224,19 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat master.push_back("Morrowind"); } - if (master.size() > 1) + StringsVector plugin = variables["plugin"].as(); + // Removed check for 255 files, which would be the hard-coded limit in Morrowind. + // I'll keep the following variable in, maybe we can use it for somethng different. + int cnt = master.size() + plugin.size(); + + // Prepare loading master/plugin files (i.e. send filenames to engine) + for (std::vector::size_type i = 0; i < master.size(); i++) { - std::cout - << "Ignoring all but the first master file (multiple master files not yet supported)." - << std::endl; + engine.addMaster(master[i]); } - engine.addMaster(master[0]); - - StringsVector plugin = variables["plugin"].as(); - if (!plugin.empty()) + for (std::vector::size_type i = 0; i < plugin.size(); i++) { - std::cout << "Ignoring plugin files (plugins not yet supported)." << std::endl; + engine.addPlugin(plugin[i]); } // startup-settings diff --git a/apps/openmw/mwrender/terrain.cpp b/apps/openmw/mwrender/terrain.cpp index 691e7c4af..e39cdd150 100644 --- a/apps/openmw/mwrender/terrain.cpp +++ b/apps/openmw/mwrender/terrain.cpp @@ -141,7 +141,7 @@ namespace MWRender std::map indexes; initTerrainTextures(&terrainData, cellX, cellY, x * numTextures, y * numTextures, - numTextures, indexes); + numTextures, indexes, land->plugin); if (mTerrainGroup.getTerrain(terrainX, terrainY) == NULL) { @@ -200,8 +200,13 @@ namespace MWRender void TerrainManager::initTerrainTextures(Terrain::ImportData* terrainData, int cellX, int cellY, int fromX, int fromY, int size, - std::map& indexes) + std::map& indexes, size_t plugin) { + // FIXME: In a multiple esm configuration, we have multiple palettes. Since this code + // crosses cell boundaries, we no longer have a unique terrain palette. Instead, we need + // to adopt the following code for a dynamic palette. And this is evil - the current design + // does not work well for this task... + assert(terrainData != NULL && "Must have valid terrain data"); assert(fromX >= 0 && fromY >= 0 && "Can't get a terrain texture on terrain outside the current cell"); @@ -214,12 +219,20 @@ namespace MWRender // //If we don't sort the ltex indexes, the splatting order may differ between //cells which may lead to inconsistent results when shading between cells + int num = MWBase::Environment::get().getWorld()->getStore().landTexts.getSizePlugin(plugin); std::set ltexIndexes; for ( int y = fromY - 1; y < fromY + size + 1; y++ ) { for ( int x = fromX - 1; x < fromX + size + 1; x++ ) { - ltexIndexes.insert(getLtexIndexAt(cellX, cellY, x, y)); + int idx = getLtexIndexAt(cellX, cellY, x, y); + // This is a quick hack to prevent the program from trying to fetch textures + // from a neighboring cell, which might originate from a different plugin, + // and use a separate texture palette. Right now, we simply cast it to the + // default texture (i.e. 0). + if (idx > num) + idx = 0; + ltexIndexes.insert(idx); } } @@ -231,7 +244,7 @@ namespace MWRender iter != ltexIndexes.end(); ++iter ) { - const uint16_t ltexIndex = *iter; + uint16_t ltexIndex = *iter; //this is the base texture, so we can ignore this at present if ( ltexIndex == baseTexture ) { @@ -244,8 +257,12 @@ namespace MWRender { //NB: All vtex ids are +1 compared to the ltex ids - assert( (int)MWBase::Environment::get().getWorld()->getStore().landTexts.getSize() >= (int)ltexIndex - 1 && - "LAND.VTEX must be within the bounds of the LTEX array"); + // NOTE: using the quick hack above, we should no longer end up with textures indices + // that are out of bounds. However, I haven't updated the test to a multi-palette + // system yet. We probably need more work here, so we skip it for now. + + //assert( (int)MWBase::Environment::get().getWorld()->getStore().landTexts.getSize() >= (int)ltexIndex - 1 && + //"LAND.VTEX must be within the bounds of the LTEX array"); std::string texture; if ( ltexIndex == 0 ) @@ -254,7 +271,7 @@ namespace MWRender } else { - texture = MWBase::Environment::get().getWorld()->getStore().landTexts.search(ltexIndex-1)->texture; + texture = MWBase::Environment::get().getWorld()->getStore().landTexts.search(ltexIndex-1, plugin)->texture; //TODO this is needed due to MWs messed up texture handling texture = texture.substr(0, texture.rfind(".")) + ".dds"; } diff --git a/apps/openmw/mwrender/terrain.hpp b/apps/openmw/mwrender/terrain.hpp index c83d96cf4..484a0dbe3 100644 --- a/apps/openmw/mwrender/terrain.hpp +++ b/apps/openmw/mwrender/terrain.hpp @@ -75,7 +75,7 @@ namespace MWRender{ void initTerrainTextures(Ogre::Terrain::ImportData* terrainData, int cellX, int cellY, int fromX, int fromY, int size, - std::map& indexes); + std::map& indexes, size_t plugin); /** * Creates the blend (splatting maps) for the given terrain from the ltex data. diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 38063b051..dd3717f97 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -164,7 +164,8 @@ namespace MWWorld World::World (OEngine::Render::OgreRenderer& renderer, const Files::Collections& fileCollections, - const std::string& master, const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, bool newGame, + const std::vector& master, const std::vector& plugins, + const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, bool newGame, const std::string& encoding, std::map fallbackMap) : mPlayer (0), mLocalScripts (mStore), mGlobalVariables (0), mSky (true), mNextDynamicRecord (0), mCells (mStore, mEsm), @@ -177,14 +178,32 @@ namespace MWWorld mWeatherManager = new MWWorld::WeatherManager(mRendering); - boost::filesystem::path masterPath (fileCollections.getCollection (".esm").getPath (master)); - - std::cout << "Loading ESM " << masterPath.string() << "\n"; - - // This parses the ESM file and loads a sample cell - mEsm.setEncoding(encoding); - mEsm.open (masterPath.string()); - mStore.load (mEsm); + int idx = 0; + for (std::vector::size_type i = 0; i < master.size(); i++, idx++) + { + boost::filesystem::path masterPath (fileCollections.getCollection (".esm").getPath (master[i])); + + std::cout << "Loading ESM " << masterPath.string() << "\n"; + + // This parses the ESM file + mEsm.setEncoding(encoding); + mEsm.open (masterPath.string()); + mEsm.setIndex(idx); + mStore.load (mEsm); + } + + for (std::vector::size_type i = 0; i < plugins.size(); i++, idx++) + { + boost::filesystem::path pluginPath (fileCollections.getCollection (".esp").getPath (plugins[i])); + + std::cout << "Loading ESP " << pluginPath.string() << "\n"; + + // This parses the ESP file + mEsm.setEncoding(encoding); + mEsm.open (pluginPath.string()); + mEsm.setIndex(idx); + mStore.load (mEsm); + } mPlayer = new MWWorld::Player (mStore.npcs.find ("player"), *this); mRendering->attachCameraTo(mPlayer->getPlayer()); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 90cd2151b..52bda6749 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -96,7 +96,8 @@ namespace MWWorld World (OEngine::Render::OgreRenderer& renderer, const Files::Collections& fileCollections, - const std::string& master, const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, bool newGame, + const std::vector& master, const std::vector& plugins, + const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, bool newGame, const std::string& encoding, std::map fallbackMap); virtual ~World(); diff --git a/components/esm/esm_reader.hpp b/components/esm/esm_reader.hpp index 66c0710a6..afa6da213 100644 --- a/components/esm/esm_reader.hpp +++ b/components/esm/esm_reader.hpp @@ -189,6 +189,14 @@ public: void openRaw(const std::string &file); + // This is a quick hack for multiple esm/esp files. Each plugin introduces its own + // terrain palette, but ESMReader does not pass a reference to the correct plugin + // to the individual load() methods. This hack allows to pass this reference + // indirectly to the load() method. + int idx; + void setIndex(const int index) {idx = index;} + const int getIndex() {return idx;} + /************************************************************************* * * Medium-level reading shortcuts diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index 05cadae7f..101617071 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -23,6 +23,7 @@ Land::~Land() void Land::load(ESMReader &esm) { mEsm = &esm; + plugin = mEsm->getIndex(); // Get the grid location esm.getSubNameIs("INTV"); diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index ebc314a28..898134d72 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -17,6 +17,7 @@ struct Land int flags; // Only first four bits seem to be used, don't know what // they mean. int X, Y; // Map coordinates. + int plugin; // Plugin index, used to reference the correct material palette. // File context. This allows the ESM reader to be 'reset' to this // location later when we are ready to load the full data set. diff --git a/components/esm_store/reclists.hpp b/components/esm_store/reclists.hpp index ffecfc8de..42597bea4 100644 --- a/components/esm_store/reclists.hpp +++ b/components/esm_store/reclists.hpp @@ -26,6 +26,7 @@ namespace ESMS virtual void load(ESMReader &esm, const std::string &id) = 0; virtual int getSize() = 0; + virtual void remove(const std::string &id) {}; virtual void listIdentifier (std::vector& identifier) const = 0; static std::string toLower (const std::string& name) @@ -57,6 +58,14 @@ namespace ESMS list[id2].load(esm); } + // Delete the given object ID. Plugin files support this, so we need to do this, too. + void remove(const std::string &id) + { + std::string id2 = toLower (id); + + list.erase(id2); + } + // Find the given object ID, or return NULL if not found. const X* search(const std::string &id) const { @@ -268,38 +277,63 @@ namespace ESMS { virtual ~LTexList() {} - // TODO: For multiple ESM/ESP files we need one list per file. - std::vector ltex; + // For multiple ESM/ESP files we need one list per file. + typedef std::vector LandTextureList; + std::vector ltex; LTexList() { - // More than enough to hold Morrowind.esm. - ltex.reserve(128); + ltex.push_back(LandTextureList()); + LandTextureList <exl = ltex[0]; + // More than enough to hold Morrowind.esm. Extra lists for plugins will we + // added on-the-fly in a different method. + ltexl.reserve(128); } - const LandTexture* search(size_t index) const + const LandTexture* search(size_t index, size_t plugin) const { - assert(index < ltex.size()); - return <ex.at(index); + assert(plugin < ltex.size()); + const LandTextureList <exl = ltex[plugin]; + + assert(index < ltexl.size()); + return <exl.at(index); } + // "getSize" returns the number of individual terrain palettes. + // "getSizePlugin" returns the number of land textures in a specific palette. int getSize() { return ltex.size(); } int getSize() const { return ltex.size(); } + int getSizePlugin(size_t plugin) { assert(plugin < ltex.size()); return ltex[plugin].size(); } + int getSizePlugin(size_t plugin) const { assert(plugin < ltex.size()); return ltex[plugin].size(); } + virtual void listIdentifier (std::vector& identifier) const {} - void load(ESMReader &esm, const std::string &id) + void load(ESMReader &esm, const std::string &id, size_t plugin) { LandTexture lt; lt.load(esm); lt.id = id; // Make sure we have room for the structure - if(lt.index + 1 > (int)ltex.size()) - ltex.resize(lt.index+1); + if (plugin >= ltex.size()) { + ltex.resize(plugin+1); + } + LandTextureList <exl = ltex[plugin]; + if(lt.index + 1 > (int)ltexl.size()) + ltexl.resize(lt.index+1); // Store it - ltex[lt.index] = lt; + ltexl[lt.index] = lt; + } + + // Load all terrain palettes at the same size. Inherited virtual function + // from "RecList". Mostly useless, because we need the implementation + // above this one. + void load(ESMReader &esm, const std::string &id) + { + size_t plugin = esm.getIndex(); + load(esm, id, plugin); } }; diff --git a/components/esm_store/store.cpp b/components/esm_store/store.cpp index c676601e5..d13cd373d 100644 --- a/components/esm_store/store.cpp +++ b/components/esm_store/store.cpp @@ -67,6 +67,15 @@ void ESMStore::load(ESMReader &esm) { // Load it std::string id = esm.getHNOString("NAME"); + // ... unless it got deleted! This means that the following record + // has been deleted, and trying to load it using standard assumptions + // on the structure will (probably) fail. + if (esm.isNextSub("DELE")) { + esm.skipRecord(); + all.erase(id); + it->second->remove(id); + continue; + } it->second->load(esm, id); if (n.val==ESM::REC_DIAL)