1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-01-16 17:29:55 +00:00

Add Cell Lua bindings

This commit is contained in:
Petr Mikheev 2021-04-17 11:50:20 +02:00
parent de349962d1
commit c463f52390
12 changed files with 306 additions and 102 deletions

View file

@ -57,7 +57,7 @@ add_openmw_dir (mwscript
add_openmw_dir (mwlua
luamanagerimp actions object worldview userdataserializer eventqueue query
luabindings localscripts objectbindings asyncbindings camerabindings uibindings
luabindings localscripts objectbindings cellbindings asyncbindings camerabindings uibindings
)
add_openmw_dir (mwsound

View file

@ -2,6 +2,7 @@
#include <components/debug/debuglog.hpp>
#include "../mwworld/cellstore.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwworld/player.hpp"
@ -11,23 +12,14 @@ namespace MWLua
void TeleportAction::apply(WorldView& worldView) const
{
MWBase::World* world = MWBase::Environment::get().getWorld();
bool exterior = mCell.empty() || world->getExterior(mCell);
MWWorld::CellStore* cell;
if (exterior)
{
int cellX, cellY;
world->positionToIndex(mPos.x(), mPos.y(), cellX, cellY);
cell = world->getExterior(cellX, cellY);
}
else
cell = world->getInterior(mCell);
MWWorld::CellStore* cell = worldView.findCell(mCell, mPos);
if (!cell)
{
Log(Debug::Error) << "LuaManager::applyTeleport -> cell not found: '" << mCell << "'";
return;
}
MWBase::World* world = MWBase::Environment::get().getWorld();
MWWorld::Ptr obj = worldView.getObjectRegistry()->getPtr(mObject, false);
const MWWorld::Class& cls = obj.getClass();
bool isPlayer = obj == world->getPlayerPtr();
@ -40,7 +32,7 @@ namespace MWLua
std::memcpy(esmPos.pos, &mPos, sizeof(osg::Vec3f));
std::memcpy(esmPos.rot, &mRot, sizeof(osg::Vec3f));
world->getPlayer().setTeleported(true);
if (exterior)
if (cell->isExterior())
world->changeToExteriorCell(esmPos, true);
else
world->changeToInteriorCell(mCell, esmPos, true);

View file

@ -0,0 +1,62 @@
#include "luabindings.hpp"
#include <components/esm/loadcell.hpp>
#include "../mwworld/cellstore.hpp"
namespace MWLua
{
template <class CellT, class ObjectT>
static void initCellBindings(const std::string& prefix, const Context& context)
{
sol::usertype<CellT> cellT = context.mLua->sol().new_usertype<CellT>(prefix + "Cell");
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();
std::stringstream res;
if (cell->isExterior())
res << "exterior(" << cell->getGridX() << ", " << cell->getGridY() << ")";
else
res << "interior(" << cell->mName << ")";
return res.str();
};
cellT["name"] = sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->mName; });
cellT["region"] = sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->mRegion; });
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["isExterior"] = sol::readonly_property([](const CellT& c) { return c.mStore->isExterior(); });
cellT["hasWater"] = sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->hasWater(); });
cellT["isInSameSpace"] = [](const CellT& c, const ObjectT& obj)
{
const MWWorld::Ptr& ptr = obj.ptr();
if (!ptr.isInCell())
return false;
MWWorld::CellStore* cell = ptr.getCell();
return cell == c.mStore || (cell->isExterior() && c.mStore->isExterior());
};
if constexpr (std::is_same_v<CellT, GCell>)
{ // only for global scripts
cellT["selectObjects"] = [context](const CellT& cell, const Queries::Query& query)
{
return GObjectList{selectObjectsFromCellStore(query, cell.mStore, context)};
};
}
}
void initCellBindingsForLocalScripts(const Context& context)
{
initCellBindings<LCell, LObject>("L", context);
}
void initCellBindingsForGlobalScripts(const Context& context)
{
initCellBindings<GCell, GObject>("G", context);
}
}

View file

