Merge branch 'teleport' into 'master'

Additional Lua bindings for cells and fixes for teleporting

See merge request OpenMW/openmw!3017
simplify_debugging
psi29a 2 years ago
commit 5fba79ece3

@ -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<CellT, GCell>)
{ // only for global scripts
cellT["getAll"] = [ids = getPackageToTypeTable(context.mLua->sol())](
const CellT& cell, sol::optional<sol::table> type) {
if (cell.mStore->getState() != MWWorld::CellStore::State_Loaded)
cell.mStore->load();
ObjectIdList res = std::make_shared<std::vector<ObjectId>>();
auto visitor = [&](const MWWorld::Ptr& ptr) {
if (ptr.getRefData().isDeleted())

@ -34,6 +34,21 @@
#include "types/types.hpp"
#include "uibindings.hpp"
namespace MWLua
{
struct CellsStore
{
};
}
namespace sol
{
template <>
struct is_automagical<MWLua::CellsStore> : 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<GCell>())
worldspace = cellOrName.as<GCell>().mStore->getCell()->getWorldSpace();
else if (cellOrName.is<std::string_view>() && !cellOrName.as<std::string_view>().empty())
worldspace = MWBase::Environment::get()
.getWorldModel()
->getCell(cellOrName.as<std::string_view>())
.getCell()
->getWorldSpace();
else
worldspace = ESM::Cell::sDefaultWorldspaceId;
return GCell{ &MWBase::Environment::get().getWorldModel()->getExterior(
ESM::ExteriorCellLocation(x, y, worldspace), /*forceLoad=*/false) };
};
const MWWorld::Store<ESM::Cell>* cells3Store = &MWBase::Environment::get().getESMStore()->get<ESM::Cell>();
const MWWorld::Store<ESM4::Cell>* cells4Store = &MWBase::Environment::get().getESMStore()->get<ESM4::Cell>();
sol::usertype<CellsStore> cells = context.mLua->sol().new_usertype<CellsStore>("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<sol::function>();
cells[sol::meta_function::ipairs] = context.mLua->sol()["ipairsForArray"].template get<sol::function>();
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<int> count) -> GObject {
// Doesn't matter which cell to use because the new object will be in disabled state.

@ -66,7 +66,8 @@ namespace MWLua
return &wm->getCellByPosition(pos, cell);
}
void teleportPlayer(MWWorld::CellStore* destCell, const osg::Vec3f& pos, const osg::Vec3f& rot)
void teleportPlayer(
MWWorld::CellStore* destCell, const osg::Vec3f& pos, const osg::Vec3f& rot, bool placeOnGround)
{
MWBase::World* world = MWBase::Environment::get().getWorld();
ESM::Position esmPos;
@ -74,20 +75,38 @@ namespace MWLua
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);
auto& stats = ptr.getClass().getCreatureStats(ptr);
stats.land(true);
stats.setTeleported(true);
world->getPlayer().setTeleported(true);
world->changeToCell(destCell->getCell()->getId(), esmPos, true);
world->changeToCell(destCell->getCell()->getId(), esmPos, false);
MWWorld::Ptr newPtr = world->getPlayerPtr();
world->moveObject(newPtr, pos);
world->rotateObject(newPtr, rot);
if (placeOnGround)
world->adjustPosition(newPtr, true);
}
void teleportNotPlayer(
const MWWorld::Ptr& ptr, MWWorld::CellStore* destCell, const osg::Vec3f& pos, const osg::Vec3f& rot)
void teleportNotPlayer(const MWWorld::Ptr& ptr, MWWorld::CellStore* destCell, const osg::Vec3f& pos,
const osg::Vec3f& rot, bool placeOnGround)
{
MWBase::World* world = MWBase::Environment::get().getWorld();
const MWWorld::Class& cls = ptr.getClass();
if (cls.isActor())
cls.getCreatureStats(ptr).land(false);
{
auto& stats = ptr.getClass().getCreatureStats(ptr);
stats.land(false);
stats.setTeleported(true);
}
MWWorld::Ptr newPtr = world->moveObject(ptr, destCell, pos);
world->rotateObject(newPtr, rot, MWBase::RotationFlag_none);
if (placeOnGround)
world->adjustPosition(newPtr, true);
if (cls.isDoor())
{ // Change "original position and rotation" because without it teleported animated doors don't work
// properly.
newPtr.getCellRef().setPosition(newPtr.getRefData().getPosition());
}
if (!newPtr.getRefData().isEnabled())
world->enable(newPtr);
}
@ -343,38 +362,47 @@ namespace MWLua
});
};
objectT["teleport"] = [removeFn, context](const GObject& object, const sol::object& cellOrName,
const osg::Vec3f& pos, const sol::optional<osg::Vec3f>& optRot) {
const osg::Vec3f& pos, const sol::object& options) {
MWWorld::CellStore* cell = findCell(cellOrName, pos);
MWWorld::Ptr ptr = object.ptr();
int count = ptr.getRefData().getCount();
if (count == 0)
throw std::runtime_error("Object is either removed or already in the process of teleporting");
osg::Vec3f rot = optRot ? *optRot : ptr.getRefData().getPosition().asRotationVec3();
osg::Vec3f rot = ptr.getRefData().getPosition().asRotationVec3();
bool placeOnGround = false;
if (options.is<osg::Vec3f>())
rot = options.as<osg::Vec3f>();
else if (options != sol::nil)
{
sol::table t = LuaUtil::cast<sol::table>(options);
rot = LuaUtil::getValueOrDefault(t["rotation"], rot);
placeOnGround = LuaUtil::getValueOrDefault(t["onGround"], placeOnGround);
}
if (ptr.getContainerStore())
{
DelayedRemovalFn delayedRemovalFn = *removeFn(ptr, count);
context.mLuaManager->addAction(
[object, cell, pos, rot, count, delayedRemovalFn] {
[object, cell, pos, rot, count, delayedRemovalFn, placeOnGround] {
MWWorld::Ptr oldPtr = object.ptr();
oldPtr.getRefData().setCount(count);
MWWorld::Ptr newPtr = oldPtr.getClass().moveToCell(oldPtr, *cell);
oldPtr.getRefData().setCount(0);
newPtr.getRefData().disable();
teleportNotPlayer(newPtr, cell, pos, rot);
teleportNotPlayer(newPtr, cell, pos, rot, placeOnGround);
delayedRemovalFn(oldPtr);
},
"TeleportFromContainerAction");
}
else if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr())
context.mLuaManager->addTeleportPlayerAction(
[cell, pos, rot] { teleportPlayer(cell, pos, rot); });
[cell, pos, rot, placeOnGround] { teleportPlayer(cell, pos, rot, placeOnGround); });
else
{
ptr.getRefData().setCount(0);
context.mLuaManager->addAction(
[object, cell, pos, rot, count] {
[object, cell, pos, rot, count, placeOnGround] {
object.ptr().getRefData().setCount(count);
teleportNotPlayer(object.ptr(), cell, pos, rot);
teleportNotPlayer(object.ptr(), cell, pos, rot, placeOnGround);
},
"TeleportAction");
}

@ -1862,7 +1862,7 @@ namespace MWMechanics
float scale = mPtr.getCellRef().getScale();
static const bool normalizeSpeed = Settings::Manager::getBool("normalise race speed", "Game");
if (!normalizeSpeed && mPtr.getClass().isNpc())
if (!normalizeSpeed && cls.isNpc())
{
const ESM::NPC* npc = mPtr.get<ESM::NPC>()->mBase;
const ESM::Race* race = world->getStore().get<ESM::Race>().find(npc->mRace);
@ -1870,6 +1870,12 @@ namespace MWMechanics
scale *= weight;
}
if (cls.isActor() && cls.getCreatureStats(mPtr).wasTeleported())
{
mSmoothedSpeed = osg::Vec2f();
cls.getCreatureStats(mPtr).setTeleported(false);
}
if (!cls.isActor())
updateAnimQueue();
else if (!cls.getCreatureStats(mPtr).isDead())

@ -85,6 +85,8 @@ namespace MWMechanics
// The difference between view direction and lower body direction.
float mSideMovementAngle;
bool mTeleported = false;
private:
std::multimap<int, int> mSummonedCreatures; // <Effect, ActorId>
@ -288,6 +290,9 @@ namespace MWMechanics
float getSideMovementAngle() const { return mSideMovementAngle; }
void setSideMovementAngle(float angle) { mSideMovementAngle = angle; }
bool wasTeleported() const { return mTeleported; }
void setTeleported(bool v) { mTeleported = v; }
};
}

