Merge branch 'load-ESM4-Cell' into 'master'

Can load and coc into an interrior oblivion cell

See merge request OpenMW/openmw!2647
7220-lua-add-a-general-purpose-lexical-parser
psi29a 2 years ago
commit 4032c447e9

@ -78,7 +78,7 @@ add_openmw_dir (mwworld
actionequip timestamp actionalchemy cellstore actionapply actioneat
store esmstore fallback actionrepair actionsoulgem livecellref actiondoor
contentloader esmloader actiontrap cellreflist cellref weather projectilemanager
cellpreloader datetimemanager groundcoverstore magiceffects
cellpreloader datetimemanager groundcoverstore magiceffects cell
)
add_openmw_dir (mwphysics

@ -92,6 +92,7 @@ namespace MWWorld
class TimeStamp;
class ESMStore;
class RefData;
class Cell;
typedef std::vector<std::pair<MWWorld::Ptr, MWMechanics::Movement>> PtrMovementList;
}
@ -179,6 +180,8 @@ namespace MWBase
///
/// \note If cell==0, the cell the player is currently in will be used instead to
/// generate a name.
virtual std::string_view getCellName(const MWWorld::Cell& cell) const = 0;
virtual std::string_view getCellName(const ESM::Cell* cell) const = 0;
virtual void removeRefScript(MWWorld::RefData* ref) = 0;

@ -47,5 +47,7 @@ namespace MWClass
Repair::registerSelf();
Static::registerSelf();
BodyPart::registerSelf();
ESM4Static::registerSelf();
}
}

@ -1,6 +1,7 @@
#include "static.hpp"
#include <components/esm3/loadstat.hpp>
#include <components/esm4/loadstat.hpp>
#include <components/sceneutil/positionattitudetransform.hpp>
#include "../mwphysics/physicssystem.hpp"
@ -63,4 +64,53 @@ namespace MWClass
return MWWorld::Ptr(cell.insert(ref), &cell);
}
ESM4Static::ESM4Static()
: MWWorld::RegisteredClass<ESM4Static>(ESM4::Static::sRecordId)
{
}
void ESM4Static ::insertObjectRendering(
const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const
{
if (!model.empty())
{
renderingInterface.getObjects().insertModel(ptr, model);
ptr.getRefData().getBaseNode()->setNodeMask(MWRender::Mask_Static);
}
}
void ESM4Static::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation,
MWPhysics::PhysicsSystem& physics) const
{
insertObjectPhysics(ptr, model, rotation, physics);
}
void ESM4Static::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation,
MWPhysics::PhysicsSystem& physics) const
{
physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World);
}
std::string ESM4Static::getModel(const MWWorld::ConstPtr& ptr) const
{
return getClassModel<ESM4::Static>(ptr);
}
std::string_view ESM4Static ::getName(const MWWorld::ConstPtr& ptr) const
{
return {};
}
bool ESM4Static::hasToolTip(const MWWorld::ConstPtr& ptr) const
{
return false;
}
MWWorld::Ptr ESM4Static::copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const
{
const MWWorld::LiveCellRef<ESM4::Static>* ref = ptr.get<ESM4::Static>();
return MWWorld::Ptr(cell.insert(ref), &cell);
}
}

@ -31,6 +31,33 @@ namespace MWClass
std::string getModel(const MWWorld::ConstPtr& ptr) const override;
};
class ESM4Static : public MWWorld::RegisteredClass<ESM4Static>
{
friend MWWorld::RegisteredClass<ESM4Static>;
ESM4Static();
MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const override;
public:
void insertObjectRendering(const MWWorld::Ptr& ptr, const std::string& model,
MWRender::RenderingInterface& renderingInterface) const override;
///< Add reference into a cell for rendering
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation,
MWPhysics::PhysicsSystem& physics) const override;
void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation,
MWPhysics::PhysicsSystem& physics) const override;
std::string_view getName(const MWWorld::ConstPtr& ptr) const override;
///< \return name or ID; can return an empty string.
bool hasToolTip(const MWWorld::ConstPtr& ptr) const override;
///< @return true if this object has a tooltip when focused (default implementation: true)
std::string getModel(const MWWorld::ConstPtr& ptr) const override;
};
}
#endif

@ -705,7 +705,7 @@ namespace MWGui
ESM::Position markedPosition;
MWBase::Environment::get().getWorld()->getPlayer().getMarkedPosition(markedCell, markedPosition);
if (markedCell && markedCell->isExterior() == !mInterior
&& (!mInterior || Misc::StringUtils::ciEqual(markedCell->getCell()->mName, mPrefix)))
&& (!mInterior || Misc::StringUtils::ciEqual(markedCell->getCell()->getNameId(), mPrefix)))
{
MarkerUserData markerPos(mLocalMapRender);
MyGUI::ImageBox* markerWidget = mLocalMap->createWidget<MyGUI::ImageBox>("ImageBox",

@ -200,7 +200,7 @@ namespace MWGui
MWWorld::Ptr player = world->getPlayerPtr();
if (mSleeping && player.getCell()->isExterior())
{
const ESM::RefId& regionstr = player.getCell()->getCell()->mRegion;
const ESM::RefId& regionstr = player.getCell()->getCell()->getRegion();
if (!regionstr.empty())
{
const ESM::Region* region = world->getStore().get<ESM::Region>().find(regionstr);

@ -954,20 +954,21 @@ namespace MWGui
mMap->setCellName(name);
mHud->setCellName(name);
auto cellCommon = cell->getCell();
if (cell->getCell()->isExterior())
if (cellCommon->isExterior())
{
if (!cell->getCell()->mName.empty())
mMap->addVisitedLocation(name, cell->getCell()->getGridX(), cell->getCell()->getGridY());
if (!cellCommon->getNameId().empty())
mMap->addVisitedLocation(name, cellCommon->getGridX(), cellCommon->getGridY());
mMap->cellExplored(cell->getCell()->getGridX(), cell->getCell()->getGridY());
mMap->cellExplored(cellCommon->getGridX(), cellCommon->getGridY());
setActiveMap(cell->getCell()->getGridX(), cell->getCell()->getGridY(), false);
setActiveMap(cellCommon->getGridX(), cellCommon->getGridY(), false);
}
else
{
mMap->setCellPrefix(cell->getCell()->mName);
mHud->setCellPrefix(cell->getCell()->mName);
mMap->setCellPrefix(std::string(cellCommon->getNameId()));
mHud->setCellPrefix(std::string(cellCommon->getNameId()));
osg::Vec3f worldPos;
if (!MWBase::Environment::get().getWorld()->findInteriorPositionInWorldSpace(cell, worldPos))

@ -1,5 +1,6 @@
#include "luabindings.hpp"
#include <components/esm/esmbridge.hpp>
#include <components/esm/records.hpp>
#include "../mwworld/cellstore.hpp"
@ -29,35 +30,35 @@ namespace MWLua
cellT[sol::meta_function::equal_to] = [](const CellT& a, const CellT& b) { return a.mStore == b.mStore; };
cellT[sol::meta_function::to_string] = [](const CellT& c) {
const ESM::Cell* cell = c.mStore->getCell();
auto cell = c.mStore->getCell();
std::stringstream res;
if (cell->isExterior())
res << "exterior(" << cell->getGridX() << ", " << cell->getGridY() << ")";
else
res << "interior(" << cell->mName << ")";
res << "interior(" << cell->getNameId() << ")";
return res.str();
};
cellT["name"] = sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->mName; });
cellT["name"] = sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->getNameId(); });
cellT["region"]
= sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->mRegion.getRefIdString(); });
= sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->getRegion().getRefIdString(); });
cellT["gridX"] = sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->getGridX(); });
cellT["gridY"] = sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->getGridY(); });
cellT["hasWater"] = sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->hasWater(); });
cellT["hasSky"] = sol::readonly_property([](const CellT& c) {
return c.mStore->getCell()->isExterior() || (c.mStore->getCell()->mData.mFlags & ESM::Cell::QuasiEx) != 0;
return c.mStore->getCell()->isExterior() || (c.mStore->getCell()->isQuasiExterior()) != 0;
});
cellT["isExterior"] = sol::readonly_property([](const CellT& c) { return c.mStore->isExterior(); });
// deprecated, use cell:hasTag("QuasiExterior") instead
cellT["isQuasiExterior"] = sol::readonly_property(
[](const CellT& c) { return (c.mStore->getCell()->mData.mFlags & ESM::Cell::QuasiEx) != 0; });
cellT["isQuasiExterior"]
= sol::readonly_property([](const CellT& c) { return (c.mStore->getCell()->isQuasiExterior()) != 0; });
cellT["hasTag"] = [](const CellT& c, std::string_view tag) -> bool {
if (tag == "NoSleep")
return (c.mStore->getCell()->mData.mFlags & ESM::Cell::NoSleep) != 0;
return (c.mStore->getCell()->noSleep()) != 0;
else if (tag == "QuasiExterior")
return (c.mStore->getCell()->mData.mFlags & ESM::Cell::QuasiEx) != 0;
return (c.mStore->getCell()->isQuasiExterior()) != 0;
return false;
};

