diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a0fcf8d0b..3b1a590d4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -141,6 +141,7 @@ Bug #7676: Incorrect magic effect order in alchemy Bug #7679: Scene luminance value flashes when toggling shaders Bug #7685: Corky sometimes doesn't follow Llovyn Andus + Bug #7707: (OpenCS): New landscape records do not contain appropriate flags Bug #7712: Casting doesn't support spells and enchantments with no effects Bug #7721: CS: Special Chars Not Allowed in IDs Bug #7723: Assaulting vampires and werewolves shouldn't be a crime diff --git a/apps/esmtool/labels.cpp b/apps/esmtool/labels.cpp index 83b4a486e1..3d64563923 100644 --- a/apps/esmtool/labels.cpp +++ b/apps/esmtool/labels.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -766,18 +767,16 @@ std::string enchantmentFlags(int flags) std::string landFlags(std::uint32_t flags) { std::string properties; - // The ESM component says that this first four bits are used, but - // only the first three bits are used as far as I can tell. - // There's also no enumeration of the bit in the ESM component. if (flags == 0) properties += "[None] "; - if (flags & 0x00000001) - properties += "Unknown1 "; - if (flags & 0x00000004) - properties += "Unknown3 "; - if (flags & 0x00000002) - properties += "Unknown2 "; - if (flags & 0xFFFFFFF8) + if (flags & ESM::Land::Flag_HeightsNormals) + properties += "HeightsNormals "; + if (flags & ESM::Land::Flag_Colors) + properties += "Colors "; + if (flags & ESM::Land::Flag_Textures) + properties += "Textures "; + int unused = 0xFFFFFFFF ^ (ESM::Land::Flag_HeightsNormals | ESM::Land::Flag_Colors | ESM::Land::Flag_Textures); + if (flags & unused) properties += "Invalid "; properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; diff --git a/apps/opencs/model/world/columnimp.cpp b/apps/opencs/model/world/columnimp.cpp index 215e4c3dfc..a97fc85ec7 100644 --- a/apps/opencs/model/world/columnimp.cpp +++ b/apps/opencs/model/world/columnimp.cpp @@ -202,6 +202,8 @@ namespace CSMWorld copy.getLandData()->mHeights[i] = values[i]; } + copy.mFlags |= Land::Flag_HeightsNormals; + record.setModified(copy); } @@ -249,6 +251,8 @@ namespace CSMWorld copy.getLandData()->mColours[i] = values[i]; } + copy.mFlags |= Land::Flag_Colors; + record.setModified(copy); } @@ -296,6 +300,8 @@ namespace CSMWorld copy.getLandData()->mTextures[i] = values[i]; } + copy.mFlags |= Land::Flag_Textures; + record.setModified(copy); } diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index f2c851cc72..3d3b82acf8 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -16,6 +16,7 @@ #include #include +#include "../../model/doc/document.hpp" #include "../../model/world/idtable.hpp" #include "cellarrow.hpp" @@ -31,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -130,40 +132,31 @@ void CSVRender::Cell::updateLand() return; } - // Setup land if available const CSMWorld::IdCollection& land = mData.getLand(); - int landIndex = land.searchId(mId); - if (landIndex != -1 && !land.getRecord(mId).isDeleted()) + + if (land.getRecord(mId).isDeleted()) + return; + + const ESM::Land& esmLand = land.getRecord(mId).get(); + + if (mTerrain) { - const ESM::Land& esmLand = land.getRecord(mId).get(); - - if (esmLand.getLandData(ESM::Land::DATA_VHGT)) - { - if (mTerrain) - { - mTerrain->unloadCell(mCoordinates.getX(), mCoordinates.getY()); - mTerrain->clearAssociatedCaches(); - } - else - { - constexpr double expiryDelay = 0; - mTerrain = std::make_unique(mCellNode, mCellNode, mData.getResourceSystem().get(), - mTerrainStorage, Mask_Terrain, ESM::Cell::sDefaultWorldspaceId, expiryDelay); - } - - mTerrain->loadCell(esmLand.mX, esmLand.mY); - - if (!mCellBorder) - mCellBorder = std::make_unique(mCellNode, mCoordinates); - - mCellBorder->buildShape(esmLand); - - return; - } + mTerrain->unloadCell(mCoordinates.getX(), mCoordinates.getY()); + mTerrain->clearAssociatedCaches(); + } + else + { + constexpr double expiryDelay = 0; + mTerrain = std::make_unique(mCellNode, mCellNode, mData.getResourceSystem().get(), + mTerrainStorage, Mask_Terrain, ESM::Cell::sDefaultWorldspaceId, expiryDelay); } - // No land data - unloadLand(); + mTerrain->loadCell(esmLand.mX, esmLand.mY); + + if (!mCellBorder) + mCellBorder = std::make_unique(mCellNode, mCoordinates); + + mCellBorder->buildShape(esmLand); } void CSVRender::Cell::unloadLand() @@ -175,13 +168,14 @@ void CSVRender::Cell::unloadLand() mCellBorder.reset(); } -CSVRender::Cell::Cell(CSMWorld::Data& data, osg::Group* rootNode, const std::string& id, bool deleted) - : mData(data) +CSVRender::Cell::Cell( + CSMDoc::Document& document, osg::Group* rootNode, const std::string& id, bool deleted, bool isExterior) + : mData(document.getData()) , mId(ESM::RefId::stringRefId(id)) , mDeleted(deleted) , mSubMode(0) , mSubModeElementMask(0) - , mUpdateLand(true) + , mUpdateLand(isExterior) , mLandDeleted(false) { std::pair result = CSMWorld::CellCoordinates::fromId(id); @@ -207,7 +201,17 @@ CSVRender::Cell::Cell(CSMWorld::Data& data, osg::Group* rootNode, const std::str addObjects(0, rows - 1); - updateLand(); + if (mUpdateLand) + { + int landIndex = document.getData().getLand().searchId(mId); + if (landIndex == -1) + { + CSMWorld::IdTable& landTable + = dynamic_cast(*mData.getTableModel(CSMWorld::UniversalId::Type_Land)); + document.getUndoStack().push(new CSMWorld::CreateCommand(landTable, mId.getRefIdString())); + } + updateLand(); + } mPathgrid = std::make_unique(mData, mCellNode, mId.getRefIdString(), mCoordinates); mCellWater = std::make_unique(mData, mCellNode, mId.getRefIdString(), mCoordinates); diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index 5bfce47904..5ec8d87c33 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -9,6 +9,7 @@ #include #include +#include "../../model/doc/document.hpp" #include "../../model/world/cellcoordinates.hpp" #include "instancedragmodes.hpp" #include @@ -89,7 +90,8 @@ namespace CSVRender public: /// \note Deleted covers both cells that are deleted and cells that don't exist in /// the first place. - Cell(CSMWorld::Data& data, osg::Group* rootNode, const std::string& id, bool deleted = false); + Cell(CSMDoc::Document& document, osg::Group* rootNode, const std::string& id, bool deleted = false, + bool isExterior = false); ~Cell(); diff --git a/apps/opencs/view/render/cellborder.cpp b/apps/opencs/view/render/cellborder.cpp index f63814dcb7..704db13e89 100644 --- a/apps/opencs/view/render/cellborder.cpp +++ b/apps/opencs/view/render/cellborder.cpp @@ -47,9 +47,6 @@ void CSVRender::CellBorder::buildShape(const ESM::Land& esmLand) { const ESM::Land::LandData* landData = esmLand.getLandData(ESM::Land::DATA_VHGT); - if (!landData) - return; - mBaseNode->removeChild(mBorderGeometry); mBorderGeometry = new osg::Geometry(); @@ -62,20 +59,40 @@ void CSVRender::CellBorder::buildShape(const ESM::Land& esmLand) Traverse the cell border counter-clockwise starting at the SW corner vertex (0, 0). Each loop starts at a corner vertex and ends right before the next corner vertex. */ - for (; x < ESM::Land::LAND_SIZE - 1; ++x) - vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), landData->mHeights[landIndex(x, y)])); + if (landData) + { + for (; x < ESM::Land::LAND_SIZE - 1; ++x) + vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), landData->mHeights[landIndex(x, y)])); - x = ESM::Land::LAND_SIZE - 1; - for (; y < ESM::Land::LAND_SIZE - 1; ++y) - vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), landData->mHeights[landIndex(x, y)])); + x = ESM::Land::LAND_SIZE - 1; + for (; y < ESM::Land::LAND_SIZE - 1; ++y) + vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), landData->mHeights[landIndex(x, y)])); - y = ESM::Land::LAND_SIZE - 1; - for (; x > 0; --x) - vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), landData->mHeights[landIndex(x, y)])); + y = ESM::Land::LAND_SIZE - 1; + for (; x > 0; --x) + vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), landData->mHeights[landIndex(x, y)])); - x = 0; - for (; y > 0; --y) - vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), landData->mHeights[landIndex(x, y)])); + x = 0; + for (; y > 0; --y) + vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), landData->mHeights[landIndex(x, y)])); + } + else + { + for (; x < ESM::Land::LAND_SIZE - 1; ++x) + vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), ESM::Land::DEFAULT_HEIGHT)); + + x = ESM::Land::LAND_SIZE - 1; + for (; y < ESM::Land::LAND_SIZE - 1; ++y) + vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), ESM::Land::DEFAULT_HEIGHT)); + + y = ESM::Land::LAND_SIZE - 1; + for (; x > 0; --x) + vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), ESM::Land::DEFAULT_HEIGHT)); + + x = 0; + for (; y > 0; --y) + vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), ESM::Land::DEFAULT_HEIGHT)); + } mBorderGeometry->setVertexArray(vertices); diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index 062882bd04..3fd35b7740 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -86,8 +86,8 @@ bool CSVRender::PagedWorldspaceWidget::adjustCells() { modified = true; - auto cell = std::make_unique( - mDocument.getData(), mRootNode, iter->first.getId(mWorldspace), deleted); + auto cell + = std::make_unique(mDocument, mRootNode, iter->first.getId(mWorldspace), deleted, true); delete iter->second; iter->second = cell.release(); @@ -465,7 +465,7 @@ void CSVRender::PagedWorldspaceWidget::addCellToScene(const CSMWorld::CellCoordi bool deleted = index == -1 || cells.getRecord(index).mState == CSMWorld::RecordBase::State_Deleted; - auto cell = std::make_unique(mDocument.getData(), mRootNode, coordinates.getId(mWorldspace), deleted); + auto cell = std::make_unique(mDocument, mRootNode, coordinates.getId(mWorldspace), deleted, true); EditMode* editMode = getEditMode(); cell->setSubMode(editMode->getSubMode(), editMode->getInteractionMask()); diff --git a/apps/opencs/view/render/terrainstorage.cpp b/apps/opencs/view/render/terrainstorage.cpp index ff9a8e09b1..7b59310bcd 100644 --- a/apps/opencs/view/render/terrainstorage.cpp +++ b/apps/opencs/view/render/terrainstorage.cpp @@ -154,6 +154,8 @@ namespace CSVRender void TerrainStorage::adjustColor(int col, int row, const ESM::LandData* heightData, osg::Vec4ub& color) const { + if (!heightData) + return; // Highlight broken height changes int heightWarningLimit = 1024; if (((col > 0 && row > 0) && leftOrUpIsOverTheLimit(col, row, heightWarningLimit, heightData->getHeights())) diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index fee43b5de5..a7d8af0a62 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -79,7 +79,7 @@ CSVRender::UnpagedWorldspaceWidget::UnpagedWorldspaceWidget( update(); - mCell = std::make_unique(document.getData(), mRootNode, mCellId); + mCell = std::make_unique(document, mRootNode, mCellId); } void CSVRender::UnpagedWorldspaceWidget::cellDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) @@ -127,7 +127,7 @@ bool CSVRender::UnpagedWorldspaceWidget::handleDrop( mCellId = universalIdData.begin()->getId(); - mCell = std::make_unique(getDocument().getData(), mRootNode, mCellId); + mCell = std::make_unique(getDocument(), mRootNode, mCellId); mCamPositionSet = false; mOrbitCamControl->reset(); diff --git a/components/esm3/loadland.cpp b/components/esm3/loadland.cpp index da3cb58344..7ab14cc304 100644 --- a/components/esm3/loadland.cpp +++ b/components/esm3/loadland.cpp @@ -101,23 +101,28 @@ namespace ESM { case fourCC("VNML"): esm.skipHSub(); - mDataTypes |= DATA_VNML; + if (mFlags & Flag_HeightsNormals) + mDataTypes |= DATA_VNML; break; case fourCC("VHGT"): esm.skipHSub(); - mDataTypes |= DATA_VHGT; + if (mFlags & Flag_HeightsNormals) + mDataTypes |= DATA_VHGT; break; case fourCC("WNAM"): esm.getHT(mWnam); - mDataTypes |= DATA_WNAM; + if (mFlags & Flag_HeightsNormals) + mDataTypes |= DATA_WNAM; break; case fourCC("VCLR"): esm.skipHSub(); - mDataTypes |= DATA_VCLR; + if (mFlags & Flag_Colors) + mDataTypes |= DATA_VCLR; break; case fourCC("VTEX"): esm.skipHSub(); - mDataTypes |= DATA_VTEX; + if (mFlags & Flag_Textures) + mDataTypes |= DATA_VTEX; break; default: esm.fail("Unknown subrecord"); @@ -204,9 +209,9 @@ namespace ESM if (mLandData == nullptr) mLandData = std::make_unique(); - mLandData->mHeights.fill(0); - mLandData->mMinHeight = 0; - mLandData->mMaxHeight = 0; + mLandData->mHeights.fill(DEFAULT_HEIGHT); + mLandData->mMinHeight = DEFAULT_HEIGHT; + mLandData->mMaxHeight = DEFAULT_HEIGHT; for (size_t i = 0; i < LandRecordData::sLandNumVerts; ++i) { mLandData->mNormals[i * 3 + 0] = 0; diff --git a/components/esm3/loadland.hpp b/components/esm3/loadland.hpp index 1e0ec81ecf..f6a25c1ecb 100644 --- a/components/esm3/loadland.hpp +++ b/components/esm3/loadland.hpp @@ -66,6 +66,13 @@ namespace ESM DATA_VTEX = 16 }; + enum Flags + { + Flag_HeightsNormals = 0x1, + Flag_Colors = 0x2, + Flag_Textures = 0x4 + }; + // default height to use in case there is no Land record static constexpr int DEFAULT_HEIGHT = -2048;