From 60a8d08e66d1b96ed800fc140cc504efd9a94227 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Mon, 10 Apr 2023 14:32:24 +0200 Subject: [PATCH 1/2] Refactor teleporting in Lua; fix a bug in worldmodel.cpp --- apps/openmw/mwlua/luamanagerimp.cpp | 5 ++ apps/openmw/mwlua/luamanagerimp.hpp | 2 +- apps/openmw/mwlua/objectbindings.cpp | 97 +++++++++++++--------------- apps/openmw/mwworld/worldmodel.cpp | 70 ++++++++------------ apps/openmw/mwworld/worldmodel.hpp | 13 ++-- files/lua_api/openmw/core.lua | 7 +- 6 files changed, 86 insertions(+), 108 deletions(-) diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 43dd3fbfc1..7b98f570f9 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -528,6 +528,11 @@ namespace MWLua mActionQueue.push_back(std::make_unique(&mLua, std::move(action), name)); } + void LuaManager::addTeleportPlayerAction(std::function action) + { + mTeleportPlayerAction = std::make_unique(&mLua, std::move(action), "TeleportPlayer"); + } + void LuaManager::reportStats(unsigned int frameNumber, osg::Stats& stats) const { stats.setAttribute(frameNumber, "Lua UsedMemory", mLua.getTotalMemoryUsage()); diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index a6955ff307..7f56301edc 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -106,7 +106,7 @@ namespace MWLua void addAction(std::function action, std::string_view name = ""); void addAction(std::unique_ptr&& action) { mActionQueue.push_back(std::move(action)); } - void addTeleportPlayerAction(std::unique_ptr&& action) { mTeleportPlayerAction = std::move(action); } + void addTeleportPlayerAction(std::function action); // Saving void write(ESM::ESMWriter& writer, Loading::Listener& progress) override; diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 767b056bdf..09ed8d26c2 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -48,59 +48,48 @@ namespace MWLua namespace { - - class TeleportAction final : public LuaManager::Action + MWWorld::CellStore* findCell(const sol::object& cellOrName, const osg::Vec3f& pos) { - public: - TeleportAction(LuaUtil::LuaState* state, ObjectId object, std::string_view cell, const osg::Vec3f& pos, - const osg::Vec3f& rot) - : Action(state) - , mObject(object) - , mCell(std::string(cell)) - , mPos(pos) - , mRot(rot) + MWWorld::WorldModel* wm = MWBase::Environment::get().getWorldModel(); + MWWorld::CellStore* cell; + if (cellOrName.is()) + cell = cellOrName.as().mStore; + else { - } - - void apply() const override - { - MWWorld::WorldModel& wm = *MWBase::Environment::get().getWorldModel(); - MWWorld::CellStore* cell = wm.getCellByPosition(mPos, mCell); - MWBase::World* world = MWBase::Environment::get().getWorld(); - MWWorld::Ptr obj = wm.getPtr(mObject); - const MWWorld::Class& cls = obj.getClass(); - bool isPlayer = obj == world->getPlayerPtr(); - if (cls.isActor()) - cls.getCreatureStats(obj).land(isPlayer); - if (isPlayer) - { - ESM::Position esmPos; - static_assert(sizeof(esmPos) == sizeof(osg::Vec3f) * 2); - std::memcpy(esmPos.pos, &mPos, sizeof(osg::Vec3f)); - std::memcpy(esmPos.rot, &mRot, sizeof(osg::Vec3f)); - world->getPlayer().setTeleported(true); - if (cell->isExterior()) - world->changeToExteriorCell(esmPos, true); - else - world->changeToInteriorCell(mCell, esmPos, true); - } + std::string_view name = cellOrName.as(); + if (name.empty()) + cell = nullptr; // default exterior worldspace else - { - MWWorld::Ptr newObj = world->moveObject(obj, cell, mPos); - world->rotateObject(newObj, mRot); - if (!newObj.getRefData().isEnabled()) - world->enable(newObj); - } + cell = wm->getCell(name); } + return wm->getCellByPosition(pos, cell); + } - std::string toString() const override { return "TeleportAction"; } + void teleportPlayer(MWWorld::CellStore* destCell, const osg::Vec3f& pos, const osg::Vec3f& rot) + { + MWBase::World* world = MWBase::Environment::get().getWorld(); + ESM::Position esmPos; + static_assert(sizeof(esmPos) == sizeof(osg::Vec3f) * 2); + std::memcpy(esmPos.pos, &pos, sizeof(osg::Vec3f)); + std::memcpy(esmPos.rot, &rot, sizeof(osg::Vec3f)); + MWWorld::Ptr ptr = world->getPlayerPtr(); + ptr.getClass().getCreatureStats(ptr).land(false); + world->getPlayer().setTeleported(true); + world->changeToCell(destCell->getCell()->getId(), esmPos, true); + } - private: - ObjectId mObject; - std::string mCell; - osg::Vec3f mPos; - osg::Vec3f mRot; - }; + void teleportNotPlayer( + const MWWorld::Ptr& ptr, MWWorld::CellStore* destCell, const osg::Vec3f& pos, const osg::Vec3f& rot) + { + MWBase::World* world = MWBase::Environment::get().getWorld(); + const MWWorld::Class& cls = ptr.getClass(); + if (cls.isActor()) + cls.getCreatureStats(ptr).land(false); + MWWorld::Ptr newPtr = world->moveObject(ptr, destCell, pos); + world->rotateObject(newPtr, rot); + if (!newPtr.getRefData().isEnabled()) + world->enable(newPtr); + } class ActivateAction final : public LuaManager::Action { @@ -323,8 +312,9 @@ namespace MWLua refData.setCount(0); }); }; - objectT["teleport"] = [removeFn, context](const GObject& object, std::string_view cell, + objectT["teleport"] = [removeFn, context](const GObject& object, const sol::object& cellOrName, const osg::Vec3f& pos, const sol::optional& optRot) { + MWWorld::CellStore* cell = findCell(cellOrName, pos); MWWorld::Ptr ptr = object.ptr(); if (ptr.getRefData().isDeleted()) throw std::runtime_error("Object is removed"); @@ -332,18 +322,19 @@ namespace MWLua { // Currently moving to or from containers makes a copy and removes the original. // TODO(#6148): actually move rather than copy and preserve RefNum - auto* cellStore = MWBase::Environment::get().getWorldModel()->getCellByPosition(pos, cell); - MWWorld::Ptr newPtr = ptr.getClass().copyToCell(ptr, *cellStore, ptr.getRefData().getCount()); + MWWorld::Ptr newPtr = ptr.getClass().copyToCell(ptr, *cell, ptr.getRefData().getCount()); newPtr.getRefData().disable(); removeFn(object, ptr.getRefData().getCount()); ptr = newPtr; } osg::Vec3f rot = optRot ? *optRot : ptr.getRefData().getPosition().asRotationVec3(); - auto action = std::make_unique(context.mLua, getId(ptr), cell, pos, rot); if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) - context.mLuaManager->addTeleportPlayerAction(std::move(action)); + context.mLuaManager->addTeleportPlayerAction( + [cell, pos, rot] { teleportPlayer(cell, pos, rot); }); else - context.mLuaManager->addAction(std::move(action)); + context.mLuaManager->addAction( + [object, cell, pos, rot] { teleportNotPlayer(object.ptr(), cell, pos, rot); }, + "TeleportAction"); }; } } diff --git a/apps/openmw/mwworld/worldmodel.cpp b/apps/openmw/mwworld/worldmodel.cpp index 07a5ee37ee..e3973f27c9 100644 --- a/apps/openmw/mwworld/worldmodel.cpp +++ b/apps/openmw/mwworld/worldmodel.cpp @@ -195,35 +195,36 @@ MWWorld::CellStore* MWWorld::WorldModel::getExterior(int x, int y) return result->second; } -MWWorld::CellStore* MWWorld::WorldModel::getInterior(std::string_view name) +MWWorld::CellStore* MWWorld::WorldModel::getInteriorOrNull(std::string_view name) { auto result = mInteriors.find(name); - if (result == mInteriors.end()) { - const ESM4::Cell* cell4 = mStore.get().searchCellName(name); CellStore* newCellStore = nullptr; - if (!cell4) - { - const ESM::Cell* cell = mStore.get().find(name); + if (const ESM::Cell* cell = mStore.get().search(name)) newCellStore = &mCells.emplace(cell->mId, CellStore(MWWorld::Cell(*cell), mStore, mReaders)).first->second; - } - else - { + else if (const ESM4::Cell* cell4 = mStore.get().searchCellName(name)) newCellStore = &mCells.emplace(cell4->mId, CellStore(MWWorld::Cell(*cell4), mStore, mReaders)).first->second; - } + if (!newCellStore) + return nullptr; // Cell not found result = mInteriors.emplace(name, newCellStore).first; } if (result->second->getState() != CellStore::State_Loaded) - { result->second->load(); - } return result->second; } +MWWorld::CellStore* MWWorld::WorldModel::getInterior(std::string_view name) +{ + CellStore* res = getInteriorOrNull(name); + if (res == nullptr) + throw std::runtime_error("Interior not found: '" + std::string(name) + "'"); + return res; +} + MWWorld::CellStore* MWWorld::WorldModel::getCell(const ESM::RefId& id) { auto result = mCells.find(id); @@ -261,11 +262,14 @@ MWWorld::CellStore* MWWorld::WorldModel::getCell(const ESM::RefId& id) return newCellStore; } -const ESM::Cell* MWWorld::WorldModel::getESMCellByName(std::string_view name) +MWWorld::CellStore* MWWorld::WorldModel::getCell(std::string_view name) { - const ESM::Cell* cell = mStore.get().search(name); // first try interiors - if (!cell) // try named exteriors - cell = mStore.get().searchExtByName(name); + if (CellStore* res = getInteriorOrNull(name)) // first try interiors + return res; + + // try named exteriors + const ESM::Cell* cell = mStore.get().searchExtByName(name); + if (!cell) { // treat "Wilderness" like an empty string @@ -288,39 +292,17 @@ const ESM::Cell* MWWorld::WorldModel::getESMCellByName(std::string_view name) } if (!cell) throw std::runtime_error(std::string("Can't find cell with name ") + std::string(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().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); - if (cell->isExterior()) - return getExterior(cell->getGridX(), cell->getGridY()); - else - return getInterior(name); + return getExterior(cell->getGridX(), cell->getGridY()); } MWWorld::CellStore* MWWorld::WorldModel::getCellByPosition( - const osg::Vec3f& pos, std::string_view cellNameInSameWorldSpace) + const osg::Vec3f& pos, MWWorld::CellStore* cellInSameWorldSpace) { - if (cellNameInSameWorldSpace.empty() || getESMCellByName(cellNameInSameWorldSpace)->isExterior()) - { - const osg::Vec2i cellIndex = positionToCellIndex(pos.x(), pos.y()); - return getExterior(cellIndex.x(), cellIndex.y()); - } - else - return getInterior(cellNameInSameWorldSpace); + if (cellInSameWorldSpace && !cellInSameWorldSpace->isExterior()) + return cellInSameWorldSpace; + const osg::Vec2i cellIndex = positionToCellIndex(pos.x(), pos.y()); + return getExterior(cellIndex.x(), cellIndex.y()); } MWWorld::Ptr MWWorld::WorldModel::getPtr(const ESM::RefId& name, CellStore& cell) diff --git a/apps/openmw/mwworld/worldmodel.hpp b/apps/openmw/mwworld/worldmodel.hpp index f415a9d48a..db8c103777 100644 --- a/apps/openmw/mwworld/worldmodel.hpp +++ b/apps/openmw/mwworld/worldmodel.hpp @@ -49,10 +49,8 @@ namespace MWWorld std::size_t mPtrIndexUpdateCounter = 0; ESM::RefNum mLastGeneratedRefnum; - const ESM::Cell* getESMCellByName(std::string_view name); - ESM::CellVariant getCellByName(std::string_view name); - CellStore* getCellStore(const ESM::Cell* cell); + CellStore* getInteriorOrNull(std::string_view name); Ptr getPtrAndCache(const ESM::RefId& name, CellStore& cellStore); void writeCell(ESM::ESMWriter& writer, CellStore& cell) const; @@ -70,10 +68,11 @@ namespace MWWorld CellStore* getCell(std::string_view name); // interior or named exterior 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. - // At the moment multiple world spaces are not supported, so all exteriors are in one world space. - CellStore* getCellByPosition(const osg::Vec3f& pos, std::string_view cellNameInSameWorldSpace); + // Returns the cell that is in the same worldspace as `cellInSameWorldSpace` + // (in case of nullptr - default exterior worldspace) and contains given position. + // Interiors are single-cell worldspaces, so in case of an interior it just returns + // the same cell. + CellStore* getCellByPosition(const osg::Vec3f& pos, CellStore* cellInSameWorldSpace = nullptr); void registerPtr(const MWWorld::Ptr& ptr); void deregisterPtr(const MWWorld::Ptr& ptr); diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 361bb9a3d5..6939e82b45 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -170,9 +170,10 @@ -- Can be used to move objects from an inventory or a container to the world. -- @function [parent=#GameObject] teleport -- @param self --- @param #string cellName Name of the cell to teleport into. For exteriors can be empty. --- @param openmw.util#Vector3 position New position --- @param openmw.util#Vector3 rotation New rotation. Optional argument. If missing, then the current rotation is used. +-- @param #any cellOrName A cell to define the destination worldspace; can be either #Cell, or cell name, or an empty string (empty string means the default exterior worldspace). +-- If the worldspace has multiple cells (i.e. an exterior), the destination cell is calculated using `position`. +-- @param openmw.util#Vector3 position New position. +-- @param openmw.util#Vector3 rotation (optional) New rotation. If missing, then the current rotation is used. --- -- Moves object into a container or an inventory. Enables if was disabled. From 0cebaec360fbfbe0a1d3a7dd62ec2d4d1b662b51 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Mon, 17 Apr 2023 18:47:29 +0200 Subject: [PATCH 2/2] Fix bug: world.createObject(id):moveTo(inventory) produces disabled objects that are visible in inventory, but can not be droped to the ground. --- apps/openmw/mwlua/objectbindings.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 09ed8d26c2..e48e1d2582 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -308,6 +308,7 @@ namespace MWLua context.mLuaManager->addAction([item = object, count, cont = inventory.mObj] { auto& refData = item.ptr().getRefData(); refData.setCount(count); // temporarily undo removal to run ContainerStore::add + refData.enable(); cont.ptr().getClass().getContainerStore(cont.ptr()).add(item.ptr(), count, false); refData.setCount(0); });