@ -176,12 +176,12 @@ namespace MWLua
// TODO: change AiEscort implementation to accept ptr instead of a non-unique refId.
const ESM::RefId& refId = target.ptr().getCellRef().getRefId();
int gameHoursDuration = static_cast<int>(std::ceil(duration / 3600.0));
const ESM::Cell* esmCell = cell.mStore->getCell();
auto* esmCell = cell.mStore->getCell();
if (esmCell->isExterior())
ai.stack(MWMechanics::AiEscort(refId, gameHoursDuration, dest.x(), dest.y(), dest.z(), false), ptr);
else
ai.stack(MWMechanics::AiEscort(
refId, esmCell->mName, gameHoursDuration, dest.x(), dest.y(), dest.z(), false),
refId, esmCell->getNameId(), gameHoursDuration, dest.x(), dest.y(), dest.z(), false),
ptr);
};
selfAPI["_startAiWander"] = [](SelfObject& self, int distance, float duration) {

@ -274,8 +274,8 @@ namespace MWMechanics
.find("fInteriorHeadTrackMult")
->mValue.getFloat();
float maxDistance = fMaxHeadTrackDistance;
const ESM::Cell* currentCell = actor.getCell()->getCell();
if (!currentCell->isExterior() && !(currentCell->mData.mFlags & ESM::Cell::QuasiEx))
auto currentCell = actor.getCell()->getCell();
if (!currentCell->isExterior() && !(currentCell->isQuasiExterior()))
maxDistance *= fInteriorHeadTrackMult;
const osg::Vec3f actor1Pos(actorRefData.getPosition().asVec3());

@ -357,12 +357,11 @@ namespace MWMechanics
case AiCombatStorage::FleeState_Idle:
{
float triggerDist = getMaxAttackDistance(target);
const MWWorld::Cell* cellVariant = storage.mCell->getCell();
if (storage.mLOS && (triggerDist >= 1000 || getDistanceMinusHalfExtents(actor, target) <= triggerDist))
{
const ESM::Pathgrid* pathgrid
= MWBase::Environment::get().getWorld()->getStore().get<ESM::Pathgrid>().search(
*storage.mCell->getCell());
= MWBase::Environment::get().getWorld()->getStore().get<ESM::Pathgrid>().search(*cellVariant);
bool runFallback = true;
@ -370,7 +369,7 @@ namespace MWMechanics
&& !actor.getClass().isPureWaterCreature(actor))
{
ESM::Pathgrid::PointList points;
Misc::CoordinateConverter coords(storage.mCell->getCell());
Misc::CoordinateConverter coords(*storage.mCell->getCell());
osg::Vec3f localPos = actor.getRefData().getPosition().asVec3();
coords.toLocal(localPos);

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

@ -411,11 +411,11 @@ bool MWMechanics::AiPackage::doesPathNeedRecalc(const osg::Vec3f& newDest, const
bool MWMechanics::AiPackage::isNearInactiveCell(osg::Vec3f position)
{
const ESM::Cell* playerCell(getPlayer().getCell()->getCell());
const MWWorld::Cell* playerCell = getPlayer().getCell()->getCell();
if (playerCell->isExterior())
{
// get actor's distance from origin of center cell
Misc::CoordinateConverter(playerCell).toLocal(position);
Misc::CoordinateConverter(*playerCell).toLocal(position);
// currently assumes 3 x 3 grid for exterior cells, with player at center cell.
// AI shuts down actors before they reach edges of 3 x 3 grid.

@ -730,7 +730,7 @@ namespace MWMechanics
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
int index = Misc::Rng::rollDice(storage.mAllowedNodes.size(), prng);
ESM::Pathgrid::Point worldDest = storage.mAllowedNodes[index];
auto converter = Misc::CoordinateConverter(actor.getCell()->getCell());
auto converter = Misc::CoordinateConverter(*actor.getCell()->getCell());
ESM::Pathgrid::Point dest = converter.toLocalPoint(worldDest);
bool isPathGridOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(
@ -811,7 +811,7 @@ namespace MWMechanics
getPathGridGraph(currentCell).getNeighbouringPoints(index, points);
}
void AiWander::getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell, AiWanderStorage& storage)
void AiWander::getAllowedNodes(const MWWorld::Ptr& actor, const MWWorld::Cell* cell, AiWanderStorage& storage)
{
// infrequently used, therefore no benefit in caching it as a member
const ESM::Pathgrid* pathgrid
@ -835,7 +835,7 @@ namespace MWMechanics
if (mDistance && storage.mCanWanderAlongPathGrid && !actor.getClass().isPureWaterCreature(actor))
{
// get NPC's position in local (i.e. cell) coordinates
auto converter = Misc::CoordinateConverter(cell);
auto converter = Misc::CoordinateConverter(*cell);
const osg::Vec3f npcPos = converter.toLocalVec3(mInitialActorPosition);
// Find closest pathgrid point

@ -23,6 +23,10 @@ namespace Misc
class CoordinateConverter;
}
namespace MWWorld
{
class Cell;
}
namespace MWMechanics
{
/// \brief This class holds the variables AiWander needs which are deleted if the package becomes inactive.
@ -147,7 +151,7 @@ namespace MWMechanics
void getNeighbouringNodes(
ESM::Pathgrid::Point dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points);
void getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell, AiWanderStorage& storage);
void getAllowedNodes(const MWWorld::Ptr& actor, const MWWorld::Cell* cell, AiWanderStorage& storage);
void trimAllowedNodes(std::vector<ESM::Pathgrid::Point>& nodes, const PathFinder& pathfinder);

@ -196,7 +196,7 @@ namespace MWMechanics
return;
// NOTE: getClosestPoint expects local coordinates
Misc::CoordinateConverter converter(mCell->getCell());
Misc::CoordinateConverter converter(*mCell->getCell());
// NOTE: It is possible that getClosestPoint returns a pathgrind point index
// that is unreachable in some situations. e.g. actor is standing

@ -105,7 +105,8 @@ namespace MWMechanics
return true;
mCell = cell->getCell();
mPathgrid = MWBase::Environment::get().getWorld()->getStore().get<ESM::Pathgrid>().search(*cell->getCell());
mPathgrid = MWBase::Environment::get().getWorld()->getStore().get<ESM::Pathgrid>().search(*mCell);
if (!mPathgrid)
return false;

@ -13,6 +13,7 @@ namespace ESM
namespace MWWorld
{
class CellStore;
class Cell;
}
namespace MWMechanics
@ -41,7 +42,7 @@ namespace MWMechanics
std::deque<ESM::Pathgrid::Point> aStarSearch(const int start, const int end) const;
private:
const ESM::Cell* mCell;
const MWWorld::Cell* mCell;
const ESM::Pathgrid* mPathgrid;
struct ConnectedPoint // edge

@ -489,7 +489,7 @@ namespace MWMechanics
{
std::string_view dest;
if (!markedCell->isExterior())
dest = markedCell->getCell()->mName;
dest = markedCell->getCell()->getNameId();
MWWorld::ActionTeleport action(dest, markedPosition, false);
action.execute(target);
if (!caster.isEmpty())

@ -2,11 +2,15 @@
#include <algorithm>
#include <components/esm/esmbridge.hpp>
#include <components/esm3/loadcell.hpp>
#include <components/esm4/loadcell.hpp>
#include <components/fallback/fallback.hpp>
#include <components/sceneutil/util.hpp>
#include <components/settings/settings.hpp>
#include <apps/openmw/mwworld/cell.hpp>
namespace
{
float DLLandFogStart;
@ -38,13 +42,14 @@ namespace MWRender
DLInteriorFogEnd = Settings::Manager::getFloat("distant interior fog end", "Fog");
}
void FogManager::configure(float viewDistance, const ESM::Cell* cell)
void FogManager::configure(float viewDistance, const MWWorld::Cell& cell)
{
osg::Vec4f color = SceneUtil::colourFromRGB(cell->mAmbi.mFog);
osg::Vec4f color = SceneUtil::colourFromRGB(cell.getMood().mFogColor);
const float fogDensity = cell.getMood().mFogDensity;
if (mDistantFog)
{
float density = std::max(0.2f, cell->mAmbi.mFogDensity);
float density = std::max(0.2f, fogDensity);
mLandFogStart = DLInteriorFogEnd * (1.0f - density) + DLInteriorFogStart * density;
mLandFogEnd = DLInteriorFogEnd;
mUnderwaterFogStart = DLUnderwaterFogStart;
@ -52,7 +57,7 @@ namespace MWRender
mFogColor = color;
}
else
configure(viewDistance, cell->mAmbi.mFogDensity, mUnderwaterIndoorFog, 1.0f, 0.0f, color);
configure(viewDistance, fogDensity, mUnderwaterIndoorFog, 1.0f, 0.0f, color);
}
void FogManager::configure(float viewDistance, float fogDepth, float underwaterFog, float dlFactor, float dlOffset,

@ -3,9 +3,9 @@
#include <osg/Vec4f>
namespace ESM
namespace MWWorld
{
struct Cell;
class Cell;
}
namespace MWRender
@ -15,7 +15,7 @@ namespace MWRender
public:
FogManager();
void configure(float viewDistance, const ESM::Cell* cell);
void configure(float viewDistance, const MWWorld::Cell& cell);
void configure(float viewDistance, float fogDepth, float underwaterFog, float dlFactor, float dlOffset,
const osg::Vec4f& color);

@ -102,12 +102,13 @@ namespace MWRender
void Pathgrid::enableCellPathgrid(const MWWorld::CellStore* store)
{
MWBase::World* world = MWBase::Environment::get().getWorld();
const ESM::Pathgrid* pathgrid = world->getStore().get<ESM::Pathgrid>().search(*store->getCell());
if (!pathgrid)
return;
osg::Vec3f cellPathGridPos(0, 0, 0);
Misc::CoordinateConverter(store->getCell()).toWorld(cellPathGridPos);
Misc::CoordinateConverter(*store->getCell()).toWorld(cellPathGridPos);
osg::ref_ptr<osg::PositionAttitudeTransform> cellPathGrid = new osg::PositionAttitudeTransform;
cellPathGrid->setPosition(cellPathGridPos);

@ -49,6 +49,7 @@
#include <components/terrain/terraingrid.hpp>
#include <components/esm3/loadcell.hpp>
#include <components/esm4/loadcell.hpp>
#include <components/debug/debugdraw.hpp>
#include <components/detournavigator/navigator.hpp>
@ -720,14 +721,14 @@ namespace MWRender
mSky->setMoonColour(red);
}
void RenderingManager::configureAmbient(const ESM::Cell* cell)
void RenderingManager::configureAmbient(const MWWorld::Cell& cell)
{
bool isInterior = !cell->isExterior() && !(cell->mData.mFlags & ESM::Cell::QuasiEx);
bool isInterior = !cell.isExterior() && !cell.isQuasiExterior();
bool needsAdjusting = false;
if (mResourceSystem->getSceneManager()->getLightingMethod() != SceneUtil::LightingMethod::FFP)
needsAdjusting = isInterior;
auto ambient = SceneUtil::colourFromRGB(cell->mAmbi.mAmbient);
osg::Vec4f ambient = SceneUtil::colourFromRGB(cell.getMood().mAmbiantColor);
if (needsAdjusting)
{
@ -751,7 +752,8 @@ namespace MWRender
setAmbientColour(ambient);
osg::Vec4f diffuse = SceneUtil::colourFromRGB(cell->mAmbi.mSunlight);
osg::Vec4f diffuse = SceneUtil::colourFromRGB(cell.getMood().mDirectionalColor);
setSunColour(diffuse, diffuse, 1.f);
const osg::Vec4f interiorSunPos = osg::Vec4f(-0.15f, 0.15f, 1.f, 0.f);
@ -871,7 +873,7 @@ namespace MWRender
return false;
}
void RenderingManager::configureFog(const ESM::Cell* cell)
void RenderingManager::configureFog(const MWWorld::Cell& cell)
{
mFog->configure(mViewDistance, cell);
}
@ -1405,7 +1407,7 @@ namespace MWRender
mMinimumAmbientLuminance
= std::clamp(Settings::Manager::getFloat("minimum interior brightness", "Shaders"), 0.f, 1.f);
if (MWMechanics::getPlayer().isInCell())
configureAmbient(MWMechanics::getPlayer().getCell()->getCell());
configureAmbient(*MWMechanics::getPlayer().getCell()->getCell());
}
else if (it->first == "Shaders"
&& (it->second == "light bounds multiplier" || it->second == "maximum light distance"

@ -73,6 +73,7 @@ namespace DetourNavigator
namespace MWWorld
{
class GroundcoverStore;
class Cell;
}
namespace Debug
@ -140,8 +141,8 @@ namespace MWRender
void setSunColour(const osg::Vec4f& diffuse, const osg::Vec4f& specular, float sunVis);
void setNight(bool isNight) { mNight = isNight; }
void configureAmbient(const ESM::Cell* cell);
void configureFog(const ESM::Cell* cell);
void configureAmbient(const MWWorld::Cell& cell);
void configureFog(const MWWorld::Cell& cell);
void configureFog(
float fogDepth, float underwaterFog, float dlFactor, float dlOffset, const osg::Vec4f& colour);

@ -744,7 +744,8 @@ namespace MWRender
bool wasInterior = mInterior;
if (!isInterior)
{
mWaterNode->setPosition(getSceneNodeCoordinates(store->getCell()->mData.mX, store->getCell()->mData.mY));
mWaterNode->setPosition(
getSceneNodeCoordinates(store->getCell()->getGridX(), store->getCell()->getGridY()));
mInterior = false;
}
else

@ -766,14 +766,14 @@ namespace MWSound
{
MWBase::World* world = MWBase::Environment::get().getWorld();
const MWWorld::ConstPtr player = world->getPlayerPtr();
const ESM::Cell* cell = player.getCell()->getCell();
auto cell = player.getCell()->getCell();
if (!cell->isExterior())
return;
if (mCurrentRegionSound && mOutput->isSoundPlaying(mCurrentRegionSound))
return;
if (const auto next = mRegionSoundSelector.getNextRandom(duration, cell->mRegion, *world))
if (const auto next = mRegionSoundSelector.getNextRandom(duration, cell->getRegion(), *world))
mCurrentRegionSound = playSound(*next, 1.0f, 1.0f);
}
@ -781,7 +781,8 @@ namespace MWSound
{
MWBase::World* world = MWBase::Environment::get().getWorld();
const MWWorld::ConstPtr player = world->getPlayerPtr();
const ESM::Cell* curcell = player.getCell()->getCell();
const MWWorld::Cell* curcell = player.getCell()->getCell();
const auto update = mWaterSoundUpdater.update(player, *world);
WaterSoundAction action;
@ -810,7 +811,7 @@ namespace MWSound
}
std::pair<SoundManager::WaterSoundAction, Sound_Buffer*> SoundManager::getWaterSoundAction(
const WaterSoundUpdate& update, const ESM::Cell* cell) const
const WaterSoundUpdate& update, const MWWorld::Cell* cell) const
{
if (mNearWaterSound)
{

@ -30,6 +30,11 @@ namespace ESM
struct Cell;
}
namespace MWWorld
{
class Cell;
}
namespace MWSound
{
class Sound_Output;
@ -107,7 +112,7 @@ namespace MWSound
float mTimePassed;
const ESM::Cell* mLastCell;
const MWWorld::Cell* mLastCell;
Sound* mCurrentRegionSound;
@ -143,7 +148,7 @@ namespace MWSound
};
std::pair<WaterSoundAction, Sound_Buffer*> getWaterSoundAction(
const WaterSoundUpdate& update, const ESM::Cell* cell) const;
const WaterSoundUpdate& update, const MWWorld::Cell* cell) const;
SoundManager(const SoundManager& rhs);
SoundManager& operator=(const SoundManager& rhs);

@ -0,0 +1,61 @@
#include "cell.hpp"
#include <components/esm3/loadcell.hpp>
#include <components/esm4/loadcell.hpp>
#include <components/misc/algorithm.hpp>
namespace MWWorld
{
Cell::Cell(const ESM4::Cell& cell)
: ESM::CellVariant(cell)
, mIsExterior(!(cell.mCellFlags & ESM4::CELL_Interior))
, mIsQuasiExterior(cell.mCellFlags & ESM4::CELL_QuasiExt)
, mHasWater(cell.mCellFlags & ESM4::CELL_HasWater)
, mNoSleep(false) // No such notion in ESM4
, mGridPos(cell.mX, cell.mY)
, mDisplayname(cell.mFullName)
, mNameID(cell.mEditorId)
, mRegion(ESM::RefId::sEmpty) // Unimplemented for now
, mCellId{
.mWorldspace{ Misc::StringUtils::lowerCase(cell.mEditorId) },
.mIndex{ cell.getGridX(), cell.getGridY() },
.mPaged = isExterior(),}
,mMood{
.mAmbiantColor = cell.mLighting.ambient,
.mDirectionalColor = cell.mLighting.directional,
.mFogColor = cell.mLighting.fogColor,
.mFogDensity = cell.mLighting.fogPower,}
,mWaterHeight(cell.mWaterHeight)
{
}
Cell::Cell(const ESM::Cell& cell)
: ESM::CellVariant(cell)
, mIsExterior(!(cell.mData.mFlags & ESM::Cell::Interior))
, mIsQuasiExterior(cell.mData.mFlags & ESM::Cell::QuasiEx)
, mHasWater(cell.mData.mFlags & ESM::Cell::HasWater)
, mNoSleep(cell.mData.mFlags & ESM::Cell::NoSleep)
, mGridPos(cell.getGridX(), cell.getGridY())
, mDisplayname(cell.mName)
, mNameID(cell.mName)
, mRegion(ESM::RefId::sEmpty) // Unimplemented for now
, mCellId(cell.getCellId())
, mMood{
.mAmbiantColor = cell.mAmbi.mAmbient,
.mDirectionalColor = cell.mAmbi.mSunlight,
.mFogColor = cell.mAmbi.mFog,
.mFogDensity = cell.mAmbi.mFogDensity,
}
,mWaterHeight(cell.mWater)
{
}
std::string Cell::getDescription() const
{
return ESM::visit(ESM::VisitOverload{
[&](const ESM::Cell& cell) { return cell.getDescription(); },
[&](const ESM4::Cell& cell) { return cell.mEditorId; },
},
*this);
}
}

@ -0,0 +1,70 @@
#ifndef OPENW_MWORLD_CELL
#define OPENW_MWORLD_CELL
#include <osg/Vec2i>
#include <components/esm/esmbridge.hpp>
#include <components/esm/refid.hpp>
#include <components/esm3/cellid.hpp>
namespace ESM
{
struct Cell;
struct CellId;
}
namespace ESM4
{
struct Cell;
}
namespace MWWorld
{
class CellStore;
class Cell : public ESM::CellVariant
{
struct MoodData
{
uint32_t mAmbiantColor;
uint32_t mDirectionalColor;
uint32_t mFogColor;
float mFogDensity;
};
public:
explicit Cell(const ESM4::Cell& cell);
explicit Cell(const ESM::Cell& cell);
int getGridX() const { return mGridPos.x(); }
int getGridY() const { return mGridPos.y(); }
bool isExterior() const { return mIsExterior; }
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; }
private:
bool mIsExterior;
bool mIsQuasiExterior;
bool mHasWater;
bool mNoSleep;
osg::Vec2i mGridPos;
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;
MoodData mMood;
float mWaterHeight;
};
}
#endif

@ -7,45 +7,97 @@
namespace MWWorld
{
CellRef::CellRef(const ESM::CellRef& ref)
: mCellRef(ESM::ReferenceVariant(ref))
{
}
CellRef::CellRef(const ESM4::Reference& ref)
: mCellRef(ESM::ReferenceVariant(ref))
{
}
static const ESM::RefNum emptyRefNum = {};
const ESM::RefNum& CellRef::getRefNum() const
{
return std::visit(ESM::VisitOverload{
[&](const ESM4::Reference& /*ref*/) -> const ESM::RefNum& { return emptyRefNum; },
[&](const ESM::CellRef& ref) -> const ESM::RefNum& { return ref.mRefNum; },
},
mCellRef.mVariant);
}
const ESM::RefNum& CellRef::getOrAssignRefNum(ESM::RefNum& lastAssignedRefNum)
{
if (!mCellRef.mRefNum.isSet())
{
// Generated RefNums have negative mContentFile
assert(lastAssignedRefNum.mContentFile < 0);
lastAssignedRefNum.mIndex++;
if (lastAssignedRefNum.mIndex == 0) // mIndex overflow, so mContentFile should be changed
auto esm3Visit = [&](ESM::CellRef& ref) -> const ESM::RefNum& {
if (!ref.mRefNum.isSet())
{
if (lastAssignedRefNum.mContentFile > std::numeric_limits<int32_t>::min())
lastAssignedRefNum.mContentFile--;
else
Log(Debug::Error) << "RefNum counter overflow in CellRef::getOrAssignRefNum";
// Generated RefNums have negative mContentFile
assert(lastAssignedRefNum.mContentFile < 0);
lastAssignedRefNum.mIndex++;
if (lastAssignedRefNum.mIndex == 0) // mIndex overflow, so mContentFile should be changed
{
if (lastAssignedRefNum.mContentFile > std::numeric_limits<int32_t>::min())
lastAssignedRefNum.mContentFile--;
else
Log(Debug::Error) << "RefNum counter overflow in CellRef::getOrAssignRefNum";
}
ref.mRefNum = lastAssignedRefNum;
mChanged = true;
}
mCellRef.mRefNum = lastAssignedRefNum;
mChanged = true;
}
return mCellRef.mRefNum;
return ref.mRefNum;
};
return std::visit(
ESM::VisitOverload{
[&](ESM4::Reference& /*ref*/) -> const ESM::RefNum& { return emptyRefNum; },
esm3Visit,
},
mCellRef.mVariant);
}
void CellRef::unsetRefNum()
{
mCellRef.mRefNum = ESM::RefNum{};
std::visit(ESM::VisitOverload{
[&](ESM4::Reference& /*ref*/) {},
[&](ESM::CellRef& ref) { ref.mRefNum = emptyRefNum; },
},
mCellRef.mVariant);
}
static const std::string emptyString = "";
const std::string& CellRef::getDestCell() 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; },
},
mCellRef.mVariant);
}
void CellRef::setScale(float scale)
{
if (scale != mCellRef.mScale)
if (scale != getScale())
{
mChanged = true;
mCellRef.mScale = scale;
std::visit([scale](auto&& ref) { ref.mScale = scale; }, mCellRef.mVariant);
}
}
void CellRef::setPosition(const ESM::Position& position)
{
mChanged = true;
mCellRef.mPos = position;
std::visit([&position](auto&& ref) { ref.mPos = position; }, mCellRef.mVariant);
}
float CellRef::getEnchantmentCharge() const
{
return std::visit(ESM::VisitOverload{
[&](const ESM4::Reference& /*ref*/) { return 0.f; },
[&](const ESM::CellRef& ref) { return ref.mEnchantmentCharge; },
},
mCellRef.mVariant);
}
float CellRef::getNormalizedEnchantmentCharge(int maxCharge) const
@ -54,112 +106,149 @@ namespace MWWorld
{
return 0;
}
else if (mCellRef.mEnchantmentCharge == -1)
else if (getEnchantmentCharge() == -1)
{
return 1;
}
else
{
return mCellRef.mEnchantmentCharge / static_cast<float>(maxCharge);
return getEnchantmentCharge() / static_cast<float>(maxCharge);
}
}
void CellRef::setEnchantmentCharge(float charge)
{
if (charge != mCellRef.mEnchantmentCharge)
if (charge != getEnchantmentCharge())
{
mChanged = true;
mCellRef.mEnchantmentCharge = charge;
std::visit(ESM::VisitOverload{
[&](ESM4::Reference& /*ref*/) {},
[&](ESM::CellRef& ref) { ref.mEnchantmentCharge = charge; },
},
mCellRef.mVariant);
}
}
void CellRef::setCharge(int charge)
{
if (charge != mCellRef.mChargeInt)
{
mChanged = true;
mCellRef.mChargeInt = charge;
}
std::visit(ESM::VisitOverload{
[&](ESM4::Reference& /*ref*/) {},
[&](ESM::CellRef& ref) { ref.mChargeInt = charge; },
},
mCellRef.mVariant);
}
void CellRef::applyChargeRemainderToBeSubtracted(float chargeRemainder)
{
mCellRef.mChargeIntRemainder += std::abs(chargeRemainder);
if (mCellRef.mChargeIntRemainder > 1.0f)
{
float newChargeRemainder = (mCellRef.mChargeIntRemainder - std::floor(mCellRef.mChargeIntRemainder));
if (mCellRef.mChargeInt <= static_cast<int>(mCellRef.mChargeIntRemainder))
{
mCellRef.mChargeInt = 0;
}
else
auto esm3Visit = [&](ESM::CellRef& cellRef3) {
cellRef3.mChargeIntRemainder += std::abs(chargeRemainder);
if (cellRef3.mChargeIntRemainder > 1.0f)
{
mCellRef.mChargeInt -= static_cast<int>(mCellRef.mChargeIntRemainder);
float newChargeRemainder = (cellRef3.mChargeIntRemainder - std::floor(cellRef3.mChargeIntRemainder));
if (cellRef3.mChargeInt <= static_cast<int>(cellRef3.mChargeIntRemainder))
{
cellRef3.mChargeInt = 0;
}
else
{
cellRef3.mChargeInt -= static_cast<int>(cellRef3.mChargeIntRemainder);
}
cellRef3.mChargeIntRemainder = newChargeRemainder;
}
mCellRef.mChargeIntRemainder = newChargeRemainder;
}
};
std::visit(
ESM::VisitOverload{
[&](ESM4::Reference& /*ref*/) {},
esm3Visit,
},
mCellRef.mVariant);
}
void CellRef::setChargeFloat(float charge)
{
if (charge != mCellRef.mChargeFloat)
{
mChanged = true;
mCellRef.mChargeFloat = charge;
}
std::visit(ESM::VisitOverload{
[&](ESM4::Reference& /*ref*/) {},
[&](ESM::CellRef& ref) { ref.mChargeFloat = charge; },
},
mCellRef.mVariant);
}
const std::string& CellRef::getGlobalVariable() const
{
return std::visit(ESM::VisitOverload{
[&](const ESM4::Reference& /*ref*/) -> const std::string& { return emptyString; },
[&](const ESM::CellRef& ref) -> const std::string& { return ref.mGlobalVariable; },
},
mCellRef.mVariant);
}
void CellRef::resetGlobalVariable()
{
if (!mCellRef.mGlobalVariable.empty())
if (!getGlobalVariable().empty())
{
mChanged = true;
mCellRef.mGlobalVariable.erase();
std::visit(ESM::VisitOverload{
[&](ESM4::Reference& /*ref*/) {},
[&](ESM::CellRef& ref) { ref.mGlobalVariable.erase(); },
},
mCellRef.mVariant);
}
}
void CellRef::setFactionRank(int factionRank)
{
if (factionRank != mCellRef.mFactionRank)
if (factionRank != getFactionRank())
{
mChanged = true;
mCellRef.mFactionRank = factionRank;
std::visit([&](auto&& ref) { ref.mFactionRank = factionRank; }, mCellRef.mVariant);
}
}
void CellRef::setOwner(const ESM::RefId& owner)
{
if (owner != mCellRef.mOwner)
if (owner != getOwner())
{
mChanged = true;
mCellRef.mOwner = owner;
std::visit(ESM::VisitOverload{
[&](ESM4::Reference& /*ref*/) {},
[&](ESM::CellRef& ref) { ref.mOwner = owner; },
},
mCellRef.mVariant);
}
}
void CellRef::setSoul(const ESM::RefId& soul)
{
if (soul != mCellRef.mSoul)
if (soul != getSoul())
{
mChanged = true;
mCellRef.mSoul = soul;
std::visit(ESM::VisitOverload{
[&](ESM4::Reference& /*ref*/) {},
[&](ESM::CellRef& ref) { ref.mSoul = soul; },
},
mCellRef.mVariant);
}
}
void CellRef::setFaction(const ESM::RefId& faction)
{
if (faction != mCellRef.mFaction)
if (faction != getFaction())
{
mChanged = true;
mCellRef.mFaction = faction;
std::visit(ESM::VisitOverload{
[&](ESM4::Reference& /*ref*/) {},
[&](ESM::CellRef& ref) { ref.mFaction = faction; },
},
mCellRef.mVariant);
}
}
void CellRef::setLockLevel(int lockLevel)
{
if (lockLevel != mCellRef.mLockLevel)
if (lockLevel != getLockLevel())
{
mChanged = true;
mCellRef.mLockLevel = lockLevel;
std::visit([&](auto&& ref) { ref.mLockLevel = lockLevel; }, mCellRef.mVariant);
}
}
@ -173,30 +262,41 @@ namespace MWWorld
void CellRef::unlock()
{
setLockLevel(-abs(mCellRef.mLockLevel)); // Makes lockLevel negative
setLockLevel(-abs(getLockLevel())); // Makes lockLevel negative
}
void CellRef::setTrap(const ESM::RefId& trap)
{
if (trap != mCellRef.mTrap)
if (trap != getTrap())
{
mChanged = true;
mCellRef.mTrap = trap;
std::visit(ESM::VisitOverload{
[&](ESM4::Reference& /*ref*/) {},
[&](ESM::CellRef& ref) { ref.mTrap = trap; },
},
mCellRef.mVariant);
}
}
void CellRef::setGoldValue(int value)
{
if (value != mCellRef.mGoldValue)
if (value != getGoldValue())
{
mChanged = true;
mCellRef.mGoldValue = value;
std::visit(ESM::VisitOverload{
[&](ESM4::Reference& /*ref*/) {},
[&](ESM::CellRef& ref) { ref.mGoldValue = value; },
},
mCellRef.mVariant);
}
}
void CellRef::writeState(ESM::ObjectState& state) const
{
state.mRef = mCellRef;
std::visit(ESM::VisitOverload{
[&](const ESM4::Reference& /*ref*/) {},
[&](const ESM::CellRef& ref) { state.mRef = ref; },
},
mCellRef.mVariant);
}
}

@ -3,7 +3,9 @@
#include <string_view>
#include <components/esm/esmbridge.hpp>
#include <components/esm3/cellref.hpp>
#include <components/esm4/loadrefr.hpp>
namespace ESM
{
@ -16,15 +18,14 @@ namespace MWWorld
/// \brief Encapsulated variant of ESM::CellRef with change tracking
class CellRef
{
protected:
public:
CellRef(const ESM::CellRef& ref)
: mCellRef(ref)
{
mChanged = false;
}
explicit CellRef(const ESM::CellRef& ref);
explicit CellRef(const ESM4::Reference& ref);
// Note: Currently unused for items in containers
const ESM::RefNum& getRefNum() const { return mCellRef.mRefNum; }
const ESM::RefNum& getRefNum() const;
// Returns RefNum.
// If RefNum is not set, assigns a generated one and changes the "lastAssignedRefNum" counter.
@ -34,32 +35,62 @@ namespace MWWorld
void unsetRefNum();
/// Does the RefNum have a content file?
bool hasContentFile() const { return mCellRef.mRefNum.hasContentFile(); }
bool hasContentFile() const { return getRefNum().hasContentFile(); }
// Id of object being referenced
const ESM::RefId& getRefId() const { return mCellRef.mRefID; }
const ESM::RefId& getRefId() const
{
struct Visitor
{
const ESM::RefId& operator()(const ESM::CellRef& ref) { return ref.mRefID; }
const ESM::RefId& operator()(const ESM4::Reference& ref) { return ref.mBaseObj; }
};
return std::visit(Visitor(), mCellRef.mVariant);
}
// For doors - true if this door teleports to somewhere else, false
// if it should open through animation.
bool getTeleport() const { return mCellRef.mTeleport; }
bool getTeleport() const
{
struct Visitor
{
bool operator()(const ESM::CellRef& ref) { return ref.mTeleport; }
bool operator()(const ESM4::Reference& ref) { return 0; }
};
return std::visit(Visitor(), mCellRef.mVariant);
}
// Teleport location for the door, if this is a teleporting door.
const ESM::Position& getDoorDest() const { return mCellRef.mDoorDest; }
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);
}
// Destination cell for doors (optional)
const std::string& getDestCell() const { return mCellRef.mDestCell; }
const std::string& getDestCell() const;
// Scale applied to mesh
float getScale() const { return mCellRef.mScale; }
float getScale() const
{
return std::visit([&](auto&& ref) { return ref.mScale; }, mCellRef.mVariant);
}
void setScale(float scale);
// The *original* position and rotation as it was given in the Construction Set.
// Current position and rotation of the object is stored in RefData.
const ESM::Position& getPosition() const { return mCellRef.mPos; }
const ESM::Position& getPosition() const
{
return std::visit([](auto&& ref) -> const ESM::Position& { return ref.mPos; }, mCellRef.mVariant);
}
void setPosition(const ESM::Position& position);
// Remaining enchantment charge. This could be -1 if the charge was not touched yet (i.e. full).
float getEnchantmentCharge() const { return mCellRef.mEnchantmentCharge; }
float getEnchantmentCharge() const;
// Remaining enchantment charge rescaled to the supplied maximum charge (such as one of the enchantment).
float getNormalizedEnchantmentCharge(int maxCharge) const;
@ -69,50 +100,115 @@ namespace MWWorld
// For weapon or armor, this is the remaining item health.
// For tools (lockpicks, probes, repair hammer) it is the remaining uses.
// If this returns int(-1) it means full health.
int getCharge() const { return mCellRef.mChargeInt; }
float getChargeFloat() const { return mCellRef.mChargeFloat; } // Implemented as union with int charge
int getCharge() const
{
struct Visitor
{
int operator()(const ESM::CellRef& ref) { return ref.mChargeInt; }
int operator()(const ESM4::Reference& /*ref*/) { return 0; }
};
return std::visit(Visitor(), mCellRef.mVariant);
}
float getChargeFloat() const
{
struct Visitor
{
float operator()(const ESM::CellRef& ref) { return ref.mChargeFloat; }
float operator()(const ESM4::Reference& /*ref*/) { return 0; }
};
return std::visit(Visitor(), mCellRef.mVariant);
} // Implemented as union with int charge
void setCharge(int charge);
void setChargeFloat(float charge);
void applyChargeRemainderToBeSubtracted(float chargeRemainder); // Stores remainders and applies if > 1
// The NPC that owns this object (and will get angry if you steal it)
const ESM::RefId& getOwner() const { return mCellRef.mOwner; }
const ESM::RefId& getOwner() const
{
struct Visitor
{
const ESM::RefId& operator()(const ESM::CellRef& ref) { return ref.mOwner; }
const ESM::RefId& operator()(const ESM4::Reference& /*ref*/) { return ESM::RefId::sEmpty; }
};
return std::visit(Visitor(), mCellRef.mVariant);
}
void setOwner(const ESM::RefId& owner);
// Name of a global variable. If the global variable is set to '1', using the object is temporarily allowed
// even if it has an Owner field.
// Used by bed rent scripts to allow the player to use the bed for the duration of the rent.
const std::string& getGlobalVariable() const { return mCellRef.mGlobalVariable; }
const std::string& getGlobalVariable() const;
void resetGlobalVariable();
// ID of creature trapped in this soul gem
const ESM::RefId& getSoul() const { return mCellRef.mSoul; }
const ESM::RefId& getSoul() const
{
struct Visitor
{
const ESM::RefId& operator()(const ESM::CellRef& ref) { return ref.mSoul; }
const ESM::RefId& operator()(const ESM4::Reference& /*ref*/) { return ESM::RefId::sEmpty; }
};
return std::visit(Visitor(), mCellRef.mVariant);
}
void setSoul(const ESM::RefId& soul);
// The faction that owns this object (and will get angry if
// you take it and are not a faction member)
const ESM::RefId& getFaction() const { return mCellRef.mFaction; }
const ESM::RefId& getFaction() const
{
struct Visitor
{
const ESM::RefId& operator()(const ESM::CellRef& ref) { return ref.mFaction; }
const ESM::RefId& operator()(const ESM4::Reference& /*ref*/) { return ESM::RefId::sEmpty; }
};
return std::visit(Visitor(), mCellRef.mVariant);
}
void setFaction(const ESM::RefId& faction);
// PC faction rank required to use the item. Sometimes is -1, which means "any rank".
void setFactionRank(int factionRank);
int getFactionRank() const { return mCellRef.mFactionRank; }
int getFactionRank() const
{
return std::visit([&](auto&& ref) { return ref.mFactionRank; }, mCellRef.mVariant);
}
// Lock level for doors and containers
// Positive for a locked door. 0 for a door that was never locked.
// For an unlocked door, it is set to -(previous locklevel)
int getLockLevel() const { return mCellRef.mLockLevel; }
int getLockLevel() const
{
return std::visit([](auto&& ref) { return static_cast<int>(ref.mLockLevel); }, mCellRef.mVariant);
}
void setLockLevel(int lockLevel);
void lock(int lockLevel);
void unlock();
// Key and trap ID names, if any
const ESM::RefId& getKey() const { return mCellRef.mKey; }
const ESM::RefId& getTrap() const { return mCellRef.mTrap; }
const ESM::RefId& getKey() const
{
return std::visit([](auto&& ref) -> const ESM::RefId& { return ref.mKey; }, mCellRef.mVariant);
}
const ESM::RefId& getTrap() const
{
struct Visitor
{
const ESM::RefId& operator()(const ESM::CellRef& ref) { return ref.mTrap; }
const ESM::RefId& operator()(const ESM4::Reference& /*ref*/) { return ESM::RefId::sEmpty; }
};
return std::visit(Visitor(), mCellRef.mVariant);
}
void setTrap(const ESM::RefId& trap);
// This is 5 for Gold_005 references, 100 for Gold_100 and so on.
int getGoldValue() const { return mCellRef.mGoldValue; }
int getGoldValue() const
{
struct Visitor
{
int operator()(const ESM::CellRef& ref) { return ref.mGoldValue; }
int operator()(const ESM4::Reference& /*ref*/) { return 0; }
};
return std::visit(Visitor(), mCellRef.mVariant);
}
void setGoldValue(int value);
// Write the content of this CellRef into the given ObjectState
@ -122,8 +218,8 @@ namespace MWWorld
bool hasChanged() const { return mChanged; }
private:
bool mChanged;
ESM::CellRef mCellRef;
bool mChanged = false;
ESM::ReferenceVariant mCellRef;
};
}

@ -28,6 +28,8 @@ namespace MWWorld
/// all methods are known.
void load(ESM::CellRef& ref, bool deleted, const MWWorld::ESMStore& esmStore);
void load(const ESM4::Reference& ref, bool deleted, const MWWorld::ESMStore& esmStore);
LiveRef& insert(const LiveRef& item)
{
mList.push_back(item);

@ -39,6 +39,8 @@
#include <components/esm3/npcstate.hpp>
#include <components/esm3/objectstate.hpp>
#include <components/esm3/readerscache.hpp>
#include <components/esm4/loadrefr.hpp>
#include <components/esm4/loadstat.hpp>
#include <components/misc/tuplehelpers.hpp>
#include "../mwbase/environment.hpp"
@ -369,6 +371,29 @@ namespace MWWorld
}
}
template <typename X>
void CellRefList<X>::load(const ESM4::Reference& ref, bool deleted, const MWWorld::ESMStore& esmStore)
{
if constexpr (!ESM::isESM4Rec(X::sRecordId))
return;
const MWWorld::Store<X>& store = esmStore.get<X>();
if (const X* ptr = store.search(ref.mBaseObj))
{
LiveRef liveCellRef(ref, ptr);
if (deleted)
liveCellRef.mData.setDeletedByContentFile(true);
mList.push_back(liveCellRef);
}
else
{
Log(Debug::Warning) << "Warning: could not resolve cell reference '" << ref.mId << "'"
<< " (dropping reference)";
}
}
template <typename X>
bool operator==(const LiveCellRef<X>& ref, int pRefnum)
{
@ -503,26 +528,27 @@ namespace MWWorld
return false;
}
CellStore::CellStore(const ESM::Cell* cell, const MWWorld::ESMStore& esmStore, ESM::ReadersCache& readers)
CellStore::CellStore(MWWorld::Cell cell, const MWWorld::ESMStore& esmStore, ESM::ReadersCache& readers)
: mStore(esmStore)
, mReaders(readers)
, mCell(cell)
, mCellVariant(std::move(cell))
, mState(State_Unloaded)
, mHasState(false)
, mLastRespawn(0, 0)
, mCellStoreImp(std::make_unique<CellStoreImp>())
, mRechargingItemsUpToDate(false)
{
std::apply([this](auto&... x) { (CellStoreImp::assignStoreToIndex(*this, x), ...); }, mCellStoreImp->mRefLists);
mWaterLevel = cell->mWater;
mWaterLevel = mCellVariant.getWaterHeight();
}
CellStore::~CellStore() = default;
CellStore::CellStore(CellStore&&) = default;
const ESM::Cell* CellStore::getCell() const
const MWWorld::Cell* CellStore::getCell() const
{
return mCell;
return &mCellVariant;
}
CellStore::State CellStore::getState() const
@ -676,22 +702,20 @@ namespace MWWorld
}
}
void CellStore::listRefs()
void CellStore::listRefs(const ESM::Cell& cell)
{
assert(mCell);
if (mCell->mContextList.empty())
if (cell.mContextList.empty())
return; // this is a dynamically generated cell -> skipping.
// Load references from all plugins that do something with this cell.
for (size_t i = 0; i < mCell->mContextList.size(); i++)
for (size_t i = 0; i < cell.mContextList.size(); i++)
{
try
{
// Reopen the ESM reader and seek to the right position.
const std::size_t index = static_cast<std::size_t>(mCell->mContextList[i].index);
const std::size_t index = static_cast<std::size_t>(cell.mContextList[i].index);
const ESM::ReadersCache::BusyItem reader = mReaders.get(index);
mCell->restore(*reader, i);
cell.restore(*reader, i);
ESM::CellRef ref;
@ -707,8 +731,8 @@ namespace MWWorld
// Don't list reference if it was moved to a different cell.
ESM::MovedCellRefTracker::const_iterator iter
= std::find(mCell->mMovedRefs.begin(), mCell->mMovedRefs.end(), ref.mRefNum);
if (iter != mCell->mMovedRefs.end())
= std::find(cell.mMovedRefs.begin(), cell.mMovedRefs.end(), ref.mRefNum);
if (iter != cell.mMovedRefs.end())
{
continue;
}
@ -724,33 +748,46 @@ namespace MWWorld
}
// List moved references, from separately tracked list.
for (const auto& [ref, deleted] : mCell->mLeasedRefs)
for (const auto& [ref, deleted] : cell.mLeasedRefs)
{
if (!deleted)
mIds.push_back(ref.mRefID);
}
}
std::sort(mIds.begin(), mIds.end());
void CellStore::listRefs(const ESM4::Cell& cell)
{
auto& refs = MWBase::Environment::get().getWorld()->getStore().get<ESM4::Reference>();
for (const auto& ref : refs)
{
if (ref.mParent == cell.mId)
{
mIds.push_back(ref.mBaseObj);
}
}
}
void CellStore::loadRefs()
void CellStore::listRefs()
{
assert(mCell);
ESM::visit([&](auto&& cell) { listRefs(cell); }, mCellVariant);
std::sort(mIds.begin(), mIds.end());
}
if (mCell->mContextList.empty())
void CellStore::loadRefs(const ESM::Cell& cell, std::map<ESM::RefNum, ESM::RefId>& refNumToID)
{
if (cell.mContextList.empty())
return; // this is a dynamically generated cell -> skipping.
std::map<ESM::RefNum, ESM::RefId> refNumToID; // used to detect refID modifications
// Load references from all plugins that do something with this cell.
for (size_t i = 0; i < mCell->mContextList.size(); i++)
for (size_t i = 0; i < cell.mContextList.size(); i++)
{
try
{
// Reopen the ESM reader and seek to the right position.
const std::size_t index = static_cast<std::size_t>(mCell->mContextList[i].index);
const std::size_t index = static_cast<std::size_t>(cell.mContextList[i].index);
const ESM::ReadersCache::BusyItem reader = mReaders.get(index);
mCell->restore(*reader, i);
cell.restore(*reader, i);
ESM::CellRef ref;
// Get each reference in turn
@ -765,8 +802,8 @@ namespace MWWorld
// Don't load reference if it was moved to a different cell.
ESM::MovedCellRefTracker::const_iterator iter
= std::find(mCell->mMovedRefs.begin(), mCell->mMovedRefs.end(), ref.mRefNum);
if (iter != mCell->mMovedRefs.end())
= std::find(cell.mMovedRefs.begin(), cell.mMovedRefs.end(), ref.mRefNum);
if (iter != cell.mMovedRefs.end())
{
continue;
}
@ -780,27 +817,46 @@ namespace MWWorld
<< ": " << e.what();
}
}
// Load moved references, from separately tracked list.
for (const auto& leasedRef : mCell->mLeasedRefs)
for (const auto& leasedRef : cell.mLeasedRefs)
{
ESM::CellRef& ref = const_cast<ESM::CellRef&>(leasedRef.first);
bool deleted = leasedRef.second;
loadRef(ref, deleted, refNumToID);
}
}
void CellStore::loadRefs(const ESM4::Cell& cell, std::map<ESM::RefNum, ESM::RefId>& refNumToID)
{
auto& refs = MWBase::Environment::get().getWorld()->getStore().get<ESM4::Reference>();
for (const auto& ref : refs)
{
if (ref.mParent == cell.mId)
{
loadRef(ref, false);
}
}
}
void CellStore::loadRefs()
{
std::map<ESM::RefNum, ESM::RefId> refNumToID; // used to detect refID modifications
ESM::visit([&](auto&& cell) { loadRefs(cell, refNumToID); }, mCellVariant);
updateMergedRefs();
}
bool CellStore::isExterior() const
{
return mCell->isExterior();
return mCellVariant.isExterior();
}
bool CellStore::isQuasiExterior() const
{
return (mCell->mData.mFlags & ESM::Cell::QuasiEx) != 0;
return mCellVariant.isQuasiExterior();
}
Ptr CellStore::searchInContainer(const ESM::RefId& id)
@ -823,6 +879,18 @@ namespace MWWorld
return Ptr();
}
void CellStore::loadRef(const ESM4::Reference& ref, bool deleted)
{
const MWWorld::ESMStore& store = mStore;
ESM::RecNameInts foundType = static_cast<ESM::RecNameInts>(store.find(ref.mBaseObj));
Misc::tupleForEach(this->mCellStoreImp->mRefLists, [&ref, &deleted, &store, foundType](auto& x) {
recNameSwitcher(
x, foundType, [&ref, &deleted, &store](auto& storeIn) { storeIn.load(ref, deleted, store); });
});
}
void CellStore::loadRef(ESM::CellRef& ref, bool deleted, std::map<ESM::RefNum, ESM::RefId>& refNumToID)
{
const MWWorld::ESMStore& store = mStore;
@ -874,7 +942,7 @@ namespace MWWorld
{
mHasState = true;
if (mCell->mData.mFlags & ESM::Cell::Interior && mCell->mData.mFlags & ESM::Cell::HasWater)
if (!mCellVariant.isExterior() && mCellVariant.hasWater())
mWaterLevel = state.mWaterLevel;
mLastRespawn = MWWorld::TimeStamp(state.mLastRespawn);
@ -882,9 +950,9 @@ namespace MWWorld
void CellStore::saveState(ESM::CellState& state) const
{
state.mId = mCell->getCellId();
state.mId = mCellVariant.getCellId();
if (mCell->mData.mFlags & ESM::Cell::Interior && mCell->mData.mFlags & ESM::Cell::HasWater)
if (!mCellVariant.isExterior() && mCellVariant.hasWater())
state.mWaterLevel = mWaterLevel;
state.mHasFogOfWar = (mFogState.get() ? 1 : 0);
@ -895,7 +963,7 @@ namespace MWWorld
{
if (mFogState.get())
{
mFogState->save(writer, mCell->mData.mFlags & ESM::Cell::Interior);
mFogState->save(writer, !mCellVariant.isExterior());
}
}
@ -999,8 +1067,8 @@ namespace MWWorld
Log(Debug::Warning) << "Warning: Dropping moved ref tag for " << movedRef.getCellRef().getRefId()
<< " (target cell " << movedTo.mWorldspace
<< " 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:
// Note by dropping tag the object will automatically re-appear in its original cell, though
// potentially at inapproriate coordinates. Restore original coordinates:
movedRef.getRefData().setPosition(movedRef.getCellRef().getPosition());
continue;
}
@ -1016,14 +1084,21 @@ namespace MWWorld
}
}
bool operator==(const CellStore& left, const CellStore& right)
struct IsEqualVisitor
{
return left.getCell()->getCellId() == right.getCell()->getCellId();
}
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 operator!=(const CellStore& left, const CellStore& right)
bool CellStore::operator==(const CellStore& right) const
{
return !(left == right);
return ESM::visit(IsEqualVisitor(), this->mCellVariant, right.mCellVariant);
}
void CellStore::setFog(std::unique_ptr<ESM::FogState>&& fog)
@ -1230,4 +1305,5 @@ namespace MWWorld
}
return {};
}
}

