Merge branch 'i-have-no-land-and-i-must-scream' into 'master'

Fix(CS): Add landscape flag if it doesn't exist at all and improve landscape QOL

Closes #7707

See merge request OpenMW/openmw!3617
pull/3236/head
psi29a 7 months ago
commit c1d74763ed

@ -141,6 +141,7 @@
Bug #7676: Incorrect magic effect order in alchemy Bug #7676: Incorrect magic effect order in alchemy
Bug #7679: Scene luminance value flashes when toggling shaders Bug #7679: Scene luminance value flashes when toggling shaders
Bug #7685: Corky sometimes doesn't follow Llovyn Andus 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 #7712: Casting doesn't support spells and enchantments with no effects
Bug #7721: CS: Special Chars Not Allowed in IDs Bug #7721: CS: Special Chars Not Allowed in IDs
Bug #7723: Assaulting vampires and werewolves shouldn't be a crime Bug #7723: Assaulting vampires and werewolves shouldn't be a crime

@ -7,6 +7,7 @@
#include <components/esm3/loadcont.hpp> #include <components/esm3/loadcont.hpp>
#include <components/esm3/loadcrea.hpp> #include <components/esm3/loadcrea.hpp>
#include <components/esm3/loadench.hpp> #include <components/esm3/loadench.hpp>
#include <components/esm3/loadland.hpp>
#include <components/esm3/loadlevlist.hpp> #include <components/esm3/loadlevlist.hpp>
#include <components/esm3/loadligh.hpp> #include <components/esm3/loadligh.hpp>
#include <components/esm3/loadmgef.hpp> #include <components/esm3/loadmgef.hpp>
@ -766,18 +767,16 @@ std::string enchantmentFlags(int flags)
std::string landFlags(std::uint32_t flags) std::string landFlags(std::uint32_t flags)
{ {
std::string properties; 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) if (flags == 0)
properties += "[None] "; properties += "[None] ";
if (flags & 0x00000001) if (flags & ESM::Land::Flag_HeightsNormals)
properties += "Unknown1 "; properties += "HeightsNormals ";
if (flags & 0x00000004) if (flags & ESM::Land::Flag_Colors)
properties += "Unknown3 "; properties += "Colors ";
if (flags & 0x00000002) if (flags & ESM::Land::Flag_Textures)
properties += "Unknown2 "; properties += "Textures ";
if (flags & 0xFFFFFFF8) int unused = 0xFFFFFFFF ^ (ESM::Land::Flag_HeightsNormals | ESM::Land::Flag_Colors | ESM::Land::Flag_Textures);
if (flags & unused)
properties += "Invalid "; properties += "Invalid ";
properties += Misc::StringUtils::format("(0x%08X)", flags); properties += Misc::StringUtils::format("(0x%08X)", flags);
return properties; return properties;

@ -202,6 +202,8 @@ namespace CSMWorld
copy.getLandData()->mHeights[i] = values[i]; copy.getLandData()->mHeights[i] = values[i];
} }
copy.mFlags |= Land::Flag_HeightsNormals;
record.setModified(copy); record.setModified(copy);
} }
@ -249,6 +251,8 @@ namespace CSMWorld
copy.getLandData()->mColours[i] = values[i]; copy.getLandData()->mColours[i] = values[i];
} }
copy.mFlags |= Land::Flag_Colors;
record.setModified(copy); record.setModified(copy);
} }
@ -296,6 +300,8 @@ namespace CSMWorld
copy.getLandData()->mTextures[i] = values[i]; copy.getLandData()->mTextures[i] = values[i];
} }
copy.mFlags |= Land::Flag_Textures;
record.setModified(copy); record.setModified(copy);
} }

