From 7e63afdecfb9b453c566d127c40f68fe8c3aeb0c Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sat, 7 Nov 2020 02:05:16 +0300 Subject: [PATCH 1/5] Allow loading arbitrary NIF files --- components/nif/niffile.cpp | 43 +++++++++++++++++-- .../reference/modding/settings/index.rst | 1 + .../reference/modding/settings/models.rst | 27 ++++++++++++ files/settings-default.cfg | 5 +++ 4 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 docs/source/reference/modding/settings/models.rst diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 5642dfebb1..098f6aba24 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -1,9 +1,12 @@ #include "niffile.hpp" #include "effect.hpp" +#include #include #include +#include + namespace Nif { @@ -137,15 +140,46 @@ void NIFFile::parse(Files::IStreamPtr stream) // Check the header string std::string head = nif.getVersionString(); - if(head.compare(0, 22, "NetImmerse File Format") != 0) + static const std::array verStrings = + { + "NetImmerse File Format", + "Gamebryo File Format" + }; + bool supported = false; + for (const std::string& verString : verStrings) + { + supported = (head.compare(0, verString.size(), verString) == 0); + if (supported) + break; + } + if (!supported) fail("Invalid NIF header: " + head); + supported = false; + // Get BCD version ver = nif.getUInt(); // 4.0.0.0 is an older, practically identical version of the format. // It's not used by Morrowind assets but Morrowind supports it. - if(ver != NIFStream::generateVersion(4,0,0,0) && ver != VER_MW) - fail("Unsupported NIF version: " + printVersion(ver)); + static const std::array supportedVers = + { + NIFStream::generateVersion(4,0,0,0), + VER_MW + }; + for (uint32_t supportedVer : supportedVers) + { + supported = (ver == supportedVer); + if (supported) + break; + } + if (!supported) + { + static const bool ignoreUnsupported = Settings::Manager::getBool("load unsupported nif files", "Models"); + if (ignoreUnsupported) + warn("Unsupported NIF version: " + printVersion(ver) + ". Proceed with caution!"); + else + fail("Unsupported NIF version: " + printVersion(ver)); + } // NIF data endianness if (ver >= NIFStream::generateVersion(20,0,0,4)) @@ -245,6 +279,9 @@ void NIFFile::parse(Files::IStreamPtr stream) else fail("Unknown record type " + rec); + if (!supported) + Log(Debug::Verbose) << "NIF Debug: Reading record of type " << rec << ", index " << i << " (" << filename << ")"; + assert(r != nullptr); assert(r->recType != RC_MISSING); r->recName = rec; diff --git a/docs/source/reference/modding/settings/index.rst b/docs/source/reference/modding/settings/index.rst index 2261fe8e10..586a99dbb7 100644 --- a/docs/source/reference/modding/settings/index.rst +++ b/docs/source/reference/modding/settings/index.rst @@ -58,3 +58,4 @@ The ranges included with each setting are the physically possible ranges, not re windows navigator physics + models diff --git a/docs/source/reference/modding/settings/models.rst b/docs/source/reference/modding/settings/models.rst new file mode 100644 index 0000000000..da56a1709f --- /dev/null +++ b/docs/source/reference/modding/settings/models.rst @@ -0,0 +1,27 @@ +Models Settings +############### + +load unsupported nif files +-------------------------- + +:Type: boolean +:Range: True/False +:Default: False + +At the moment OpenMW's NIF loader is only tailored to load models +that can also load in Morrowind. + +However, you can enable its limited and experimental support for updates in +the definitions of record types from later NIF revisions by toggling on +this setting. + +You must keep in mind that loading unsupported NIF files may fail, +and the degree of this failure may vary. In milder cases, OpenMW will reject +the file anyway because it lacks a definition for a certain record type +that the file may use. In more severe cases OpenMW's +incomplete understanding of a record type can lead to memory corruption, +crashes or even freezes. Don't enable this if you're not sure that +you know what you're doing. + +To help debug possible issues OpenMW will log its progress in loading +every file that uses an unsupported NIF version. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index b9fb2fbf77..0b307ba09d 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -945,3 +945,8 @@ lineofsight keep inactive cache = 0 # Defer bounding boxes update until collision detection. defer aabb update = true + +[Models] +# Attempt to load any valid NIF file regardless of its version and track the progress. +# Loading arbitrary meshes is not advised and may cause instability. +load unsupported nif files = false From c79f50965229c3773069bea851d49e0f96bf76fc Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sat, 7 Nov 2020 03:33:04 +0300 Subject: [PATCH 2/5] Misc NIF loader improvements Bullet NIF loader cleanup Collect all extra records of a node Remove code duplication in geometry type detection in OSG-side NIF loader --- components/nifbullet/bulletnifloader.cpp | 53 ++++++++++-------------- components/nifosg/nifloader.cpp | 30 ++++++++++++-- 2 files changed, 50 insertions(+), 33 deletions(-) diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 15834ffade..5b531121ee 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -8,6 +8,7 @@ #include +#include #include #include @@ -24,11 +25,6 @@ osg::Matrixf getWorldTransform(const Nif::Node *node) return node->trafo.toMatrix(); } -btVector3 getbtVector(const osg::Vec3f &v) -{ - return btVector3(v.x(), v.y(), v.z()); -} - bool pathFileNameStartsWithX(const std::string& path) { const std::size_t slashpos = path.find_last_of("/\\"); @@ -36,7 +32,7 @@ bool pathFileNameStartsWithX(const std::string& path) return letterPos < path.size() && (path[letterPos] == 'x' || path[letterPos] == 'X'); } -void fillTriangleMeshWithTransform(btTriangleMesh& mesh, const Nif::NiTriShapeData& data, const osg::Matrixf &transform) +void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiTriShapeData& data, const osg::Matrixf &transform) { mesh.preallocateVertices(static_cast(data.vertices.size())); mesh.preallocateIndices(static_cast(data.triangles.size())); @@ -47,20 +43,20 @@ void fillTriangleMeshWithTransform(btTriangleMesh& mesh, const Nif::NiTriShapeDa for (std::size_t i = 0; i < triangles.size(); i += 3) { mesh.addTriangle( - getbtVector(vertices[triangles[i + 0]] * transform), - getbtVector(vertices[triangles[i + 1]] * transform), - getbtVector(vertices[triangles[i + 2]] * transform) + Misc::Convert::toBullet(vertices[triangles[i + 0]] * transform), + Misc::Convert::toBullet(vertices[triangles[i + 1]] * transform), + Misc::Convert::toBullet(vertices[triangles[i + 2]] * transform) ); } } -void fillTriangleMeshWithTransform(btTriangleMesh& mesh, const Nif::NiTriStripsData& data, const osg::Matrixf &transform) +void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiTriStripsData& data, const osg::Matrixf &transform) { const std::vector &vertices = data.vertices; const std::vector> &strips = data.strips; if (vertices.empty() || strips.empty()) return; - mesh.preallocateVertices(static_cast(data.vertices.size())); + mesh.preallocateVertices(static_cast(vertices.size())); int numTriangles = 0; for (const std::vector& strip : strips) { @@ -88,17 +84,17 @@ void fillTriangleMeshWithTransform(btTriangleMesh& mesh, const Nif::NiTriStripsD if (i%2==0) { mesh.addTriangle( - getbtVector(vertices[a] * transform), - getbtVector(vertices[b] * transform), - getbtVector(vertices[c] * transform) + Misc::Convert::toBullet(vertices[a] * transform), + Misc::Convert::toBullet(vertices[b] * transform), + Misc::Convert::toBullet(vertices[c] * transform) ); } else { mesh.addTriangle( - getbtVector(vertices[a] * transform), - getbtVector(vertices[c] * transform), - getbtVector(vertices[b] * transform) + Misc::Convert::toBullet(vertices[a] * transform), + Misc::Convert::toBullet(vertices[c] * transform), + Misc::Convert::toBullet(vertices[b] * transform) ); } } @@ -106,17 +102,12 @@ void fillTriangleMeshWithTransform(btTriangleMesh& mesh, const Nif::NiTriStripsD } } -void fillTriangleMeshWithTransform(btTriangleMesh& mesh, const Nif::Node* nifNode, const osg::Matrixf &transform) +void fillTriangleMesh(btTriangleMesh& mesh, const Nif::Node* nifNode, const osg::Matrixf &transform = osg::Matrixf()) { if (nifNode->recType == Nif::RC_NiTriShape) - fillTriangleMeshWithTransform(mesh, static_cast(nifNode)->data.get(), transform); - else // if (nifNode->recType == Nif::RC_NiTriStrips) - fillTriangleMeshWithTransform(mesh, static_cast(nifNode)->data.get(), transform); -} - -void fillTriangleMesh(btTriangleMesh& mesh, const Nif::Node* node) -{ - fillTriangleMeshWithTransform(mesh, node, osg::Matrixf()); + fillTriangleMesh(mesh, static_cast(nifNode)->data.get(), transform); + else if (nifNode->recType == Nif::RC_NiTriStrips) + fillTriangleMesh(mesh, static_cast(nifNode)->data.get(), transform); } } @@ -149,10 +140,12 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) if (findBoundingBox(node)) { + const btVector3 halfExtents = Misc::Convert::toBullet(mShape->mCollisionBoxHalfExtents); + const btVector3 origin = Misc::Convert::toBullet(mShape->mCollisionBoxTranslate); std::unique_ptr compound (new btCompoundShape); - std::unique_ptr boxShape(new btBoxShape(getbtVector(mShape->mCollisionBoxHalfExtents))); + std::unique_ptr boxShape(new btBoxShape(halfExtents)); btTransform transform = btTransform::getIdentity(); - transform.setOrigin(getbtVector(mShape->mCollisionBoxTranslate)); + transform.setOrigin(origin); compound->addChildShape(transform, boxShape.get()); boxShape.release(); @@ -383,7 +376,7 @@ void BulletNifLoader::handleNiTriShape(const Nif::Node *nifNode, int flags, cons if (!mAvoidStaticMesh) mAvoidStaticMesh.reset(new btTriangleMesh(false)); - fillTriangleMeshWithTransform(*mAvoidStaticMesh, nifNode, transform); + fillTriangleMesh(*mAvoidStaticMesh, nifNode, transform); } else { @@ -391,7 +384,7 @@ void BulletNifLoader::handleNiTriShape(const Nif::Node *nifNode, int flags, cons mStaticMesh.reset(new btTriangleMesh(false)); // Static shape, just transform all vertices into position - fillTriangleMeshWithTransform(*mStaticMesh, nifNode, transform); + fillTriangleMesh(*mStaticMesh, nifNode, transform); } } diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 7a592fb4a0..805283b428 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -60,6 +60,18 @@ namespace } } + bool isTypeGeometry(int type) + { + switch (type) + { + case Nif::RC_NiTriShape: + case Nif::RC_NiTriStrips: + case Nif::RC_NiLines: + return true; + } + return false; + } + // Collect all properties affecting the given drawable that should be handled on drawable basis rather than on the node hierarchy above it. void collectDrawableProperties(const Nif::Node* nifNode, std::vector& out) { @@ -528,7 +540,19 @@ namespace NifOsg // - finding a random child NiNode in NiBspArrayController node->setUserValue("recIndex", nifNode->recIndex); + std::vector extraCollection; + for (Nif::ExtraPtr e = nifNode->extra; !e.empty(); e = e->next) + extraCollection.emplace_back(e); + + for (size_t i = 0; i < nifNode->extralist.length(); ++i) + { + Nif::ExtraPtr e = nifNode->extralist[i]; + if (!e.empty()) + extraCollection.emplace_back(e); + } + + for (const auto& e : extraCollection) { if(e->recType == Nif::RC_NiTextKeyExtraData && textKeys) { @@ -584,7 +608,7 @@ namespace NifOsg applyNodeProperties(nifNode, node, composite, imageManager, boundTextures, animflags); - const bool isGeometry = nifNode->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_NiTriStrips || nifNode->recType == Nif::RC_NiLines; + const bool isGeometry = isTypeGeometry(nifNode->recType); if (isGeometry && !skipMeshes) { @@ -1175,7 +1199,7 @@ namespace NifOsg void handleGeometry(const Nif::Node* nifNode, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { - assert(nifNode->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_NiTriStrips || nifNode->recType == Nif::RC_NiLines); + assert(isTypeGeometry(nifNode->recType)); osg::ref_ptr drawable; osg::ref_ptr geom (new osg::Geometry); handleNiGeometry(nifNode, geom, parentNode, composite, boundTextures, animflags); @@ -1220,7 +1244,7 @@ namespace NifOsg void handleSkinnedGeometry(const Nif::Node *nifNode, osg::Group *parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { - assert(nifNode->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_NiTriStrips || nifNode->recType == Nif::RC_NiLines); + assert(isTypeGeometry(nifNode->recType)); osg::ref_ptr geometry (new osg::Geometry); handleNiGeometry(nifNode, geometry, parentNode, composite, boundTextures, animflags); osg::ref_ptr rig(new SceneUtil::RigGeometry); From afea11b70a851e6d56c7e8da355c533b73659b15 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sat, 7 Nov 2020 03:40:21 +0300 Subject: [PATCH 3/5] Read NiStringPalette and NiBoolData --- components/nif/data.cpp | 25 +++++++++++++++++++++++++ components/nif/data.hpp | 12 ++++++++++++ components/nif/niffile.cpp | 2 ++ components/nif/record.hpp | 4 +++- components/nif/recordptr.hpp | 2 ++ 5 files changed, 44 insertions(+), 1 deletion(-) diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 5b0b99c531..4df464fdf5 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -392,4 +392,29 @@ void NiPalette::read(NIFStream *nif) colors[i] = nif->getUInt() | alphaMask; } +void NiStringPalette::read(NIFStream *nif) +{ + unsigned int size = nif->getUInt(); + if (!size) + return; + std::vector source; + nif->getChars(source, size); + if (nif->getUInt() != size) + nif->file->warn("Failed size check in NiStringPalette"); + if (source[source.size()-1] != '\0') + source.emplace_back('\0'); + const char* buffer = source.data(); + while (static_cast(buffer - source.data()) < source.size()) + { + palette.emplace_back(buffer); + buffer += palette.back().size() + 1; + } +} + +void NiBoolData::read(NIFStream *nif) +{ + mKeyList = std::make_shared(); + mKeyList->read(nif); +} + } // Namespace diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 0ba5446459..d0ea150e0b 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -240,5 +240,17 @@ public: void read(NIFStream *nif) override; }; +struct NiStringPalette : public Record +{ + std::vector palette; + void read(NIFStream *nif) override; +}; + +struct NiBoolData : public Record +{ + ByteKeyMapPtr mKeyList; + void read(NIFStream *nif) override; +}; + } // Namespace #endif diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 098f6aba24..b50b602d57 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -116,6 +116,8 @@ static std::map makeFactory() factory["NiColorExtraData"] = {&construct , RC_NiColorExtraData }; factory["NiFloatExtraData"] = {&construct , RC_NiFloatExtraData }; factory["NiFloatsExtraData"] = {&construct , RC_NiFloatsExtraData }; + factory["NiStringPalette"] = {&construct , RC_NiStringPalette }; + factory["NiBoolData"] = {&construct , RC_NiBoolData }; return factory; } diff --git a/components/nif/record.hpp b/components/nif/record.hpp index eb26b0cce2..0e8b80ffa7 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -109,7 +109,9 @@ enum RecordType RC_NiVectorExtraData, RC_NiColorExtraData, RC_NiFloatExtraData, - RC_NiFloatsExtraData + RC_NiFloatsExtraData, + RC_NiStringPalette, + RC_NiBoolData }; /// Base class for all records diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index 57607cb6a3..d0b3d00ab8 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -143,6 +143,7 @@ class NiAutoNormalParticlesData; class NiPalette; struct NiParticleModifier; struct NiLinesData; +struct NiBoolData; using NodePtr = RecordPtrT; using ExtraPtr = RecordPtrT; @@ -166,6 +167,7 @@ using NiRotatingParticlesDataPtr = RecordPtrT; using NiAutoNormalParticlesDataPtr = RecordPtrT; using NiPalettePtr = RecordPtrT; using NiParticleModifierPtr = RecordPtrT; +using NiBoolDataPtr = RecordPtrT; using NodeList = RecordListT; using PropertyList = RecordListT; From a38c6294250b043cb6180670956e5af12ffb99fa Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sat, 7 Nov 2020 04:04:46 +0300 Subject: [PATCH 4/5] Read NiSkinPartition --- components/nif/data.cpp | 65 +++++++++++++++++++++++++++++++++++- components/nif/data.hpp | 20 +++++++++++ components/nif/niffile.cpp | 1 + components/nif/record.hpp | 3 +- components/nif/recordptr.hpp | 2 ++ 5 files changed, 89 insertions(+), 2 deletions(-) diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 4df464fdf5..6bbab97863 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -320,7 +320,7 @@ void NiSkinData::read(NIFStream *nif) int boneNum = nif->getInt(); if (nif->getVersion() >= NIFFile::NIFVersion::VER_MW && nif->getVersion() <= NIFStream::generateVersion(10,1,0,0)) - nif->skip(4); // NiSkinPartition link + partitions.read(nif); // Has vertex weights flag if (nif->getVersion() > NIFStream::generateVersion(4,2,1,0) && !nif->getBoolean()) @@ -345,6 +345,69 @@ void NiSkinData::read(NIFStream *nif) } } +void NiSkinData::post(NIFFile *nif) +{ + partitions.post(nif); +} + +void NiSkinPartition::read(NIFStream *nif) +{ + unsigned int num = nif->getUInt(); + data.resize(num); + for (auto& partition : data) + partition.read(nif); +} + +void NiSkinPartition::Partition::read(NIFStream *nif) +{ + unsigned short numVertices = nif->getUShort(); + unsigned short numTriangles = nif->getUShort(); + unsigned short numBones = nif->getUShort(); + unsigned short numStrips = nif->getUShort(); + unsigned short bonesPerVertex = nif->getUShort(); + if (numBones) + nif->getUShorts(bones, numBones); + + bool hasVertexMap = true; + if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0)) + hasVertexMap = nif->getBoolean(); + if (hasVertexMap && numVertices) + nif->getUShorts(vertexMap, numVertices); + + bool hasVertexWeights = true; + if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0)) + hasVertexWeights = nif->getBoolean(); + if (hasVertexWeights && numVertices && bonesPerVertex) + nif->getFloats(weights, numVertices * bonesPerVertex); + + std::vector stripLengths; + if (numStrips) + nif->getUShorts(stripLengths, numStrips); + + bool hasFaces = true; + if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0)) + hasFaces = nif->getBoolean(); + if (hasFaces) + { + if (numStrips) + { + strips.resize(numStrips); + for (unsigned short i = 0; i < numStrips; i++) + nif->getUShorts(strips[i], stripLengths[i]); + } + else if (numTriangles) + nif->getUShorts(triangles, numTriangles * 3); + } + bool hasBoneIndices = nif->getChar() != 0; + if (hasBoneIndices && numVertices && bonesPerVertex) + nif->getChars(boneIndices, numVertices * bonesPerVertex); + if (nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3) + { + nif->getChar(); // LOD level + nif->getBoolean(); // Global VB + } +} + void NiMorphData::read(NIFStream *nif) { int morphCount = nif->getInt(); diff --git a/components/nif/data.hpp b/components/nif/data.hpp index d0ea150e0b..4d13afb9d0 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -174,6 +174,7 @@ class NiSkinInstance : public Record { public: NiSkinDataPtr data; + NiSkinPartitionPtr partitions; NodePtr root; NodeList bones; @@ -200,6 +201,25 @@ public: Transformation trafo; std::vector bones; + NiSkinPartitionPtr partitions; + + void read(NIFStream *nif) override; + void post(NIFFile *nif) override; +}; + +struct NiSkinPartition : public Record +{ + struct Partition + { + std::vector bones; + std::vector vertexMap; + std::vector weights; + std::vector> strips; + std::vector triangles; + std::vector boneIndices; + void read(NIFStream *nif); + }; + std::vector data; void read(NIFStream *nif) override; }; diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index b50b602d57..04cac535c0 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -118,6 +118,7 @@ static std::map makeFactory() factory["NiFloatsExtraData"] = {&construct , RC_NiFloatsExtraData }; factory["NiStringPalette"] = {&construct , RC_NiStringPalette }; factory["NiBoolData"] = {&construct , RC_NiBoolData }; + factory["NiSkinPartition"] = {&construct , RC_NiSkinPartition }; return factory; } diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 0e8b80ffa7..2a6be58e22 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -111,7 +111,8 @@ enum RecordType RC_NiFloatExtraData, RC_NiFloatsExtraData, RC_NiStringPalette, - RC_NiBoolData + RC_NiBoolData, + RC_NiSkinPartition }; /// Base class for all records diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index d0b3d00ab8..2748f40736 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -144,6 +144,7 @@ class NiPalette; struct NiParticleModifier; struct NiLinesData; struct NiBoolData; +struct NiSkinPartition; using NodePtr = RecordPtrT; using ExtraPtr = RecordPtrT; @@ -168,6 +169,7 @@ using NiAutoNormalParticlesDataPtr = RecordPtrT; using NiPalettePtr = RecordPtrT; using NiParticleModifierPtr = RecordPtrT; using NiBoolDataPtr = RecordPtrT; +using NiSkinPartitionPtr = RecordPtrT; using NodeList = RecordListT; using PropertyList = RecordListT; From 96769ab4a5a1c8df30842497a2e94be7f83a780d Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sun, 8 Nov 2020 13:53:52 +0300 Subject: [PATCH 5/5] Try to reword unsupported NIF loading docs --- .../reference/modding/settings/models.rst | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/docs/source/reference/modding/settings/models.rst b/docs/source/reference/modding/settings/models.rst index da56a1709f..b0da374d6c 100644 --- a/docs/source/reference/modding/settings/models.rst +++ b/docs/source/reference/modding/settings/models.rst @@ -8,20 +8,24 @@ load unsupported nif files :Range: True/False :Default: False -At the moment OpenMW's NIF loader is only tailored to load models -that can also load in Morrowind. +Allow the engine to load arbitrary NIF files as long as they appear to be valid. -However, you can enable its limited and experimental support for updates in -the definitions of record types from later NIF revisions by toggling on -this setting. +OpenMW has limited and **experimental** support for NIF files +that Morrowind itself cannot load, which normally goes unused. -You must keep in mind that loading unsupported NIF files may fail, -and the degree of this failure may vary. In milder cases, OpenMW will reject -the file anyway because it lacks a definition for a certain record type -that the file may use. In more severe cases OpenMW's -incomplete understanding of a record type can lead to memory corruption, -crashes or even freezes. Don't enable this if you're not sure that -you know what you're doing. +If enabled, this setting allows the NIF loader to make use of that functionality. + +.. warning:: + You must keep in mind that since the mentioned support is experimental, + loading unsupported NIF files may fail, and the degree of this failure may vary. + + In milder cases, OpenMW will reject the file anyway because + it lacks a definition for a certain record type that the file may use. + + In more severe cases OpenMW's incomplete understanding of a record type + can lead to memory corruption, freezes or even crashes. + + **Do not enable** this if you're not so sure that you know what you're doing. To help debug possible issues OpenMW will log its progress in loading every file that uses an unsupported NIF version.