@ -11,6 +11,7 @@
#include <typeinfo>
#include <vector>
#include "cell.hpp"
#include "cellreflist.hpp"
#include "livecellref.hpp"
@ -49,6 +50,15 @@ namespace ESM
struct Static;
struct Weapon;
struct BodyPart;
struct CellCommon;
}
namespace ESM4
{
class Reader;
struct Cell;
struct Reference;
struct Static;
}
namespace MWWorld
@ -61,7 +71,9 @@ namespace MWWorld
CellRefList<ESM::Container>, CellRefList<ESM::Creature>, CellRefList<ESM::Door>, CellRefList<ESM::Ingredient>,
CellRefList<ESM::CreatureLevList>, CellRefList<ESM::ItemLevList>, CellRefList<ESM::Light>,
CellRefList<ESM::Lockpick>, CellRefList<ESM::Miscellaneous>, CellRefList<ESM::NPC>, CellRefList<ESM::Probe>,
CellRefList<ESM::Repair>, CellRefList<ESM::Static>, CellRefList<ESM::Weapon>, CellRefList<ESM::BodyPart>>;
CellRefList<ESM::Repair>, CellRefList<ESM::Static>, CellRefList<ESM::Weapon>, CellRefList<ESM::BodyPart>,
CellRefList<ESM4::Static>>;
/// \brief Mutable state of a cell
class CellStore
@ -85,7 +97,7 @@ namespace MWWorld
// Note this is nullptr until the cell is explored to save some memory
std::unique_ptr<ESM::FogState> mFogState;
const ESM::Cell* mCell;
MWWorld::Cell mCellVariant;
State mState;
bool mHasState;
std::vector<ESM::RefId> mIds;
@ -180,11 +192,11 @@ namespace MWWorld
}
/// @param readerList The readers to use for loading of the cell on-demand.
CellStore(const ESM::Cell* cell, const MWWorld::ESMStore& store, ESM::ReadersCache& readers);
CellStore(MWWorld::Cell cell, const MWWorld::ESMStore& store, ESM::ReadersCache& readers);
CellStore(CellStore&&);
~CellStore();
const ESM::Cell* getCell() const;
const MWWorld::Cell* getCell() const;
State getState() const;
@ -331,6 +343,7 @@ namespace MWWorld
// Should be phased out when we have const version of forEach
inline const CellRefList<ESM::Door>& getReadOnlyDoors() const { return get<ESM::Door>(); }
inline const CellRefList<ESM::Static>& getReadOnlyStatics() const { return get<ESM::Static>(); }
inline const CellRefList<ESM4::Static>& getReadOnlyEsm4Statics() const { return get<ESM4::Static>(); }
bool isExterior() const;
@ -366,20 +379,27 @@ namespace MWWorld
Ptr getMovedActor(int actorId) const;
bool operator==(const CellStore& right) const;
private:
/// Run through references and store IDs
void listRefs(const ESM::Cell& cell);
void listRefs(const ESM4::Cell& cell);
void listRefs();
void loadRefs(const ESM::Cell& cell, std::map<ESM::RefNum, ESM::RefId>& refNumToID);
void loadRefs(const ESM4::Cell& cell, std::map<ESM::RefNum, ESM::RefId>& refNumToID);
void loadRefs();
void loadRef(const ESM4::Reference& ref, bool deleted);
void loadRef(ESM::CellRef& ref, bool deleted, std::map<ESM::RefNum, ESM::RefId>& refNumToID);
///< Make case-adjustments to \a ref and insert it into the respective container.
///
/// Invalid \a ref objects are silently dropped.
///
};
bool operator==(const CellStore& left, const CellStore& right);
bool operator!=(const CellStore& left, const CellStore& right);
}
#endif

