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 #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

@ -7,6 +7,7 @@
#include <components/esm3/loadcont.hpp>
#include <components/esm3/loadcrea.hpp>
#include <components/esm3/loadench.hpp>
#include <components/esm3/loadland.hpp>
#include <components/esm3/loadlevlist.hpp>
#include <components/esm3/loadligh.hpp>
#include <components/esm3/loadmgef.hpp>
@ -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;

@ -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);
}

@ -16,6 +16,7 @@
#include <components/misc/strings/lower.hpp>
#include <components/terrain/terraingrid.hpp>
#include "../../model/doc/document.hpp"
#include "../../model/world/idtable.hpp"
#include "cellarrow.hpp"
@ -31,6 +32,7 @@
#include <apps/opencs/model/world/cell.hpp>
#include <apps/opencs/model/world/cellcoordinates.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/idcollection.hpp>
#include <apps/opencs/model/world/land.hpp>
@ -130,40 +132,31 @@ void CSVRender::Cell::updateLand()
return;
}
// Setup land if available
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 (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);
}
if (land.getRecord(mId).isDeleted())
return;
mTerrain->loadCell(esmLand.mX, esmLand.mY);
const ESM::Land& esmLand = land.getRecord(mId).get();
if (!mCellBorder)
mCellBorder = std::make_unique<CellBorder>(mCellNode, mCoordinates);
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);
}
mCellBorder->buildShape(esmLand);
mTerrain->loadCell(esmLand.mX, esmLand.mY);
return;
}
}
if (!mCellBorder)
mCellBorder = std::make_unique<CellBorder>(mCellNode, mCoordinates);
// No land data
unloadLand();
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<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);
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);
mCellWater = std::make_unique<CellWater>(mData, mCellNode, mId.getRefIdString(), mCoordinates);

@ -9,6 +9,7 @@
#include <osg/Vec3d>
#include <osg/ref_ptr>
#include "../../model/doc/document.hpp"
#include "../../model/world/cellcoordinates.hpp"
#include "instancedragmodes.hpp"
#include <components/esm/refid.hpp>
@ -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();

@ -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)]));
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)]));
x = 0;
for (; y > 0; --y)
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)]));
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)]));
}
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);

@ -86,8 +86,8 @@ bool CSVRender::PagedWorldspaceWidget::adjustCells()
{
modified = true;
auto cell = std::make_unique<Cell>(
mDocument.getData(), mRootNode, iter->first.getId(mWorldspace), deleted);
auto cell
= std::make_unique<Cell>(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<Cell>(mDocument.getData(), mRootNode, coordinates.getId(mWorldspace), deleted);
auto cell = std::make_unique<Cell>(mDocument, mRootNode, coordinates.getId(mWorldspace), deleted, true);
EditMode* editMode = getEditMode();
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
{
if (!heightData)
return;
// Highlight broken height changes
int heightWarningLimit = 1024;
if (((col > 0 && row > 0) && leftOrUpIsOverTheLimit(col, row, heightWarningLimit, heightData->getHeights()))

@ -79,7 +79,7 @@ CSVRender::UnpagedWorldspaceWidget::UnpagedWorldspaceWidget(
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)
@ -127,7 +127,7 @@ bool CSVRender::UnpagedWorldspaceWidget::handleDrop(
mCellId = universalIdData.begin()->getId();
mCell = std::make_unique<Cell>(getDocument().getData(), mRootNode, mCellId);
mCell = std::make_unique<Cell>(getDocument(), mRootNode, mCellId);
mCamPositionSet = false;
mOrbitCamControl->reset();

@ -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<LandData>();
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;

@ -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;

Loading…
Cancel
Save