@ -16,6 +16,7 @@
#include <components/misc/strings/lower.hpp> #include <components/misc/strings/lower.hpp>
#include <components/terrain/terraingrid.hpp> #include <components/terrain/terraingrid.hpp>
#include "../../model/doc/document.hpp"
#include "../../model/world/idtable.hpp" #include "../../model/world/idtable.hpp"
#include "cellarrow.hpp" #include "cellarrow.hpp"
@ -31,6 +32,7 @@
#include <apps/opencs/model/world/cell.hpp> #include <apps/opencs/model/world/cell.hpp>
#include <apps/opencs/model/world/cellcoordinates.hpp> #include <apps/opencs/model/world/cellcoordinates.hpp>
#include <apps/opencs/model/world/columns.hpp> #include <apps/opencs/model/world/columns.hpp>
#include <apps/opencs/model/world/commands.hpp>
#include <apps/opencs/model/world/data.hpp> #include <apps/opencs/model/world/data.hpp>
#include <apps/opencs/model/world/idcollection.hpp> #include <apps/opencs/model/world/idcollection.hpp>
#include <apps/opencs/model/world/land.hpp> #include <apps/opencs/model/world/land.hpp>
@ -130,40 +132,31 @@ void CSVRender::Cell::updateLand()
return; return;
} }
// Setup land if available
const CSMWorld::IdCollection<CSMWorld::Land>& land = mData.getLand(); const CSMWorld::IdCollection<CSMWorld::Land>& land = mData.getLand();
int landIndex = land.searchId(mId);
if (landIndex != -1 && !land.getRecord(mId).isDeleted())
{
const ESM::Land& esmLand = land.getRecord(mId).get();
if (esmLand.getLandData(ESM::Land::DATA_VHGT)) if (land.getRecord(mId).isDeleted())
{ return;
if (mTerrain)
{
mTerrain->unloadCell(mCoordinates.getX(), mCoordinates.getY());
mTerrain->clearAssociatedCaches();
}
else
{
constexpr double expiryDelay = 0;
mTerrain = std::make_unique<Terrain::TerrainGrid>(mCellNode, mCellNode, mData.getResourceSystem().get(),
mTerrainStorage, Mask_Terrain, ESM::Cell::sDefaultWorldspaceId, expiryDelay);
}
mTerrain->loadCell(esmLand.mX, esmLand.mY); const ESM::Land& esmLand = land.getRecord(mId).get();
if (!mCellBorder) if (mTerrain)
mCellBorder = std::make_unique<CellBorder>(mCellNode, mCoordinates); {
mTerrain->unloadCell(mCoordinates.getX(), mCoordinates.getY());
mTerrain->clearAssociatedCaches();
}
else
{
constexpr double expiryDelay = 0;
mTerrain = std::make_unique<Terrain::TerrainGrid>(mCellNode, mCellNode, mData.getResourceSystem().get(),
mTerrainStorage, Mask_Terrain, ESM::Cell::sDefaultWorldspaceId, expiryDelay);
}
mCellBorder->buildShape(esmLand); mTerrain->loadCell(esmLand.mX, esmLand.mY);
return; if (!mCellBorder)
} mCellBorder = std::make_unique<CellBorder>(mCellNode, mCoordinates);
}
// No land data mCellBorder->buildShape(esmLand);
unloadLand();
} }
void CSVRender::Cell::unloadLand() void CSVRender::Cell::unloadLand()
@ -175,13 +168,14 @@ void CSVRender::Cell::unloadLand()
mCellBorder.reset(); mCellBorder.reset();
} }
CSVRender::Cell::Cell(CSMWorld::Data& data, osg::Group* rootNode, const std::string& id, bool deleted) CSVRender::Cell::Cell(
: mData(data) CSMDoc::Document& document, osg::Group* rootNode, const std::string& id, bool deleted, bool isExterior)
: mData(document.getData())
, mId(ESM::RefId::stringRefId(id)) , mId(ESM::RefId::stringRefId(id))
, mDeleted(deleted) , mDeleted(deleted)
, mSubMode(0) , mSubMode(0)
, mSubModeElementMask(0) , mSubModeElementMask(0)
, mUpdateLand(true) , mUpdateLand(isExterior)
, mLandDeleted(false) , mLandDeleted(false)
{ {
std::pair<CSMWorld::CellCoordinates, bool> result = CSMWorld::CellCoordinates::fromId(id); std::pair<CSMWorld::CellCoordinates, bool> 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); addObjects(0, rows - 1);
updateLand(); if (mUpdateLand)
{
int landIndex = document.getData().getLand().searchId(mId);
if (landIndex == -1)
{
CSMWorld::IdTable& landTable
= dynamic_cast<CSMWorld::IdTable&>(*mData.getTableModel(CSMWorld::UniversalId::Type_Land));
document.getUndoStack().push(new CSMWorld::CreateCommand(landTable, mId.getRefIdString()));
}
updateLand();
}
mPathgrid = std::make_unique<Pathgrid>(mData, mCellNode, mId.getRefIdString(), mCoordinates); mPathgrid = std::make_unique<Pathgrid>(mData, mCellNode, mId.getRefIdString(), mCoordinates);
mCellWater = std::make_unique<CellWater>(mData, mCellNode, mId.getRefIdString(), mCoordinates); mCellWater = std::make_unique<CellWater>(mData, mCellNode, mId.getRefIdString(), mCoordinates);

@ -9,6 +9,7 @@
#include <osg/Vec3d> #include <osg/Vec3d>
#include <osg/ref_ptr> #include <osg/ref_ptr>
#include "../../model/doc/document.hpp"
#include "../../model/world/cellcoordinates.hpp" #include "../../model/world/cellcoordinates.hpp"
#include "instancedragmodes.hpp" #include "instancedragmodes.hpp"
#include <components/esm/refid.hpp> #include <components/esm/refid.hpp>
@ -89,7 +90,8 @@ namespace CSVRender
public: public:
/// \note Deleted covers both cells that are deleted and cells that don't exist in /// \note Deleted covers both cells that are deleted and cells that don't exist in
/// the first place. /// 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(); ~Cell();

@ -47,9 +47,6 @@ void CSVRender::CellBorder::buildShape(const ESM::Land& esmLand)
{ {
const ESM::Land::LandData* landData = esmLand.getLandData(ESM::Land::DATA_VHGT); const ESM::Land::LandData* landData = esmLand.getLandData(ESM::Land::DATA_VHGT);
if (!landData)
return;
mBaseNode->removeChild(mBorderGeometry); mBaseNode->removeChild(mBorderGeometry);
mBorderGeometry = new osg::Geometry(); 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). 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. Each loop starts at a corner vertex and ends right before the next corner vertex.
*/ */
for (; x < ESM::Land::LAND_SIZE - 1; ++x) if (landData)
vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), landData->mHeights[landIndex(x, y)])); {
for (; x < ESM::Land::LAND_SIZE - 1; ++x)
x = ESM::Land::LAND_SIZE - 1; vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), landData->mHeights[landIndex(x, y)]));
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)
y = ESM::Land::LAND_SIZE - 1; vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), landData->mHeights[landIndex(x, y)]));
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)
x = 0; vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), landData->mHeights[landIndex(x, y)]));
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); mBorderGeometry->setVertexArray(vertices);