@ -210,8 +210,8 @@ namespace MWWorld
static bool readRecord(ESM4::Reader& reader, ESMStore& store)
{
return std::apply([&reader](auto&... x) { return (ESMStoreImp::typedReadRecordESM4(reader, x) || ...); },
store.mStoreImp->mStores);
return std::apply(
[&reader](auto&... x) { return (typedReadRecordESM4(reader, x) || ...); }, store.mStoreImp->mStores);
}
};
@ -278,6 +278,7 @@ namespace MWWorld
case ESM::REC_STAT:
case ESM::REC_WEAP:
case ESM::REC_BODY:
case ESM::REC_STAT4:
return true;
break;
}
@ -595,8 +596,8 @@ namespace MWWorld
removeMissingObjects(getWritable<ESM::ItemLevList>());
}
// Leveled lists can be modified by scripts. This removes items that no longer exist (presumably because the plugin
// was removed) from modified lists
// Leveled lists can be modified by scripts. This removes items that no longer exist (presumably because the
// plugin was removed) from modified lists
template <class T>
void ESMStore::removeMissingObjects(Store<T>& store)
{

@ -22,9 +22,16 @@ MWWorld::LiveCellRefBase::LiveCellRefBase(unsigned int type, const ESM::CellRef&
{
}
MWWorld::LiveCellRefBase::LiveCellRefBase(unsigned int type, const ESM4::Reference& cref)
: mClass(&Class::get(type))
, mRef(cref)
, mData(cref)
{
}
void MWWorld::LiveCellRefBase::loadImp(const ESM::ObjectState& state)
{
mRef = state.mRef;
mRef = MWWorld::CellRef(state.mRef);
mData = RefData(state, mData.isDeletedByContentFile());
Ptr ptr(this);

@ -35,6 +35,7 @@ namespace MWWorld
RefData mData;
LiveCellRefBase(unsigned int type, const ESM::CellRef& cref = ESM::CellRef());
LiveCellRefBase(unsigned int type, const ESM4::Reference& cref);
/* Need this for the class to be recognized as polymorphic */
virtual ~LiveCellRefBase() {}
@ -87,7 +88,8 @@ namespace MWWorld
{
if (const LiveCellRef<T>* ref = dynamic_cast<const LiveCellRef<T>*>(value))
return ref;
throw std::runtime_error(makeDynamicCastErrorMessage(value, T::getRecordType()));
throw std::runtime_error(
makeDynamicCastErrorMessage(value, ESM::getRecNameString(T::sRecordId).toStringView()));
}
template <class T>
@ -95,7 +97,8 @@ namespace MWWorld
{
if (LiveCellRef<T>* ref = dynamic_cast<LiveCellRef<T>*>(value))
return ref;
throw std::runtime_error(makeDynamicCastErrorMessage(value, T::getRecordType()));
throw std::runtime_error(
makeDynamicCastErrorMessage(value, ESM::getRecNameString(T::sRecordId).toStringView()));
}
/// A reference to one object (of any type) in a cell.
@ -113,6 +116,12 @@ namespace MWWorld
{
}
LiveCellRef(const ESM4::Reference& cref, const X* b = nullptr)
: LiveCellRefBase(X::sRecordId, cref)
, mBase(b)
{
}
LiveCellRef(const X* b = nullptr)
: LiveCellRefBase(X::sRecordId)
, mBase(b)
@ -130,7 +139,13 @@ namespace MWWorld
void save(ESM::ObjectState& state) const override;
///< Save LiveCellRef state into \a state.
std::string_view getTypeDescription() const override { return X::getRecordType(); }
std::string_view getTypeDescription() const override
{
if constexpr (ESM::isESM4Rec(X::sRecordId))
return ESM::getRecNameString(X::sRecordId).toStringView();
else
return X::getRecordType();
}
static bool checkState(const ESM::ObjectState& state);
///< Check if state is valid and report errors.

@ -1,5 +1,6 @@
#include "manualref.hpp"
#include <components/esm/records.hpp>
#include <components/esm4/loadstat.hpp>
#include "esmstore.hpp"
@ -89,7 +90,9 @@ MWWorld::ManualRef::ManualRef(const MWWorld::ESMStore& store, const ESM::RefId&
case ESM::REC_BODY:
create(store.get<ESM::BodyPart>(), name, mRef, mPtr);
break;
case ESM::REC_STAT4:
create(store.get<ESM4::Static>(), name, mRef, mPtr);
break;
case 0:
throw std::logic_error("failed to create manual cell ref for " + name.getRefIdString() + " (unknown ID)");

@ -85,6 +85,19 @@ namespace MWWorld
{
}
RefData::RefData(const ESM4::Reference& cellRef)
: mBaseNode(nullptr)
, mDeletedByContentFile(false)
, mEnabled(true)
, mPhysicsPostponed(false)
, mCount(1)
, mPosition(cellRef.mPos)
, mCustomData(nullptr)
, mChanged(false)
, mFlags(0)
{
}
RefData::RefData(const ESM::ObjectState& objectState, bool deletedByContentFile)
: mBaseNode(nullptr)
, mDeletedByContentFile(deletedByContentFile)

@ -25,6 +25,11 @@ namespace ESM
struct ObjectState;
}
namespace ESM4
{
struct Reference;
}
namespace MWLua
{
class LocalScripts;
@ -76,6 +81,7 @@ namespace MWWorld
/// be altered without affecting the original data. This makes it possible
/// to reset the position as the original data is still held in the CellRef
RefData(const ESM::CellRef& cellRef);
RefData(const ESM4::Reference& cellRef);
RefData(const ESM::ObjectState& objectState, bool deletedByContentFile);
///< Ignores local variables and custom data (not enough context available here to

@ -317,7 +317,6 @@ namespace MWWorld
{
if (mActiveCells.find(cell) == mActiveCells.end())
return;
Log(Debug::Info) << "Unloading cell " << cell->getCell()->getDescription();
ListAndResetObjectsVisitor visitor;
@ -355,8 +354,14 @@ namespace MWWorld
if (cell->getCell()->hasWater())
mNavigator.removeWater(osg::Vec2i(cellX, cellY), navigatorUpdateGuard);
if (const auto pathgrid = mWorld.getStore().get<ESM::Pathgrid>().search(*cell->getCell()))
mNavigator.removePathgrid(*pathgrid);
ESM::visit(ESM::VisitOverload{
[&](const ESM::Cell& cell) {
if (const auto pathgrid = mWorld.getStore().get<ESM::Pathgrid>().search(cell))
mNavigator.removePathgrid(*pathgrid);
},
[&](const ESM4::Cell& cell) {},
},
*cell->getCell());
MWBase::Environment::get().getMechanicsManager()->drop(cell);
@ -384,8 +389,9 @@ namespace MWWorld
const int cellX = cell->getCell()->getGridX();
const int cellY = cell->getCell()->getGridY();
const MWWorld::Cell& cellVariant = *cell->getCell();
if (cell->getCell()->isExterior())
if (cellVariant.isExterior())
{
osg::ref_ptr<const ESMTerrain::LandObject> land = mRendering.getLandManager()->getLand(cellX, cellY);
const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr;
@ -427,8 +433,14 @@ namespace MWWorld
}
}
if (const auto pathgrid = mWorld.getStore().get<ESM::Pathgrid>().search(*cell->getCell()))
mNavigator.addPathgrid(*cell->getCell(), *pathgrid);
ESM::visit(ESM::VisitOverload{
[&](const ESM::Cell& cell) {
if (const auto pathgrid = mWorld.getStore().get<ESM::Pathgrid>().search(cell))
mNavigator.addPathgrid(cell, *pathgrid);
},
[&](const ESM4::Cell& cell) {},
},
*cell->getCell());
// register local scripts
// do this before insertCell, to make sure we don't add scripts from levelled creature spawning twice
@ -442,7 +454,7 @@ namespace MWWorld
mRendering.addCell(cell);
MWBase::Environment::get().getWindowManager()->addCell(cell);
bool waterEnabled = cell->getCell()->hasWater() || cell->isExterior();
bool waterEnabled = cellVariant.hasWater() || cell->isExterior();
float waterLevel = cell->getWaterLevel();
mRendering.setWaterEnabled(waterEnabled);
if (waterEnabled)
@ -450,7 +462,7 @@ namespace MWWorld
mPhysics->enableWater(waterLevel);
mRendering.setWaterHeight(waterLevel);
if (cell->getCell()->isExterior())
if (cellVariant.isExterior())
{
if (const auto heightField = mPhysics->getHeightField(cellX, cellY))
mNavigator.addWater(
@ -465,8 +477,8 @@ namespace MWWorld
else
mPhysics->disableWater();
if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx))
mRendering.configureAmbient(cell->getCell());
if (!cell->isExterior() && !cellVariant.isQuasiExterior())
mRendering.configureAmbient(cellVariant);
mPreloader->notifyLoaded(cell);
}
@ -542,7 +554,7 @@ namespace MWWorld
mNavigator.setWorldspace(
Misc::StringUtils::lowerCase(
mWorld.getWorldModel().getExterior(playerCellX, playerCellY)->getCell()->mCellId.mWorldspace),
mWorld.getWorldModel().getExterior(playerCellX, playerCellY)->getCell()->getCellId().mWorldspace),
navigatorUpdateGuard.get());
mNavigator.updateBounds(pos, navigatorUpdateGuard.get());
@ -665,7 +677,7 @@ namespace MWWorld
CellStore* cell = mWorld.getWorldModel().getExterior(it->mData.mX, it->mData.mY);
mNavigator.setWorldspace(
Misc::StringUtils::lowerCase(cell->getCell()->mCellId.mWorldspace), navigatorUpdateGuard.get());
Misc::StringUtils::lowerCase(cell->getCell()->getCellId().mWorldspace), 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());
@ -723,7 +735,7 @@ namespace MWWorld
CellStore* cell = mWorld.getWorldModel().getInterior(it->mName);
mNavigator.setWorldspace(
Misc::StringUtils::lowerCase(cell->getCell()->mCellId.mWorldspace), navigatorUpdateGuard.get());
Misc::StringUtils::lowerCase(cell->getCell()->getCellId().mWorldspace), navigatorUpdateGuard.get());
ESM::Position position;
mWorld.findInteriorPosition(it->mName, position);
mNavigator.updateBounds(position.asVec3(), navigatorUpdateGuard.get());
@ -739,7 +751,7 @@ namespace MWWorld
{
assert(!(*iter)->getCell()->isExterior());
if (it->mName == (*iter)->getCell()->mName)
if (it->mName == (*iter)->getCell()->getNameId())
{
unloadCell(*iter, navigatorUpdateGuard.get());
break;
@ -880,7 +892,7 @@ namespace MWWorld
loadingListener->setProgressRange(cell->count());
mNavigator.setWorldspace(
Misc::StringUtils::lowerCase(cell->getCell()->mCellId.mWorldspace), navigatorUpdateGuard.get());
Misc::StringUtils::lowerCase(cell->getCell()->getCellId().mWorldspace), navigatorUpdateGuard.get());
mNavigator.updateBounds(position.asVec3(), navigatorUpdateGuard.get());
// Load cell.
@ -892,7 +904,7 @@ namespace MWWorld
changePlayerCell(cell, position, adjustPlayerPos);
// adjust fog
mRendering.configureFog(mCurrentCell->getCell());
mRendering.configureFog(*mCurrentCell->getCell());
// Sky system
mWorld.adjustSky();
@ -907,8 +919,7 @@ namespace MWWorld
MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell);
MWBase::Environment::get().getWorld()->getPostProcessor()->setExteriorFlag(
cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx);
MWBase::Environment::get().getWorld()->getPostProcessor()->setExteriorFlag(cell->getCell()->isQuasiExterior());
}
void Scene::changeToExteriorCell(const ESM::Position& position, bool adjustPlayerPos, bool changeEvent)