@ -21,6 +21,7 @@
#include "../mwworld/worldmodel.hpp"
#include "../mwmechanics/actorutil.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "interpretercontext.hpp"
#include "ref.hpp"
@ -389,10 +390,10 @@ namespace MWScript
bool isPlayer = ptr == MWMechanics::getPlayer();
auto world = MWBase::Environment::get().getWorld();
auto worldModel = MWBase::Environment::get().getWorldModel();
if (ptr.getClass().isActor())
ptr.getClass().getCreatureStats(ptr).setTeleported(true);
if (isPlayer)
{
world->getPlayer().setTeleported(true);
}
MWWorld::CellStore* store = nullptr;
try
@ -463,10 +464,10 @@ namespace MWScript
bool isPlayer = ptr == MWMechanics::getPlayer();
auto world = MWBase::Environment::get().getWorld();
if (ptr.getClass().isActor())
ptr.getClass().getCreatureStats(ptr).setTeleported(true);
if (isPlayer)
{
world->getPlayer().setTeleported(true);
}
const ESM::ExteriorCellLocation cellIndex
= ESM::positionToCellIndex(x, y, ESM::Cell::sDefaultWorldspaceId);

@ -47,7 +47,9 @@ namespace MWWorld
{
MWBase::World* world = MWBase::Environment::get().getWorld();
MWWorld::WorldModel* worldModel = MWBase::Environment::get().getWorldModel();
actor.getClass().getCreatureStats(actor).land(actor == world->getPlayerPtr());
auto& stats = actor.getClass().getCreatureStats(actor);
stats.land(actor == world->getPlayerPtr());
stats.setTeleported(true);
Ptr teleported;
if (actor == world->getPlayerPtr())

@ -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<ESM::RefId>& list) const override;
ESM::Cell* insert(const ESM::Cell& cell);

@ -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<ESM::ExteriorCellLocation, CellStore*>::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<ESM::ESM3ExteriorCellRefId>())
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<ESM4::Cell>().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<ESM::Cell>().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(

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

@ -177,7 +177,13 @@
-- @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.
-- @param #TeleportOptions options (optional) Either table @{#TeleportOptions} or @{openmw.util#Vector3} rotation.
---
-- Either table with options or @{openmw.util#Vector3} rotation.
-- @type TeleportOptions
-- @field openmw.util#Vector3 rotation New rotation; if missing, then the current rotation is used.
-- @field #boolean onGround If true, adjust destination position to the ground.
---
-- Moves object into a container or an inventory. Enables if was disabled.
@ -219,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.

@ -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<openmw.core#Cell> 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.

Loading…
Cancel
Save