@ -86,8 +86,8 @@ bool CSVRender::PagedWorldspaceWidget::adjustCells()
{ {
modified = true; modified = true;
auto cell = std::make_unique<Cell>( auto cell
mDocument.getData(), mRootNode, iter->first.getId(mWorldspace), deleted); = std::make_unique<Cell>(mDocument, mRootNode, iter->first.getId(mWorldspace), deleted, true);
delete iter->second; delete iter->second;
iter->second = cell.release(); 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; bool deleted = index == -1 || cells.getRecord(index).mState == CSMWorld::RecordBase::State_Deleted;
auto cell = std::make_unique<Cell>(mDocument.getData(), mRootNode, coordinates.getId(mWorldspace), deleted); auto cell = std::make_unique<Cell>(mDocument, mRootNode, coordinates.getId(mWorldspace), deleted, true);
EditMode* editMode = getEditMode(); EditMode* editMode = getEditMode();
cell->setSubMode(editMode->getSubMode(), editMode->getInteractionMask()); cell->setSubMode(editMode->getSubMode(), editMode->getInteractionMask());

@ -154,6 +154,8 @@ namespace CSVRender
void TerrainStorage::adjustColor(int col, int row, const ESM::LandData* heightData, osg::Vec4ub& color) const void TerrainStorage::adjustColor(int col, int row, const ESM::LandData* heightData, osg::Vec4ub& color) const
{ {
if (!heightData)
return;
// Highlight broken height changes // Highlight broken height changes
int heightWarningLimit = 1024; int heightWarningLimit = 1024;
if (((col > 0 && row > 0) && leftOrUpIsOverTheLimit(col, row, heightWarningLimit, heightData->getHeights())) if (((col > 0 && row > 0) && leftOrUpIsOverTheLimit(col, row, heightWarningLimit, heightData->getHeights()))

@ -79,7 +79,7 @@ CSVRender::UnpagedWorldspaceWidget::UnpagedWorldspaceWidget(
update(); update();
mCell = std::make_unique<Cell>(document.getData(), mRootNode, mCellId); mCell = std::make_unique<Cell>(document, mRootNode, mCellId);
} }
void CSVRender::UnpagedWorldspaceWidget::cellDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) void CSVRender::UnpagedWorldspaceWidget::cellDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight)
@ -127,7 +127,7 @@ bool CSVRender::UnpagedWorldspaceWidget::handleDrop(
mCellId = universalIdData.begin()->getId(); mCellId = universalIdData.begin()->getId();
mCell = std::make_unique<Cell>(getDocument().getData(), mRootNode, mCellId); mCell = std::make_unique<Cell>(getDocument(), mRootNode, mCellId);
mCamPositionSet = false; mCamPositionSet = false;
mOrbitCamControl->reset(); mOrbitCamControl->reset();

@ -101,23 +101,28 @@ namespace ESM
{ {
case fourCC("VNML"): case fourCC("VNML"):
esm.skipHSub(); esm.skipHSub();
mDataTypes |= DATA_VNML; if (mFlags & Flag_HeightsNormals)
mDataTypes |= DATA_VNML;
break; break;
case fourCC("VHGT"): case fourCC("VHGT"):
esm.skipHSub(); esm.skipHSub();
mDataTypes |= DATA_VHGT; if (mFlags & Flag_HeightsNormals)
mDataTypes |= DATA_VHGT;
break; break;
case fourCC("WNAM"): case fourCC("WNAM"):
esm.getHT(mWnam); esm.getHT(mWnam);
mDataTypes |= DATA_WNAM; if (mFlags & Flag_HeightsNormals)
mDataTypes |= DATA_WNAM;
break; break;
case fourCC("VCLR"): case fourCC("VCLR"):
esm.skipHSub(); esm.skipHSub();
mDataTypes |= DATA_VCLR; if (mFlags & Flag_Colors)
mDataTypes |= DATA_VCLR;
break; break;
case fourCC("VTEX"): case fourCC("VTEX"):
esm.skipHSub(); esm.skipHSub();
mDataTypes |= DATA_VTEX; if (mFlags & Flag_Textures)
mDataTypes |= DATA_VTEX;
break; break;
default: default:
esm.fail("Unknown subrecord"); esm.fail("Unknown subrecord");
@ -204,9 +209,9 @@ namespace ESM
if (mLandData == nullptr) if (mLandData == nullptr)
mLandData = std::make_unique<LandData>(); mLandData = std::make_unique<LandData>();
mLandData->mHeights.fill(0); mLandData->mHeights.fill(DEFAULT_HEIGHT);
mLandData->mMinHeight = 0; mLandData->mMinHeight = DEFAULT_HEIGHT;
mLandData->mMaxHeight = 0; mLandData->mMaxHeight = DEFAULT_HEIGHT;
for (size_t i = 0; i < LandRecordData::sLandNumVerts; ++i) for (size_t i = 0; i < LandRecordData::sLandNumVerts; ++i)
{ {
mLandData->mNormals[i * 3 + 0] = 0; mLandData->mNormals[i * 3 + 0] = 0;

@ -66,6 +66,13 @@ namespace ESM
DATA_VTEX = 16 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 // default height to use in case there is no Land record
static constexpr int DEFAULT_HEIGHT = -2048; static constexpr int DEFAULT_HEIGHT = -2048;

Loading…
Cancel
Save