@ -14,6 +14,8 @@
#include <components/loadinglistener/loadinglistener.hpp>
#include <components/misc/rng.hpp>
#include <apps/openmw/mwworld/cell.hpp>
namespace
{
// TODO: Switch to C++23 to get a working version of std::unordered_map::erase
@ -961,6 +963,14 @@ namespace MWWorld
else
return search(ESM::RefId::stringRefId(cell.mName));
}
const ESM::Pathgrid* Store<ESM::Pathgrid>::search(const MWWorld::Cell& cellVariant) const
{
return ESM::visit(ESM::VisitOverload{
[&](const ESM::Cell& cell) { return search(cell); },
[&](const ESM4::Cell& cell) -> const ESM::Pathgrid* { return nullptr; },
},
cellVariant);
}
const ESM::Pathgrid* Store<ESM::Pathgrid>::find(const ESM::Cell& cell) const
{
if (!(cell.mData.mFlags & ESM::Cell::Interior))
@ -1170,18 +1180,29 @@ namespace MWWorld
return mKeywordSearch;
}
ESM::FixedString<6> getRecNameString(ESM::RecNameInts recName)
// ESM4 Cell
//=========================================================================
const ESM4::Cell* Store<ESM4::Cell>::searchCellName(std::string_view cellName) const
{
ESM::FixedString<6> name;
name.assign("");
ESM::NAME fourCCName(recName & ~ESM::sEsm4RecnameFlag);
for (int i = 0; i < 4; i++)
name.mData[i] = fourCCName.mData[i];
if (ESM::isESM4Rec(recName))
{
name.mData[4] = '4';
}
return name;
const auto foundCell = mCellNameIndex.find(cellName);
if (foundCell == mCellNameIndex.end())
return nullptr;
return foundCell->second;
}
ESM4::Cell* Store<ESM4::Cell>::insert(const ESM4::Cell& item, bool overrideOnly)
{
auto cellPtr = TypedDynamicStore<ESM4::Cell>::insert(item, overrideOnly);
mCellNameIndex[cellPtr->mEditorId] = cellPtr;
return cellPtr;
}
ESM4::Cell* Store<ESM4::Cell>::insertStatic(const ESM4::Cell& item)
{
auto cellPtr = TypedDynamicStore<ESM4::Cell>::insertStatic(item);
mCellNameIndex[cellPtr->mEditorId] = cellPtr;
return cellPtr;
}
}

