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.
This commit is contained in:
Bo Svensson 2021-11-13 22:37:53 +00:00 committed by GitHub
parent 68e7a4083e
commit a62b22cd31
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 62 additions and 86 deletions

View file

@ -5,6 +5,7 @@
#include <osg/BlendFunc> #include <osg/BlendFunc>
#include <osg/Geometry> #include <osg/Geometry>
#include <osg/VertexAttribDivisor> #include <osg/VertexAttribDivisor>
#include <osg/Program>
#include <components/esm/esmreader.hpp> #include <components/esm/esmreader.hpp>
#include <components/sceneutil/lightmanager.hpp> #include <components/sceneutil/lightmanager.hpp>
@ -20,15 +21,12 @@
namespace MWRender 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) const ESM::Static* stat = groundcoverStore.get<ESM::Static>().searchStatic(id);
{ if (!stat)
case ESM::REC_STAT: stat = store.get<ESM::Static>().searchStatic(id);
return store.get<ESM::Static>().searchStatic(id)->mModel; return stat ? stat->mModel : std::string();
default:
return std::string();
}
} }
class InstancingVisitor : public osg::NodeVisitor 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<GroundcoverChunkId>(nullptr) : GenericResourceManager<GroundcoverChunkId>(nullptr)
, mSceneManager(sceneManager) , mSceneManager(sceneManager)
, mDensity(density) , mDensity(density)
, mStateset(new osg::StateSet) , mStateset(new osg::StateSet)
, mGroundcoverStore(store)
{ {
setViewDistance(viewDistance); setViewDistance(viewDistance);
// MGE uses default alpha settings for groundcover, so we can not rely on alpha properties // 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); mProgramTemplate->addBindAttribLocation("aRotation", 7);
} }
Groundcover::~Groundcover()
{
}
void Groundcover::collectInstances(InstanceMap& instances, float size, const osg::Vec2f& center) 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 minBound = (center - osg::Vec2f(size/2.f, size/2.f));
osg::Vec2f maxBound = (center + osg::Vec2f(size/2.f, size/2.f)); osg::Vec2f maxBound = (center + osg::Vec2f(size/2.f, size/2.f));
DensityCalculator calculator(mDensity); DensityCalculator calculator(mDensity);
@ -188,7 +191,7 @@ namespace MWRender
{ {
for (int cellY = startCell.y(); cellY < startCell.y() + size; ++cellY) for (int cellY = startCell.y(); cellY < startCell.y() + size; ++cellY)
{ {
const ESM::Cell* cell = store.get<ESM::Cell>().searchStatic(cellX, cellY); const ESM::Cell* cell = mGroundcoverStore.get<ESM::Cell>().searchStatic(cellX, cellY);
if (!cell) continue; if (!cell) continue;
calculator.reset(); calculator.reset();
@ -204,14 +207,11 @@ namespace MWRender
while(cell->getNextRef(esm[index], ref, deleted)) while(cell->getNextRef(esm[index], ref, deleted))
{ {
if (deleted) continue; if (deleted) continue;
if (!ref.mRefNum.fromGroundcoverFile()) continue;
if (!calculator.isInstanceEnabled()) continue; if (!calculator.isInstanceEnabled()) continue;
if (!isInChunkBorders(ref, minBound, maxBound)) continue; if (!isInChunkBorders(ref, minBound, maxBound)) continue;
Misc::StringUtils::lowerCaseInPlace(ref.mRefID); std::string model = getGroundcoverModel(ref.mRefID, mGroundcoverStore, worldStore);
int type = store.findStatic(ref.mRefID);
std::string model = getGroundcoverModel(type, ref.mRefID, store);
if (model.empty()) continue; if (model.empty()) continue;
model = "meshes/" + model; model = "meshes/" + model;

View file

@ -4,7 +4,15 @@
#include <components/terrain/quadtreeworld.hpp> #include <components/terrain/quadtreeworld.hpp>
#include <components/resource/scenemanager.hpp> #include <components/resource/scenemanager.hpp>
#include <components/esm/loadcell.hpp> #include <components/esm/loadcell.hpp>
#include <osg/Program>
namespace MWWorld
{
class ESMStore;
}
namespace osg
{
class Program;
}
namespace MWRender namespace MWRender
{ {
@ -12,8 +20,8 @@ namespace MWRender
class Groundcover : public Resource::GenericResourceManager<GroundcoverChunkId>, public Terrain::QuadTreeWorld::ChunkManager class Groundcover : public Resource::GenericResourceManager<GroundcoverChunkId>, public Terrain::QuadTreeWorld::ChunkManager
{ {
public: public:
Groundcover(Resource::SceneManager* sceneManager, float density, float viewDistance); Groundcover(Resource::SceneManager* sceneManager, float density, float viewDistance, const MWWorld::ESMStore& groundcoverStore);
~Groundcover() = default; ~Groundcover();
osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override; osg::ref_ptr<osg::Node> 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; float mDensity;
osg::ref_ptr<osg::StateSet> mStateset; osg::ref_ptr<osg::StateSet> mStateset;
osg::ref_ptr<osg::Program> mProgramTemplate; osg::ref_ptr<osg::Program> 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<std::string, std::vector<GroundcoverEntry>> InstanceMap; typedef std::map<std::string, std::vector<GroundcoverEntry>> InstanceMap;
osg::ref_ptr<osg::Node> createChunk(InstanceMap& instances, const osg::Vec2f& center); osg::ref_ptr<osg::Node> createChunk(InstanceMap& instances, const osg::Vec2f& center);

View file

@ -432,7 +432,6 @@ namespace MWRender
int type = store.findStatic(ref.mRefID); int type = store.findStatic(ref.mRefID);
if (!typeFilter(type,size>=2)) continue; if (!typeFilter(type,size>=2)) continue;
if (deleted) { refs.erase(ref.mRefNum); continue; } if (deleted) { refs.erase(ref.mRefNum); continue; }
if (ref.mRefNum.fromGroundcoverFile()) continue;
refs[ref.mRefNum] = std::move(ref); refs[ref.mRefNum] = std::move(ref);
} }
} }

View file

@ -294,7 +294,7 @@ namespace MWRender
RenderingManager::RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode, RenderingManager::RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode,
Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, 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) : mViewer(viewer)
, mRootNode(rootNode) , mRootNode(rootNode)
, mResourceSystem(resourceSystem) , mResourceSystem(resourceSystem)
@ -450,7 +450,7 @@ namespace MWRender
float density = Settings::Manager::getFloat("density", "Groundcover"); float density = Settings::Manager::getFloat("density", "Groundcover");
density = std::clamp(density, 0.f, 1.f); 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<Terrain::QuadTreeWorld*>(mTerrain.get())->addChunkManager(mGroundcover.get()); static_cast<Terrain::QuadTreeWorld*>(mTerrain.get())->addChunkManager(mGroundcover.get());
mResourceSystem->addResourceManager(mGroundcover.get()); mResourceSystem->addResourceManager(mGroundcover.get());
} }

View file

@ -95,7 +95,7 @@ namespace MWRender
public: public:
RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode, RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode,
Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, 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(); ~RenderingManager();
osgUtil::IncrementalCompileOperation* getIncrementalCompileOperation(); osgUtil::IncrementalCompileOperation* getIncrementalCompileOperation();

View file

@ -741,11 +741,7 @@ namespace MWWorld
case ESM::REC_NPC_: mNpcs.load(ref, deleted, store); break; case ESM::REC_NPC_: mNpcs.load(ref, deleted, store); break;
case ESM::REC_PROB: mProbes.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_REPA: mRepairs.load(ref, deleted, store); break;
case ESM::REC_STAT: case ESM::REC_STAT: mStatics.load(ref, deleted, store); break;
{
if (ref.mRefNum.fromGroundcoverFile()) return;
mStatics.load(ref, deleted, store); break;
}
case ESM::REC_WEAP: mWeapons.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; case ESM::REC_BODY: mBodyParts.load(ref, deleted, store); break;

View file

@ -152,23 +152,16 @@ namespace MWWorld
mLevitationEnabled(true), mGoToJail(false), mDaysInPrison(0), mLevitationEnabled(true), mGoToJail(false), mDaysInPrison(0),
mPlayerTraveling(false), mPlayerInJail(false), mSpellPreloadTimer(0.f) 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(); Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
listener->loadingOn(); listener->loadingOn();
GameContentLoader gameContentLoader(*listener); loadContentFiles(fileCollections, contentFiles, mStore, mEsm, encoder, listener);
EsmLoader esmLoader(mStore, mEsm, encoder, *listener); if (!groundcoverFiles.empty())
{
gameContentLoader.addLoader(".esm", &esmLoader); std::vector<ESM::ESMReader> tempReaders (groundcoverFiles.size());
gameContentLoader.addLoader(".esp", &esmLoader); loadContentFiles(fileCollections, groundcoverFiles, mGroundcoverStore, tempReaders, encoder, listener, false);
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);
listener->loadingOff(); listener->loadingOff();
@ -176,10 +169,6 @@ namespace MWWorld
if (mEsm[0].getFormat() == 0) if (mEsm[0].getFormat() == 0)
ensureNeededRecords(); 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()); mCurrentDate.reset(new DateTimeManager());
fillGlobalVariables(); fillGlobalVariables();
@ -202,7 +191,7 @@ namespace MWWorld
mNavigator = DetourNavigator::makeNavigatorStub(); 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())); mProjectileManager.reset(new ProjectileManager(mRendering->getLightRoot(), resourceSystem, mRendering.get(), mPhysics.get()));
mRendering->preloadCommonAssets(); mRendering->preloadCommonAssets();
@ -2959,9 +2948,22 @@ namespace MWWorld
return mScriptsEnabled; return mScriptsEnabled;
} }
void World::loadContentFiles(const Files::Collections& fileCollections, void World::loadContentFiles(const Files::Collections& fileCollections, const std::vector<std::string>& content, ESMStore& store, std::vector<ESM::ESMReader>& readers, ToUTF8::Utf8Encoder* encoder, Loading::Listener* listener, bool validate)
const std::vector<std::string>& content, const std::vector<std::string>& groundcover, ContentLoader& contentLoader)
{ {
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; int idx = 0;
for (const std::string &file : content) for (const std::string &file : content)
{ {
@ -2969,7 +2971,7 @@ namespace MWWorld
const Files::MultiDirCollection& col = fileCollections.getCollection(filename.extension().string()); const Files::MultiDirCollection& col = fileCollections.getCollection(filename.extension().string());
if (col.doesExist(file)) if (col.doesExist(file))
{ {
contentLoader.load(col.getPath(file), idx); gameContentLoader.load(col.getPath(file), idx);
} }
else else
{ {
@ -2978,24 +2980,6 @@ namespace MWWorld
} }
idx++; 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) bool World::startSpellCast(const Ptr &actor)

View file

@ -80,6 +80,7 @@ namespace MWWorld
std::vector<ESM::ESMReader> mEsm; std::vector<ESM::ESMReader> mEsm;
MWWorld::ESMStore mStore; MWWorld::ESMStore mStore;
MWWorld::ESMStore mGroundcoverStore;
LocalScripts mLocalScripts; LocalScripts mLocalScripts;
MWWorld::Globals mGlobalVariables; MWWorld::Globals mGlobalVariables;
@ -163,14 +164,10 @@ namespace MWWorld
void updateSkyDate(); void updateSkyDate();
/** // A helper method called automatically during World construction.
* @brief loadContentFiles - Loads content files (esm,esp,omwgame,omwaddon) void loadContentFiles(const Files::Collections& fileCollections, const std::vector<std::string>& content,
* @param fileCollections- Container which holds content file names and their paths ESMStore& store, std::vector<ESM::ESMReader>& readers, ToUTF8::Utf8Encoder* encoder, Loading::Listener* listener, bool validateMasterFiles = true);
* @param content - Container which holds content file names
* @param contentLoader -
*/
void loadContentFiles(const Files::Collections& fileCollections,
const std::vector<std::string>& content, const std::vector<std::string>& groundcover, ContentLoader& contentLoader);
float feetToGameUnits(float feet); float feetToGameUnits(float feet);
float getActivationDistancePlusTelekinesis(); float getActivationDistancePlusTelekinesis();

View file

@ -5,11 +5,6 @@
#include "esmreader.hpp" #include "esmreader.hpp"
#include "esmwriter.hpp" #include "esmwriter.hpp"
namespace ESM
{
int GroundcoverIndex = std::numeric_limits<int>::max();
}
void ESM::RefNum::load (ESMReader& esm, bool wide, const std::string& tag) void ESM::RefNum::load (ESMReader& esm, bool wide, const std::string& tag)
{ {
if (wide) if (wide)

View file

@ -12,7 +12,6 @@ namespace ESM
class ESMReader; class ESMReader;
const int UnbreakableLock = std::numeric_limits<int>::max(); const int UnbreakableLock = std::numeric_limits<int>::max();
extern int GroundcoverIndex;
struct RefNum struct RefNum
{ {
@ -27,10 +26,6 @@ namespace ESM
inline bool isSet() const { return mIndex != 0 || mContentFile != -1; } inline bool isSet() const { return mIndex != 0 || mContentFile != -1; }
inline void unset() { *this = {0, -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 /* Cell reference. This represents ONE object (of many) inside the