Merge branch 'cell-refId' into 'master'

Give ESM3 cells a RefId

See merge request OpenMW/openmw!2752
depth-refraction
psi29a 2 years ago
commit a9c7354338

@ -220,7 +220,7 @@ namespace EsmTool
{
void CellState::load(ESM::ESMReader& reader, bool& deleted)
{
mCellState.mId.load(reader);
mCellState.mId = reader.getCellId();
mCellState.load(reader);
if (mCellState.mHasFogOfWar)
mFogState.load(reader);
@ -1358,11 +1358,8 @@ namespace EsmTool
void Record<CellState>::print()
{
std::cout << " Id:" << std::endl;
std::cout << " Worldspace: " << mData.mCellState.mId.mWorldspace << std::endl;
std::cout << " CellId: " << mData.mCellState.mId << std::endl;
std::cout << " Index:" << std::endl;
std::cout << " X: " << mData.mCellState.mId.mIndex.mX << std::endl;
std::cout << " Y: " << mData.mCellState.mId.mIndex.mY << std::endl;
std::cout << " Paged: " << mData.mCellState.mId.mPaged << std::endl;
std::cout << " WaterLevel: " << mData.mCellState.mWaterLevel << std::endl;
std::cout << " HasFogOfWar: " << mData.mCellState.mHasFogOfWar << std::endl;
std::cout << " LastRespawn:" << std::endl;
@ -1420,8 +1417,7 @@ namespace EsmTool
std::string Record<CellState>::getId() const
{
std::ostringstream stream;
stream << mData.mCellState.mId.mWorldspace << " " << mData.mCellState.mId.mIndex.mX << " "
<< mData.mCellState.mId.mIndex.mY << " " << mData.mCellState.mId.mPaged;
stream << mData.mCellState.mId;
return stream.str();
}

@ -204,7 +204,7 @@ namespace ESSImport
// note if the player is in a nameless exterior cell, we will assign the cellId later based on player position
if (Misc::StringUtils::ciEqual(cell.mName, mContext->mPlayerCellName))
{
mContext->mPlayer.mCellId = cell.getCellId();
mContext->mPlayer.mCellId = cell.mId;
}
Cell newcell;
@ -301,7 +301,7 @@ namespace ESSImport
marker.mWorldX = notepos[0];
marker.mWorldY = notepos[1];
marker.mNote = note;
marker.mCell = cell.getCellId();
marker.mCell = cell.mId;
mMarkers.push_back(marker);
}
@ -321,8 +321,9 @@ namespace ESSImport
csta.mHasFogOfWar = 0;
csta.mLastRespawn.mDay = 0;
csta.mLastRespawn.mHour = 0;
csta.mId = esmcell.getCellId();
csta.mId.save(esm);
csta.mId = esmcell.mId;
csta.mIsInterior = !esmcell.isExterior();
esm.writeCellId(csta.mId);
// TODO csta.mLastRespawn;
// shouldn't be needed if we respawn on global schedule like in original MW
csta.mWaterLevel = esmcell.mWater;

@ -2,6 +2,7 @@
#include <cmath>
#include <components/esm3/loadcell.hpp>
#include <components/misc/constants.hpp>
#include <components/misc/strings/lower.hpp>
@ -60,19 +61,9 @@ namespace ESSImport
const PCDT::PNAM::MarkLocation& mark = pcdt.mPNAM.mMarkLocation;
ESM::CellId cell;
cell.mWorldspace = ESM::CellId::sDefaultWorldspace;
cell.mPaged = true;
cell.mIndex.mX = mark.mCellX;
cell.mIndex.mY = mark.mCellY;
// TODO: Figure out a better way to detect interiors. (0, 0) is a valid exterior cell.
if (mark.mCellX == 0 && mark.mCellY == 0)
{
cell.mWorldspace = pcdt.mMNAM;
cell.mPaged = false;
}
bool interior = mark.mCellX == 0 && mark.mCellY == 0;
ESM::RefId cell = ESM::Cell::generateIdForCell(!interior, pcdt.mMNAM, mark.mCellX, mark.mCellY);
out.mMarkedCell = cell;
out.mMarkedPosition.pos[0] = mark.mX;

@ -14,6 +14,7 @@
#include <components/esm3/player.hpp>
#include <components/esm3/savedgame.hpp>
#include <components/esm3/cellid.hpp>
#include <components/esm3/loadalch.hpp>
#include <components/esm3/loadarmo.hpp>
#include <components/esm3/loadclot.hpp>
@ -409,16 +410,11 @@ namespace ESSImport
}
writer.startRecord(ESM::REC_PLAY);
if (context.mPlayer.mCellId.mPaged)
{
// exterior cell -> determine cell coordinates based on position
int cellX
= static_cast<int>(std::floor(context.mPlayer.mObject.mPosition.pos[0] / Constants::CellSizeInUnits));
int cellY
= static_cast<int>(std::floor(context.mPlayer.mObject.mPosition.pos[1] / Constants::CellSizeInUnits));
context.mPlayer.mCellId.mIndex.mX = cellX;
context.mPlayer.mCellId.mIndex.mY = cellY;
}
ESM::CellId cellId = ESM::CellId::extractFromRefId(context.mPlayer.mCellId);
int cellX = static_cast<int>(std::floor(context.mPlayer.mObject.mPosition.pos[0] / Constants::CellSizeInUnits));
int cellY = static_cast<int>(std::floor(context.mPlayer.mObject.mPosition.pos[1] / Constants::CellSizeInUnits));
context.mPlayer.mCellId = ESM::Cell::generateIdForCell(cellId.mPaged, cellId.mWorldspace, cellX, cellY);
context.mPlayer.save(writer);
writer.endRecord(ESM::REC_PLAY);

@ -6,6 +6,7 @@
#include <components/esm3/controlsstate.hpp>
#include <components/esm3/dialoguestate.hpp>
#include <components/esm3/globalmap.hpp>
#include <components/esm3/loadcell.hpp>
#include <components/esm3/loadcrea.hpp>
#include <components/esm3/loadnpc.hpp>
#include <components/esm3/player.hpp>
@ -59,10 +60,7 @@ namespace ESSImport
, mHour(0.f)
, mNextActorId(0)
{
ESM::CellId playerCellId;
playerCellId.mPaged = true;
playerCellId.mIndex.mX = playerCellId.mIndex.mY = 0;
mPlayer.mCellId = playerCellId;
mPlayer.mCellId = ESM::RefId::esm3ExteriorCell(0, 0);
mPlayer.mLastKnownExteriorPosition[0] = mPlayer.mLastKnownExteriorPosition[1]
= mPlayer.mLastKnownExteriorPosition[2] = 0.0f;
mPlayer.mHasMark = 0;

@ -263,16 +263,18 @@ namespace NavMeshTool
const osg::Vec2i cellPosition(cell.mData.mX, cell.mData.mY);
const std::size_t cellObjectsBegin = data.mObjects.size();
const auto cellNameLowerCase = Misc::StringUtils::lowerCase(cell.mCellId.mWorldspace);
const auto cellWorldspace = Misc::StringUtils::lowerCase(
(cell.isExterior() ? ESM::RefId::stringRefId(ESM::Cell::sDefaultWorldspace) : cell.mId)
.serializeText());
WorldspaceNavMeshInput& navMeshInput = [&]() -> WorldspaceNavMeshInput& {
auto it = navMeshInputs.find(cellNameLowerCase);
auto it = navMeshInputs.find(cellWorldspace);
if (it == navMeshInputs.end())
{
it = navMeshInputs
.emplace(cellNameLowerCase,
std::make_unique<WorldspaceNavMeshInput>(cellNameLowerCase, settings.mRecast))
.emplace(cellWorldspace,
std::make_unique<WorldspaceNavMeshInput>(cellWorldspace, settings.mRecast))
.first;
it->second->mTileCachedRecastMeshManager.setWorldspace(cellNameLowerCase, nullptr);
it->second->mTileCachedRecastMeshManager.setWorldspace(cellWorldspace, nullptr);
}
return *it->second;
}();

@ -16,7 +16,7 @@
#include <apps/opencs/model/world/record.hpp>
#include <apps/opencs/model/world/universalid.hpp>
#include <components/esm3/cellid.hpp>
#include <components/esm3/loadcell.hpp>
#include <components/misc/strings/lower.hpp>
#include "collectionbase.hpp"
@ -337,7 +337,7 @@ std::pair<CSMWorld::UniversalId, std::string> CSMWorld::IdTable::view(int row) c
return std::make_pair(UniversalId::Type_None, "");
if (id[0] == '#')
id = ESM::CellId::sDefaultWorldspace;
id = ESM::Cell::sDefaultWorldspace;
return std::make_pair(UniversalId(UniversalId::Type_Scene, id), hint);
}

@ -53,14 +53,17 @@ void CSMWorld::RefCollection::load(ESM::ESMReader& reader, int cellIndex, bool b
Cell& cell2 = base ? cell.mBase : cell.mModified;
CellRef ref;
ref.mNew = false;
ESM::MovedCellRef mref;
bool isDeleted = false;
bool isMoved = false;
while (ESM::Cell::getNextRef(reader, ref, isDeleted, mref, isMoved))
while (true)
{
CellRef ref;
ref.mNew = false;
if (!ESM::Cell::getNextRef(reader, ref, isDeleted, mref, isMoved))
break;
// Keep mOriginalCell empty when in modified (as an indicator that the
// original cell will always be equal the current cell).
ref.mOriginalCell = base ? cell2.mId : ESM::RefId();
@ -70,7 +73,7 @@ void CSMWorld::RefCollection::load(ESM::ESMReader& reader, int cellIndex, bool b
// Autocalculate the cell index from coordinates first
std::pair<int, int> index = ref.getCellIndex();
ref.mCell = ESM::RefId::stringRefId("#" + std::to_string(index.first) + " " + std::to_string(index.second));
ref.mCell = ESM::RefId::esm3ExteriorCell(index.first, index.second);
// Handle non-base moved references
if (!base && isMoved)
@ -86,12 +89,11 @@ void CSMWorld::RefCollection::load(ESM::ESMReader& reader, int cellIndex, bool b
if (index.first != mref.mTarget[0] || index.second != mref.mTarget[1])
{
ESM::RefId indexCell = ref.mCell;
ref.mCell = ESM::RefId::stringRefId(
"#" + std::to_string(mref.mTarget[0]) + " " + std::to_string(mref.mTarget[1]));
ref.mCell = ESM::RefId::esm3ExteriorCell(mref.mTarget[0], mref.mTarget[1]);
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Cell, mCells.getId(cellIndex));
messages.add(id, "The position of the moved reference " + ref.mRefID.getRefIdString() + " (cell " + indexCell.getRefIdString() + ")"
" does not match the target cell (" + ref.mCell.getRefIdString() + ")",
messages.add(id, "The position of the moved reference " + ref.mRefID.toDebugString() + " (cell " + indexCell.toDebugString() + ")"
" does not match the target cell (" + ref.mCell.toDebugString() + ")",
std::string(), CSMDoc::Message::Severity_Warning);
}
}
@ -118,7 +120,7 @@ void CSMWorld::RefCollection::load(ESM::ESMReader& reader, int cellIndex, bool b
messages.add(id,
"Attempt to move a non-existent reference - RefNum index " + std::to_string(ref.mRefNum.mIndex)
+ ", refID " + ref.mRefID.getRefIdString() + ", content file index "
+ ", refID " + ref.mRefID.toDebugString() + ", content file index "
+ std::to_string(ref.mRefNum.mContentFile),
/*hint*/ "", CSMDoc::Message::Severity_Warning);
continue;

@ -16,8 +16,6 @@
#include <apps/opencs/view/world/dragrecordtable.hpp>
#include <components/esm3/cellid.hpp>
#include "../../model/doc/document.hpp"
#include "../../model/world/columns.hpp"
@ -307,7 +305,7 @@ void CSVWorld::RegionMap::view()
}
emit editRequest(
CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Scene, ESM::CellId::sDefaultWorldspace), hint.str());
CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Scene, ESM::Cell::sDefaultWorldspace), hint.str());
}
void CSVWorld::RegionMap::viewInTable()

