diff --git a/apps/openmw/mwlua/cellbindings.cpp b/apps/openmw/mwlua/cellbindings.cpp index 2021bca728..35f9af0f2b 100644 --- a/apps/openmw/mwlua/cellbindings.cpp +++ b/apps/openmw/mwlua/cellbindings.cpp @@ -33,7 +33,8 @@ namespace MWLua auto cell = c.mStore->getCell(); std::stringstream res; if (cell->isExterior()) - res << "exterior(" << cell->getGridX() << ", " << cell->getGridY() << ")"; + res << "exterior(" << cell->getGridX() << ", " << cell->getGridY() << ", " + << cell->getWorldSpace().toDebugString() << ")"; else res << "interior(" << cell->getNameId() << ")"; return res.str(); @@ -42,6 +43,8 @@ namespace MWLua cellT["name"] = sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->getNameId(); }); cellT["region"] = sol::readonly_property( [](const CellT& c) -> std::string { return c.mStore->getCell()->getRegion().serializeText(); }); + cellT["worldSpaceId"] = sol::readonly_property( + [](const CellT& c) -> std::string { return c.mStore->getCell()->getWorldSpace().serializeText(); }); 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(); }); @@ -67,13 +70,15 @@ namespace MWLua if (!ptr.isInCell()) return false; MWWorld::CellStore* cell = ptr.getCell(); - return cell == c.mStore || (cell->isExterior() && c.mStore->isExterior()); + return cell == c.mStore || (cell->getCell()->getWorldSpace() == c.mStore->getCell()->getWorldSpace()); }; if constexpr (std::is_same_v) { // only for global scripts cellT["getAll"] = [ids = getPackageToTypeTable(context.mLua->sol())]( const CellT& cell, sol::optional type) { + if (cell.mStore->getState() != MWWorld::CellStore::State_Loaded) + cell.mStore->load(); ObjectIdList res = std::make_shared>(); auto visitor = [&](const MWWorld::Ptr& ptr) { if (ptr.getRefData().isDeleted()) diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 0084c4a901..0f32cba562 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -34,6 +34,21 @@ #include "types/types.hpp" #include "uibindings.hpp" +namespace MWLua +{ + struct CellsStore + { + }; +} + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; +} + namespace MWLua { @@ -68,7 +83,7 @@ namespace MWLua { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); - api["API_REVISION"] = 37; + api["API_REVISION"] = 38; api["quit"] = [lua]() { Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback(); MWBase::Environment::get().getStateManager()->requestQuit(); @@ -106,17 +121,58 @@ namespace MWLua return LuaUtil::makeReadOnly(api); } + static void addCellGetters(sol::table& api, const Context& context) + { + api["getCellByName"] = [](std::string_view name) { + return GCell{ &MWBase::Environment::get().getWorldModel()->getCell(name, /*forceLoad=*/false) }; + }; + api["getExteriorCell"] = [](int x, int y, sol::object cellOrName) { + ESM::RefId worldspace; + if (cellOrName.is()) + worldspace = cellOrName.as().mStore->getCell()->getWorldSpace(); + else if (cellOrName.is() && !cellOrName.as().empty()) + worldspace = MWBase::Environment::get() + .getWorldModel() + ->getCell(cellOrName.as()) + .getCell() + ->getWorldSpace(); + else + worldspace = ESM::Cell::sDefaultWorldspaceId; + return GCell{ &MWBase::Environment::get().getWorldModel()->getExterior( + ESM::ExteriorCellLocation(x, y, worldspace), /*forceLoad=*/false) }; + }; + + const MWWorld::Store* cells3Store = &MWBase::Environment::get().getESMStore()->get(); + const MWWorld::Store* cells4Store = &MWBase::Environment::get().getESMStore()->get(); + sol::usertype cells = context.mLua->sol().new_usertype("Cells"); + cells[sol::meta_function::length] + = [cells3Store, cells4Store](const CellsStore&) { return cells3Store->getSize() + cells4Store->getSize(); }; + cells[sol::meta_function::index] = [cells3Store, cells4Store](const CellsStore&, size_t index) -> GCell { + index--; // Translate from Lua's 1-based indexing. + if (index < cells3Store->getSize()) + { + const ESM::Cell* cellRecord = cells3Store->at(index); + return GCell{ &MWBase::Environment::get().getWorldModel()->getCell( + cellRecord->mId, /*forceLoad=*/false) }; + } + else + { + const ESM4::Cell* cellRecord = cells4Store->at(index - cells3Store->getSize()); + return GCell{ &MWBase::Environment::get().getWorldModel()->getCell( + cellRecord->mId, /*forceLoad=*/false) }; + } + }; + cells[sol::meta_function::pairs] = context.mLua->sol()["ipairsForArray"].template get(); + cells[sol::meta_function::ipairs] = context.mLua->sol()["ipairsForArray"].template get(); + api["cells"] = CellsStore{}; + } + static sol::table initWorldPackage(const Context& context) { sol::table api(context.mLua->sol(), sol::create); WorldView* worldView = context.mWorldView; addTimeBindings(api, context, true); - api["getCellByName"] - = [](std::string_view name) { return GCell{ &MWBase::Environment::get().getWorldModel()->getCell(name) }; }; - api["getExteriorCell"] = [](int x, int y) { - return GCell{ &MWBase::Environment::get().getWorldModel()->getExterior( - ESM::ExteriorCellLocation(x, y, ESM::Cell::sDefaultWorldspaceId)) }; - }; + addCellGetters(api, context); api["activeActors"] = GObjectList{ worldView->getActorsInScene() }; api["createObject"] = [](std::string_view recordId, sol::optional count) -> GObject { // Doesn't matter which cell to use because the new object will be in disabled state. diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 662fc2b558..75c41af25e 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -413,6 +413,14 @@ namespace MWWorld size_t getExtSize() const; size_t getIntSize() const; + const ESM::Cell* at(size_t index) const + { + if (index < mSharedInt.size()) + return mSharedInt.at(index); + else + return mSharedExt.at(index - mSharedInt.size()); + } + void listIdentifier(std::vector& list) const override; ESM::Cell* insert(const ESM::Cell& cell); diff --git a/apps/openmw/mwworld/worldmodel.cpp b/apps/openmw/mwworld/worldmodel.cpp index e9b1cf1300..4f53dd329c 100644 --- a/apps/openmw/mwworld/worldmodel.cpp +++ b/apps/openmw/mwworld/worldmodel.cpp @@ -160,7 +160,7 @@ MWWorld::WorldModel::WorldModel(const MWWorld::ESMStore& store, ESM::ReadersCach { } -MWWorld::CellStore& MWWorld::WorldModel::getExterior(ESM::ExteriorCellLocation cellIndex) +MWWorld::CellStore& MWWorld::WorldModel::getExterior(ESM::ExteriorCellLocation cellIndex, bool forceLoad) { std::map::iterator result; @@ -211,7 +211,7 @@ MWWorld::CellStore& MWWorld::WorldModel::getExterior(ESM::ExteriorCellLocation c result = mExteriors.emplace(cellIndex, cellStore).first; } } - if (result->second->getState() != CellStore::State_Loaded) + if (forceLoad && result->second->getState() != CellStore::State_Loaded) { result->second->load(); } @@ -234,22 +234,20 @@ MWWorld::CellStore* MWWorld::WorldModel::getInteriorOrNull(std::string_view name 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) +MWWorld::CellStore& MWWorld::WorldModel::getInterior(std::string_view name, bool forceLoad) { CellStore* res = getInteriorOrNull(name); if (res == nullptr) throw std::runtime_error("Interior not found: '" + std::string(name) + "'"); + if (forceLoad && res->getState() != CellStore::State_Loaded) + res->load(); return *res; } -MWWorld::CellStore& MWWorld::WorldModel::getCell(const ESM::RefId& id) +MWWorld::CellStore& MWWorld::WorldModel::getCell(const ESM::RefId& id, bool forceLoad) { auto result = mCells.find(id); if (result != mCells.end()) @@ -257,7 +255,8 @@ MWWorld::CellStore& MWWorld::WorldModel::getCell(const ESM::RefId& id) if (const auto* exteriorId = id.getIf()) return getExterior( - ESM::ExteriorCellLocation(exteriorId->getX(), exteriorId->getY(), ESM::Cell::sDefaultWorldspaceId)); + ESM::ExteriorCellLocation(exteriorId->getX(), exteriorId->getY(), ESM::Cell::sDefaultWorldspaceId), + forceLoad); const ESM4::Cell* cell4 = mStore.get().search(id); CellStore* newCellStore = nullptr; @@ -281,17 +280,21 @@ MWWorld::CellStore& MWWorld::WorldModel::getCell(const ESM::RefId& id) { mInteriors.emplace(newCellStore->getCell()->getNameId(), newCellStore); } - if (newCellStore->getState() != CellStore::State_Loaded) + if (forceLoad && newCellStore->getState() != CellStore::State_Loaded) { newCellStore->load(); } return *newCellStore; } -MWWorld::CellStore& MWWorld::WorldModel::getCell(std::string_view name) +MWWorld::CellStore& MWWorld::WorldModel::getCell(std::string_view name, bool forceLoad) { if (CellStore* res = getInteriorOrNull(name)) // first try interiors + { + if (forceLoad && res->getState() != CellStore::State_Loaded) + res->load(); return *res; + } // try named exteriors const ESM::Cell* cell = mStore.get().searchExtByName(name); @@ -319,7 +322,8 @@ MWWorld::CellStore& MWWorld::WorldModel::getCell(std::string_view name) if (!cell) throw std::runtime_error(std::string("Can't find cell with name ") + std::string(name)); - return getExterior(ESM::ExteriorCellLocation(cell->getGridX(), cell->getGridY(), ESM::Cell::sDefaultWorldspaceId)); + return getExterior( + ESM::ExteriorCellLocation(cell->getGridX(), cell->getGridY(), ESM::Cell::sDefaultWorldspaceId), forceLoad); } MWWorld::CellStore& MWWorld::WorldModel::getCellByPosition( diff --git a/apps/openmw/mwworld/worldmodel.hpp b/apps/openmw/mwworld/worldmodel.hpp index b90a063c69..1048a27e84 100644 --- a/apps/openmw/mwworld/worldmodel.hpp +++ b/apps/openmw/mwworld/worldmodel.hpp @@ -65,10 +65,10 @@ namespace MWWorld void clear(); - CellStore& getExterior(ESM::ExteriorCellLocation cellIndex); - CellStore& getInterior(std::string_view name); - CellStore& getCell(std::string_view name); // interior or named exterior - CellStore& getCell(const ESM::RefId& Id); + CellStore& getExterior(ESM::ExteriorCellLocation cellIndex, bool forceLoad = true); + CellStore& getInterior(std::string_view name, bool forceLoad = true); + CellStore& getCell(std::string_view name, bool forceLoad = true); // interior or named exterior + CellStore& getCell(const ESM::RefId& Id, bool forceLoad = true); // Returns the cell that is in the same worldspace as `cellInSameWorldSpace` // (in case of nullptr - default exterior worldspace) and contains given position. diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index a472b8ac88..b9c56f8a8e 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -225,6 +225,7 @@ -- @field #boolean isQuasiExterior (DEPRECATED, use `hasTag("QuasiExterior")`) Whether the cell is a quasi exterior (like interior but with the sky and the wheather). -- @field #number gridX Index of the cell by X (only for exteriors). -- @field #number gridY Index of the cell by Y (only for exteriors). +-- @field #string worldSpaceId Id of the world space. -- @field #boolean hasWater True if the cell contains water. -- @field #boolean hasSky True if in this cell sky should be rendered. diff --git a/files/lua_api/openmw/world.lua b/files/lua_api/openmw/world.lua index ad18c2193e..732ce1e938 100644 --- a/files/lua_api/openmw/world.lua +++ b/files/lua_api/openmw/world.lua @@ -21,8 +21,14 @@ -- @function [parent=#world] getExteriorCell -- @param #number gridX -- @param #number gridY +-- @param #any cellOrName (optional) other cell or cell name in the same exterior world space -- @return openmw.core#Cell +--- +-- List of all cells +-- @field [parent=#world] #list cells +-- @usage for i, cell in ipairs(world.cells) do print(cell) end + --- -- Simulation time in seconds. -- The number of simulation seconds passed in the game world since starting a new game.