@ -62,6 +62,22 @@ namespace MWLua
{
sol::table api(context.mLua->sol(), sol::create);
WorldView* worldView = context.mWorldView;
api["getCellByName"] = [worldView=context.mWorldView](const std::string& name) -> sol::optional<GCell>
{
MWWorld::CellStore* cell = worldView->findNamedCell(name);
if (cell)
return GCell{cell};
else
return {};
};
api["getExteriorCell"] = [worldView=context.mWorldView](int x, int y) -> sol::optional<GCell>
{
MWWorld::CellStore* cell = worldView->findExteriorCell(x, y);
if (cell)
return GCell{cell};
else
return {};
};
api["activeActors"] = GObjectList{worldView->getActorsInScene()};
api["selectObjects"] = [context](const Queries::Query& query)
{

View file

@ -11,6 +11,11 @@
#include "query.hpp"
#include "worldview.hpp"
namespace MWWorld
{
class CellStore;
}
namespace MWLua
{
@ -25,6 +30,18 @@ namespace MWLua
void initObjectBindingsForLocalScripts(const Context&);
void initObjectBindingsForGlobalScripts(const Context&);
// Implemented in cellbindings.cpp
struct LCell // for local scripts
{
MWWorld::CellStore* mStore;
};
struct GCell // for global scripts
{
MWWorld::CellStore* mStore;
};
void initCellBindingsForLocalScripts(const Context&);
void initCellBindingsForGlobalScripts(const Context&);
// Implemented in asyncbindings.cpp
struct AsyncPackageId
{

View file

@ -49,11 +49,18 @@ namespace MWLua
return fallback;
}
bool isMarker(const MWWorld::Ptr& ptr)
{
std::string_view id = *ptr.getCellRef().getRefIdPtr();
return id == "prisonmarker" || id == "divinemarker" || id == "templemarker" || id == "northmarker";
}
std::string_view getMWClassName(const MWWorld::Ptr& ptr)
{
if (*ptr.getCellRef().getRefIdPtr() == "player")
return "Player";
else
if (isMarker(ptr))
return "Marker";
return getMWClassName(typeid(ptr.getClass()), ptr.getTypeName());
}

View file

@ -18,6 +18,7 @@ namespace MWLua
inline const ObjectId& getId(const MWWorld::Ptr& ptr) { return ptr.getCellRef().getRefNum(); }
std::string idToString(const ObjectId& id);
std::string ptrToString(const MWWorld::Ptr& ptr);
bool isMarker(const MWWorld::Ptr& ptr);
std::string_view getMWClassName(const std::type_index& cls_type, std::string_view fallback = "Unknown");
std::string_view getMWClassName(const MWWorld::Ptr& ptr);

View file

@ -39,6 +39,9 @@ namespace sol
namespace MWLua
{
template <typename ObjT>
using Cell = std::conditional_t<std::is_same_v<ObjT, LObject>, LCell, GCell>;
template <class Class>
static const MWWorld::Ptr& requireClass(const MWWorld::Ptr& ptr)
{
@ -95,10 +98,13 @@ namespace MWLua
{
return o.ptr().getCellRef().getRefId();
});
objectT["cell"] = sol::readonly_property([](const ObjectT& o)
objectT["cell"] = sol::readonly_property([](const ObjectT& o) -> sol::optional<Cell<ObjectT>>
{
MWBase::World* world = MWBase::Environment::get().getWorld();
return world->getCellName(o.ptr().getCell());
const MWWorld::Ptr& ptr = o.ptr();
if (ptr.isInCell())
return Cell<ObjectT>{ptr.getCell()};
else
return {};
});
objectT["position"] = sol::readonly_property([](const ObjectT& o) -> osg::Vec3f
{
@ -117,6 +123,22 @@ namespace MWLua
context.mLocalEventQueue->push_back({dest.id(), std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer)});
};
objectT["canMove"] = [context](const ObjectT& o)
{
const MWWorld::Class& cls = o.ptr().getClass();
return cls.getMaxSpeed(o.ptr()) > 0;
};
objectT["getRunSpeed"] = [context](const ObjectT& o)
{
const MWWorld::Class& cls = o.ptr().getClass();
return cls.getRunSpeed(o.ptr());
};
objectT["getWalkSpeed"] = [context](const ObjectT& o)
{
const MWWorld::Class& cls = o.ptr().getClass();
return cls.getWalkSpeed(o.ptr());
};
if constexpr (std::is_same_v<ObjectT, GObject>)
{ // Only for global scripts
objectT["addScript"] = [luaManager=context.mLuaManager](const GObject& object, const std::string& path)
@ -155,9 +177,17 @@ namespace MWLua
{
return ptr(o).getCellRef().getDoorDest().asRotationVec3();
});
objectT["destCell"] = sol::readonly_property([ptr](const ObjectT& o) -> std::string_view
objectT["destCell"] = sol::readonly_property(
[ptr, worldView=context.mWorldView](const ObjectT& o) -> sol::optional<Cell<ObjectT>>
{
return ptr(o).getCellRef().getDestCell();
const MWWorld::CellRef& cellRef = ptr(o).getCellRef();
if (!cellRef.getTeleport())
return {};
MWWorld::CellStore* cell = worldView->findCell(cellRef.getDestCell(), cellRef.getDoorDest().asVec3());
if (cell)
return Cell<ObjectT>{cell};
else
return {};
});
}

View file

@ -5,6 +5,7 @@
#include <components/lua/luastate.hpp>
#include "../mwclass/container.hpp"
#include "../mwworld/cellstore.hpp"
#include "worldview.hpp"
@ -24,11 +25,16 @@ namespace MWLua
static std::array objectFields = {
Queries::Field({"type"}, typeid(std::string)),
Queries::Field({"recordId"}, typeid(std::string)),
Queries::Field({"cell", "name"}, typeid(std::string)),
Queries::Field({"cell", "region"}, typeid(std::string)),
Queries::Field({"cell", "isExterior"}, typeid(bool)),
Queries::Field({"count"}, typeid(int32_t)),
};
static std::array doorFields = {
Queries::Field({"isTeleport"}, typeid(bool)),
Queries::Field({"destCell"}, typeid(std::string)),
Queries::Field({"destCell", "name"}, typeid(std::string)),
Queries::Field({"destCell", "region"}, typeid(std::string)),
Queries::Field({"destCell", "isExterior"}, typeid(bool)),
};
return std::vector<QueryFieldGroup>{
createGroup("OBJECT", objectFields),
@ -42,13 +48,8 @@ namespace MWLua
return fieldGroups;
}
ObjectIdList selectObjectsFromList(const Queries::Query& query, const ObjectIdList& list, const Context& context)
bool checkQueryConditions(const Queries::Query& query, const ObjectId& id, const Context& context)
{
if (!query.mOrderBy.empty() || !query.mGroupBy.empty() || query.mOffset > 0)
throw std::runtime_error("OrderBy, GroupBy, and Offset are not supported");
ObjectIdList res = std::make_shared<std::vector<ObjectId>>();
std::vector<char> condStack;
auto compareFn = [](auto&& a, auto&& b, Queries::Condition::Type t)
{
switch (t)
@ -63,37 +64,40 @@ namespace MWLua
throw std::runtime_error("Unsupported condition type");
}
};
for (const ObjectId& id : *list)
{
if (static_cast<int64_t>(res->size()) == query.mLimit)
break;
sol::object obj;
MWWorld::Ptr ptr;
if (context.mIsGlobal)
{
GObject g(id, context.mWorldView->getObjectRegistry());
if (!g.isValid()) continue;
if (!g.isValid())
return false;
ptr = g.ptr();
obj = sol::make_object(context.mLua->sol(), g);
}
else
{
LObject l(id, context.mWorldView->getObjectRegistry());
if (!l.isValid()) continue;
if (!l.isValid())
return false;
ptr = l.ptr();
obj = sol::make_object(context.mLua->sol(), l);
}
if (ptr.getRefData().getCount() == 0)
return false;
// It is stupid, but "prisonmarker" has class "Door" despite that it is only an invisible marker. So we ignore all markers.
if (isMarker(ptr))
return false;
const MWWorld::Class& cls = ptr.getClass();
if (cls.isActivator() && query.mQueryType != ObjectQueryTypes::ACTIVATORS)
continue;
if (cls.isActor() && query.mQueryType != ObjectQueryTypes::ACTORS)
continue;
if (cls.isDoor() && query.mQueryType != ObjectQueryTypes::DOORS)
continue;
if (typeid(cls) == typeid(MWClass::Container) && query.mQueryType != ObjectQueryTypes::CONTAINERS)
continue;
if (cls.isActivator() != (query.mQueryType == ObjectQueryTypes::ACTIVATORS))
return false;
if (cls.isActor() != (query.mQueryType == ObjectQueryTypes::ACTORS))
return false;
if (cls.isDoor() != (query.mQueryType == ObjectQueryTypes::DOORS))
return false;
if ((typeid(cls) == typeid(MWClass::Container)) != (query.mQueryType == ObjectQueryTypes::CONTAINERS))
return false;
condStack.clear();
std::vector<char> condStack;
for (const Queries::Operation& op : query.mFilter.mOperations)
{
switch(op.mType)
@ -103,9 +107,11 @@ namespace MWLua
const Queries::Condition& cond = query.mFilter.mConditions[op.mConditionIndex];
sol::object fieldObj = obj;
for (const std::string& field : cond.mField->path())
fieldObj = sol::table(fieldObj)[field];
fieldObj = LuaUtil::getFieldOrNil(fieldObj, field);
bool c;
if (cond.mField->type() == typeid(std::string))
if (fieldObj == sol::nil)
c = false;
else if (cond.mField->type() == typeid(std::string))
c = compareFn(fieldObj.as<std::string_view>(), std::get<std::string>(cond.mValue), cond.mType);
else if (cond.mField->type() == typeid(float))
c = compareFn(fieldObj.as<float>(), std::get<float>(cond.mValue), cond.mType);
@ -141,10 +147,42 @@ namespace MWLua
}
}
}
if (condStack.empty() || condStack.back() != 0)
return condStack.empty() || condStack.back() != 0;
}
ObjectIdList selectObjectsFromList(const Queries::Query& query, const ObjectIdList& list, const Context& context)
{
if (!query.mOrderBy.empty() || !query.mGroupBy.empty() || query.mOffset > 0)
throw std::runtime_error("OrderBy, GroupBy, and Offset are not supported");
ObjectIdList res = std::make_shared<std::vector<ObjectId>>();
for (const ObjectId& id : *list)
{
if (static_cast<int64_t>(res->size()) == query.mLimit)
break;
if (checkQueryConditions(query, id, context))
res->push_back(id);
}
return res;
}
ObjectIdList selectObjectsFromCellStore(const Queries::Query& query, MWWorld::CellStore* store, const Context& context)
{
if (!query.mOrderBy.empty() || !query.mGroupBy.empty() || query.mOffset > 0)
throw std::runtime_error("OrderBy, GroupBy, and Offset are not supported");
ObjectIdList res = std::make_shared<std::vector<ObjectId>>();
auto visitor = [&](const MWWorld::Ptr& ptr)
{
if (static_cast<int64_t>(res->size()) == query.mLimit)
return false;
context.mWorldView->getObjectRegistry()->registerPtr(ptr);
if (checkQueryConditions(query, getId(ptr), context))
res->push_back(getId(ptr));
return static_cast<int64_t>(res->size()) != query.mLimit;
};
store->forEach(std::move(visitor)); // TODO: maybe use store->forEachType<TYPE> depending on query.mType
return res;
}
}

View file

@ -32,6 +32,7 @@ namespace MWLua
// TODO: Implement custom fields. QueryFieldGroup registerCustomFields(...);
ObjectIdList selectObjectsFromList(const Queries::Query& query, const ObjectIdList& list, const Context&);
ObjectIdList selectObjectsFromCellStore(const Queries::Query& query, MWWorld::CellStore* store, const Context&);
}

View file

@ -2,6 +2,7 @@
#include <components/esm/esmreader.hpp>
#include <components/esm/esmwriter.hpp>
#include <components/esm/loadcell.hpp>
#include "../mwclass/container.hpp"
@ -33,6 +34,10 @@ namespace MWLua
WorldView::ObjectGroup* WorldView::chooseGroup(const MWWorld::Ptr& ptr)
{
// It is important to check `isMarker` first.
// For example "prisonmarker" has class "Door" despite that it is only an invisible marker.
if (isMarker(ptr))
return nullptr;
const MWWorld::Class& cls = ptr.getClass();
if (cls.isActivator())
return &mActivatorsInScene;
@ -113,4 +118,35 @@ namespace MWLua
group.mChanged = true;
}
// TODO: If Lua scripts will use several threads at the same time, then `find*Cell` functions should have critical sections.
MWWorld::CellStore* WorldView::findCell(const std::string& name, osg::Vec3f position)
{
MWBase::World* world = MWBase::Environment::get().getWorld();
bool exterior = name.empty() || world->getExterior(name);
if (exterior)
{
int cellX, cellY;
world->positionToIndex(position.x(), position.y(), cellX, cellY);
return world->getExterior(cellX, cellY);
}
else
return world->getInterior(name);
}
MWWorld::CellStore* WorldView::findNamedCell(const std::string& name)
{
MWBase::World* world = MWBase::Environment::get().getWorld();
const ESM::Cell* esmCell = world->getExterior(name);
if (esmCell)
return world->getExterior(esmCell->getGridX(), esmCell->getGridY());
else
return world->getInterior(name);
}
MWWorld::CellStore* WorldView::findExteriorCell(int x, int y)
{
MWBase::World* world = MWBase::Environment::get().getWorld();
return world->getExterior(x, y);
}
}

View file

@ -44,6 +44,10 @@ namespace MWLua
// If onlyActive = true, then search only among the objects that are currently in the scene.
// TODO: ObjectIdList selectObjects(const Queries::Query& query, bool onlyActive);
MWWorld::CellStore* findCell(const std::string& name, osg::Vec3f position);
MWWorld::CellStore* findNamedCell(const std::string& name);
MWWorld::CellStore* findExteriorCell(int x, int y);
void load(ESM::ESMReader& esm);
void save(ESM::ESMWriter& esm) const;