@ -14,8 +14,6 @@
#include <apps/opencs/view/doc/subview.hpp>
#include <apps/opencs/view/render/worldspacewidget.hpp>
#include <components/esm3/cellid.hpp>
#include "../../model/doc/document.hpp"
#include "../../model/world/cellselection.hpp"
@ -49,7 +47,7 @@ CSVWorld::SceneSubView::SceneSubView(const CSMWorld::UniversalId& id, CSMDoc::Do
CSVRender::WorldspaceWidget* worldspaceWidget = nullptr;
widgetType whatWidget;
if (Misc::StringUtils::ciEqual(id.getId(), ESM::CellId::sDefaultWorldspace))
if (Misc::StringUtils::ciEqual(id.getId(), ESM::Cell::sDefaultWorldspace))
{
whatWidget = widget_Paged;
@ -170,7 +168,7 @@ void CSVWorld::SceneSubView::cellSelectionChanged(const CSMWorld::UniversalId& i
void CSVWorld::SceneSubView::cellSelectionChanged(const CSMWorld::CellSelection& selection)
{
setUniversalId(CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Scene, ESM::CellId::sDefaultWorldspace));
setUniversalId(CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Scene, ESM::Cell::sDefaultWorldspace));
int size = selection.getSize();
std::ostringstream stream;

@ -40,7 +40,6 @@ namespace ESM
{
class ESMReader;
class ESMWriter;
struct CellId;
}
namespace MWMechanics

@ -10,7 +10,6 @@
#include <string_view>
#include <vector>
#include <components/esm3/cellid.hpp>
#include <components/misc/rng.hpp>
#include <osg/Timer>
@ -114,7 +113,7 @@ namespace MWBase
{
std::string name;
float x, y; // world position
ESM::CellId dest;
ESM::RefId dest;
};
World() {}
@ -256,9 +255,8 @@ namespace MWBase
= 0;
///< Move to exterior cell.
///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes
virtual void changeToCell(
const ESM::CellId& cellId, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent = true)
const ESM::RefId& cellId, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent = true)
= 0;
///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes
@ -514,13 +512,16 @@ namespace MWBase
virtual bool screenshot360(osg::Image* image) = 0;
/// Find default position inside exterior cell specified by name
/// \return false if exterior with given name not exists, true otherwise
virtual bool findExteriorPosition(std::string_view name, ESM::Position& pos) = 0;
/// \return empty RefId if exterior with given name not exists, the cell's RefId otherwise
virtual ESM::RefId findExteriorPosition(std::string_view name, ESM::Position& pos) = 0;
/// Find default position inside interior cell specified by name
/// \return false if interior with given name not exists, true otherwise
virtual bool findInteriorPosition(std::string_view name, ESM::Position& pos) = 0;
/// \return empty RefId if interior with given name not exists, the cell's RefId otherwise
virtual ESM::RefId findInteriorPosition(std::string_view name, ESM::Position& pos) = 0;
/// Find default position inside interior or exterior cell specified by name
/// \return empty RefId if interior with given name not exists, the cell's RefId otherwise
virtual ESM::RefId findCellPosition(std::string_view cellName, ESM::Position& pos) = 0;
/// Enables or disables use of teleport spell effects (recall, intervention, etc).
virtual void enableTeleporting(bool enable) = 0;

@ -23,6 +23,7 @@
#include "../mwworld/esmstore.hpp"
#include "../mwworld/failedaction.hpp"
#include "../mwworld/ptr.hpp"
#include "../mwworld/worldmodel.hpp"
#include "../mwgui/tooltips.hpp"
#include "../mwgui/ustring.hpp"
@ -298,16 +299,8 @@ namespace MWClass
std::string Door::getDestination(const MWWorld::LiveCellRef<ESM::Door>& door)
{
std::string_view dest = door.mRef.getDestCell();
if (dest.empty())
{
// door leads to exterior, use cell name (if any), otherwise translated region name
auto world = MWBase::Environment::get().getWorld();
const osg::Vec2i index
= MWWorld::positionToCellIndex(door.mRef.getDoorDest().pos[0], door.mRef.getDoorDest().pos[1]);
const ESM::Cell* cell = world->getStore().get<ESM::Cell>().search(index.x(), index.y());
dest = world->getCellName(cell);
}
std::string_view dest
= MWBase::Environment::get().getWorldModel()->getCell(door.mRef.getDestCell())->getCell()->getDisplayName();
return "#{sCell=" + std::string{ dest } + "}";
}

@ -158,7 +158,7 @@ namespace MWGui
return mMarkers.end();
}
CustomMarkerCollection::RangeType CustomMarkerCollection::getMarkers(const ESM::CellId& cellId) const
CustomMarkerCollection::RangeType CustomMarkerCollection::getMarkers(const ESM::RefId& cellId) const
{
return mMarkers.equal_range(cellId);
}
@ -351,13 +351,9 @@ namespace MWGui
{
for (int dY = -mCellDistance; dY <= mCellDistance; ++dY)
{
ESM::CellId cellId;
cellId.mPaged = !mInterior;
cellId.mWorldspace = (mInterior ? mPrefix : ESM::CellId::sDefaultWorldspace);
cellId.mIndex.mX = mCurX + dX;
cellId.mIndex.mY = mCurY + dY;
ESM::RefId cellRefId = ESM::Cell::generateIdForCell(!mInterior, mPrefix, mCurX + dX, mCurY + dY);
CustomMarkerCollection::RangeType markers = mCustomMarkers.getMarkers(cellId);
CustomMarkerCollection::RangeType markers = mCustomMarkers.getMarkers(cellRefId);
for (CustomMarkerCollection::ContainerType::const_iterator it = markers.first; it != markers.second;
++it)
{
@ -885,16 +881,9 @@ namespace MWGui
mEditingMarker.mWorldX = worldPos.x();
mEditingMarker.mWorldY = worldPos.y();
ESM::RefId clickedId = ESM::Cell::generateIdForCell(!mInterior, LocalMapBase::mPrefix, x, y);
mEditingMarker.mCell.mPaged = !mInterior;
if (mInterior)
mEditingMarker.mCell.mWorldspace = LocalMapBase::mPrefix;
else
{
mEditingMarker.mCell.mWorldspace = ESM::CellId::sDefaultWorldspace;
mEditingMarker.mCell.mIndex.mX = x;
mEditingMarker.mCell.mIndex.mY = y;
}
mEditingMarker.mCell = clickedId;
mEditNoteDialog.setVisible(true);
mEditNoteDialog.showDeleteButton(false);
@ -1120,12 +1109,8 @@ namespace MWGui
void MapWindow::setGlobalMapMarkerTooltip(MyGUI::Widget* markerWidget, int x, int y)
{
ESM::CellId cellId;
cellId.mIndex.mX = x;
cellId.mIndex.mY = y;
cellId.mWorldspace = ESM::CellId::sDefaultWorldspace;
cellId.mPaged = true;
CustomMarkerCollection::RangeType markers = mCustomMarkers.getMarkers(cellId);
ESM::RefId cellRefId = ESM::RefId::esm3ExteriorCell(x, y);
CustomMarkerCollection::RangeType markers = mCustomMarkers.getMarkers(cellRefId);
std::vector<std::string> destNotes;
for (CustomMarkerCollection::ContainerType::const_iterator it = markers.first; it != markers.second; ++it)
destNotes.push_back(it->second.mNote);

@ -10,8 +10,6 @@
#include "windowpinnablebase.hpp"
#include <components/esm3/cellid.hpp>
#include <components/esm3/custommarkerstate.hpp>
#include <components/misc/constants.hpp>
@ -56,14 +54,14 @@ namespace MWGui
size_t size() const;
typedef std::multimap<ESM::CellId, ESM::CustomMarker> ContainerType;
typedef std::multimap<ESM::RefId, ESM::CustomMarker> ContainerType;
typedef std::pair<ContainerType::const_iterator, ContainerType::const_iterator> RangeType;
ContainerType::const_iterator begin() const;
ContainerType::const_iterator end() const;
RangeType getMarkers(const ESM::CellId& cellId) const;
RangeType getMarkers(const ESM::RefId& cellId) const;
typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void;
EventHandle_Void eventMarkersChanged;

@ -195,9 +195,11 @@ namespace MWGui
MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode();
MWBase::Environment::get().getWindowManager()->fadeScreenOut(1);
osg::Vec2i posCell = MWWorld::positionToCellIndex(pos.pos[0], pos.pos[1]);
ESM::RefId cellId = ESM::Cell::generateIdForCell(!interior, cellname, posCell.x(), posCell.y());
// Teleports any followers, too.
MWWorld::ActionTeleport action(interior ? cellname : "", pos, true);
MWWorld::ActionTeleport action(cellId, pos, true);
action.execute(player);
MWBase::Environment::get().getWindowManager()->fadeScreenOut(0);

@ -36,8 +36,7 @@ namespace MWLua
const MWWorld::CellRef& cellRef = doorPtr(o).getCellRef();
if (!cellRef.getTeleport())
return sol::nil;
MWWorld::CellStore* cell = MWBase::Environment::get().getWorldModel()->getCellByPosition(
cellRef.getDoorDest().asVec3(), cellRef.getDestCell());
MWWorld::CellStore* cell = MWBase::Environment::get().getWorldModel()->getCell(cellRef.getDestCell());
assert(cell);
return o.getCell(lua, cell);
};

@ -2,6 +2,7 @@
#include <components/esm3/aisequence.hpp>
#include <components/esm3/loadcell.hpp>
#include <components/misc/algorithm.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
@ -78,7 +79,7 @@ namespace MWMechanics
}
}
if (!mCellId.empty() && mCellId != actor.getCell()->getCell()->getCellId().mWorldspace)
if (!mCellId.empty() && !Misc::StringUtils::ciEqual(mCellId, actor.getCell()->getCell()->getNameId()))
return false; // Not in the correct cell, pause and rely on the player to go back through a teleport door
actor.getClass().getCreatureStats(actor).setDrawState(DrawState::Nothing);

@ -174,8 +174,7 @@ namespace MWMechanics
return true;
}
}
else if (Misc::StringUtils::ciEqual(
mCellId, actor.getCell()->getCell()->getNameId())) // Cell to travel to
else if (mCellId == actor.getCell()->getCell()->getWorldSpace()) // Cell to travel to
{
mRemainingDuration = mDuration;
return true;

@ -330,9 +330,9 @@ void MWMechanics::AiPackage::openDoors(const MWWorld::Ptr& actor)
const MWMechanics::PathgridGraph& MWMechanics::AiPackage::getPathGridGraph(const MWWorld::CellStore* cell)
{
const ESM::CellId& id = cell->getCell()->getCellId();
const ESM::RefId id = cell->getCell()->getId();
// static cache is OK for now, pathgrids can never change during runtime
typedef std::map<ESM::CellId, std::unique_ptr<MWMechanics::PathgridGraph>> CacheMap;
typedef std::map<ESM::RefId, std::unique_ptr<MWMechanics::PathgridGraph>> CacheMap;
static CacheMap cache;
CacheMap::iterator found = cache.find(id);
if (found == cache.end())

@ -487,9 +487,9 @@ namespace MWMechanics
world->getPlayer().getMarkedPosition(markedCell, markedPosition);
if (markedCell)
{
std::string_view dest;
ESM::RefId dest;
if (!markedCell->isExterior())
dest = markedCell->getCell()->getNameId();
dest = markedCell->getCell()->getId();
MWWorld::ActionTeleport action(dest, markedPosition, false);
action.execute(target);
if (!caster.isEmpty())

@ -92,19 +92,9 @@ namespace MWScript
ESM::Position pos;
MWBase::World* world = MWBase::Environment::get().getWorld();
const MWWorld::Ptr playerPtr = world->getPlayerPtr();
if (world->findExteriorPosition(cell, pos))
{
MWWorld::ActionTeleport({}, pos, false).execute(playerPtr);
world->adjustPosition(playerPtr, false);
}
else
{
// Change to interior even if findInteriorPosition()
// yields false. In this case position will be zero-point.
world->findInteriorPosition(cell, pos);
MWWorld::ActionTeleport(cell, pos, false).execute(playerPtr);
}
ESM::RefId cellId = world->findCellPosition(cell, pos);
MWWorld::ActionTeleport(cellId, pos, false).execute(playerPtr);
world->adjustPosition(playerPtr, false);
}
};

@ -87,8 +87,7 @@ namespace MWScript
float distance;
// If the objects are in different worldspaces, return a large value (just like vanilla)
if (!to.isInCell() || !from.isInCell()
|| to.getCell()->getCell()->getCellId().mWorldspace
!= from.getCell()->getCell()->getCellId().mWorldspace)
|| to.getCell()->getCell()->getWorldSpace() != from.getCell()->getCell()->getWorldSpace())
distance = std::numeric_limits<float>::max();
else
{

@ -4,7 +4,6 @@
#include <components/debug/debuglog.hpp>
#include <components/esm3/cellid.hpp>
#include <components/esm3/esmreader.hpp>
#include <components/esm3/esmwriter.hpp>
#include <components/esm3/loadcell.hpp>
@ -555,7 +554,7 @@ void MWState::StateManager::loadGame(const Character* character, const std::file
if (ptr.isInCell())
{
const ESM::CellId& cellId = ptr.getCell()->getCell()->getCellId();
const ESM::RefId cellId = ptr.getCell()->getCell()->getId();
// Use detectWorldSpaceChange=false, otherwise some of the data we just loaded would be cleared again
MWBase::Environment::get().getWorld()->changeToCell(cellId, ptr.getRefData().getPosition(), false, false);
@ -574,7 +573,7 @@ void MWState::StateManager::loadGame(const Character* character, const std::file
pos.rot[0] = 0;
pos.rot[1] = 0;
pos.rot[2] = 0;
MWBase::Environment::get().getWorld()->changeToCell(cell->getCell()->getCellId(), pos, true, false);
MWBase::Environment::get().getWorld()->changeToCell(cell->getCell()->getId(), pos, true, false);
}
MWBase::Environment::get().getWorld()->updateProjectilesCasters();

@ -19,9 +19,9 @@
namespace MWWorld
{
ActionTeleport::ActionTeleport(std::string_view cellName, const ESM::Position& position, bool teleportFollowers)
ActionTeleport::ActionTeleport(ESM::RefId cellId, const ESM::Position& position, bool teleportFollowers)
: Action(true)
, mCellName(cellName)
, mCellId(cellId)
, mPosition(position)
, mTeleportFollowers(teleportFollowers)
{
@ -33,7 +33,9 @@ namespace MWWorld
{
// Find any NPCs that are following the actor and teleport them with him
std::set<MWWorld::Ptr> followers;
getFollowers(actor, followers, mCellName.empty(), true);
bool toExterior = MWBase::Environment::get().getWorldModel()->getCell(mCellId)->isExterior();
getFollowers(actor, followers, toExterior, true);
for (std::set<MWWorld::Ptr>::iterator it = followers.begin(); it != followers.end(); ++it)
teleport(*it);
@ -52,10 +54,7 @@ namespace MWWorld
if (actor == world->getPlayerPtr())
{
world->getPlayer().setTeleported(true);
if (mCellName.empty())
world->changeToExteriorCell(mPosition, true);
else
world->changeToInteriorCell(mCellName, mPosition, true);
world->changeToCell(mCellId, mPosition, true);
teleported = world->getPlayerPtr();
}
else
@ -65,15 +64,9 @@ namespace MWWorld
actor.getClass().getCreatureStats(actor).getAiSequence().stopCombat();
return;
}
else if (mCellName.empty())
{
const osg::Vec2i index = positionToCellIndex(mPosition.pos[0], mPosition.pos[1]);
teleported = world->moveObject(
actor, worldModel->getExterior(index.x(), index.y()), mPosition.asVec3(), true, true);
}
else
teleported
= world->moveObject(actor, worldModel->getInterior(mCellName), mPosition.asVec3(), true, true);
teleported = world->moveObject(actor, worldModel->getCell(mCellId), mPosition.asVec3(), true, true);
}
if (!world->isWaterWalkingCastableOnTarget(teleported) && MWMechanics::hasWaterWalking(teleported))

@ -13,7 +13,7 @@ namespace MWWorld
{
class ActionTeleport : public Action
{
std::string mCellName;
ESM::RefId mCellId;
ESM::Position mPosition;
bool mTeleportFollowers;
@ -26,7 +26,7 @@ namespace MWWorld
public:
/// If cellName is empty, an exterior cell is assumed.
/// @param teleportFollowers Whether to teleport any following actors of the target actor as well.
ActionTeleport(std::string_view cellName, const ESM::Position& position, bool teleportFollowers);
ActionTeleport(ESM::RefId cellId, const ESM::Position& position, bool teleportFollowers);
/// @param includeHostiles If true, include hostile followers (which won't actually be teleported) in the
/// output,

@ -16,10 +16,8 @@ namespace MWWorld
, mDisplayname(cell.mFullName)
, mNameID(cell.mEditorId)
, mRegion(ESM::RefId()) // Unimplemented for now
, mCellId{
.mWorldspace{ Misc::StringUtils::lowerCase(cell.mEditorId) },
.mIndex{ cell.getGridX(), cell.getGridY() },
.mPaged = isExterior(),}
, mId(cell.mId)
, mParent(cell.mParent)
,mMood{
.mAmbiantColor = cell.mLighting.ambient,
.mDirectionalColor = cell.mLighting.directional,
@ -40,7 +38,8 @@ namespace MWWorld
, mDisplayname(cell.mName)
, mNameID(cell.mName)
, mRegion(cell.mRegion)
, mCellId(cell.getCellId())
, mId(cell.mId)
, mParent(ESM::RefId::stringRefId(ESM::Cell::sDefaultWorldspace))
, mMood{
.mAmbiantColor = cell.mAmbi.mAmbient,
.mDirectionalColor = cell.mAmbi.mSunlight,
@ -59,4 +58,11 @@ namespace MWWorld
},
*this);
}
ESM::RefId Cell::getWorldSpace() const
{
if (isExterior())
return mParent;
else
return mId;
}
}

@ -5,12 +5,10 @@
#include <components/esm/esmbridge.hpp>
#include <components/esm/refid.hpp>
#include <components/esm3/cellid.hpp>
namespace ESM
{
struct Cell;
struct CellId;
}
namespace ESM4
@ -42,13 +40,14 @@ namespace MWWorld
bool isQuasiExterior() const { return mIsQuasiExterior; }
bool hasWater() const { return mHasWater; }
bool noSleep() const { return mNoSleep; }
const ESM::CellId& getCellId() const { return mCellId; }
const ESM::RefId& getRegion() const { return mRegion; }
std::string_view getNameId() const { return mNameID; }
std::string_view getDisplayName() const { return mDisplayname; }
std::string getDescription() const;
const MoodData& getMood() const { return mMood; }
float getWaterHeight() const { return mWaterHeight; }
const ESM::RefId& getId() const { return mId; }
ESM::RefId getWorldSpace() const;
private:
bool mIsExterior;
@ -60,7 +59,8 @@ namespace MWWorld
std::string mDisplayname; // How the game displays it
std::string mNameID; // The name that will be used by the script and console commands
ESM::RefId mRegion;
ESM::CellId mCellId;
ESM::RefId mId;
ESM::RefId mParent;
MoodData mMood;
float mWaterHeight;

@ -3,8 +3,11 @@
#include <cassert>
#include <components/debug/debuglog.hpp>
#include <components/esm3/loadcell.hpp>
#include <components/esm3/objectstate.hpp>
#include <apps/openmw/mwworld/cellutils.hpp>
namespace MWWorld
{
CellRef::CellRef(const ESM::CellRef& ref)
@ -67,15 +70,38 @@ namespace MWWorld
static const std::string emptyString = "";
const std::string& CellRef::getDestCell() const
ESM::Position CellRef::getDoorDest() const
{
return std::visit(ESM::VisitOverload{
[&](const ESM4::Reference& /*ref*/) -> const std::string& { return emptyString; },
[&](const ESM::CellRef& ref) -> const std::string& { return ref.mDestCell; },
[&](const ESM4::Reference& ref) { return ref.mDoor.destPos; },
[&](const ESM::CellRef& ref) -> ESM::Position { return ref.mDoorDest; },
},
mCellRef.mVariant);
}
ESM::RefId CellRef::getDestCell() const
{
auto esm3Visit = [&](const ESM::CellRef& ref) -> ESM::RefId {
if (!ref.mDestCell.empty())
{
return ESM::RefId::stringRefId(ref.mDestCell);
}
else
{
const osg::Vec2i index = positionToCellIndex(ref.mDoorDest.pos[0], ref.mDoorDest.pos[1]);
return ESM::RefId::esm3ExteriorCell(index.x(), index.y());
}
};
return std::visit(
ESM::VisitOverload{
[&](const ESM4::Reference& ref) -> ESM::RefId { return ESM::RefId::sEmpty; },
esm3Visit,
},
mCellRef.mVariant);
}
void CellRef::setScale(float scale)
{
if (scale != getScale())

@ -61,18 +61,10 @@ namespace MWWorld
}
// Teleport location for the door, if this is a teleporting door.
const ESM::Position& getDoorDest() const
{
struct Visitor
{
const ESM::Position& operator()(const ESM::CellRef& ref) { return ref.mDoorDest; }
const ESM::Position& operator()(const ESM4::Reference& ref) { return ref.mDoor.destPos; }
};
return std::visit(Visitor(), mCellRef.mVariant);
}
ESM::Position getDoorDest() const;
// Destination cell for doors (optional)
const std::string& getDestCell() const;
ESM::RefId getDestCell() const;
// Scale applied to mesh
float getScale() const

@ -7,7 +7,6 @@
#include <components/debug/debuglog.hpp>
#include <components/esm/format.hpp>
#include <components/esm3/cellid.hpp>
#include <components/esm3/cellref.hpp>
#include <components/esm3/cellstate.hpp>
#include <components/esm3/containerstate.hpp>
@ -988,11 +987,11 @@ namespace MWWorld
void CellStore::saveState(ESM::CellState& state) const
{
state.mId = mCellVariant.getCellId();
state.mId = mCellVariant.getId();
if (!mCellVariant.isExterior() && mCellVariant.hasWater())
state.mWaterLevel = mWaterLevel;
state.mIsInterior = !mCellVariant.isExterior();
state.mHasFogOfWar = (mFogState.get() ? 1 : 0);
state.mLastRespawn = mLastRespawn.toEsm();
}
@ -1019,10 +1018,10 @@ namespace MWWorld
for (const auto& [base, store] : mMovedToAnotherCell)
{
ESM::RefNum refNum = base->mRef.getRefNum();
ESM::CellId movedTo = store->getCell()->getCellId();
ESM::RefId movedTo = store->getCell()->getId();
refNum.save(writer, true, "MVRF");
movedTo.save(writer);
writer.writeCellId(movedTo);
}
}
@ -1078,10 +1077,8 @@ namespace MWWorld
{
reader.cacheSubName();
ESM::RefNum refnum;
ESM::CellId movedTo;
refnum.load(reader, true, "MVRF");
movedTo.load(reader);
ESM::RefId movedToId = reader.getCellId();
if (refnum.hasContentFile())
{
auto iter = contentFileMap.find(refnum.mContentFile);
@ -1098,12 +1095,12 @@ namespace MWWorld
continue;
}
CellStore* otherCell = callback->getCellStore(movedTo);
CellStore* otherCell = callback->getCellStore(movedToId);
if (otherCell == nullptr)
{
Log(Debug::Warning) << "Warning: Dropping moved ref tag for " << movedRef.getCellRef().getRefId()
<< " (target cell " << movedTo.mWorldspace
<< " (target cell " << movedToId
<< " no longer exists). Reference moved back to its original location.";
// Note by dropping tag the object will automatically re-appear in its original cell, though
// potentially at inapproriate coordinates. Restore original coordinates:
@ -1122,21 +1119,9 @@ namespace MWWorld
}
}
struct IsEqualVisitor
{
bool operator()(const ESM::Cell& a, const ESM::Cell& b) const { return a.getCellId() == b.getCellId(); }
bool operator()(const ESM4::Cell& a, const ESM4::Cell& b) const { return a.mId == b.mId; }
template <class L, class R>
bool operator()(const L&, const R&) const
{
return false;
}
};
bool CellStore::operator==(const CellStore& right) const
{
return ESM::visit(IsEqualVisitor(), this->mCellVariant, right.mCellVariant);
return right.mCellVariant.getId() == mCellVariant.getId();
}
void CellStore::setFog(std::unique_ptr<ESM::FogState>&& fog)

@ -27,7 +27,6 @@ namespace ESM
class ReadersCache;
struct Cell;
struct CellState;
struct CellId;
struct RefNum;
struct Activator;
struct Potion;
@ -291,7 +290,7 @@ namespace MWWorld
struct GetCellStoreCallback
{
///@note must return nullptr if the cell is not found
virtual CellStore* getCellStore(const ESM::CellId& cellId) = 0;
virtual CellStore* getCellStore(const ESM::RefId& cellId) = 0;
virtual ~GetCellStoreCallback() = default;
};

@ -51,7 +51,7 @@ namespace MWWorld
{
if (!cell.isExterior())
continue;
auto cellIndex = std::make_pair(cell.getCellId().mIndex.mX, cell.getCellId().mIndex.mY);
auto cellIndex = std::make_pair(cell.getGridX(), cell.getGridY());
mCellContexts[cellIndex] = std::move(cell.mContextList);
}
}

@ -283,7 +283,7 @@ namespace MWWorld
ESM::Player player;
mPlayer.save(player.mObject);
player.mCellId = mCellStore->getCell()->getCellId();
player.mCellId = mCellStore->getCell()->getId();
player.mCurrentCrimeId = mCurrentCrimeId;
player.mPaidCrimeId = mPaidCrimeId;
@ -298,7 +298,7 @@ namespace MWWorld
{
player.mHasMark = true;
player.mMarkedPosition = mMarkedPosition;
player.mMarkedCell = mMarkedCell->getCell()->getCellId();
player.mMarkedCell = mMarkedCell->getCell()->getId();
}
else
player.mHasMark = false;
@ -371,7 +371,7 @@ namespace MWWorld
}
catch (...)
{
Log(Debug::Warning) << "Warning: Player cell '" << player.mCellId.mWorldspace << "' no longer exists";
Log(Debug::Warning) << "Warning: Player cell '" << player.mCellId << "' no longer exists";
// Cell no longer exists. The loader will have to choose a default cell.
mCellStore = nullptr;
}
@ -392,12 +392,9 @@ namespace MWWorld
mLastKnownExteriorPosition.y() = player.mLastKnownExteriorPosition[1];
mLastKnownExteriorPosition.z() = player.mLastKnownExteriorPosition[2];
if (player.mHasMark && !player.mMarkedCell.mPaged)
if (player.mHasMark)
{
// interior cell -> need to check if it exists (exterior cell will be
// generated on the fly)
if (!world.getStore().get<ESM::Cell>().search(player.mMarkedCell.mWorldspace))
if (!world.getStore().get<ESM::Cell>().search(player.mMarkedCell))
player.mHasMark = false; // drop mark silently
}

@ -552,9 +552,11 @@ namespace MWWorld
unloadCell(cell, navigatorUpdateGuard.get());
}
mNavigator.setWorldspace(
Misc::StringUtils::lowerCase(
mWorld.getWorldModel().getExterior(playerCellX, playerCellY)->getCell()->getCellId().mWorldspace),
mNavigator.setWorldspace(Misc::StringUtils::lowerCase(mWorld.getWorldModel()
.getExterior(playerCellX, playerCellY)
->getCell()
->getWorldSpace()
.serializeText()),
navigatorUpdateGuard.get());
mNavigator.updateBounds(pos, navigatorUpdateGuard.get());
@ -675,8 +677,8 @@ namespace MWWorld
"Testing exterior cells (" + std::to_string(i) + "/" + std::to_string(cells.getExtSize()) + ")...");
CellStore* cell = mWorld.getWorldModel().getExterior(it->mData.mX, it->mData.mY);
mNavigator.setWorldspace(
Misc::StringUtils::lowerCase(cell->getCell()->getCellId().mWorldspace), navigatorUpdateGuard.get());
mNavigator.setWorldspace(Misc::StringUtils::lowerCase(cell->getCell()->getWorldSpace().serializeText()),
navigatorUpdateGuard.get());
const osg::Vec3f position
= osg::Vec3f(it->mData.mX + 0.5f, it->mData.mY + 0.5f, 0) * Constants::CellSizeInUnits;
mNavigator.updateBounds(position, navigatorUpdateGuard.get());
@ -733,8 +735,8 @@ namespace MWWorld
"Testing interior cells (" + std::to_string(i) + "/" + std::to_string(cells.getIntSize()) + ")...");
CellStore* cell = mWorld.getWorldModel().getInterior(it->mName);
mNavigator.setWorldspace(
Misc::StringUtils::lowerCase(cell->getCell()->getCellId().mWorldspace), navigatorUpdateGuard.get());
mNavigator.setWorldspace(Misc::StringUtils::lowerCase(cell->getCell()->getWorldSpace().serializeText()),
navigatorUpdateGuard.get());
ESM::Position position;
mWorld.findInteriorPosition(it->mName, position);
mNavigator.updateBounds(position.asVec3(), navigatorUpdateGuard.get());
@ -890,7 +892,7 @@ namespace MWWorld
loadingListener->setProgressRange(cell->count());
mNavigator.setWorldspace(
Misc::StringUtils::lowerCase(cell->getCell()->getCellId().mWorldspace), navigatorUpdateGuard.get());
Misc::StringUtils::lowerCase(cell->getCell()->getWorldSpace().serializeText()), navigatorUpdateGuard.get());
mNavigator.updateBounds(position.asVec3(), navigatorUpdateGuard.get());
// Load cell.
@ -920,16 +922,18 @@ namespace MWWorld
MWBase::Environment::get().getWorld()->getPostProcessor()->setExteriorFlag(cell->getCell()->isQuasiExterior());
}
void Scene::changeToExteriorCell(const ESM::Position& position, bool adjustPlayerPos, bool changeEvent)
void Scene::changeToExteriorCell(
const ESM::RefId& extCellId, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent)
{
const osg::Vec2i cellIndex = positionToCellIndex(position.pos[0], position.pos[1]);
if (changeEvent)
MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5);
CellStore* current = mWorld.getWorldModel().getCell(extCellId);
const osg::Vec2i cellIndex(current->getCell()->getGridX(), current->getCell()->getGridY());
changeCellGrid(position.asVec3(), cellIndex.x(), cellIndex.y(), changeEvent);
CellStore* current = mWorld.getWorldModel().getExterior(cellIndex.x(), cellIndex.y());
changePlayerCell(current, position, adjustPlayerPos);
if (changeEvent)
@ -1129,15 +1133,7 @@ namespace MWWorld
{
try
{
if (!door.getCellRef().getDestCell().empty())
preloadCell(mWorld.getWorldModel().getInterior(door.getCellRef().getDestCell()));
else
{
osg::Vec3f pos = door.getCellRef().getDoorDest().asVec3();
const osg::Vec2i cellIndex = positionToCellIndex(pos.x(), pos.y());
preloadCell(mWorld.getWorldModel().getExterior(cellIndex.x(), cellIndex.y()), true);
exteriorPositions.emplace_back(pos, gridCenterToBounds(getNewGridCenter(pos)));
}
preloadCell(mWorld.getWorldModel().getCell(door.getCellRef().getDestCell()));
}
catch (std::exception&)
{

@ -167,7 +167,8 @@ namespace MWWorld
///< Move to interior cell.
/// @param changeEvent Set cellChanged flag?
void changeToExteriorCell(const ESM::Position& position, bool adjustPlayerPos, bool changeEvent = true);
void changeToExteriorCell(
const ESM::RefId& extCellId, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent = true);
///< Move to exterior cell.
/// @param changeEvent Set cellChanged flag?

@ -468,13 +468,17 @@ namespace MWWorld
// Cell
//=========================================================================
const ESM::Cell* Store<ESM::Cell>::search(const ESM::RefId& cellId) const
{
auto foundCellIt = mCells.find(cellId);
if (foundCellIt != mCells.end())
return &foundCellIt->second;
return nullptr;
}
const ESM::Cell* Store<ESM::Cell>::search(const ESM::Cell& cell) const
{
if (cell.isExterior())
{
return search(cell.getGridX(), cell.getGridY());
}
return search(cell.mName);
return search(cell.mId);
}
// this method *must* be called right after esm3.loadCell()
@ -521,13 +525,13 @@ namespace MWWorld
DynamicInt::const_iterator it = mInt.find(name);
if (it != mInt.end())
{
return &(it->second);
return it->second;
}
DynamicInt::const_iterator dit = mDynamicInt.find(name);
if (dit != mDynamicInt.end())
{
return &dit->second;
return dit->second;
}
return nullptr;
@ -537,11 +541,11 @@ namespace MWWorld
std::pair<int, int> key(x, y);
DynamicExt::const_iterator it = mExt.find(key);
if (it != mExt.end())
return &(it->second);
return it->second;
DynamicExt::const_iterator dit = mDynamicExt.find(key);
if (dit != mDynamicExt.end())
return &dit->second;
return dit->second;
return nullptr;
}
@ -549,7 +553,7 @@ namespace MWWorld
{
DynamicExt::const_iterator it = mExt.find(std::make_pair(x, y));
if (it != mExt.end())
return &(it->second);
return (it->second);
return nullptr;
}
const ESM::Cell* Store<ESM::Cell>::searchOrCreate(int x, int y)
@ -557,11 +561,11 @@ namespace MWWorld
std::pair<int, int> key(x, y);
DynamicExt::const_iterator it = mExt.find(key);
if (it != mExt.end())
return &(it->second);
return (it->second);
DynamicExt::const_iterator dit = mDynamicExt.find(key);
if (dit != mDynamicExt.end())
return &dit->second;
return dit->second;
ESM::Cell newCell;
newCell.mData.mX = x;
@ -571,11 +575,11 @@ namespace MWWorld
newCell.mAmbi.mSunlight = 0;
newCell.mAmbi.mFog = 0;
newCell.mAmbi.mFogDensity = 0;
newCell.mCellId.mPaged = true;
newCell.mCellId.mIndex.mX = x;
newCell.mCellId.mIndex.mY = y;
newCell.updateId();
ESM::Cell* newCellInserted = &mCells.insert(std::make_pair(newCell.mId, newCell)).first->second;
return &mExt.insert(std::make_pair(key, newCell)).first->second;
return mExt.insert(std::make_pair(key, newCellInserted)).first->second;
}
const ESM::Cell* Store<ESM::Cell>::find(std::string_view id) const
{
@ -607,12 +611,12 @@ namespace MWWorld
mSharedInt.clear();
mSharedInt.reserve(mInt.size());
for (auto& [_, cell] : mInt)
mSharedInt.push_back(&cell);
mSharedInt.push_back(cell);
mSharedExt.clear();
mSharedExt.reserve(mExt.size());
for (auto& [_, cell] : mExt)
mSharedExt.push_back(&cell);
mSharedExt.push_back(cell);
}
RecordId Store<ESM::Cell>::load(ESM::ESMReader& esm)
{
@ -622,13 +626,17 @@ namespace MWWorld
// are not available until both cells have been loaded at least partially!
// All cells have a name record, even nameless exterior cells.
ESM::Cell cell;
ESM::Cell* emplacedCell = nullptr;
bool isDeleted = false;
{
ESM::Cell cellToLoad;
cellToLoad.loadNameAndData(esm, isDeleted);
emplacedCell = &mCells.insert(std::make_pair(cellToLoad.mId, cellToLoad)).first->second;
}
ESM::Cell& cell = *emplacedCell;
// Load the (x,y) coordinates of the cell, if it is an exterior cell,
// so we can find the cell we need to merge with
cell.loadNameAndData(esm, isDeleted);
if (cell.mData.mFlags & ESM::Cell::Interior)
{
// Store interior cell by name, try to merge with existing parent data.
@ -647,7 +655,7 @@ namespace MWWorld
// spawn a new cell
cell.loadCell(esm, true);
mInt[cell.mName] = cell;
mInt[cell.mName] = &cell;
}
}
else
@ -700,19 +708,19 @@ namespace MWWorld
else
{
// spawn a new cell
cell.loadCell(esm, false);
emplacedCell->loadCell(esm, false);
// handle moved ref (MVRF) subrecords
handleMovedCellRefs(esm, &cell);
handleMovedCellRefs(esm, emplacedCell);
// push the new references on the list of references to manage
cell.postLoad(esm);
emplacedCell->postLoad(esm);
mExt[std::make_pair(cell.mData.mX, cell.mData.mY)] = cell;
mExt[std::make_pair(cell.mData.mX, cell.mData.mY)] = &cell;
}
}
return RecordId(ESM::RefId::stringRefId(cell.mName), isDeleted);
return RecordId(cell.mId, isDeleted);
}
Store<ESM::Cell>::iterator Store<ESM::Cell>::intBegin() const
{
@ -790,21 +798,22 @@ namespace MWWorld
const std::string cellType = (cell.isExterior()) ? "exterior" : "interior";
throw std::runtime_error("Failed to create " + cellType + " cell");
}
ESM::Cell* insertedCell = &mCells.emplace(cell.mId, cell).first->second;
if (cell.isExterior())
{
std::pair<int, int> key(cell.getGridX(), cell.getGridY());
// duplicate insertions are avoided by search(ESM::Cell &)
DynamicExt::iterator result = mDynamicExt.emplace(key, cell).first;
mSharedExt.push_back(&result->second);
return &result->second;
DynamicExt::iterator result = mDynamicExt.emplace(key, insertedCell).first;
mSharedExt.push_back(result->second);
return result->second;
}
else
{
// duplicate insertions are avoided by search(ESM::Cell &)
DynamicInt::iterator result = mDynamicInt.emplace(cell.mName, cell).first;
mSharedInt.push_back(&result->second);
return &result->second;
DynamicInt::iterator result = mDynamicInt.emplace(cell.mName, insertedCell).first;
mSharedInt.push_back(result->second);
return result->second;
}
}
bool Store<ESM::Cell>::erase(const ESM::Cell& cell)
@ -828,7 +837,7 @@ namespace MWWorld
for (it = mDynamicInt.begin(); it != mDynamicInt.end(); ++it)
{
mSharedInt.push_back(&it->second);
mSharedInt.push_back(it->second);
}
return true;
@ -847,7 +856,7 @@ namespace MWWorld
for (it = mDynamicExt.begin(); it != mDynamicExt.end(); ++it)
{
mSharedExt.push_back(&it->second);
mSharedExt.push_back(it->second);
}
return true;