@ -15,6 +15,7 @@
#include <components/esm3/loadgmst.hpp>
#include <components/esm3/loadland.hpp>
#include <components/esm3/loadpgrd.hpp>
#include <components/esm4/loadcell.hpp>
#include <components/misc/rng.hpp>
#include <components/misc/strings/algorithm.hpp>
@ -38,6 +39,7 @@ namespace Loading
namespace MWWorld
{
class Cell;
struct RecordId
{
ESM::RefId mId;
@ -268,6 +270,19 @@ namespace MWWorld
const ESM::GameSetting* find(const std::string_view id) const;
const ESM::GameSetting* search(const std::string_view id) const;
};
template <>
class Store<ESM4::Cell> : public TypedDynamicStore<ESM4::Cell>
{
std::unordered_map<std::string, ESM4::Cell*, Misc::StringUtils::CiHash, Misc::StringUtils::CiEqual>
mCellNameIndex;
public:
const ESM4::Cell* searchCellName(std::string_view) const;
ESM4::Cell* insert(const ESM4::Cell& item, bool overrideOnly = false);
ESM4::Cell* insertStatic(const ESM4::Cell& item);
};
template <>
class Store<ESM::Land> : public DynamicStore
{
@ -416,6 +431,7 @@ namespace MWWorld
const ESM::Pathgrid* find(int x, int y) const;
const ESM::Pathgrid* find(const ESM::RefId& name) const;
const ESM::Pathgrid* search(const ESM::Cell& cell) const;
const ESM::Pathgrid* search(const MWWorld::Cell& cell) const;
const ESM::Pathgrid* find(const ESM::Cell& cell) const;
};
@ -519,8 +535,6 @@ namespace MWWorld
const MWDialogue::KeywordSearch<std::string, int>& getDialogIdKeywordSearch() const;
};
ESM::FixedString<6> getRecNameString(ESM::RecNameInts recName);
} // end namespace
#endif

