From a62b22cd31c736cf83e85ec12475633557b440a7 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Sat, 13 Nov 2021 22:37:53 +0000 Subject: [PATCH] isolates groundcover content files (#3208) Specifications developed in PR #3206 require that groundcover content files must not be allowed to corrupt normal content files. With this PR we simply isolate our existing loading logic by instantiating a separate `ESMStore` for `Groundcover`. In addition, we remove some outdated workarounds. --- apps/openmw/mwrender/groundcover.cpp | 30 +++++------ apps/openmw/mwrender/groundcover.hpp | 16 ++++-- apps/openmw/mwrender/objectpaging.cpp | 1 - apps/openmw/mwrender/renderingmanager.cpp | 4 +- apps/openmw/mwrender/renderingmanager.hpp | 2 +- apps/openmw/mwworld/cellstore.cpp | 6 +-- apps/openmw/mwworld/worldimp.cpp | 66 +++++++++-------------- apps/openmw/mwworld/worldimp.hpp | 13 ++--- components/esm/cellref.cpp | 5 -- components/esm/cellref.hpp | 5 -- 10 files changed, 62 insertions(+), 86 deletions(-) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 3f4e592688..2998ee509a 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -20,15 +21,12 @@ namespace MWRender { - std::string getGroundcoverModel(int type, const std::string& id, const MWWorld::ESMStore& store) + std::string getGroundcoverModel(const std::string& id, const MWWorld::ESMStore& groundcoverStore, const MWWorld::ESMStore& store) { - switch (type) - { - case ESM::REC_STAT: - return store.get().searchStatic(id)->mModel; - default: - return std::string(); - } + const ESM::Static* stat = groundcoverStore.get().searchStatic(id); + if (!stat) + stat = store.get().searchStatic(id); + return stat ? stat->mModel : std::string(); } class InstancingVisitor : public osg::NodeVisitor @@ -155,11 +153,12 @@ namespace MWRender } } - Groundcover::Groundcover(Resource::SceneManager* sceneManager, float density, float viewDistance) + Groundcover::Groundcover(Resource::SceneManager* sceneManager, float density, float viewDistance, const MWWorld::ESMStore& store) : GenericResourceManager(nullptr) , mSceneManager(sceneManager) , mDensity(density) , mStateset(new osg::StateSet) + , mGroundcoverStore(store) { setViewDistance(viewDistance); // MGE uses default alpha settings for groundcover, so we can not rely on alpha properties @@ -176,9 +175,13 @@ namespace MWRender mProgramTemplate->addBindAttribLocation("aRotation", 7); } + Groundcover::~Groundcover() + { + } + void Groundcover::collectInstances(InstanceMap& instances, float size, const osg::Vec2f& center) { - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore& worldStore = MWBase::Environment::get().getWorld()->getStore(); osg::Vec2f minBound = (center - osg::Vec2f(size/2.f, size/2.f)); osg::Vec2f maxBound = (center + osg::Vec2f(size/2.f, size/2.f)); DensityCalculator calculator(mDensity); @@ -188,7 +191,7 @@ namespace MWRender { for (int cellY = startCell.y(); cellY < startCell.y() + size; ++cellY) { - const ESM::Cell* cell = store.get().searchStatic(cellX, cellY); + const ESM::Cell* cell = mGroundcoverStore.get().searchStatic(cellX, cellY); if (!cell) continue; calculator.reset(); @@ -204,14 +207,11 @@ namespace MWRender while(cell->getNextRef(esm[index], ref, deleted)) { if (deleted) continue; - if (!ref.mRefNum.fromGroundcoverFile()) continue; if (!calculator.isInstanceEnabled()) continue; if (!isInChunkBorders(ref, minBound, maxBound)) continue; - Misc::StringUtils::lowerCaseInPlace(ref.mRefID); - int type = store.findStatic(ref.mRefID); - std::string model = getGroundcoverModel(type, ref.mRefID, store); + std::string model = getGroundcoverModel(ref.mRefID, mGroundcoverStore, worldStore); if (model.empty()) continue; model = "meshes/" + model; diff --git a/apps/openmw/mwrender/groundcover.hpp b/apps/openmw/mwrender/groundcover.hpp index cd73e46eb0..ea0cfa9b62 100644 --- a/apps/openmw/mwrender/groundcover.hpp +++ b/apps/openmw/mwrender/groundcover.hpp @@ -4,7 +4,15 @@ #include #include #include -#include + +namespace MWWorld +{ + class ESMStore; +} +namespace osg +{ + class Program; +} namespace MWRender { @@ -12,8 +20,8 @@ namespace MWRender class Groundcover : public Resource::GenericResourceManager, public Terrain::QuadTreeWorld::ChunkManager { public: - Groundcover(Resource::SceneManager* sceneManager, float density, float viewDistance); - ~Groundcover() = default; + Groundcover(Resource::SceneManager* sceneManager, float density, float viewDistance, const MWWorld::ESMStore& groundcoverStore); + ~Groundcover(); osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override; @@ -35,6 +43,8 @@ namespace MWRender float mDensity; osg::ref_ptr mStateset; osg::ref_ptr mProgramTemplate; + /// @note mGroundcoverStore is separated from World's store because groundcover files must not be allowed to corrupt normal content files. + const MWWorld::ESMStore& mGroundcoverStore; typedef std::map> InstanceMap; osg::ref_ptr createChunk(InstanceMap& instances, const osg::Vec2f& center); diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 90069c2d8d..756769bc7d 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -432,7 +432,6 @@ namespace MWRender int type = store.findStatic(ref.mRefID); if (!typeFilter(type,size>=2)) continue; if (deleted) { refs.erase(ref.mRefNum); continue; } - if (ref.mRefNum.fromGroundcoverFile()) continue; refs[ref.mRefNum] = std::move(ref); } } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index e0cd3f713a..2ba18378f9 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -294,7 +294,7 @@ namespace MWRender RenderingManager::RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, - const std::string& resourcePath, DetourNavigator::Navigator& navigator) + const std::string& resourcePath, DetourNavigator::Navigator& navigator, const MWWorld::ESMStore& groundcoverStore) : mViewer(viewer) , mRootNode(rootNode) , mResourceSystem(resourceSystem) @@ -450,7 +450,7 @@ namespace MWRender float density = Settings::Manager::getFloat("density", "Groundcover"); density = std::clamp(density, 0.f, 1.f); - mGroundcover.reset(new Groundcover(mResourceSystem->getSceneManager(), density, groundcoverDistance)); + mGroundcover.reset(new Groundcover(mResourceSystem->getSceneManager(), density, groundcoverDistance, groundcoverStore)); static_cast(mTerrain.get())->addChunkManager(mGroundcover.get()); mResourceSystem->addResourceManager(mGroundcover.get()); } diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index b8d5d955c8..99d0bb5f5e 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -95,7 +95,7 @@ namespace MWRender public: RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, - const std::string& resourcePath, DetourNavigator::Navigator& navigator); + const std::string& resourcePath, DetourNavigator::Navigator& navigator, const MWWorld::ESMStore& groundcoverStore); ~RenderingManager(); osgUtil::IncrementalCompileOperation* getIncrementalCompileOperation(); diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index b2ac511509..0448d0e28a 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -741,11 +741,7 @@ namespace MWWorld case ESM::REC_NPC_: mNpcs.load(ref, deleted, store); break; case ESM::REC_PROB: mProbes.load(ref, deleted, store); break; case ESM::REC_REPA: mRepairs.load(ref, deleted, store); break; - case ESM::REC_STAT: - { - if (ref.mRefNum.fromGroundcoverFile()) return; - mStatics.load(ref, deleted, store); break; - } + case ESM::REC_STAT: mStatics.load(ref, deleted, store); break; case ESM::REC_WEAP: mWeapons.load(ref, deleted, store); break; case ESM::REC_BODY: mBodyParts.load(ref, deleted, store); break; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index ed963b8b28..f434d059da 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -152,23 +152,16 @@ namespace MWWorld mLevitationEnabled(true), mGoToJail(false), mDaysInPrison(0), mPlayerTraveling(false), mPlayerInJail(false), mSpellPreloadTimer(0.f) { - mEsm.resize(contentFiles.size() + groundcoverFiles.size()); + mEsm.resize(contentFiles.size()); Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); listener->loadingOn(); - - GameContentLoader gameContentLoader(*listener); - EsmLoader esmLoader(mStore, mEsm, encoder, *listener); - - gameContentLoader.addLoader(".esm", &esmLoader); - gameContentLoader.addLoader(".esp", &esmLoader); - gameContentLoader.addLoader(".omwgame", &esmLoader); - gameContentLoader.addLoader(".omwaddon", &esmLoader); - gameContentLoader.addLoader(".project", &esmLoader); - - OMWScriptsLoader omwScriptsLoader(*listener, mStore); - gameContentLoader.addLoader(".omwscripts", &omwScriptsLoader); - - loadContentFiles(fileCollections, contentFiles, groundcoverFiles, gameContentLoader); + + loadContentFiles(fileCollections, contentFiles, mStore, mEsm, encoder, listener); + if (!groundcoverFiles.empty()) + { + std::vector tempReaders (groundcoverFiles.size()); + loadContentFiles(fileCollections, groundcoverFiles, mGroundcoverStore, tempReaders, encoder, listener, false); + } listener->loadingOff(); @@ -176,10 +169,6 @@ namespace MWWorld if (mEsm[0].getFormat() == 0) ensureNeededRecords(); - // TODO: We can and should validate before we call loadContentFiles(). - // Currently we validate here to prevent merge conflicts with groundcover ESMStore fixes. - validateMasterFiles(mEsm); - mCurrentDate.reset(new DateTimeManager()); fillGlobalVariables(); @@ -202,7 +191,7 @@ namespace MWWorld mNavigator = DetourNavigator::makeNavigatorStub(); } - mRendering.reset(new MWRender::RenderingManager(viewer, rootNode, resourceSystem, workQueue, resourcePath, *mNavigator)); + mRendering.reset(new MWRender::RenderingManager(viewer, rootNode, resourceSystem, workQueue, resourcePath, *mNavigator, mGroundcoverStore)); mProjectileManager.reset(new ProjectileManager(mRendering->getLightRoot(), resourceSystem, mRendering.get(), mPhysics.get())); mRendering->preloadCommonAssets(); @@ -2959,9 +2948,22 @@ namespace MWWorld return mScriptsEnabled; } - void World::loadContentFiles(const Files::Collections& fileCollections, - const std::vector& content, const std::vector& groundcover, ContentLoader& contentLoader) + void World::loadContentFiles(const Files::Collections& fileCollections, const std::vector& content, ESMStore& store, std::vector& readers, ToUTF8::Utf8Encoder* encoder, Loading::Listener* listener, bool validate) { + GameContentLoader gameContentLoader(*listener); + EsmLoader esmLoader(store, readers, encoder, *listener); + if (validate) + validateMasterFiles(readers); + + gameContentLoader.addLoader(".esm", &esmLoader); + gameContentLoader.addLoader(".esp", &esmLoader); + gameContentLoader.addLoader(".omwgame", &esmLoader); + gameContentLoader.addLoader(".omwaddon", &esmLoader); + gameContentLoader.addLoader(".project", &esmLoader); + + OMWScriptsLoader omwScriptsLoader(*listener, store); + gameContentLoader.addLoader(".omwscripts", &omwScriptsLoader); + int idx = 0; for (const std::string &file : content) { @@ -2969,7 +2971,7 @@ namespace MWWorld const Files::MultiDirCollection& col = fileCollections.getCollection(filename.extension().string()); if (col.doesExist(file)) { - contentLoader.load(col.getPath(file), idx); + gameContentLoader.load(col.getPath(file), idx); } else { @@ -2978,24 +2980,6 @@ namespace MWWorld } idx++; } - - ESM::GroundcoverIndex = idx; - - for (const std::string &file : groundcover) - { - boost::filesystem::path filename(file); - const Files::MultiDirCollection& col = fileCollections.getCollection(filename.extension().string()); - if (col.doesExist(file)) - { - contentLoader.load(col.getPath(file), idx); - } - else - { - std::string message = "Failed loading " + file + ": the groundcover file does not exist"; - throw std::runtime_error(message); - } - idx++; - } } bool World::startSpellCast(const Ptr &actor) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index afad359cfd..c8eebd9a8b 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -80,6 +80,7 @@ namespace MWWorld std::vector mEsm; MWWorld::ESMStore mStore; + MWWorld::ESMStore mGroundcoverStore; LocalScripts mLocalScripts; MWWorld::Globals mGlobalVariables; @@ -163,14 +164,10 @@ namespace MWWorld void updateSkyDate(); - /** - * @brief loadContentFiles - Loads content files (esm,esp,omwgame,omwaddon) - * @param fileCollections- Container which holds content file names and their paths - * @param content - Container which holds content file names - * @param contentLoader - - */ - void loadContentFiles(const Files::Collections& fileCollections, - const std::vector& content, const std::vector& groundcover, ContentLoader& contentLoader); + // A helper method called automatically during World construction. + void loadContentFiles(const Files::Collections& fileCollections, const std::vector& content, + ESMStore& store, std::vector& readers, ToUTF8::Utf8Encoder* encoder, Loading::Listener* listener, bool validateMasterFiles = true); + float feetToGameUnits(float feet); float getActivationDistancePlusTelekinesis(); diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index 7126459871..002a885d92 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -5,11 +5,6 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -namespace ESM -{ - int GroundcoverIndex = std::numeric_limits::max(); -} - void ESM::RefNum::load (ESMReader& esm, bool wide, const std::string& tag) { if (wide) diff --git a/components/esm/cellref.hpp b/components/esm/cellref.hpp index f6eff24cbf..0013329ccc 100644 --- a/components/esm/cellref.hpp +++ b/components/esm/cellref.hpp @@ -12,7 +12,6 @@ namespace ESM class ESMReader; const int UnbreakableLock = std::numeric_limits::max(); - extern int GroundcoverIndex; struct RefNum { @@ -27,10 +26,6 @@ namespace ESM inline bool isSet() const { return mIndex != 0 || mContentFile != -1; } inline void unset() { *this = {0, -1}; } - - // Note: this method should not be used for objects with invalid RefNum - // (for example, for objects from disabled plugins in savegames). - inline bool fromGroundcoverFile() const { return mContentFile >= GroundcoverIndex; } }; /* Cell reference. This represents ONE object (of many) inside the