@ -347,10 +347,12 @@ namespace MWWorld
}
};
typedef std::unordered_map<std::string, ESM::Cell, Misc::StringUtils::CiHash, Misc::StringUtils::CiEqual>
typedef std::unordered_map<std::string, ESM::Cell*, Misc::StringUtils::CiHash, Misc::StringUtils::CiEqual>
DynamicInt;
typedef std::map<std::pair<int, int>, ESM::Cell, DynamicExtCmp> DynamicExt;
typedef std::map<std::pair<int, int>, ESM::Cell*, DynamicExtCmp> DynamicExt;
std::unordered_map<ESM::RefId, ESM::Cell> mCells;
DynamicInt mInt;
DynamicExt mExt;
@ -367,6 +369,7 @@ namespace MWWorld
public:
typedef SharedIterator<ESM::Cell> iterator;
const ESM::Cell* search(const ESM::RefId& id) const;
const ESM::Cell* search(std::string_view id) const;
const ESM::Cell* search(int x, int y) const;
const ESM::Cell* searchStatic(int x, int y) const;

@ -13,7 +13,6 @@
#include <components/debug/debuglog.hpp>
#include <components/esm3/cellid.hpp>
#include <components/esm3/cellref.hpp>
#include <components/esm3/esmreader.hpp>
#include <components/esm3/esmwriter.hpp>
@ -353,7 +352,7 @@ namespace MWWorld
if (bypass && !mStartCell.empty())
{
ESM::Position pos;
if (findExteriorPosition(mStartCell, pos))
if (findExteriorPosition(mStartCell, pos).empty())
{
changeToExteriorCell(pos, true);
adjustPosition(getPlayerPtr(), false);
@ -378,7 +377,10 @@ namespace MWWorld
pos.rot[0] = 0;
pos.rot[1] = 0;
pos.rot[2] = 0;
mWorldScene->changeToExteriorCell(pos, true);
osg::Vec2i exteriorCellPos = positionToCellIndex(pos.pos[0], pos.pos[1]);
ESM::RefId cellId = ESM::RefId::esm3ExteriorCell(exteriorCellPos.x(), exteriorCellPos.y());
mWorldScene->changeToExteriorCell(cellId, pos, true);
}
}
@ -974,30 +976,43 @@ namespace MWWorld
mPhysics->clearQueuedMovement();
mDiscardMovements = true;
if (changeEvent && mCurrentWorldSpace != ESM::CellId::sDefaultWorldspace)
if (changeEvent && mCurrentWorldSpace != ESM::Cell::sDefaultWorldspace)
{
// changed worldspace
mProjectileManager->clear();
mRendering->notifyWorldSpaceChanged();
}
removeContainerScripts(getPlayerPtr());
mWorldScene->changeToExteriorCell(position, adjustPlayerPos, changeEvent);
osg::Vec2i exteriorCellPos = positionToCellIndex(position.pos[0], position.pos[1]);
ESM::RefId cellId = ESM::RefId::esm3ExteriorCell(exteriorCellPos.x(), exteriorCellPos.y());
mWorldScene->changeToExteriorCell(cellId, position, adjustPlayerPos, changeEvent);
addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell());
mRendering->getCamera()->instantTransition();
}
void World::changeToCell(
const ESM::CellId& cellId, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent)
const ESM::RefId& cellId, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent)
{
if (!changeEvent)
mCurrentWorldSpace = cellId.mWorldspace;
const MWWorld::Cell* destinationCell = getWorldModel().getCell(cellId)->getCell();
bool exteriorCell = destinationCell->isExterior();
if (cellId.mPaged)
changeToExteriorCell(position, adjustPlayerPos, changeEvent);
else
changeToInteriorCell(cellId.mWorldspace, position, adjustPlayerPos, changeEvent);
mPhysics->clearQueuedMovement();
mDiscardMovements = true;
mCurrentDate->setup(mGlobalVariables);
if (changeEvent && mCurrentWorldSpace != destinationCell->getNameId())
{
// changed worldspace
mProjectileManager->clear();
mRendering->notifyWorldSpaceChanged();
mCurrentWorldSpace = destinationCell->getNameId();
}
removeContainerScripts(getPlayerPtr());
if (exteriorCell)
mWorldScene->changeToExteriorCell(cellId, position, adjustPlayerPos, changeEvent);
else
mWorldScene->changeToInteriorCell(destinationCell->getNameId(), position, adjustPlayerPos, changeEvent);
addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell());
mRendering->getCamera()->instantTransition();
}
float World::getMaxActivationDistance() const
@ -1169,7 +1184,7 @@ namespace MWWorld
if (mWorldScene->isCellActive(*newCell))
mWorldScene->changePlayerCell(newCell, pos, false);
else
mWorldScene->changeToExteriorCell(pos, false);
mWorldScene->changeToExteriorCell(newCell->getCell()->getId(), pos, false);
}
addContainerScripts(getPlayerPtr(), newCell);
newPtr = getPlayerPtr();
@ -1420,9 +1435,9 @@ namespace MWWorld
esmPos.pos[0] = traced.x();
esmPos.pos[1] = traced.y();
esmPos.pos[2] = traced.z();
std::string_view cell;
ESM::RefId cell;
if (!actor.getCell()->isExterior())
cell = actor.getCell()->getCell()->getNameId();
cell = actor.getCell()->getCell()->getId();
MWWorld::ActionTeleport(cell, esmPos, false).execute(actor);
}
}
@ -2060,24 +2075,7 @@ namespace MWWorld
{
World::DoorMarker newMarker;
newMarker.name = MWClass::Door::getDestination(ref);
ESM::CellId cellid;
if (!ref.mRef.getDestCell().empty())
{
cellid.mWorldspace = ref.mRef.getDestCell();
cellid.mPaged = false;
cellid.mIndex.mX = 0;
cellid.mIndex.mY = 0;
}
else
{
cellid.mPaged = true;
const osg::Vec2i index
= positionToCellIndex(ref.mRef.getDoorDest().pos[0], ref.mRef.getDoorDest().pos[1]);
cellid.mIndex.mX = index.x();
cellid.mIndex.mY = index.y();
}
newMarker.dest = cellid;
newMarker.dest = ref.mRef.getDestCell();
ESM::Position pos = ref.mData.getPosition();
@ -2764,7 +2762,7 @@ namespace MWWorld
physicActor->enableCollisionBody(enable);
}
bool World::findInteriorPosition(std::string_view name, ESM::Position& pos)
ESM::RefId World::findInteriorPosition(std::string_view name, ESM::Position& pos)
{
pos.rot[0] = pos.rot[1] = pos.rot[2] = 0;
pos.pos[0] = pos.pos[1] = pos.pos[2] = 0;
@ -2772,8 +2770,9 @@ namespace MWWorld
MWWorld::CellStore* cellStore = mWorldModel.getInterior(name);
if (!cellStore)
return false;
return ESM::RefId::sEmpty;
ESM::RefId cellId = cellStore->getCell()->getId();
std::vector<const MWWorld::CellRef*> sortedDoors;
for (const MWWorld::LiveCellRef<ESM::Door>& door : cellStore->getReadOnlyDoors().mList)
{
@ -2794,19 +2793,8 @@ namespace MWWorld
for (const MWWorld::CellRef* door : sortedDoors)
{
MWWorld::CellStore* source = nullptr;
source = mWorldModel.getCell(door->getDestCell());
// door to exterior
if (door->getDestCell().empty())
{
ESM::Position doorDest = door->getDoorDest();
const osg::Vec2i index = positionToCellIndex(doorDest.pos[0], doorDest.pos[1]);
source = mWorldModel.getExterior(index.x(), index.y());
}
// door to interior
else
{
source = mWorldModel.getInterior(door->getDestCell());
}
if (source)
{
// Find door leading to our current teleport door
@ -2819,7 +2807,7 @@ namespace MWWorld
/// not the one pointed to current door.
pos = destDoor.mRef.getDoorDest();
pos.rot[0] = pos.rot[1] = pos.rot[2] = 0;
return true;
return cellId;
}
}
}
@ -2831,7 +2819,7 @@ namespace MWWorld
// found the COC position?
pos = stat4.mRef.getPosition();
pos.rot[0] = pos.rot[1] = pos.rot[2] = 0;
return true;
return cellId;
}
}
// Fall back to the first static location.
@ -2840,7 +2828,7 @@ namespace MWWorld
{
pos = statics4.begin()->mRef.getPosition();
pos.rot[0] = pos.rot[1] = pos.rot[2] = 0;
return true;
return cellId;
}
// Fall back to the first static location.
const MWWorld::CellRefList<ESM::Static>::List& statics = cellStore->getReadOnlyStatics().mList;
@ -2848,13 +2836,31 @@ namespace MWWorld
{
pos = statics.begin()->mRef.getPosition();
pos.rot[0] = pos.rot[1] = pos.rot[2] = 0;
return true;
return cellId;
}
return false;
return ESM::RefId::sEmpty;
}
bool World::findExteriorPosition(std::string_view nameId, ESM::Position& pos)
ESM::RefId World::findCellPosition(std::string_view cellName, ESM::Position& pos)
{
ESM::RefId foundCell;
try
{
foundCell = findInteriorPosition(cellName, pos);
}
catch (std::exception&)
{
}
if (foundCell.empty())
{
return findExteriorPosition(cellName, pos);
}
return foundCell;
}
ESM::RefId World::findExteriorPosition(std::string_view nameId, ESM::Position& pos)
{
pos.rot[0] = pos.rot[1] = pos.rot[2] = 0;
@ -2863,7 +2869,7 @@ namespace MWWorld
{
ext = mWorldModel.getCell(nameId)->getCell();
if (!ext->isExterior())
return false;
return ESM::RefId();
}
catch (std::exception&)
{
@ -2895,10 +2901,10 @@ namespace MWWorld
// Note: Z pos will be adjusted by adjustPosition later
pos.pos[2] = 0;
return true;
return ext->getId();
}
return false;
return ESM::RefId::sEmpty;
}
void World::enableTeleporting(bool enable)
@ -3289,10 +3295,10 @@ namespace MWWorld
// Search for a 'nearest' exterior, counting each cell between the starting
// cell and the exterior as a distance of 1. Will fail for isolated interiors.
std::set<std::string_view> checkedCells;
std::set<std::string_view> currentCells;
std::set<std::string_view> nextCells;
nextCells.insert(cell->getCell()->getNameId());
std::set<ESM::RefId> checkedCells;
std::set<ESM::RefId> currentCells;
std::set<ESM::RefId> nextCells;
nextCells.insert(cell->getCell()->getId());
while (!nextCells.empty())
{
@ -3300,7 +3306,7 @@ namespace MWWorld
nextCells.clear();
for (const auto& currentCell : currentCells)
{
MWWorld::CellStore* next = mWorldModel.getInterior(currentCell);
MWWorld::CellStore* next = mWorldModel.getCell(currentCell);
if (!next)
continue;
@ -3318,7 +3324,7 @@ namespace MWWorld
}
else
{
const std::string_view dest = ref.mRef.getDestCell();
ESM::RefId dest = ref.mRef.getDestCell();
if (!checkedCells.count(dest) && !currentCells.count(dest))
nextCells.insert(dest);
}
@ -3342,19 +3348,19 @@ namespace MWWorld
// Search for a 'nearest' marker, counting each cell between the starting
// cell and the exterior as a distance of 1. If an exterior is found, jump
// to the nearest exterior marker, without further interior searching.
std::set<std::string_view> checkedCells;
std::set<std::string_view> currentCells;
std::set<std::string_view> nextCells;
std::set<ESM::RefId> checkedCells;
std::set<ESM::RefId> currentCells;
std::set<ESM::RefId> nextCells;
MWWorld::ConstPtr closestMarker;
nextCells.insert(ptr.getCell()->getCell()->getNameId());
nextCells.insert(ptr.getCell()->getCell()->getId());
while (!nextCells.empty())
{
currentCells = nextCells;
nextCells.clear();
for (const auto& cell : currentCells)
{
MWWorld::CellStore* next = mWorldModel.getInterior(cell);
MWWorld::CellStore* next = mWorldModel.getCell(cell);
checkedCells.insert(cell);
if (!next)
continue;
@ -3440,11 +3446,11 @@ namespace MWWorld
return;
}
std::string_view cellName = "";
ESM::RefId cellId;
if (!closestMarker.mCell->isExterior())
cellName = closestMarker.mCell->getCell()->getNameId();
cellId = closestMarker.mCell->getCell()->getId();
MWWorld::ActionTeleport action(cellName, closestMarker.getRefData().getPosition(), false);
MWWorld::ActionTeleport action(cellId, closestMarker.getRefData().getPosition(), false);
action.execute(ptr);
}
@ -3646,13 +3652,13 @@ namespace MWWorld
Log(Debug::Warning) << "Failed to confiscate items: no closest prison marker found.";
return;
}
std::string_view prisonName = prisonMarker.getCellRef().getDestCell();
ESM::RefId prisonName = prisonMarker.getCellRef().getDestCell();
if (prisonName.empty())
{
Log(Debug::Warning) << "Failed to confiscate items: prison marker not linked to prison interior";
return;
}
MWWorld::CellStore* prison = mWorldModel.getInterior(prisonName);
MWWorld::CellStore* prison = mWorldModel.getCell(prisonName);
if (!prison)
{
Log(Debug::Warning) << "Failed to confiscate items: failed to load cell " << prisonName;

@ -349,7 +349,7 @@ namespace MWWorld
///< Move to exterior cell.
///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes
void changeToCell(const ESM::CellId& cellId, const ESM::Position& position, bool adjustPlayerPos,
void changeToCell(const ESM::RefId& cellId, const ESM::Position& position, bool adjustPlayerPos,
bool changeEvent = true) override;
///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes
@ -601,12 +601,12 @@ namespace MWWorld
/// Find center of exterior cell above land surface
/// \return false if exterior with given name not exists, true otherwise
bool findExteriorPosition(std::string_view nameId, ESM::Position& pos) override;
ESM::RefId findExteriorPosition(std::string_view nameId, ESM::Position& pos) override;
/// Find position in interior cell near door entrance
/// \return false if interior with given name not exists, true otherwise
bool findInteriorPosition(std::string_view name, ESM::Position& pos) override;
ESM::RefId findInteriorPosition(std::string_view name, ESM::Position& pos) override;
ESM::RefId findCellPosition(std::string_view cellName, ESM::Position& pos) override;
/// Enables or disables use of teleport spell effects (recall, intervention, etc).
void enableTeleporting(bool enable) override;

@ -2,6 +2,7 @@
#include <components/debug/debuglog.hpp>
#include <components/esm/defs.hpp>
#include <components/esm3/cellid.hpp>
#include <components/esm3/cellref.hpp>
#include <components/esm3/cellstate.hpp>
#include <components/esm3/esmreader.hpp>
@ -20,7 +21,8 @@
namespace
{
template <class Visitor, class Key, class Comp>
bool forEachInStore(const ESM::RefId& id, Visitor&& visitor, std::map<Key, MWWorld::CellStore, Comp>& cellStore)
bool forEachInStore(
const ESM::RefId& id, Visitor&& visitor, std::unordered_map<Key, MWWorld::CellStore, Comp>& cellStore)
{
for (auto& cell : cellStore)
{
@ -62,27 +64,25 @@ namespace
MWWorld::CellStore* MWWorld::WorldModel::getCellStore(const ESM::Cell* cell)
{
CellStore* cellStore = &mCells.emplace(cell->mId, CellStore(MWWorld::Cell(*cell), mStore, mReaders)).first->second;
if (cell->mData.mFlags & ESM::Cell::Interior)
{
auto result = mInteriors.find(cell->mName);
if (result == mInteriors.end())
result = mInteriors.emplace(cell->mName, CellStore(MWWorld::Cell(*cell), mStore, mReaders)).first;
result = mInteriors.emplace(cell->mName, cellStore).first;
return &result->second;
return result->second;
}
else
{
std::map<std::pair<int, int>, CellStore>::iterator result
std::map<std::pair<int, int>, CellStore*>::iterator result
= mExteriors.find(std::make_pair(cell->getGridX(), cell->getGridY()));
if (result == mExteriors.end())
result = mExteriors
.emplace(std::make_pair(cell->getGridX(), cell->getGridY()),
CellStore(MWWorld::Cell(*cell), mStore, mReaders))
.first;
result = mExteriors.emplace(std::make_pair(cell->getGridX(), cell->getGridY()), cellStore).first;
return &result->second;
return result->second;
}
}
@ -93,6 +93,7 @@ void MWWorld::WorldModel::clear()
mLastGeneratedRefnum = ESM::RefNum{};
mInteriors.clear();
mExteriors.clear();
mCells.clear();
std::fill(mIdCache.begin(), mIdCache.end(), std::make_pair(ESM::RefId(), (MWWorld::CellStore*)nullptr));
mIdCacheIndex = 0;
}
@ -143,7 +144,8 @@ void MWWorld::WorldModel::writeCell(ESM::ESMWriter& writer, CellStore& cell) con
cell.saveState(cellState);
writer.startRecord(ESM::REC_CSTA);
cellState.mId.save(writer);
writer.writeCellId(cellState.mId);
cellState.save(writer);
cell.writeFog(writer);
cell.writeReferences(writer);
@ -160,7 +162,7 @@ MWWorld::WorldModel::WorldModel(const MWWorld::ESMStore& store, ESM::ReadersCach
MWWorld::CellStore* MWWorld::WorldModel::getExterior(int x, int y)
{
std::map<std::pair<int, int>, CellStore>::iterator result = mExteriors.find(std::make_pair(x, y));
std::map<std::pair<int, int>, CellStore*>::iterator result = mExteriors.find(std::make_pair(x, y));
if (result == mExteriors.end())
{
@ -170,29 +172,27 @@ MWWorld::CellStore* MWWorld::WorldModel::getExterior(int x, int y)
{
// Cell isn't predefined. Make one on the fly.
ESM::Cell record;
record.mCellId.mWorldspace = ESM::CellId::sDefaultWorldspace;
record.mCellId.mPaged = true;
record.mCellId.mIndex.mX = x;
record.mCellId.mIndex.mY = y;
record.mData.mFlags = ESM::Cell::HasWater;
record.mData.mX = x;
record.mData.mY = y;
record.mWater = 0;
record.mMapColor = 0;
record.updateId();
cell = MWBase::Environment::get().getWorld()->createRecord(record);
}
result = mExteriors.emplace(std::make_pair(x, y), CellStore(MWWorld::Cell(*cell), mStore, mReaders)).first;
CellStore* cellStore
= &mCells.emplace(cell->mId, CellStore(MWWorld::Cell(*cell), mStore, mReaders)).first->second;
result = mExteriors.emplace(std::make_pair(x, y), cellStore).first;
}
if (result->second.getState() != CellStore::State_Loaded)
if (result->second->getState() != CellStore::State_Loaded)
{
result->second.load();
result->second->load();
}
return &result->second;
return result->second;
}
MWWorld::CellStore* MWWorld::WorldModel::getInterior(std::string_view name)
@ -202,32 +202,85 @@ MWWorld::CellStore* MWWorld::WorldModel::getInterior(std::string_view name)
if (result == mInteriors.end())
{
const ESM4::Cell* cell4 = mStore.get<ESM4::Cell>().searchCellName(name);
CellStore* newCellStore = nullptr;
if (!cell4)
{
const ESM::Cell* cell = mStore.get<ESM::Cell>().find(name);
result = mInteriors.emplace(name, CellStore(MWWorld::Cell(*cell), mStore, mReaders)).first;
newCellStore = &mCells.emplace(cell->mId, CellStore(MWWorld::Cell(*cell), mStore, mReaders)).first->second;
}
else
{
result = mInteriors.emplace(name, CellStore(MWWorld::Cell(*cell4), mStore, mReaders)).first;
newCellStore
= &mCells.emplace(cell4->mId, CellStore(MWWorld::Cell(*cell4), mStore, mReaders)).first->second;
}
result = mInteriors.emplace(name, newCellStore).first;
}
if (result->second.getState() != CellStore::State_Loaded)
if (result->second->getState() != CellStore::State_Loaded)
{
result->second.load();
result->second->load();
}
return &result->second;
return result->second;
}
MWWorld::CellStore* MWWorld::WorldModel::getCell(const ESM::CellId& id)
struct VisitorCellIdIsESM3Ext
{
if (id.mPaged)
return getExterior(id.mIndex.mX, id.mIndex.mY);
bool operator()(const ESM::ESM3ExteriorCellRefId& id)
{
coordOut = { id.getX(), id.getY() };
return true;
}
template <typename T>
bool operator()(const T&)
{
return false;
}
std::pair<int32_t, int32_t> coordOut = {};
};
MWWorld::CellStore* MWWorld::WorldModel::getCell(const ESM::RefId& id)
{
auto result = mCells.find(id);
if (result != mCells.end())
return &result->second;
VisitorCellIdIsESM3Ext isESM3ExteriorVisitor;
if (visit(isESM3ExteriorVisitor, id)) // That is an exterior cell Id
{
return getExterior(isESM3ExteriorVisitor.coordOut.first, isESM3ExteriorVisitor.coordOut.second);
}
return getInterior(id.mWorldspace);
const ESM4::Cell* cell4 = mStore.get<ESM4::Cell>().search(id);
CellStore* newCellStore = nullptr;
if (!cell4)
{
const ESM::Cell* cell = mStore.get<ESM::Cell>().search(id);
newCellStore = &mCells.emplace(cell->mId, CellStore(MWWorld::Cell(*cell), mStore, mReaders)).first->second;
}
else
{
newCellStore = &mCells.emplace(cell4->mId, CellStore(MWWorld::Cell(*cell4), mStore, mReaders)).first->second;
}
if (newCellStore->getCell()->isExterior())
{
std::pair<int, int> coord
= std::make_pair(newCellStore->getCell()->getGridX(), newCellStore->getCell()->getGridY());
mExteriors.emplace(coord, newCellStore);
}
else
{
mInteriors.emplace(newCellStore->getCell()->getNameId(), newCellStore);
}
if (newCellStore->getState() != CellStore::State_Loaded)
{
newCellStore->load();
}
return newCellStore;
}
const ESM::Cell* MWWorld::WorldModel::getESMCellByName(std::string_view name)
@ -329,17 +382,17 @@ MWWorld::Ptr MWWorld::WorldModel::getPtr(const ESM::RefId& name)
// Then check cells that are already listed
// Search in reverse, this is a workaround for an ambiguous chargen_plank reference in the vanilla game.
// there is one at -22,16 and one at -2,-9, the latter should be used.
for (std::map<std::pair<int, int>, CellStore>::reverse_iterator iter = mExteriors.rbegin();
for (std::map<std::pair<int, int>, CellStore*>::reverse_iterator iter = mExteriors.rbegin();
iter != mExteriors.rend(); ++iter)
{
Ptr ptr = getPtrAndCache(name, iter->second);
Ptr ptr = getPtrAndCache(name, *iter->second);
if (!ptr.isEmpty())
return ptr;
}
for (auto iter = mInteriors.begin(); iter != mInteriors.end(); ++iter)
{
Ptr ptr = getPtrAndCache(name, iter->second);
Ptr ptr = getPtrAndCache(name, *iter->second);
if (!ptr.isEmpty())
return ptr;
}
@ -389,8 +442,7 @@ void MWWorld::WorldModel::getExteriorPtrs(const ESM::RefId& name, std::vector<MW
std::vector<MWWorld::Ptr> MWWorld::WorldModel::getAll(const ESM::RefId& id)
{
PtrCollector visitor;
if (forEachInStore(id, visitor, mInteriors))
forEachInStore(id, visitor, mExteriors);
forEachInStore(id, visitor, mCells);
return visitor.mPtrs;
}
@ -398,12 +450,7 @@ int MWWorld::WorldModel::countSavedGameRecords() const
{
int count = 0;
for (auto iter(mInteriors.begin()); iter != mInteriors.end(); ++iter)
if (iter->second.hasState())
++count;
for (std::map<std::pair<int, int>, CellStore>::const_iterator iter(mExteriors.begin()); iter != mExteriors.end();
++iter)
for (auto iter(mCells.begin()); iter != mCells.end(); ++iter)
if (iter->second.hasState())
++count;
@ -412,14 +459,7 @@ int MWWorld::WorldModel::countSavedGameRecords() const
void MWWorld::WorldModel::write(ESM::ESMWriter& writer, Loading::Listener& progress) const
{
for (std::map<std::pair<int, int>, CellStore>::iterator iter(mExteriors.begin()); iter != mExteriors.end(); ++iter)
if (iter->second.hasState())
{
writeCell(writer, iter->second);
progress.increaseProgress();
}
for (auto iter(mInteriors.begin()); iter != mInteriors.end(); ++iter)
for (auto iter(mCells.begin()); iter != mCells.end(); ++iter)
if (iter->second.hasState())
{
writeCell(writer, iter->second);
@ -437,7 +477,7 @@ public:
MWWorld::WorldModel& mWorldModel;
MWWorld::CellStore* getCellStore(const ESM::CellId& cellId) override
MWWorld::CellStore* getCellStore(const ESM::RefId& cellId) override
{
try
{
@ -455,7 +495,7 @@ bool MWWorld::WorldModel::readRecord(ESM::ESMReader& reader, uint32_t type, cons
if (type == ESM::REC_CSTA)
{
ESM::CellState state;
state.mId.load(reader);
state.mId = reader.getCellId();
CellStore* cellStore = nullptr;
@ -466,8 +506,7 @@ bool MWWorld::WorldModel::readRecord(ESM::ESMReader& reader, uint32_t type, cons
catch (...)
{
// silently drop cells that don't exist anymore
Log(Debug::Warning) << "Warning: Dropping state for cell " << state.mId.mWorldspace
<< " (cell no longer exists)";
Log(Debug::Warning) << "Warning: Dropping state for cell " << state.mId << " (cell no longer exists)";
reader.skipRecord();
return true;
}

@ -17,7 +17,6 @@ namespace ESM
class ESMReader;
class ESMWriter;
class ReadersCache;
struct CellId;
struct Cell;
struct RefNum;
}
@ -42,8 +41,9 @@ namespace MWWorld
typedef std::vector<std::pair<ESM::RefId, CellStore*>> IdCache;
const MWWorld::ESMStore& mStore;
ESM::ReadersCache& mReaders;
mutable std::map<std::string, CellStore, Misc::StringUtils::CiComp> mInteriors;
mutable std::map<std::pair<int, int>, CellStore> mExteriors;
mutable std::unordered_map<ESM::RefId, CellStore> mCells;
mutable std::map<std::string, CellStore*, Misc::StringUtils::CiComp> mInteriors;
mutable std::map<std::pair<int, int>, CellStore*> mExteriors;
IdCache mIdCache;
std::size_t mIdCacheIndex = 0;
std::unordered_map<ESM::RefNum, Ptr> mPtrIndex;
@ -69,7 +69,7 @@ namespace MWWorld
CellStore* getExterior(int x, int y);
CellStore* getInterior(std::string_view name);
CellStore* getCell(std::string_view name); // interior or named exterior
CellStore* getCell(const ESM::CellId& Id);
CellStore* getCell(const ESM::RefId& Id);
// If cellNameInSameWorldSpace is an interior - returns this interior.
// Otherwise returns exterior cell for given position in the same world space.
@ -91,9 +91,7 @@ namespace MWWorld
template <typename Fn>
void forEachLoadedCellStore(Fn&& fn)
{
for (auto& [_, store] : mInteriors)
fn(store);
for (auto& [_, store] : mExteriors)
for (auto& [_, store] : mCells)
fn(store);
}

@ -342,6 +342,12 @@ namespace ESM
static RefId call() { return RefId::index(REC_BOOK, 7); }
};
template <>
struct GenerateRefId<ESM3ExteriorCellRefId>
{
static RefId call() { return RefId::esm3ExteriorCell(-12, 7); }
};
template <class T>
struct ESMRefIdTypesTest : Test
{

@ -248,16 +248,10 @@ namespace ESM
record.mObject.mRef.mRefID = generateRandomRefId();
std::generate_n(std::inserter(record.mPreviousItems, record.mPreviousItems.end()), 2,
[&] { return std::make_pair(generateRandomRefId(), generateRandomRefId()); });
record.mCellId.mWorldspace = "worldspace1";
record.mCellId.mIndex.mX = 42;
record.mCellId.mIndex.mY = 13;
record.mCellId.mPaged = true;
record.mCellId = ESM::RefId::esm3ExteriorCell(0, 0);
generateArray(record.mLastKnownExteriorPosition);
record.mHasMark = true;
record.mMarkedCell.mWorldspace = "worldspace2";
record.mMarkedCell.mIndex.mX = 0;
record.mMarkedCell.mIndex.mY = 0;
record.mMarkedCell.mPaged = false;
record.mMarkedCell = ESM::RefId::esm3ExteriorCell(0, 0);
generateArray(record.mMarkedPosition.pos);
generateArray(record.mMarkedPosition.rot);
record.mCurrentCrimeId = 42;
@ -267,16 +261,11 @@ namespace ESM
EXPECT_EQ(record.mBirthsign, result.mBirthsign);
EXPECT_EQ(record.mPreviousItems, result.mPreviousItems);
EXPECT_EQ(record.mPreviousItems, result.mPreviousItems);
EXPECT_EQ(record.mCellId.mWorldspace, result.mCellId.mWorldspace);
EXPECT_EQ(record.mCellId.mIndex.mX, result.mCellId.mIndex.mX);
EXPECT_EQ(record.mCellId.mIndex.mY, result.mCellId.mIndex.mY);
EXPECT_EQ(record.mCellId.mPaged, result.mCellId.mPaged);
EXPECT_EQ(record.mCellId, result.mCellId);
EXPECT_THAT(record.mLastKnownExteriorPosition, ElementsAreArray(result.mLastKnownExteriorPosition));
EXPECT_EQ(record.mHasMark, result.mHasMark);
EXPECT_EQ(record.mMarkedCell.mWorldspace, result.mMarkedCell.mWorldspace);
EXPECT_EQ(record.mMarkedCell.mIndex.mX, result.mMarkedCell.mIndex.mX);
EXPECT_EQ(record.mMarkedCell.mIndex.mY, result.mMarkedCell.mIndex.mY);
EXPECT_EQ(record.mMarkedCell.mPaged, result.mMarkedCell.mPaged);
EXPECT_EQ(record.mMarkedCell, result.mMarkedCell);
EXPECT_THAT(record.mMarkedPosition.pos, ElementsAreArray(result.mMarkedPosition.pos));
EXPECT_THAT(record.mMarkedPosition.rot, ElementsAreArray(result.mMarkedPosition.rot));
EXPECT_EQ(record.mCurrentCrimeId, result.mCurrentCrimeId);

@ -285,6 +285,7 @@ namespace
ESM::MaxOldSkillsAndAttributesFormatVersion,
ESM::MaxOldCreatureStatsFormatVersion,
ESM::MaxStringRefIdFormatVersion,
ESM::MaxUseEsmCellIdFormatVersion,
});
for (ESM::FormatVersion v = result.back() + 1; v <= ESM::CurrentSaveGameFormatVersion; ++v)
result.push_back(v);
@ -445,6 +446,10 @@ namespace
decltype(RecordType::mId) refId;
if constexpr (ESM::hasIndex<RecordType> && !std::is_same_v<RecordType, ESM::LandTexture>)
refId = RecordType::indexToRefId(index);
else if constexpr (std::is_same_v<RecordType, ESM::Cell>)
{
refId = ESM::RefId::esm3ExteriorCell(0, 0);
}
else
refId = ESM::StringRefId(stringId);
@ -562,7 +567,7 @@ namespace
REGISTER_TYPED_TEST_SUITE_P(StoreSaveLoadTest, shouldNotChangeRefId);
static_assert(std::tuple_size_v<RecordTypesWithSave> == 38);
static_assert(std::tuple_size_v<RecordTypesWithSave> == 39);
INSTANTIATE_TYPED_TEST_SUITE_P(
RecordTypesTest, StoreSaveLoadTest, typename AsTestingTypes<RecordTypesWithSave>::Type);

@ -91,6 +91,7 @@ add_component_dir(esm attr common defs esmcommon records util luascripts format
generatedrefid
indexrefid
serializerefid
esm3exteriorcellrefid
)
add_component_dir(fx pass technique lexer widgets stateupdater)
@ -391,7 +392,7 @@ if (USE_QT)
)
add_component_qt_dir (misc
helpviewer utf8qtextstream
helpviewer utf8qtextstream hash
)
add_component_qt_dir (files

@ -0,0 +1,36 @@
#include "esm3exteriorcellrefid.hpp"
#include "serializerefid.hpp"
#include <ostream>
#include <sstream>
namespace ESM
{
std::string ESM3ExteriorCellRefId::toString() const
{
std::string result;
std::size_t integralSizeX = getIntegralSize(mX);
result.resize(integralSizeX + getIntegralSize(mY) + 3, '\0');
serializeIntegral(mX, 0, result);
result[integralSizeX] = ':';
serializeIntegral(mY, integralSizeX + 1, result);
return result;
}
std::string ESM3ExteriorCellRefId::toDebugString() const
{
std::string result;
std::size_t integralSizeX = getIntegralSize(mX);
serializeRefIdPrefix(integralSizeX + getIntegralSize(mY) + 1, esm3ExteriorCellRefIdPrefix, result);
serializeIntegral(mX, esm3ExteriorCellRefIdPrefix.size(), result);
result[esm3ExteriorCellRefIdPrefix.size() + integralSizeX] = ':';
serializeIntegral(mY, esm3ExteriorCellRefIdPrefix.size() + integralSizeX + 1, result);
return result;
}
std::ostream& operator<<(std::ostream& stream, ESM3ExteriorCellRefId value)
{
return stream << "Vec2i{" << value.mX << "," << value.mY << '}';
}
}

@ -0,0 +1,57 @@
#ifndef OPENMW_COMPONENTS_ESM_ESM3EXTERIORCELLREFID_HPP
#define OPENMW_COMPONENTS_ESM_ESM3EXTERIORCELLREFID_HPP
#include <functional>
#include <iosfwd>
#include <utility>
#include <components/misc/hash.hpp>
namespace ESM
{
class ESM3ExteriorCellRefId
{
public:
constexpr ESM3ExteriorCellRefId() = default;
constexpr explicit ESM3ExteriorCellRefId(int32_t x, int32_t y) noexcept
: mX(x)
, mY(y)
{
}
std::string toString() const;
std::string toDebugString() const;
int32_t getX() const { return mX; }
int32_t getY() const { return mY; }
constexpr bool operator==(ESM3ExteriorCellRefId rhs) const noexcept { return mX == rhs.mX && mY == rhs.mY; }
constexpr bool operator<(ESM3ExteriorCellRefId rhs) const noexcept { return mX < rhs.mX && mY < rhs.mY; }
friend std::ostream& operator<<(std::ostream& stream, ESM3ExteriorCellRefId value);
friend struct std::hash<ESM3ExteriorCellRefId>;
private:
int32_t mX = 0;
int32_t mY = 0;
};
}
namespace std
{
template <>
struct hash<ESM::ESM3ExteriorCellRefId>
{
std::size_t operator()(ESM::ESM3ExteriorCellRefId value) const noexcept
{
return Misc::hash2dCoord(value.mX, value.mY);
}
};
}
#endif

@ -15,7 +15,6 @@ namespace ESM4
namespace ESM
{
struct Cell;
struct CellId;
class RefId;
class CellVariant;

@ -237,6 +237,13 @@ namespace ESM
return ESM::RefId::index(recordType,
deserializeIntegral<std::uint32_t>(indexRefIdPrefix.size() + sizeof(recordType) + 1, value));
}
if (value.starts_with(esm3ExteriorCellRefIdPrefix))
{
std::int32_t x = deserializeIntegral<std::int32_t>(esm3ExteriorCellRefIdPrefix.size(), value);
std::int32_t y
= deserializeIntegral<std::int32_t>(esm3ExteriorCellRefIdPrefix.size() + getIntegralSize(x) + 1, value);
return ESM::ESM3ExteriorCellRefId(x, y);
}
return ESM::RefId::stringRefId(value);
}

@ -10,6 +10,7 @@
#include <components/misc/notnullptr.hpp>
#include "esm3exteriorcellrefid.hpp"
#include "formidrefid.hpp"
#include "generatedrefid.hpp"
#include "indexrefid.hpp"
@ -38,6 +39,7 @@ namespace ESM
FormId = 3,
Generated = 4,
Index = 5,
ESM3ExteriorCell = 6,
};
// RefId is used to represent an Id that identifies an ESM record. These Ids can then be used in
@ -48,7 +50,8 @@ namespace ESM
public:
const static RefId sEmpty;
using Value = std::variant<EmptyRefId, StringRefId, FormIdRefId, GeneratedRefId, IndexRefId>;
using Value
= std::variant<EmptyRefId, StringRefId, FormIdRefId, GeneratedRefId, IndexRefId, ESM3ExteriorCellRefId>;
// Constructs RefId from a serialized string containing byte by byte copy of RefId::mValue.
static ESM::RefId deserialize(std::string_view value);
@ -70,6 +73,8 @@ namespace ESM
// identified by index (i.e. ESM3 SKIL).
static RefId index(RecNameInts recordType, std::uint32_t value) { return RefId(IndexRefId(recordType, value)); }
static RefId esm3ExteriorCell(int32_t x, int32_t y) { return RefId(ESM3ExteriorCellRefId(x, y)); }
constexpr RefId() = default;
constexpr RefId(EmptyRefId value) noexcept
@ -97,6 +102,11 @@ namespace ESM
{
}
constexpr RefId(ESM3ExteriorCellRefId value) noexcept
: mValue(value)
{
}
// Returns a reference to the value of StringRefId if it's the underlying value or throws an exception.
const std::string& getRefIdString() const;

@ -12,6 +12,7 @@ namespace ESM
constexpr std::string_view formIdRefIdPrefix = "FormId:";
constexpr std::string_view generatedRefIdPrefix = "Generated:";
constexpr std::string_view indexRefIdPrefix = "Index:";
constexpr std::string_view esm3ExteriorCellRefIdPrefix = "Esm3ExteriorCell:";
template <class T>
std::size_t getIntegralSize(T value)

@ -2,12 +2,11 @@
#include "esmreader.hpp"
#include "esmwriter.hpp"
#include <components/misc/algorithm.hpp>
namespace ESM
{
const std::string CellId::sDefaultWorldspace = "sys::default";
void CellId::load(ESMReader& esm)
{
mWorldspace = esm.getHNString("SPAC");
@ -33,6 +32,46 @@ namespace ESM
esm.writeHNT("CIDX", mIndex, 8);
}
struct VisitCellRefId
{
CellId operator()(const ESM::EmptyRefId)
{
CellId out;
out.mPaged = true;
out.mIndex = {};
return out;
}
CellId operator()(const ESM::StringRefId& id)
{
CellId out;
out.mPaged = false;
out.mWorldspace = id.getValue();
out.mIndex = { 0, 0 };
return out;
}
CellId operator()(const ESM::ESM3ExteriorCellRefId& id)
{
CellId out;
out.mPaged = true;
out.mIndex = { id.getX(), id.getY() };
return out;
}
template <typename T>
CellId operator()(const T& id)
{
throw std::runtime_error("cannot extract CellId from this Id type");
}
};
CellId CellId::extractFromRefId(const ESM::RefId& id)
{
// This is bad and that code should not be merged.
return visit(VisitCellRefId(), id);
}
bool operator==(const CellId& left, const CellId& right)
{
return left.mWorldspace == right.mWorldspace && left.mPaged == right.mPaged

@ -21,10 +21,11 @@ namespace ESM
CellIndex mIndex;
bool mPaged;
static const std::string sDefaultWorldspace;
void load(ESMReader& esm);
void save(ESMWriter& esm) const;
// TODO tetramir: this probably shouldn't exist, needs it because some CellIds are saved on disk
static CellId extractFromRefId(const ESM::RefId& id);
};
bool operator==(const CellId& left, const CellId& right);

@ -21,7 +21,7 @@ namespace ESM
void CellState::save(ESMWriter& esm) const
{
if (!mId.mPaged)
if (mIsInterior)
esm.writeHNT("WLVL", mWaterLevel);
esm.writeHNT("HFOW", mHasFogOfWar);

@ -1,9 +1,8 @@
#ifndef OPENMW_ESM_CELLSTATE_H
#define OPENMW_ESM_CELLSTATE_H
#include "cellid.hpp"
#include "components/esm/defs.hpp"
#include "components/esm/refid.hpp"
namespace ESM
{
@ -15,8 +14,8 @@ namespace ESM
/// \note Does not include references
struct CellState
{
CellId mId;
RefId mId;
bool mIsInterior;
float mWaterLevel;
int mHasFogOfWar; // Do we have fog of war state (0 or 1)? (see fogstate.hpp)

@ -10,7 +10,7 @@ namespace ESM
{
esm.writeHNT("POSX", mWorldX);
esm.writeHNT("POSY", mWorldY);
mCell.save(esm);
esm.writeCellId(mCell);
if (!mNote.empty())
esm.writeHNString("NOTE", mNote);
}
@ -19,7 +19,7 @@ namespace ESM
{
esm.getHNT(mWorldX, "POSX");
esm.getHNT(mWorldY, "POSY");
mCell.load(esm);
mCell = esm.getCellId();
mNote = esm.getHNOString("NOTE");
}

@ -1,10 +1,12 @@
#ifndef OPENMW_ESM_CUSTOMMARKERSTATE_H
#define OPENMW_ESM_CUSTOMMARKERSTATE_H
#include "cellid.hpp"
#include <components/esm/refid.hpp>
namespace ESM
{
class ESMReader;
class ESMWriter;
// format 0, saved games only
struct CustomMarker
@ -12,7 +14,7 @@ namespace ESM
float mWorldX;
float mWorldY;
CellId mCell;
RefId mCell;
std::string mNote;

@ -3,6 +3,8 @@
#include "readerscache.hpp"
#include "savedgame.hpp"
#include <components/esm3/cellid.hpp>
#include <components/esm3/loadcell.hpp>
#include <components/files/conversion.hpp>
#include <components/files/openfile.hpp>
#include <components/misc/strings/algorithm.hpp>
@ -86,6 +88,24 @@ namespace ESM
}
}
ESM::RefId ESMReader::getCellId()
{
if (mHeader.mFormatVersion <= ESM::MaxUseEsmCellIdFormatVersion)
{
ESM::CellId cellId;
cellId.load(*this);
if (cellId.mPaged)
{
return ESM::RefId::esm3ExteriorCell(cellId.mIndex.mX, cellId.mIndex.mY);
}
else
{
return ESM::RefId::stringRefId(cellId.mWorldspace);
}
}
return getHNRefId("NAME");
}
void ESMReader::openRaw(std::unique_ptr<std::istream>&& stream, const std::filesystem::path& name)
{
close();
@ -471,6 +491,13 @@ namespace ESM
getExact(&index, sizeof(std::uint32_t));
return RefId::index(recordType, index);
}
case RefIdType::ESM3ExteriorCell:
{
int32_t x, y;
getExact(&x, sizeof(std::int32_t));
getExact(&y, sizeof(std::int32_t));
return RefId::esm3ExteriorCell(x, y);
}
}
fail("Unsupported RefIdType: " + std::to_string(static_cast<unsigned>(refIdType)));

@ -96,6 +96,9 @@ namespace ESM
*
*************************************************************************/
// Because we want to get rid of CellId, we isolate it's uses.
ESM::RefId getCellId();
// Read data of a given type, stored in a subrecord of a given name
template <typename X>
void getHNT(X& x, NAME name)

@ -5,6 +5,7 @@
#include <stdexcept>
#include <components/debug/debuglog.hpp>
#include <components/esm3/cellid.hpp>
#include <components/misc/notnullptr.hpp>
#include <components/to_utf8/to_utf8.hpp>
@ -60,6 +61,13 @@ namespace ESM
mWriter.writeT(v.getRecordType());
mWriter.writeT(v.getValue());
}
void operator()(ESM3ExteriorCellRefId v) const
{
mWriter.writeT(RefIdType::ESM3ExteriorCell);
mWriter.writeT(v.getX());
mWriter.writeT(v.getY());
}
};
}
@ -236,6 +244,17 @@ namespace ESM
writeHNRefId(name, value);
}
void ESMWriter::writeCellId(const ESM::RefId& cellId)
{
if (mHeader.mFormatVersion <= ESM::MaxUseEsmCellIdFormatVersion)
{
ESM::CellId generatedCellid = ESM::CellId::extractFromRefId(cellId);
generatedCellid.save(*this);
}
else
writeHNRefId("NAME", cellId);
}
void ESMWriter::writeMaybeFixedSizeString(const std::string& data, std::size_t size)
{
std::string string;

@ -103,6 +103,8 @@ namespace ESM
writeHNCRefId(name, value);
}
void writeCellId(const ESM::RefId& cellId);
template <typename T>
void writeHNT(NAME name, const T& data)
{

@ -23,7 +23,8 @@ namespace ESM
inline constexpr FormatVersion MaxStringRefIdFormatVersion = 23;
inline constexpr FormatVersion MaxSavedGameCellNameAsRefIdFormatVersion = 24;
inline constexpr FormatVersion MaxNameIsRefIdOnlyFormatVersion = 25;
inline constexpr FormatVersion CurrentSaveGameFormatVersion = 26;
inline constexpr FormatVersion MaxUseEsmCellIdFormatVersion = 26;
inline constexpr FormatVersion CurrentSaveGameFormatVersion = 27;
}
#endif

@ -7,7 +7,6 @@
#include <components/debug/debuglog.hpp>
#include <components/misc/strings/algorithm.hpp>
#include "cellid.hpp"
#include "esmreader.hpp"
#include "esmwriter.hpp"
@ -40,6 +39,8 @@ namespace ESM
namespace ESM
{
const std::string Cell::sDefaultWorldspace = "sys::default";
// Some overloaded compare operators.
bool operator==(const MovedCellRef& ref, const RefNum& refNum)
{
@ -57,6 +58,24 @@ namespace ESM
loadCell(esm, saveContext);
}
const ESM::RefId& Cell::updateId()
{
mId = generateIdForCell(isExterior(), mName, getGridX(), getGridY());
return mId;
}
ESM::RefId Cell::generateIdForCell(bool exterior, std::string_view cellName, int x, int y)
{
if (!exterior)
{
return ESM::RefId::stringRefId(cellName);
}
else
{
return ESM::RefId::esm3ExteriorCell(x, y);
}
}
void Cell::loadNameAndData(ESMReader& esm, bool& isDeleted)
{
isDeleted = false;
@ -91,20 +110,7 @@ namespace ESM
if (!hasData)
esm.fail("Missing DATA subrecord");
mCellId.mPaged = !(mData.mFlags & Interior);
if (mCellId.mPaged)
{
mCellId.mWorldspace = CellId::sDefaultWorldspace;
mCellId.mIndex.mX = mData.mX;
mCellId.mIndex.mY = mData.mY;
}
else
{
mCellId.mWorldspace = Misc::StringUtils::lowerCase(mName);
mCellId.mIndex.mX = 0;
mCellId.mIndex.mY = 0;
}
updateId();
}
void Cell::loadCell(ESMReader& esm, bool saveContext)
@ -333,8 +339,4 @@ namespace ESM
mAmbi.mFogDensity = 0;
}
const CellId& Cell::getCellId() const
{
return mCellId;
}
}

@ -5,7 +5,6 @@
#include <string>
#include <vector>
#include "cellid.hpp"
#include "cellref.hpp"
#include "components/esm/defs.hpp"
#include "components/esm/esmcommon.hpp"
@ -67,6 +66,8 @@ namespace ESM
*/
struct Cell
{
static const std::string sDefaultWorldspace;
constexpr static RecNameInts sRecordId = REC_CELL;
/// Return a string descriptor for this record type. Currently used for debugging / error logs only.
@ -110,7 +111,7 @@ namespace ESM
, mRefNumCounter(0)
{
}
ESM::RefId mId;
// Interior cells are indexed by this (it's the 'id'), for exterior
// cells it is optional.
std::string mName;
@ -120,7 +121,6 @@ namespace ESM
std::vector<ESM_Context> mContextList; // File position; multiple positions for multiple plugin support
DATAstruct mData;
CellId mCellId;
AMBIstruct mAmbi;
bool mHasAmbi;
@ -191,7 +191,9 @@ namespace ESM
void blank();
///< Set record to default state (does not touch the ID/index).
const CellId& getCellId() const;
const ESM::RefId& updateId();
static ESM::RefId generateIdForCell(bool exterior, std::string_view cellName, int x, int y);
};
}
#endif

@ -11,7 +11,7 @@ namespace ESM
mObject.mRef.loadId(esm, true);
mObject.load(esm);
mCellId.load(esm);
mCellId = esm.getCellId();
esm.getHNTSized<12>(mLastKnownExteriorPosition, "LKEP");
@ -19,7 +19,7 @@ namespace ESM
{
mHasMark = true;
esm.getHTSized<24>(mMarkedPosition);
mMarkedCell.load(esm);
mMarkedCell = esm.getCellId();
}
else
mHasMark = false;
@ -92,14 +92,14 @@ namespace ESM
{
mObject.save(esm);
mCellId.save(esm);
esm.writeCellId(mCellId);
esm.writeHNT("LKEP", mLastKnownExteriorPosition);
if (mHasMark)
{
esm.writeHNT("MARK", mMarkedPosition, 24);
mMarkedCell.save(esm);
esm.writeCellId(mMarkedCell);
}
esm.writeHNRefId("SIGN", mBirthsign);

@ -3,7 +3,6 @@
#include <string>
#include "cellid.hpp"
#include "components/esm/defs.hpp"
#include "npcstate.hpp"
@ -20,12 +19,12 @@ namespace ESM
struct Player
{
NpcState mObject;
CellId mCellId;
RefId mCellId;
float mLastKnownExteriorPosition[3];
unsigned char mHasMark;
bool mSetWerewolfAcrobatics;
Position mMarkedPosition;
CellId mMarkedCell;
RefId mMarkedCell;
ESM::RefId mBirthsign;
int mCurrentCrimeId;

@ -53,7 +53,7 @@ void ESM4::Cell::load(ESM4::Reader& reader)
reader.adjustFormId(mFormId);
mId = ESM::RefId::formIdRefId(mFormId);
mFlags = reader.hdr().record.flags;
mParent = reader.currWorld();
mParent = ESM::RefId::formIdRefId(reader.currWorld());
reader.clearCellGrid(); // clear until XCLC FIXME: somehow do this automatically?

@ -36,7 +36,6 @@
#include <components/esm/defs.hpp>
#include <components/esm/refid.hpp>
#include <components/esm3/cellid.hpp>
#include <components/esm4/reader.hpp>
namespace ESM4
@ -68,7 +67,7 @@ namespace ESM4
ESM::RefId mId;
std::uint32_t mFlags; // from the header, see enum type RecordFlag for details
FormId mParent; // world formId (for grouping cells), from the loading sequence
ESM::RefId mParent; // world formId (for grouping cells), from the loading sequence
std::string mEditorId;
std::string mFullName;

@ -45,7 +45,7 @@ namespace EsmLoader
return (v.mId);
}
const ESM::CellId& operator()(const ESM::Cell& v) const { return v.mCellId; }
const ESM::RefId& operator()(const ESM::Cell& v) const { return v.mId; }
std::pair<int, int> operator()(const ESM::Land& v) const { return std::pair(v.mX, v.mY); }

@ -15,6 +15,13 @@ namespace Misc
std::hash<T> hasher;
seed ^= static_cast<Seed>(hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2));
}
// Comes from https://stackoverflow.com/questions/2634690/good-hash-function-for-a-2d-index
// Effective Java (2nd edition) is cited as the source
inline std::size_t hash2dCoord(int32_t x, int32_t y)
{
return (53 + std::hash<int32_t>{}(x)) * 53 + std::hash<int32_t>{}(y);
}
}
#endif

Loading…
Cancel
Save