@ -704,7 +704,7 @@ namespace MWWorld
if (!paused || mFastForward)
{
// Add new transitions when either the player's current external region changes.
if (updateWeatherTime() || updateWeatherRegion(player.getCell()->getCell()->mRegion))
if (updateWeatherTime() || updateWeatherRegion(player.getCell()->getCell()->getRegion()))
{
auto it = mRegions.find(mCurrentRegion);
if (it != mRegions.end())

@ -23,6 +23,8 @@
#include <components/esm3/loadmgef.hpp>
#include <components/esm3/loadregn.hpp>
#include <components/esm3/loadstat.hpp>
#include <components/esm4/loadcell.hpp>
#include <components/esm4/loadstat.hpp>
#include <components/misc/constants.hpp>
#include <components/misc/convert.hpp>
@ -631,7 +633,21 @@ namespace MWWorld
{
if (!cell)
cell = mWorldScene->getCurrentCell();
return getCellName(cell->getCell());
return getCellName(*cell->getCell());
}
std::string_view World::getCellName(const MWWorld::Cell& cell) const
{
if (!cell.isExterior() || !cell.getNameId().empty())
return cell.getNameId();
return ESM::visit(ESM::VisitOverload{
[&](const ESM::Cell& cellIn) -> std::string_view { return getCellName(&cellIn); },
[&](const ESM4::Cell& cellIn) -> std::string_view {
return mStore.get<ESM::GameSetting>().find("sDefaultCellname")->mValue.getString();
},
},
cell);
}
std::string_view World::getCellName(const ESM::Cell* cell) const
@ -644,7 +660,6 @@ namespace MWWorld
if (const ESM::Region* region = mStore.get<ESM::Region>().search(cell->mRegion))
return region->mName;
}
return mStore.get<ESM::GameSetting>().find("sDefaultCellname")->mValue.getString();
}
@ -1136,7 +1151,7 @@ namespace MWWorld
{
if (!newCell->isExterior())
{
changeToInteriorCell(newCell->getCell()->mName, pos, false);
changeToInteriorCell(newCell->getCell()->getNameId(), pos, false);
removeContainerScripts(getPlayerPtr());
}
else
@ -1397,7 +1412,7 @@ namespace MWWorld
esmPos.pos[2] = traced.z();
std::string_view cell;
if (!actor.getCell()->isExterior())
cell = actor.getCell()->getCell()->mName;
cell = actor.getCell()->getCell()->getNameId();
MWWorld::ActionTeleport(cell, esmPos, false).execute(actor);
}
}
@ -1985,10 +2000,7 @@ namespace MWWorld
const CellStore* currentCell = mWorldScene->getCurrentCell();
if (currentCell)
{
if (!(currentCell->getCell()->mData.mFlags & ESM::Cell::QuasiEx))
return false;
else
return true;
return currentCell->getCell()->isQuasiExterior();
}
return false;
}
@ -2465,8 +2477,7 @@ namespace MWWorld
|| isFlying(player))
return Rest_PlayerIsInAir;
if ((currentCell->getCell()->mData.mFlags & ESM::Cell::NoSleep)
|| player.getClass().getNpcStats(player).isWerewolf())
if (currentCell->getCell()->noSleep() || player.getClass().getNpcStats(player).isWerewolf())
return Rest_OnlyWaiting;
return Rest_Allowed;
@ -2803,6 +2814,24 @@ namespace MWWorld
}
}
}
for (const MWWorld::LiveCellRef<ESM4::Static>& stat4 : cellStore->getReadOnlyEsm4Statics().mList)
{
if (Misc::StringUtils::lowerCase(stat4.mBase->mEditorId) == "cocmarkerheading")
{
// found the COC position?
pos = stat4.mRef.getPosition();
pos.rot[0] = pos.rot[1] = pos.rot[2] = 0;
return true;
}
}
// Fall back to the first static location.
const MWWorld::CellRefList<ESM4::Static>::List& statics4 = cellStore->getReadOnlyEsm4Statics().mList;
if (!statics4.empty())
{
pos = statics4.begin()->mRef.getPosition();
pos.rot[0] = pos.rot[1] = pos.rot[2] = 0;
return true;
}
// Fall back to the first static location.
const MWWorld::CellRefList<ESM::Static>::List& statics = cellStore->getReadOnlyStatics().mList;
if (!statics.empty())
@ -2819,7 +2848,7 @@ namespace MWWorld
{
pos.rot[0] = pos.rot[1] = pos.rot[2] = 0;
const ESM::Cell* ext = nullptr;
const MWWorld::Cell* ext = nullptr;
try
{
ext = mWorldModel.getCell(nameId)->getCell();
@ -3226,9 +3255,10 @@ namespace MWWorld
}
else
{
uint32_t ambient = cell->getCell()->mAmbi.mAmbient;
const MWWorld::Cell& cellVariant = *cell->getCell();
uint32_t ambient = cellVariant.getMood().mAmbiantColor;
int ambientTotal = (ambient & 0xff) + ((ambient >> 8) & 0xff) + ((ambient >> 16) & 0xff);
return !(cell->getCell()->mData.mFlags & ESM::Cell::NoSleep) && ambientTotal <= 201;
return !cell->getCell()->noSleep() && ambientTotal <= 201;
}
}
@ -3252,7 +3282,7 @@ namespace MWWorld
std::set<std::string_view> checkedCells;
std::set<std::string_view> currentCells;
std::set<std::string_view> nextCells;
nextCells.insert(cell->getCell()->mName);
nextCells.insert(cell->getCell()->getNameId());
while (!nextCells.empty())
{
@ -3307,7 +3337,7 @@ namespace MWWorld
std::set<std::string_view> nextCells;
MWWorld::ConstPtr closestMarker;
nextCells.insert(ptr.getCell()->getCell()->mName);
nextCells.insert(ptr.getCell()->getCell()->getNameId());
while (!nextCells.empty())
{
currentCells = nextCells;
@ -3402,7 +3432,7 @@ namespace MWWorld
std::string_view cellName = "";
if (!closestMarker.mCell->isExterior())
cellName = closestMarker.mCell->getCell()->mName;
cellName = closestMarker.mCell->getCell()->getNameId();
MWWorld::ActionTeleport action(cellName, closestMarker.getRefData().getPosition(), false);
action.execute(ptr);
@ -3415,7 +3445,7 @@ namespace MWWorld
{
mPlayer->setTeleported(false);
const ESM::RefId& playerRegion = getPlayerPtr().getCell()->getCell()->mRegion;
const ESM::RefId& playerRegion = getPlayerPtr().getCell()->getCell()->getRegion();
mWeatherManager->playerTeleported(playerRegion, isExterior);
}

@ -269,6 +269,7 @@ namespace MWWorld
///
/// \note If cell==0, the cell the player is currently in will be used instead to
/// generate a name.
std::string_view getCellName(const MWWorld::Cell& cell) const override;
std::string_view getCellName(const ESM::Cell* cell) const override;
void removeRefScript(MWWorld::RefData* ref) override;

@ -67,7 +67,7 @@ MWWorld::CellStore* MWWorld::WorldModel::getCellStore(const ESM::Cell* cell)
auto result = mInteriors.find(cell->mName);
if (result == mInteriors.end())
result = mInteriors.emplace(cell->mName, CellStore(cell, mStore, mReaders)).first;
result = mInteriors.emplace(cell->mName, CellStore(MWWorld::Cell(*cell), mStore, mReaders)).first;
return &result->second;
}
@ -78,7 +78,8 @@ MWWorld::CellStore* MWWorld::WorldModel::getCellStore(const ESM::Cell* cell)
if (result == mExteriors.end())
result = mExteriors
.emplace(std::make_pair(cell->getGridX(), cell->getGridY()), CellStore(cell, mStore, mReaders))
.emplace(std::make_pair(cell->getGridX(), cell->getGridY()),
CellStore(MWWorld::Cell(*cell), mStore, mReaders))
.first;
return &result->second;
@ -185,7 +186,7 @@ MWWorld::CellStore* MWWorld::WorldModel::getExterior(int x, int y)
cell = MWBase::Environment::get().getWorld()->createRecord(record);
}
result = mExteriors.emplace(std::make_pair(x, y), CellStore(cell, mStore, mReaders)).first;
result = mExteriors.emplace(std::make_pair(x, y), CellStore(MWWorld::Cell(*cell), mStore, mReaders)).first;
}
if (result->second.getState() != CellStore::State_Loaded)
@ -202,9 +203,17 @@ MWWorld::CellStore* MWWorld::WorldModel::getInterior(std::string_view name)
if (result == mInteriors.end())
{
const ESM::Cell* cell = mStore.get<ESM::Cell>().find(name);
const ESM4::Cell* cell4 = mStore.get<ESM4::Cell>().searchCellName(name);
result = mInteriors.emplace(name, CellStore(cell, mStore, mReaders)).first;
if (!cell4)
{
const ESM::Cell* cell = mStore.get<ESM::Cell>().find(name);
result = mInteriors.emplace(name, CellStore(MWWorld::Cell(*cell), mStore, mReaders)).first;
}
else
{
result = mInteriors.emplace(name, CellStore(MWWorld::Cell(*cell4), mStore, mReaders)).first;
}
}
if (result->second.getState() != CellStore::State_Loaded)
@ -253,6 +262,17 @@ const ESM::Cell* MWWorld::WorldModel::getESMCellByName(std::string_view name)
return cell;
}
ESM::CellVariant MWWorld::WorldModel::getCellByName(std::string_view name)
{
const ESM::Cell* cellEsm3 = getESMCellByName(name);
if (!cellEsm3)
{
const ESM4::Cell* cellESM4 = mStore.get<ESM4::Cell>().searchCellName(name);
return ESM::CellVariant(*cellESM4);
}
return ESM::CellVariant(*cellEsm3);
}
MWWorld::CellStore* MWWorld::WorldModel::getCell(std::string_view name)
{
const ESM::Cell* cell = getESMCellByName(name);

@ -5,6 +5,7 @@
#include <map>
#include <string>
#include <unordered_map>
#include <variant>
#include <components/misc/algorithm.hpp>
@ -21,6 +22,11 @@ namespace ESM
struct RefNum;
}
namespace ESM4
{
struct Cell;
}
namespace Loading
{
class Listener;
@ -45,8 +51,9 @@ namespace MWWorld
WorldModel& operator=(const WorldModel&);
const ESM::Cell* getESMCellByName(std::string_view name);
CellStore* getCellStore(const ESM::Cell* cell);
ESM::CellVariant getCellByName(std::string_view name);
CellStore* getCellStore(const ESM::Cell* cell);
Ptr getPtrAndCache(const ESM::RefId& name, CellStore& cellStore);
Ptr getPtr(CellStore& cellStore, const ESM::RefId& id, const ESM::RefNum& refNum);

@ -5,6 +5,7 @@
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/variables_map.hpp>
#include <components/esm/defs.hpp>
#include <components/esm/records.hpp>
#include <components/esm3/esmreader.hpp>
#include <components/esm3/esmwriter.hpp>
@ -316,7 +317,7 @@ static void testRecNameIntCount(const MWWorld::Store<T>& store, const MWWorld::E
const unsigned int recordIdCount
= std::apply([](auto&&... x) { return (hasSameRecordId(x, T::sRecordId) + ...); }, stores);
ASSERT_EQ(recordIdCount, static_cast<unsigned int>(1))
<< "The same RecNameInt is used twice ESM::REC_" << MWWorld::getRecNameString(T::sRecordId).toStringView();
<< "The same RecNameInt is used twice ESM::REC_" << ESM::getRecNameString(T::sRecordId).toStringView();
}
}

@ -80,7 +80,7 @@ add_component_dir (to_utf8
to_utf8
)
add_component_dir(esm attr common defs esmcommon records util luascripts format refid)
add_component_dir(esm attr common defs esmcommon records util luascripts format refid esmbridge)
add_component_dir(fx pass technique lexer widgets stateupdater)

@ -9,6 +9,7 @@
#include <osg/Vec3f>
#include "components/esm/fourcc.hpp"
#include <components/esm/esmcommon.hpp>
#include <components/esm4/common.hpp>
namespace ESM
@ -338,6 +339,20 @@ namespace ESM
return RecName & sEsm4RecnameFlag;
}
inline FixedString<6> getRecNameString(ESM::RecNameInts recName)
{
ESM::FixedString<6> name;
name.assign("");
ESM::NAME fourCCName(recName & ~ESM::sEsm4RecnameFlag);
for (int i = 0; i < 4; i++)
name.mData[i] = fourCCName.mData[i];
if (ESM::isESM4Rec(recName))
{
name.mData[4] = '4';
}
return name;
}
/// Common subrecords
enum SubRecNameInts
{

@ -0,0 +1,18 @@
#include <components/esm/esmbridge.hpp>
#include <components/esm3/loadcell.hpp>
#include <components/esm4/loadcell.hpp>
namespace ESM
{
const ESM4::Cell& CellVariant::getEsm4() const
{
auto cell4 = std::get<const ESM4::Cell*>(mVariant);
return *cell4;
}
const ESM::Cell& CellVariant::getEsm3() const
{
auto cell = std::get<const ESM::Cell*>(mVariant);
return *cell;
}
}

@ -0,0 +1,83 @@
#ifndef COMPONENTS_ESM_ESMBRIDGE
#define COMPONENTS_ESM_ESMBRIDGE
#include <string>
#include <string_view>
#include <variant>
#include <components/esm3/cellref.hpp>
#include <components/esm4/loadrefr.hpp>
namespace ESM4
{
struct Cell;
}
namespace ESM
{
struct Cell;
struct CellId;
struct RefId;
class CellVariant
{
protected:
std::variant<const ESM4::Cell*, const ESM::Cell*> mVariant;
public:
explicit CellVariant(const ESM4::Cell& cell)
: mVariant(&cell)
{
}
explicit CellVariant(const ESM::Cell& cell)
: mVariant(&cell)
{
}
bool isEsm4() const { return std::holds_alternative<const ESM4::Cell*>(mVariant); }
const ESM4::Cell& getEsm4() const;
const ESM::Cell& getEsm3() const;
template <class F, class... T>
friend auto visit(F&& f, T&&... v);
};
struct ReferenceVariant
{
std::variant<ESM::CellRef, ESM4::Reference> mVariant;
explicit ReferenceVariant(const ESM4::Reference& ref)
: mVariant(ref)
{
}
explicit ReferenceVariant(const ESM::CellRef& ref)
: mVariant(ref)
{
}
bool isESM4() const { return std::holds_alternative<ESM4::Reference>(mVariant); }
const ESM::CellRef& getEsm3() const { return std::get<ESM::CellRef>(mVariant); }
const ESM4::Reference& getEsm4() const { return std::get<ESM4::Reference>(mVariant); }
ESM::CellRef& getEsm3() { return std::get<ESM::CellRef>(mVariant); }
ESM4::Reference& getEsm4() { return std::get<ESM4::Reference>(mVariant); }
};
template <class F, class... T>
auto visit(F&& f, T&&... v)
{
return std::visit([&](auto*... ptr) { return std::forward<F>(f)(*ptr...); }, std::forward<T>(v).mVariant...);
}
template <class... Ts>
struct VisitOverload : Ts...
{
using Ts::operator()...;
};
template <class... Ts>
VisitOverload(Ts...) -> VisitOverload<Ts...>;
}
#endif

@ -36,6 +36,7 @@
#include <components/esm/defs.hpp>
#include <components/esm/refid.hpp>
#include <components/esm3/cellid.hpp>
namespace ESM4
{
@ -101,6 +102,10 @@ namespace ESM4
void blank();
static constexpr ESM::RecNameInts sRecordId = ESM::REC_CELL4;
int getGridX() const { return mX; }
int getGridY() const { return mY; }
bool isExterior() const { return !(mCellFlags & CELL_Interior); }
};
}

@ -77,7 +77,7 @@ void ESM4::Reference::load(ESM4::Reader& reader)
break;
}
case ESM4::SUB_DATA:
reader.get(mPlacement);
reader.get(mPos);
break;
case ESM4::SUB_XSCL:
reader.get(mScale);
@ -236,7 +236,9 @@ void ESM4::Reference::load(ESM4::Reader& reader)
reader.get(dummy);
reader.get(dummy);
reader.get(dummy);
reader.getFormId(mKey);
FormId keyForm;
reader.getFormId(keyForm);
mKey = ESM::RefId::formIdRefId(keyForm);
reader.get(dummy); // flag?
reader.get(dummy);
reader.get(dummy);

@ -59,7 +59,7 @@ namespace ESM4
struct TeleportDest
{
FormId destDoor;
Placement destPos;
ESM::Position destPos;
std::uint32_t flags; // 0x01 no alarm (only in TES5)
};
@ -87,7 +87,7 @@ namespace ESM4
std::string mFullName;
ESM::RefId mBaseObj;
Placement mPlacement;
ESM::Position mPos;
float mScale = 1.0f;
FormId mOwner;
FormId mGlobal;
@ -108,7 +108,7 @@ namespace ESM4
TeleportDest mDoor;
bool mIsLocked;
std::int8_t mLockLevel;
FormId mKey;
ESM::RefId mKey;
FormId mTargetRef;

@ -1,9 +1,11 @@
#ifndef OPENMW_COMPONENTS_MISC_COORDINATECONVERTER_H
#define OPENMW_COMPONENTS_MISC_COORDINATECONVERTER_H
#include <components/esm/esmbridge.hpp>
#include <components/esm3/loadcell.hpp>
#include <components/esm3/loadland.hpp>
#include <components/esm3/loadpgrd.hpp>
#include <components/esm4/loadcell.hpp>
namespace Misc
{
@ -17,8 +19,15 @@ namespace Misc
{
}
explicit CoordinateConverter(const ESM::CellVariant& cell)
: CoordinateConverter(cell.isEsm4() ? cell.getEsm4().isExterior() : cell.getEsm3().isExterior(),
cell.isEsm4() ? cell.getEsm4().getGridX() : cell.getEsm3().getGridX(),
cell.isEsm4() ? cell.getEsm4().getGridY() : cell.getEsm3().getGridY())
{
}
explicit CoordinateConverter(const ESM::Cell* cell)
: CoordinateConverter(cell->isExterior(), cell->mData.mX, cell->mData.mY)
: CoordinateConverter(cell->isExterior(), cell->getGridX(), cell->getGridY())
{
}

Loading…
Cancel
Save