Merge branch 'lua_new_api' into 'master'

Redesign OpenMW Lua API for game objects

Closes #6610

See merge request OpenMW/openmw!1686
pull/3226/head
uramer 3 years ago
commit 2325b16f8f

@ -57,9 +57,10 @@ add_openmw_dir (mwscript
)
add_openmw_dir (mwlua
luamanagerimp actions object worldview userdataserializer eventqueue query
luamanagerimp actions object worldview userdataserializer eventqueue
luabindings localscripts playerscripts objectbindings cellbindings asyncbindings settingsbindings
camerabindings uibindings inputbindings nearbybindings
types/types types/door types/actor types/container
)
add_openmw_dir (mwsound

@ -3,6 +3,7 @@
#include <components/esm3/loadcell.hpp>
#include "../mwworld/cellstore.hpp"
#include "types/types.hpp"
namespace sol
{
@ -50,9 +51,64 @@ namespace MWLua
if constexpr (std::is_same_v<CellT, GCell>)
{ // only for global scripts
cellT["selectObjects"] = [context](const CellT& cell, const Queries::Query& query)
cellT["getAll"] = [worldView=context.mWorldView, ids=getPackageToTypeTable(context.mLua->sol())](
const CellT& cell, sol::optional<sol::table> type)
{
return GObjectList{selectObjectsFromCellStore(query, cell.mStore, context)};
ObjectIdList res = std::make_shared<std::vector<ObjectId>>();
auto visitor = [&](const MWWorld::Ptr& ptr)
{
worldView->getObjectRegistry()->registerPtr(ptr);
if (ptr.getLuaType() == ptr.getType())
res->push_back(getId(ptr));
return true;
};
bool ok = false;
sol::optional<uint32_t> typeId = sol::nullopt;
if (type.has_value())
typeId = ids[*type];
else
{
ok = true;
cell.mStore->forEach(std::move(visitor));
}
if (typeId.has_value())
{
ok = true;
switch (*typeId)
{
case ESM::REC_INTERNAL_PLAYER:
{
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
if (player.getCell() == cell.mStore)
res->push_back(getId(player));
}
break;
case ESM::REC_CREA: cell.mStore->template forEachType<ESM::Creature>(visitor); break;
case ESM::REC_NPC_: cell.mStore->template forEachType<ESM::NPC>(visitor); break;
case ESM::REC_ACTI: cell.mStore->template forEachType<ESM::Activator>(visitor); break;
case ESM::REC_DOOR: cell.mStore->template forEachType<ESM::Door>(visitor); break;
case ESM::REC_CONT: cell.mStore->template forEachType<ESM::Container>(visitor); break;
case ESM::REC_ALCH: cell.mStore->template forEachType<ESM::Potion>(visitor); break;
case ESM::REC_ARMO: cell.mStore->template forEachType<ESM::Armor>(visitor); break;
case ESM::REC_BOOK: cell.mStore->template forEachType<ESM::Book>(visitor); break;
case ESM::REC_CLOT: cell.mStore->template forEachType<ESM::Clothing>(visitor); break;
case ESM::REC_INGR: cell.mStore->template forEachType<ESM::Ingredient>(visitor); break;
case ESM::REC_LIGH: cell.mStore->template forEachType<ESM::Light>(visitor); break;
case ESM::REC_MISC: cell.mStore->template forEachType<ESM::Miscellaneous>(visitor); break;
case ESM::REC_WEAP: cell.mStore->template forEachType<ESM::Weapon>(visitor); break;
case ESM::REC_APPA: cell.mStore->template forEachType<ESM::Apparatus>(visitor); break;
case ESM::REC_LOCK: cell.mStore->template forEachType<ESM::Lockpick>(visitor); break;
case ESM::REC_PROB: cell.mStore->template forEachType<ESM::Probe>(visitor); break;
case ESM::REC_REPA: cell.mStore->template forEachType<ESM::Repair>(visitor); break;
default: ok = false;
}
}
if (!ok)
throw std::runtime_error(std::string("Incorrect type argument in cell:getAll: " + LuaUtil::toString(*type)));
return GObjectList{res};
};
}
}

@ -43,31 +43,12 @@ namespace MWLua
#undef CONTROL
sol::usertype<SelfObject> selfAPI =
context.mLua->sol().new_usertype<SelfObject>("SelfObject", sol::base_classes, sol::bases<LObject>());
context.mLua->sol().new_usertype<SelfObject>("SelfObject", sol::base_classes, sol::bases<LObject, Object>());
selfAPI[sol::meta_function::to_string] = [](SelfObject& self) { return "openmw.self[" + self.toString() + "]"; };
selfAPI["object"] = sol::readonly_property([](SelfObject& self) -> LObject { return LObject(self); });
selfAPI["controls"] = sol::readonly_property([](SelfObject& self) { return &self.mControls; });
selfAPI["isActive"] = [](SelfObject& self) { return &self.mIsActive; };
selfAPI["enableAI"] = [](SelfObject& self, bool v) { self.mControls.mDisableAI = !v; };
selfAPI["setEquipment"] = [context](const SelfObject& obj, sol::table equipment)
{
if (!obj.ptr().getClass().hasInventoryStore(obj.ptr()))
{
if (!equipment.empty())
throw std::runtime_error(ptrToString(obj.ptr()) + " has no equipment slots");
return;
}
SetEquipmentAction::Equipment eqp;
for (auto& [key, value] : equipment)
{
int slot = key.as<int>();
if (value.is<LObject>())
eqp[slot] = value.as<LObject>().id();
else
eqp[slot] = value.as<std::string>();
}
context.mLuaManager->addAction(std::make_unique<SetEquipmentAction>(context.mLua, obj.id(), std::move(eqp)));
};
using AiPackage = MWMechanics::AiPackage;
sol::usertype<AiPackage> aiPackage = context.mLua->sol().new_usertype<AiPackage>("AiPackage");

@ -2,7 +2,6 @@
#include <components/lua/luastate.hpp>
#include <components/lua/i18n.hpp>
#include <components/queries/luabindings.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/statemanager.hpp"
@ -12,18 +11,11 @@
#include "eventqueue.hpp"
#include "worldview.hpp"
#include "types/types.hpp"
namespace MWLua
{
static sol::table definitionList(LuaUtil::LuaState& lua, std::initializer_list<std::string_view> values)
{
sol::table res(lua.sol(), sol::create);
for (const std::string_view& v : values)
res[v] = v;
return LuaUtil::makeReadOnly(res);
}
static void addTimeBindings(sol::table& api, const Context& context, bool global)
{
api["getSimulationTime"] = [world=context.mWorldView]() { return world->getSimulationTime(); };
@ -49,7 +41,7 @@ namespace MWLua
{
auto* lua = context.mLua;
sol::table api(lua->sol(), sol::create);
api["API_REVISION"] = 17;
api["API_REVISION"] = 18;
api["quit"] = [lua]()
{
Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback();
@ -60,35 +52,6 @@ namespace MWLua
context.mGlobalEventQueue->push_back({std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer)});
};
addTimeBindings(api, context, false);
api["OBJECT_TYPE"] = definitionList(*lua,
{
ObjectTypeName::Activator, ObjectTypeName::Armor, ObjectTypeName::Book, ObjectTypeName::Clothing,
ObjectTypeName::Creature, ObjectTypeName::Door, ObjectTypeName::Ingredient, ObjectTypeName::Light,
ObjectTypeName::MiscItem, ObjectTypeName::NPC, ObjectTypeName::Player, ObjectTypeName::Potion,
ObjectTypeName::Static, ObjectTypeName::Weapon, ObjectTypeName::Activator, ObjectTypeName::Lockpick,
ObjectTypeName::Probe, ObjectTypeName::Repair
});
api["EQUIPMENT_SLOT"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs<std::string_view, int>({
{"Helmet", MWWorld::InventoryStore::Slot_Helmet},
{"Cuirass", MWWorld::InventoryStore::Slot_Cuirass},
{"Greaves", MWWorld::InventoryStore::Slot_Greaves},
{"LeftPauldron", MWWorld::InventoryStore::Slot_LeftPauldron},
{"RightPauldron", MWWorld::InventoryStore::Slot_RightPauldron},
{"LeftGauntlet", MWWorld::InventoryStore::Slot_LeftGauntlet},
{"RightGauntlet", MWWorld::InventoryStore::Slot_RightGauntlet},
{"Boots", MWWorld::InventoryStore::Slot_Boots},
{"Shirt", MWWorld::InventoryStore::Slot_Shirt},
{"Pants", MWWorld::InventoryStore::Slot_Pants},
{"Skirt", MWWorld::InventoryStore::Slot_Skirt},
{"Robe", MWWorld::InventoryStore::Slot_Robe},
{"LeftRing", MWWorld::InventoryStore::Slot_LeftRing},
{"RightRing", MWWorld::InventoryStore::Slot_RightRing},
{"Amulet", MWWorld::InventoryStore::Slot_Amulet},
{"Belt", MWWorld::InventoryStore::Slot_Belt},
{"CarriedRight", MWWorld::InventoryStore::Slot_CarriedRight},
{"CarriedLeft", MWWorld::InventoryStore::Slot_CarriedLeft},
{"Ammunition", MWWorld::InventoryStore::Slot_Ammunition}
}));
api["i18n"] = [i18n=context.mI18n](const std::string& context) { return i18n->getContext(context); };
const MWWorld::Store<ESM::GameSetting>* gmst = &MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
api["getGMST"] = [lua=context.mLua, gmst](const std::string& setting) -> sol::object
@ -126,59 +89,10 @@ namespace MWLua
return sol::nullopt;
};
api["activeActors"] = GObjectList{worldView->getActorsInScene()};
api["selectObjects"] = [context](const Queries::Query& query)
{
ObjectIdList list;
WorldView* worldView = context.mWorldView;
if (query.mQueryType == "activators")
list = worldView->getActivatorsInScene();
else if (query.mQueryType == "actors")
list = worldView->getActorsInScene();
else if (query.mQueryType == "containers")
list = worldView->getContainersInScene();
else if (query.mQueryType == "doors")
list = worldView->getDoorsInScene();
else if (query.mQueryType == "items")
list = worldView->getItemsInScene();
return GObjectList{selectObjectsFromList(query, list, context)};
// TODO: Use sqlite to search objects that are not in the scene
// return GObjectList{worldView->selectObjects(query, false)};
};
// TODO: add world.placeNewObject(recordId, cell, pos, [rot])
return LuaUtil::makeReadOnly(api);
}
sol::table initQueryPackage(const Context& context)
{
Queries::registerQueryBindings(context.mLua->sol());
sol::table query(context.mLua->sol(), sol::create);
for (std::string_view t : ObjectQueryTypes::types)
query[t] = Queries::Query(std::string(t));
for (const QueryFieldGroup& group : getBasicQueryFieldGroups())
query[group.mName] = initFieldGroup(context, group);
return query; // makeReadOnly is applied by LuaState::addCommonPackage
}
sol::table initFieldGroup(const Context& context, const QueryFieldGroup& group)
{
sol::table res(context.mLua->sol(), sol::create);
for (const Queries::Field* field : group.mFields)
{
sol::table subgroup = res;
if (field->path().empty())
throw std::logic_error("Empty path in Queries::Field");
for (size_t i = 0; i < field->path().size() - 1; ++i)
{
const std::string& name = field->path()[i];
if (subgroup[name] == sol::nil)
subgroup[name] = LuaUtil::makeReadOnly(context.mLua->newTable());
subgroup = LuaUtil::getMutableFromReadOnly(subgroup[name]);
}
subgroup[field->path().back()] = field;
}
return LuaUtil::makeReadOnly(res);
}
sol::table initGlobalStoragePackage(const Context& context, LuaUtil::LuaStorage* globalStorage)
{
sol::table res(context.mLua->sol(), sol::create);

@ -9,7 +9,6 @@
#include "context.hpp"
#include "eventqueue.hpp"
#include "object.hpp"
#include "query.hpp"
#include "worldview.hpp"
namespace MWWorld
@ -22,9 +21,6 @@ namespace MWLua
sol::table initCorePackage(const Context&);
sol::table initWorldPackage(const Context&);
sol::table initQueryPackage(const Context&);
sol::table initFieldGroup(const Context&, const QueryFieldGroup&);
sol::table initGlobalStoragePackage(const Context&, LuaUtil::LuaStorage* globalStorage);
sol::table initLocalStoragePackage(const Context&, LuaUtil::LuaStorage* globalStorage);
@ -38,14 +34,6 @@ namespace MWLua
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&);

@ -22,6 +22,7 @@
#include "luabindings.hpp"
#include "userdataserializer.hpp"
#include "types/types.hpp"
namespace MWLua
{
@ -81,10 +82,11 @@ namespace MWLua
mLua.addCommonPackage("openmw.async", getAsyncPackageInitializer(context));
mLua.addCommonPackage("openmw.util", LuaUtil::initUtilPackage(mLua.sol()));
mLua.addCommonPackage("openmw.core", initCorePackage(context));
mLua.addCommonPackage("openmw.query", initQueryPackage(context));
mLua.addCommonPackage("openmw.types", initTypesPackage(context));
mGlobalScripts.addPackage("openmw.world", initWorldPackage(context));
mGlobalScripts.addPackage("openmw.settings", initGlobalSettingsPackage(context));
mGlobalScripts.addPackage("openmw.storage", initGlobalStoragePackage(context, &mGlobalStorage));
mCameraPackage = initCameraPackage(localContext);
mUserInterfacePackage = initUserInterfacePackage(localContext);
mInputPackage = initInputPackage(localContext);
@ -388,6 +390,7 @@ namespace MWLua
{
assert(mInitialized);
assert(flag != ESM::LuaScriptCfg::sGlobal);
assert(ptr.getType() != ESM::REC_STAT);
std::shared_ptr<LocalScripts> scripts;
if (flag == ESM::LuaScriptCfg::sPlayer)
{

@ -1,7 +1,6 @@
#include "luabindings.hpp"
#include <components/lua/luastate.hpp>
#include <components/queries/luabindings.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
@ -98,24 +97,6 @@ namespace MWLua
api["containers"] = LObjectList{worldView->getContainersInScene()};
api["doors"] = LObjectList{worldView->getDoorsInScene()};
api["items"] = LObjectList{worldView->getItemsInScene()};
api["selectObjects"] = [context](const Queries::Query& query)
{
ObjectIdList list;
WorldView* worldView = context.mWorldView;
if (query.mQueryType == "activators")
list = worldView->getActivatorsInScene();
else if (query.mQueryType == "actors")
list = worldView->getActorsInScene();
else if (query.mQueryType == "containers")
list = worldView->getContainersInScene();
else if (query.mQueryType == "doors")
list = worldView->getDoorsInScene();
else if (query.mQueryType == "items")
list = worldView->getItemsInScene();
return LObjectList{selectObjectsFromList(query, list, context)};
// TODO: Maybe use sqlite
// return LObjectList{worldView->selectObjects(query, true)};
};
return LuaUtil::makeReadOnly(api);
}
}

@ -2,6 +2,8 @@
#include <unordered_map>
#include "types/types.hpp"
namespace MWLua
{
@ -10,75 +12,12 @@ namespace MWLua
return std::to_string(id.mIndex) + "_" + std::to_string(id.mContentFile);
}
struct LuaObjectTypeInfo
{
std::string_view mName;
ESM::LuaScriptCfg::Flags mFlag = 0;
};
const static std::unordered_map<ESM::RecNameInts, LuaObjectTypeInfo> luaObjectTypeInfo = {
{ESM::REC_ACTI, {ObjectTypeName::Activator, ESM::LuaScriptCfg::sActivator}},
{ESM::REC_ARMO, {ObjectTypeName::Armor, ESM::LuaScriptCfg::sArmor}},
{ESM::REC_BOOK, {ObjectTypeName::Book, ESM::LuaScriptCfg::sBook}},
{ESM::REC_CLOT, {ObjectTypeName::Clothing, ESM::LuaScriptCfg::sClothing}},
{ESM::REC_CONT, {ObjectTypeName::Container, ESM::LuaScriptCfg::sContainer}},
{ESM::REC_CREA, {ObjectTypeName::Creature, ESM::LuaScriptCfg::sCreature}},
{ESM::REC_DOOR, {ObjectTypeName::Door, ESM::LuaScriptCfg::sDoor}},
{ESM::REC_INGR, {ObjectTypeName::Ingredient, ESM::LuaScriptCfg::sIngredient}},
{ESM::REC_LIGH, {ObjectTypeName::Light, ESM::LuaScriptCfg::sLight}},
{ESM::REC_MISC, {ObjectTypeName::MiscItem, ESM::LuaScriptCfg::sMiscItem}},
{ESM::REC_NPC_, {ObjectTypeName::NPC, ESM::LuaScriptCfg::sNPC}},
{ESM::REC_ALCH, {ObjectTypeName::Potion, ESM::LuaScriptCfg::sPotion}},
{ESM::REC_STAT, {ObjectTypeName::Static}},
{ESM::REC_WEAP, {ObjectTypeName::Weapon, ESM::LuaScriptCfg::sWeapon}},
{ESM::REC_APPA, {ObjectTypeName::Apparatus}},
{ESM::REC_LOCK, {ObjectTypeName::Lockpick}},
{ESM::REC_PROB, {ObjectTypeName::Probe}},
{ESM::REC_REPA, {ObjectTypeName::Repair}},
};
std::string_view getLuaObjectTypeName(ESM::RecNameInts type, std::string_view fallback)
{
auto it = luaObjectTypeInfo.find(type);
if (it != luaObjectTypeInfo.end())
return it->second.mName;
else
return fallback;
}
bool isMarker(const MWWorld::Ptr& ptr)
{
std::string_view id = ptr.getCellRef().getRefId();
return id == "prisonmarker" || id == "divinemarker" || id == "templemarker" || id == "northmarker";
}
std::string_view getLuaObjectTypeName(const MWWorld::Ptr& ptr)
{
// Behaviour of this function is a part of OpenMW Lua API. We can not just return
// `ptr.getTypeDescription()` because its implementation is distributed over many files
// and can be accidentally changed. We use `ptr.getTypeDescription()` only as a fallback
// for types that are not present in `luaObjectTypeInfo` (for such types result stability
// is not necessary because they are not listed in OpenMW Lua documentation).
if (ptr.getCellRef().getRefId() == "player")
return "Player";
if (isMarker(ptr))
return "Marker";
return getLuaObjectTypeName(static_cast<ESM::RecNameInts>(ptr.getType()), /*fallback=*/ptr.getTypeDescription());
}
ESM::LuaScriptCfg::Flags getLuaScriptFlag(const MWWorld::Ptr& ptr)
{
if (ptr.getCellRef().getRefId() == "player")
return ESM::LuaScriptCfg::sPlayer;
if (isMarker(ptr))
return 0;
auto it = luaObjectTypeInfo.find(static_cast<ESM::RecNameInts>(ptr.getType()));
if (it != luaObjectTypeInfo.end())
return it->second.mFlag;
else
return 0;
}
std::string ptrToString(const MWWorld::Ptr& ptr)
{
std::string res = "object";

@ -3,6 +3,8 @@
#include <typeindex>
#include <sol/sol.hpp>
#include <components/esm3/cellref.hpp>
#include <components/esm/defs.hpp>
#include <components/esm/luascripts.hpp>
@ -14,31 +16,6 @@
namespace MWLua
{
namespace ObjectTypeName
{
// Names of object types in Lua.
// These names are part of OpenMW Lua API.
constexpr std::string_view Activator = "Activator";
constexpr std::string_view Armor = "Armor";
constexpr std::string_view Book = "Book";
constexpr std::string_view Clothing = "Clothing";
constexpr std::string_view Container = "Container";
constexpr std::string_view Creature = "Creature";
constexpr std::string_view Door = "Door";
constexpr std::string_view Ingredient = "Ingredient";
constexpr std::string_view Light = "Light";
constexpr std::string_view MiscItem = "Miscellaneous";
constexpr std::string_view NPC = "NPC";
constexpr std::string_view Player = "Player";
constexpr std::string_view Potion = "Potion";
constexpr std::string_view Static = "Static";
constexpr std::string_view Weapon = "Weapon";
constexpr std::string_view Apparatus = "Apparatus";
constexpr std::string_view Lockpick = "Lockpick";
constexpr std::string_view Probe = "Probe";
constexpr std::string_view Repair = "Repair";
}
// ObjectId is a unique identifier of a game object.
// It can change only if the order of content files was change.
using ObjectId = ESM::RefNum;
@ -46,12 +23,6 @@ namespace MWLua
std::string idToString(const ObjectId& id);
std::string ptrToString(const MWWorld::Ptr& ptr);
bool isMarker(const MWWorld::Ptr& ptr);
std::string_view getLuaObjectTypeName(ESM::RecNameInts recordType, std::string_view fallback = "Unknown");
std::string_view getLuaObjectTypeName(const MWWorld::Ptr& ptr);
// Each script has a set of flags that controls to which objects the script should be
// automatically attached. This function maps each object types to one of the flags.
ESM::LuaScriptCfg::Flags getLuaScriptFlag(const MWWorld::Ptr& ptr);
// Holds a mapping ObjectId -> MWWord::Ptr.
class ObjectRegistry
@ -95,7 +66,6 @@ namespace MWLua
ObjectId id() const { return mId; }
std::string toString() const;
std::string_view type() const { return getLuaObjectTypeName(ptr()); }
// Updates and returns the underlying Ptr. Throws an exception if object is not available.
const MWWorld::Ptr& ptr() const;
@ -103,6 +73,9 @@ namespace MWLua
// Returns `true` if calling `ptr()` is safe.
bool isValid() const;
virtual sol::object getObject(lua_State* lua, ObjectId id) const = 0; // returns LObject or GOBject
virtual sol::object getCell(lua_State* lua, MWWorld::CellStore* store) const = 0; // returns LCell or GCell
protected:
virtual void updatePtr() const = 0;
@ -114,17 +87,29 @@ namespace MWLua
};
// Used only in local scripts
struct LCell
{
MWWorld::CellStore* mStore;
};
class LObject : public Object
{
using Object::Object;
void updatePtr() const final { mPtr = mObjectRegistry->getPtr(mId, true); }
sol::object getObject(lua_State* lua, ObjectId id) const final { return sol::make_object<LObject>(lua, id, mObjectRegistry); }
sol::object getCell(lua_State* lua, MWWorld::CellStore* store) const final { return sol::make_object(lua, LCell{store}); }
};
// Used only in global scripts
struct GCell
{
MWWorld::CellStore* mStore;
};
class GObject : public Object
{
using Object::Object;
void updatePtr() const final { mPtr = mObjectRegistry->getPtr(mId, false); }
sol::object getObject(lua_State* lua, ObjectId id) const final { return sol::make_object<GObject>(lua, id, mObjectRegistry); }
sol::object getCell(lua_State* lua, MWWorld::CellStore* store) const final { return sol::make_object(lua, GCell{store}); }
};
using ObjectIdList = std::shared_ptr<std::vector<ObjectId>>;
@ -133,6 +118,9 @@ namespace MWLua
using GObjectList = ObjectList<GObject>;
using LObjectList = ObjectList<LObject>;
template <typename Obj>
struct Inventory { Obj mObj; };
}
#endif // MWLUA_OBJECT_H

@ -1,24 +1,14 @@
#include "luabindings.hpp"
#include <components/lua/luastate.hpp>
#include <components/queries/query.hpp>
#include "../mwclass/door.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/containerstore.hpp"
#include "../mwworld/inventorystore.hpp"
#include "eventqueue.hpp"
#include "luamanagerimp.hpp"
namespace MWLua
{
template <typename ObjectT>
struct Inventory
{
ObjectT mObj;
};
}
#include "types/types.hpp"
namespace sol
{
@ -39,364 +29,245 @@ namespace sol
namespace MWLua
{
template <typename ObjT>
using Cell = std::conditional_t<std::is_same_v<ObjT, LObject>, LCell, GCell>;
namespace {
static const MWWorld::Ptr& requireRecord(ESM::RecNameInts recordType, const MWWorld::Ptr& ptr)
{
if (ptr.getType() != recordType)
{
std::string msg = "Requires type '";
msg.append(getLuaObjectTypeName(recordType));
msg.append("', but applied to ");
msg.append(ptrToString(ptr));
throw std::runtime_error(msg);
}
return ptr;
}
template <typename ObjT>
using Cell = std::conditional_t<std::is_same_v<ObjT, LObject>, LCell, GCell>;
template <class ObjectT>
static void registerObjectList(const std::string& prefix, const Context& context)
{
using ListT = ObjectList<ObjectT>;
sol::state& lua = context.mLua->sol();
ObjectRegistry* registry = context.mWorldView->getObjectRegistry();
sol::usertype<ListT> listT = lua.new_usertype<ListT>(prefix + "ObjectList");
listT[sol::meta_function::to_string] =
[](const ListT& list) { return "{" + std::to_string(list.mIds->size()) + " objects}"; };
listT[sol::meta_function::length] = [](const ListT& list) { return list.mIds->size(); };
listT[sol::meta_function::index] = [registry](const ListT& list, size_t index)
{
if (index > 0 && index <= list.mIds->size())
return ObjectT((*list.mIds)[index - 1], registry);
else
throw std::runtime_error("Index out of range");
};
listT[sol::meta_function::ipairs] = [registry](const ListT& list)
template <class ObjectT>
void registerObjectList(const std::string& prefix, const Context& context)
{
auto iter = [registry](const ListT& l, int64_t i) -> sol::optional<std::tuple<int64_t, ObjectT>>
using ListT = ObjectList<ObjectT>;
sol::state& lua = context.mLua->sol();
ObjectRegistry* registry = context.mWorldView->getObjectRegistry();
sol::usertype<ListT> listT = lua.new_usertype<ListT>(prefix + "ObjectList");
listT[sol::meta_function::to_string] =
[](const ListT& list) { return "{" + std::to_string(list.mIds->size()) + " objects}"; };
listT[sol::meta_function::length] = [](const ListT& list) { return list.mIds->size(); };
listT[sol::meta_function::index] = [registry](const ListT& list, size_t index)
{
if (i >= 0 && i < static_cast<int64_t>(l.mIds->size()))
return std::make_tuple(i + 1, ObjectT((*l.mIds)[i], registry));
if (index > 0 && index <= list.mIds->size())
return ObjectT((*list.mIds)[index - 1], registry);
else
return sol::nullopt;
throw std::runtime_error("Index out of range");
};
return std::make_tuple(iter, list, 0);
};
listT["select"] = [context](const ListT& list, const Queries::Query& query)
{
return ListT{selectObjectsFromList(query, list.mIds, context)};
};
}
template <class ObjectT>
static void addBasicBindings(sol::usertype<ObjectT>& objectT, const Context& context)
{
objectT["isValid"] = [](const ObjectT& o) { return o.isValid(); };
objectT["recordId"] = sol::readonly_property([](const ObjectT& o) -> std::string
{
return o.ptr().getCellRef().getRefId();
});
objectT["cell"] = sol::readonly_property([](const ObjectT& o) -> sol::optional<Cell<ObjectT>>
{
const MWWorld::Ptr& ptr = o.ptr();
if (ptr.isInCell())
return Cell<ObjectT>{ptr.getCell()};
if constexpr (std::is_same_v<ObjectT, GObject>)
{
// GObject and LObject iterators are in separate branches because if they share source code
// there is a collision in sol and only one iterator can be mapped to Lua.
auto iter = sol::make_object(lua, [registry](const GObjectList& l, int64_t i) -> sol::optional<std::tuple<int64_t, GObject>>
{
if (i >= 0 && i < static_cast<int64_t>(l.mIds->size()))
return std::make_tuple(i + 1, GObject((*l.mIds)[i], registry));
else
return sol::nullopt;
});
listT[sol::meta_function::ipairs] = [iter](const GObjectList& list) { return std::make_tuple(iter, list, 0); };
}
else
return sol::nullopt;
});
objectT["position"] = sol::readonly_property([](const ObjectT& o) -> osg::Vec3f
{
return o.ptr().getRefData().getPosition().asVec3();
});
objectT["rotation"] = sol::readonly_property([](const ObjectT& o) -> osg::Vec3f
{
return o.ptr().getRefData().getPosition().asRotationVec3();
});
objectT["type"] = sol::readonly_property(&ObjectT::type);
objectT["count"] = sol::readonly_property([](const ObjectT& o) { return o.ptr().getRefData().getCount(); });
objectT[sol::meta_function::equal_to] = [](const ObjectT& a, const ObjectT& b) { return a.id() == b.id(); };
objectT[sol::meta_function::to_string] = &ObjectT::toString;
objectT["sendEvent"] = [context](const ObjectT& dest, std::string eventName, const sol::object& eventData)
{
context.mLocalEventQueue->push_back({dest.id(), std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer)});
};
{
auto iter = sol::make_object(lua, [registry](const LObjectList& l, int64_t i) -> sol::optional<std::tuple<int64_t, LObject>>
{
if (i >= 0 && i < static_cast<int64_t>(l.mIds->size()))
return std::make_tuple(i + 1, LObject((*l.mIds)[i], registry));
else
return sol::nullopt;
});
listT[sol::meta_function::ipairs] = [iter](const LObjectList& list) { return std::make_tuple(iter, list, 0); };
}
}
objectT["canMove"] = [](const ObjectT& o)
template <class ObjectT>
void addBasicBindings(sol::usertype<ObjectT>& objectT, const Context& context)
{
const MWWorld::Class& cls = o.ptr().getClass();
return cls.getMaxSpeed(o.ptr()) > 0;
};
objectT["getRunSpeed"] = [](const ObjectT& o)
{
const MWWorld::Class& cls = o.ptr().getClass();
return cls.getRunSpeed(o.ptr());
};
objectT["getWalkSpeed"] = [](const ObjectT& o)
{
const MWWorld::Class& cls = o.ptr().getClass();
return cls.getWalkSpeed(o.ptr());
};
objectT["activateBy"] = [context](const ObjectT& o, const ObjectT& actor)
{
uint32_t esmRecordType = actor.ptr().getType();
if (esmRecordType != ESM::REC_CREA && esmRecordType != ESM::REC_NPC_)
throw std::runtime_error("The argument of `activateBy` must be an actor who activates the object. Got: " +
ptrToString(actor.ptr()));
context.mLuaManager->addAction(std::make_unique<ActivateAction>(context.mLua, o.id(), actor.id()));
};
if constexpr (std::is_same_v<ObjectT, GObject>)
{ // Only for global scripts
objectT["addScript"] = [lua=context.mLua, luaManager=context.mLuaManager](const GObject& object, std::string_view path)
objectT["isValid"] = [](const ObjectT& o) { return o.isValid(); };
objectT["recordId"] = sol::readonly_property([](const ObjectT& o) -> std::string
{
const LuaUtil::ScriptsConfiguration& cfg = lua->getConfiguration();
std::optional<int> scriptId = cfg.findId(path);
if (!scriptId)
throw std::runtime_error("Unknown script: " + std::string(path));
if (!(cfg[*scriptId].mFlags & ESM::LuaScriptCfg::sCustom))
throw std::runtime_error("Script without CUSTOM tag can not be added dynamically: " + std::string(path));
luaManager->addCustomLocalScript(object.ptr(), *scriptId);
};
objectT["hasScript"] = [lua=context.mLua](const GObject& object, std::string_view path)
return o.ptr().getCellRef().getRefId();
});
objectT["cell"] = sol::readonly_property([](const ObjectT& o) -> sol::optional<Cell<ObjectT>>
{
const LuaUtil::ScriptsConfiguration& cfg = lua->getConfiguration();
std::optional<int> scriptId = cfg.findId(path);
if (!scriptId)
return false;
MWWorld::Ptr ptr = object.ptr();
LocalScripts* localScripts = ptr.getRefData().getLuaScripts();
if (localScripts)
return localScripts->hasScript(*scriptId);
const MWWorld::Ptr& ptr = o.ptr();
if (ptr.isInCell())
return Cell<ObjectT>{ptr.getCell()};
else
return false;
};
objectT["removeScript"] = [lua=context.mLua](const GObject& object, std::string_view path)
{
const LuaUtil::ScriptsConfiguration& cfg = lua->getConfiguration();
std::optional<int> scriptId = cfg.findId(path);
if (!scriptId)
throw std::runtime_error("Unknown script: " + std::string(path));
MWWorld::Ptr ptr = object.ptr();
LocalScripts* localScripts = ptr.getRefData().getLuaScripts();
if (!localScripts || !localScripts->hasScript(*scriptId))
throw std::runtime_error("There is no script " + std::string(path) + " on " + ptrToString(ptr));
ESM::LuaScriptCfg::Flags flags = cfg[*scriptId].mFlags;
if ((flags & (localScripts->getAutoStartMode() | ESM::LuaScriptCfg::sCustom)) != ESM::LuaScriptCfg::sCustom)
throw std::runtime_error("Autostarted script can not be removed: " + std::string(path));
localScripts->removeScript(*scriptId);
};
objectT["teleport"] = [context](const GObject& object, std::string_view cell,
const osg::Vec3f& pos, const sol::optional<osg::Vec3f>& optRot)
{
MWWorld::Ptr ptr = object.ptr();
osg::Vec3f rot = optRot ? *optRot : ptr.getRefData().getPosition().asRotationVec3();
auto action = std::make_unique<TeleportAction>(context.mLua, object.id(), std::string(cell), pos, rot);
if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr())
context.mLuaManager->addTeleportPlayerAction(std::move(action));
else
context.mLuaManager->addAction(std::move(action));
};
}
else
{ // Only for local scripts
objectT["isOnGround"] = [](const ObjectT& o)
return sol::nullopt;
});
objectT["position"] = sol::readonly_property([](const ObjectT& o) -> osg::Vec3f
{
return MWBase::Environment::get().getWorld()->isOnGround(o.ptr());
};
objectT["isSwimming"] = [](const ObjectT& o)
return o.ptr().getRefData().getPosition().asVec3();
});
objectT["rotation"] = sol::readonly_property([](const ObjectT& o) -> osg::Vec3f
{
return MWBase::Environment::get().getWorld()->isSwimming(o.ptr());
};
objectT["isInWeaponStance"] = [](const ObjectT& o)
return o.ptr().getRefData().getPosition().asRotationVec3();
});
objectT["type"] = sol::readonly_property([types=getTypeToPackageTable(context.mLua->sol())](const ObjectT& o) mutable
{
const MWWorld::Class& cls = o.ptr().getClass();
return cls.isActor() && cls.getCreatureStats(o.ptr()).getDrawState() == MWMechanics::DrawState_Weapon;
};
objectT["isInMagicStance"] = [](const ObjectT& o)
return types[o.ptr().getLuaType()];
});
objectT["count"] = sol::readonly_property([](const ObjectT& o) { return o.ptr().getRefData().getCount(); });
objectT[sol::meta_function::equal_to] = [](const ObjectT& a, const ObjectT& b) { return a.id() == b.id(); };
objectT[sol::meta_function::to_string] = &ObjectT::toString;
objectT["sendEvent"] = [context](const ObjectT& dest, std::string eventName, const sol::object& eventData)
{
const MWWorld::Class& cls = o.ptr().getClass();
return cls.isActor() && cls.getCreatureStats(o.ptr()).getDrawState() == MWMechanics::DrawState_Spell;
context.mLocalEventQueue->push_back({dest.id(), std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer)});
};
objectT["getCurrentSpeed"] = [](const ObjectT& o)
objectT["activateBy"] = [context](const ObjectT& o, const ObjectT& actor)
{
const MWWorld::Class& cls = o.ptr().getClass();
return cls.getCurrentSpeed(o.ptr());
uint32_t esmRecordType = actor.ptr().getType();
if (esmRecordType != ESM::REC_CREA && esmRecordType != ESM::REC_NPC_)
throw std::runtime_error("The argument of `activateBy` must be an actor who activates the object. Got: " +
ptrToString(actor.ptr()));
context.mLuaManager->addAction(std::make_unique<ActivateAction>(context.mLua, o.id(), actor.id()));
};
}
}
template <class ObjectT>
static void addDoorBindings(sol::usertype<ObjectT>& objectT, const Context& context)
{
auto ptr = [](const ObjectT& o) -> const MWWorld::Ptr& { return requireRecord(ESM::REC_DOOR, o.ptr()); };
objectT["isTeleport"] = sol::readonly_property([ptr](const ObjectT& o)
{
return ptr(o).getCellRef().getTeleport();
});
objectT["destPosition"] = sol::readonly_property([ptr](const ObjectT& o) -> osg::Vec3f
{
return ptr(o).getCellRef().getDoorDest().asVec3();
});
objectT["destRotation"] = sol::readonly_property([ptr](const ObjectT& o) -> osg::Vec3f
{
return ptr(o).getCellRef().getDoorDest().asRotationVec3();
});
objectT["destCell"] = sol::readonly_property(
[ptr, worldView=context.mWorldView](const ObjectT& o) -> sol::optional<Cell<ObjectT>>
{
const MWWorld::CellRef& cellRef = ptr(o).getCellRef();
if (!cellRef.getTeleport())
return sol::nullopt;
MWWorld::CellStore* cell = worldView->findCell(cellRef.getDestCell(), cellRef.getDoorDest().asVec3());
if (cell)
return Cell<ObjectT>{cell};
else
return sol::nullopt;
});
}
if constexpr (std::is_same_v<ObjectT, GObject>)
{ // Only for global scripts
objectT["addScript"] = [lua=context.mLua, luaManager=context.mLuaManager](const GObject& object, std::string_view path)
{
const LuaUtil::ScriptsConfiguration& cfg = lua->getConfiguration();
std::optional<int> scriptId = cfg.findId(path);
if (!scriptId)
throw std::runtime_error("Unknown script: " + std::string(path));
if (!(cfg[*scriptId].mFlags & ESM::LuaScriptCfg::sCustom))
throw std::runtime_error("Script without CUSTOM tag can not be added dynamically: " + std::string(path));
if (object.ptr().getType() == ESM::REC_STAT)
throw std::runtime_error("Attaching scripts to Static is not allowed: " + std::string(path));
luaManager->addCustomLocalScript(object.ptr(), *scriptId);
};
objectT["hasScript"] = [lua=context.mLua](const GObject& object, std::string_view path)
{
const LuaUtil::ScriptsConfiguration& cfg = lua->getConfiguration();
std::optional<int> scriptId = cfg.findId(path);
if (!scriptId)
return false;
MWWorld::Ptr ptr = object.ptr();
LocalScripts* localScripts = ptr.getRefData().getLuaScripts();
if (localScripts)
return localScripts->hasScript(*scriptId);
else
return false;
};
objectT["removeScript"] = [lua=context.mLua](const GObject& object, std::string_view path)
{
const LuaUtil::ScriptsConfiguration& cfg = lua->getConfiguration();
std::optional<int> scriptId = cfg.findId(path);
if (!scriptId)
throw std::runtime_error("Unknown script: " + std::string(path));
MWWorld::Ptr ptr = object.ptr();
LocalScripts* localScripts = ptr.getRefData().getLuaScripts();
if (!localScripts || !localScripts->hasScript(*scriptId))
throw std::runtime_error("There is no script " + std::string(path) + " on " + ptrToString(ptr));
ESM::LuaScriptCfg::Flags flags = cfg[*scriptId].mFlags;
if ((flags & (localScripts->getAutoStartMode() | ESM::LuaScriptCfg::sCustom)) != ESM::LuaScriptCfg::sCustom)
throw std::runtime_error("Autostarted script can not be removed: " + std::string(path));
localScripts->removeScript(*scriptId);
};
static SetEquipmentAction::Equipment parseEquipmentTable(sol::table equipment)
{
SetEquipmentAction::Equipment eqp;
for (auto& [key, value] : equipment)
{
int slot = key.as<int>();
if (value.is<GObject>())
eqp[slot] = value.as<GObject>().id();
else
eqp[slot] = value.as<std::string>();
objectT["teleport"] = [context](const GObject& object, std::string_view cell,
const osg::Vec3f& pos, const sol::optional<osg::Vec3f>& optRot)
{
MWWorld::Ptr ptr = object.ptr();
osg::Vec3f rot = optRot ? *optRot : ptr.getRefData().getPosition().asRotationVec3();
auto action = std::make_unique<TeleportAction>(context.mLua, object.id(), std::string(cell), pos, rot);
if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr())
context.mLuaManager->addTeleportPlayerAction(std::move(action));
else
context.mLuaManager->addAction(std::move(action));
};
}
}
return eqp;
}
template <class ObjectT>
static void addInventoryBindings(sol::usertype<ObjectT>& objectT, const std::string& prefix, const Context& context)
{
using InventoryT = Inventory<ObjectT>;
sol::usertype<InventoryT> inventoryT = context.mLua->sol().new_usertype<InventoryT>(prefix + "Inventory");
objectT["getEquipment"] = [context](const ObjectT& o)
template <class ObjectT>
void addInventoryBindings(sol::usertype<ObjectT>& objectT, const std::string& prefix, const Context& context)
{
const MWWorld::Ptr& ptr = o.ptr();
sol::table equipment(context.mLua->sol(), sol::create);
if (!ptr.getClass().hasInventoryStore(ptr))
return equipment;
using InventoryT = Inventory<ObjectT>;
sol::usertype<InventoryT> inventoryT = context.mLua->sol().new_usertype<InventoryT>(prefix + "Inventory");
MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr);
for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
{
auto it = store.getSlot(slot);
if (it == store.end())
continue;
context.mWorldView->getObjectRegistry()->registerPtr(*it);
equipment[slot] = ObjectT(getId(*it), context.mWorldView->getObjectRegistry());
}
return equipment;
};
objectT["isEquipped"] = [](const ObjectT& actor, const ObjectT& item)
{
const MWWorld::Ptr& ptr = actor.ptr();
if (!ptr.getClass().hasInventoryStore(ptr))
return false;
MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr);
return store.isEquipped(item.ptr());
};
objectT["inventory"] = sol::readonly_property([](const ObjectT& o) { return InventoryT{o}; });
inventoryT[sol::meta_function::to_string] =
[](const InventoryT& inv) { return "Inventory[" + inv.mObj.toString() + "]"; };
inventoryT["getAll"] = [worldView=context.mWorldView](const InventoryT& inventory, sol::optional<std::string_view> type)
{
int mask;
if (!type.has_value())
mask = MWWorld::ContainerStore::Type_All;
else if (*type == ObjectTypeName::Potion)
mask = MWWorld::ContainerStore::Type_Potion;
else if (*type == ObjectTypeName::Armor)
mask = MWWorld::ContainerStore::Type_Armor;
else if (*type == ObjectTypeName::Book)
mask = MWWorld::ContainerStore::Type_Book;
else if (*type == ObjectTypeName::Clothing)
mask = MWWorld::ContainerStore::Type_Clothing;
else if (*type == ObjectTypeName::Ingredient)
mask = MWWorld::ContainerStore::Type_Ingredient;
else if (*type == ObjectTypeName::Light)
mask = MWWorld::ContainerStore::Type_Light;
else if (*type == ObjectTypeName::MiscItem)
mask = MWWorld::ContainerStore::Type_Miscellaneous;
else if (*type == ObjectTypeName::Weapon)
mask = MWWorld::ContainerStore::Type_Weapon;
else if (*type == ObjectTypeName::Apparatus)
mask = MWWorld::ContainerStore::Type_Apparatus;
else if (*type == ObjectTypeName::Lockpick)
mask = MWWorld::ContainerStore::Type_Lockpick;
else if (*type == ObjectTypeName::Probe)
mask = MWWorld::ContainerStore::Type_Probe;
else if (*type == ObjectTypeName::Repair)
mask = MWWorld::ContainerStore::Type_Repair;
else
throw std::runtime_error(std::string("inventory:getAll doesn't support type " + std::string(*type)));
inventoryT[sol::meta_function::to_string] =
[](const InventoryT& inv) { return "Inventory[" + inv.mObj.toString() + "]"; };
const MWWorld::Ptr& ptr = inventory.mObj.ptr();
MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr);
ObjectIdList list = std::make_shared<std::vector<ObjectId>>();
auto it = store.begin(mask);
while (it.getType() != -1)
inventoryT["getAll"] = [worldView=context.mWorldView, ids=getPackageToTypeTable(context.mLua->sol())](
const InventoryT& inventory, sol::optional<sol::table> type)
{
const MWWorld::Ptr& item = *(it++);
worldView->getObjectRegistry()->registerPtr(item);
list->push_back(getId(item));
}
return ObjectList<ObjectT>{list};
};
int mask = -1;
sol::optional<uint32_t> typeId = sol::nullopt;
if (type.has_value())
typeId = ids[*type];
else
mask = MWWorld::ContainerStore::Type_All;
inventoryT["countOf"] = [](const InventoryT& inventory, const std::string& recordId)
{
const MWWorld::Ptr& ptr = inventory.mObj.ptr();
MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr);
return store.count(recordId);
};
if (typeId.has_value())
{
switch (*typeId)
{
case ESM::REC_ALCH: mask = MWWorld::ContainerStore::Type_Potion; break;
case ESM::REC_ARMO: mask = MWWorld::ContainerStore::Type_Armor; break;
case ESM::REC_BOOK: mask = MWWorld::ContainerStore::Type_Book; break;
case ESM::REC_CLOT: mask = MWWorld::ContainerStore::Type_Clothing; break;
case ESM::REC_INGR: mask = MWWorld::ContainerStore::Type_Ingredient; break;
case ESM::REC_LIGH: mask = MWWorld::ContainerStore::Type_Light; break;
case ESM::REC_MISC: mask = MWWorld::ContainerStore::Type_Miscellaneous; break;
case ESM::REC_WEAP: mask = MWWorld::ContainerStore::Type_Weapon; break;
case ESM::REC_APPA: mask = MWWorld::ContainerStore::Type_Apparatus; break;
case ESM::REC_LOCK: mask = MWWorld::ContainerStore::Type_Lockpick; break;
case ESM::REC_PROB: mask = MWWorld::ContainerStore::Type_Probe; break;
case ESM::REC_REPA: mask = MWWorld::ContainerStore::Type_Repair; break;
default:;
}
}
if constexpr (std::is_same_v<ObjectT, GObject>)
{ // Only for global scripts
objectT["setEquipment"] = [context](const GObject& obj, sol::table equipment)
{
if (!obj.ptr().getClass().hasInventoryStore(obj.ptr()))
if (mask == -1)
throw std::runtime_error(std::string("Incorrect type argument in inventory:getAll: " + LuaUtil::toString(*type)));
const MWWorld::Ptr& ptr = inventory.mObj.ptr();
MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr);
ObjectIdList list = std::make_shared<std::vector<ObjectId>>();
auto it = store.begin(mask);
while (it.getType() != -1)
{
if (!equipment.empty())
throw std::runtime_error(ptrToString(obj.ptr()) + " has no equipment slots");
return;
const MWWorld::Ptr& item = *(it++);
worldView->getObjectRegistry()->registerPtr(item);
list->push_back(getId(item));
}
context.mLuaManager->addAction(std::make_unique<SetEquipmentAction>(
context.mLua, obj.id(), parseEquipmentTable(equipment)));
return ObjectList<ObjectT>{list};
};
inventoryT["countOf"] = [](const InventoryT& inventory, const std::string& recordId)
{
const MWWorld::Ptr& ptr = inventory.mObj.ptr();
MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr);
return store.count(recordId);
};
// TODO
// obj.inventory:drop(obj2, [count])
// obj.inventory:drop(recordId, [count])
// obj.inventory:addNew(recordId, [count])
// obj.inventory:remove(obj/recordId, [count])
/*objectT["moveInto"] = [](const GObject& obj, const InventoryT& inventory) {};
inventoryT["drop"] = [](const InventoryT& inventory) {};
inventoryT["addNew"] = [](const InventoryT& inventory) {};
inventoryT["remove"] = [](const InventoryT& inventory) {};*/
if constexpr (std::is_same_v<ObjectT, GObject>)
{ // Only for global scripts
// TODO
// obj.inventory:drop(obj2, [count])
// obj.inventory:drop(recordId, [count])
// obj.inventory:addNew(recordId, [count])
// obj.inventory:remove(obj/recordId, [count])
/*objectT["moveInto"] = [](const GObject& obj, const InventoryT& inventory) {};
inventoryT["drop"] = [](const InventoryT& inventory) {};
inventoryT["addNew"] = [](const InventoryT& inventory) {};
inventoryT["remove"] = [](const InventoryT& inventory) {};*/
}
}
}
template <class ObjectT>
static void initObjectBindings(const std::string& prefix, const Context& context)
{
sol::usertype<ObjectT> objectT = context.mLua->sol().new_usertype<ObjectT>(prefix + "Object");
addBasicBindings<ObjectT>(objectT, context);
addDoorBindings<ObjectT>(objectT, context);
addInventoryBindings<ObjectT>(objectT, prefix, context);
template <class ObjectT>
void initObjectBindings(const std::string& prefix, const Context& context)
{
sol::usertype<ObjectT> objectT = context.mLua->sol().new_usertype<ObjectT>(
prefix + "Object", sol::base_classes, sol::bases<Object>());
addBasicBindings<ObjectT>(objectT, context);
addInventoryBindings<ObjectT>(objectT, prefix, context);
registerObjectList<ObjectT>(prefix, context);
}
registerObjectList<ObjectT>(prefix, context);
}
} // namespace
void initObjectBindingsForLocalScripts(const Context& context)
{

@ -1,191 +0,0 @@
#include "query.hpp"
#include <sol/sol.hpp>
#include <components/lua/luastate.hpp>
#include "../mwclass/container.hpp"
#include "../mwworld/cellstore.hpp"
#include "worldview.hpp"
namespace MWLua
{
static std::vector<QueryFieldGroup> initBasicFieldGroups()
{
auto createGroup = [](std::string name, const auto& arr) -> QueryFieldGroup
{
std::vector<const Queries::Field*> fieldPtrs;
fieldPtrs.reserve(arr.size());
for (const Queries::Field& field : arr)
fieldPtrs.push_back(&field);
return {std::move(name), std::move(fieldPtrs)};
};
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", "name"}, typeid(std::string)),
Queries::Field({"destCell", "region"}, typeid(std::string)),
Queries::Field({"destCell", "isExterior"}, typeid(bool)),
};
return std::vector<QueryFieldGroup>{
createGroup("OBJECT", objectFields),
createGroup("DOOR", doorFields),
};
}
const std::vector<QueryFieldGroup>& getBasicQueryFieldGroups()
{
static std::vector<QueryFieldGroup> fieldGroups = initBasicFieldGroups();
return fieldGroups;
}
bool checkQueryConditions(const Queries::Query& query, const ObjectId& id, const Context& context)
{
auto compareFn = [](auto&& a, auto&& b, Queries::Condition::Type t)
{
switch (t)
{
case Queries::Condition::EQUAL: return a == b;
case Queries::Condition::NOT_EQUAL: return a != b;
case Queries::Condition::GREATER: return a > b;
case Queries::Condition::GREATER_OR_EQUAL: return a >= b;
case Queries::Condition::LESSER: return a < b;
case Queries::Condition::LESSER_OR_EQUAL: return a <= b;
default:
throw std::runtime_error("Unsupported condition type");
}
};
sol::object obj;
MWWorld::Ptr ptr;
if (context.mIsGlobal)
{
GObject g(id, context.mWorldView->getObjectRegistry());
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())
return false;
ptr = l.ptr();
obj = sol::make_object(context.mLua->sol(), l);
}
if (ptr.getRefData().getCount() == 0)
return false;
// It is important to exclude all markers before checking what class it is.
// For example "prisonmarker" has class "Door" despite that it is only an invisible marker.
if (isMarker(ptr))
return false;
const MWWorld::Class& cls = ptr.getClass();
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;
std::vector<char> condStack;
for (const Queries::Operation& op : query.mFilter.mOperations)
{
switch(op.mType)
{
case Queries::Operation::PUSH:
{
const Queries::Condition& cond = query.mFilter.mConditions[op.mConditionIndex];
sol::object fieldObj = obj;
for (const std::string& field : cond.mField->path())
fieldObj = LuaUtil::getFieldOrNil(fieldObj, field);
bool c;
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);
else if (cond.mField->type() == typeid(double))
c = compareFn(fieldObj.as<double>(), std::get<double>(cond.mValue), cond.mType);
else if (cond.mField->type() == typeid(bool))
c = compareFn(fieldObj.as<bool>(), std::get<bool>(cond.mValue), cond.mType);
else if (cond.mField->type() == typeid(int32_t))
c = compareFn(fieldObj.as<int32_t>(), std::get<int32_t>(cond.mValue), cond.mType);
else if (cond.mField->type() == typeid(int64_t))
c = compareFn(fieldObj.as<int64_t>(), std::get<int64_t>(cond.mValue), cond.mType);
else
throw std::runtime_error("Unknown field type");
condStack.push_back(c);
break;
}
case Queries::Operation::NOT:
condStack.back() = !condStack.back();
break;
case Queries::Operation::AND:
{
bool v = condStack.back();
condStack.pop_back();
condStack.back() = condStack.back() && v;
break;
}
case Queries::Operation::OR:
{
bool v = condStack.back();
condStack.pop_back();
condStack.back() = condStack.back() || v;
break;
}
}
}
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;
}
}

@ -1,39 +0,0 @@
#ifndef MWLUA_QUERY_H
#define MWLUA_QUERY_H
#include <string>
#include <components/queries/query.hpp>
#include "context.hpp"
#include "object.hpp"
namespace MWLua
{
struct ObjectQueryTypes
{
static constexpr std::string_view ACTIVATORS = "activators";
static constexpr std::string_view ACTORS = "actors";
static constexpr std::string_view CONTAINERS = "containers";
static constexpr std::string_view DOORS = "doors";
static constexpr std::string_view ITEMS = "items";
static constexpr std::string_view types[] = {ACTIVATORS, ACTORS, CONTAINERS, DOORS, ITEMS};
};
struct QueryFieldGroup
{
std::string mName;
std::vector<const Queries::Field*> mFields;
};
const std::vector<QueryFieldGroup>& getBasicQueryFieldGroups();
// 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&);
}
#endif // MWLUA_QUERY_H

@ -0,0 +1,137 @@
#include "types.hpp"
#include <components/lua/luastate.hpp>
#include <apps/openmw/mwmechanics/drawstate.hpp>
#include <apps/openmw/mwworld/inventorystore.hpp>
#include <apps/openmw/mwworld/class.hpp>
#include "../actions.hpp"
#include "../luabindings.hpp"
#include "../localscripts.hpp"
#include "../luamanagerimp.hpp"
namespace MWLua
{
using SelfObject = LocalScripts::SelfObject;
void addActorBindings(sol::table actor, const Context& context)
{
actor["STANCE"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs<std::string_view, int>({
{"Nothing", MWMechanics::DrawState_Nothing},
{"Weapon", MWMechanics::DrawState_Weapon},
{"Spell", MWMechanics::DrawState_Spell},
}));
actor["EQUIPMENT_SLOT"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs<std::string_view, int>({
{"Helmet", MWWorld::InventoryStore::Slot_Helmet},
{"Cuirass", MWWorld::InventoryStore::Slot_Cuirass},
{"Greaves", MWWorld::InventoryStore::Slot_Greaves},
{"LeftPauldron", MWWorld::InventoryStore::Slot_LeftPauldron},
{"RightPauldron", MWWorld::InventoryStore::Slot_RightPauldron},
{"LeftGauntlet", MWWorld::InventoryStore::Slot_LeftGauntlet},
{"RightGauntlet", MWWorld::InventoryStore::Slot_RightGauntlet},
{"Boots", MWWorld::InventoryStore::Slot_Boots},
{"Shirt", MWWorld::InventoryStore::Slot_Shirt},
{"Pants", MWWorld::InventoryStore::Slot_Pants},
{"Skirt", MWWorld::InventoryStore::Slot_Skirt},
{"Robe", MWWorld::InventoryStore::Slot_Robe},
{"LeftRing", MWWorld::InventoryStore::Slot_LeftRing},
{"RightRing", MWWorld::InventoryStore::Slot_RightRing},
{"Amulet", MWWorld::InventoryStore::Slot_Amulet},
{"Belt", MWWorld::InventoryStore::Slot_Belt},
{"CarriedRight", MWWorld::InventoryStore::Slot_CarriedRight},
{"CarriedLeft", MWWorld::InventoryStore::Slot_CarriedLeft},
{"Ammunition", MWWorld::InventoryStore::Slot_Ammunition}
}));
actor["stance"] = [](const Object& o)
{
const MWWorld::Class& cls = o.ptr().getClass();
if (cls.isActor())
return cls.getCreatureStats(o.ptr()).getDrawState();
else
throw std::runtime_error("Actor expected");
};
actor["canMove"] = [](const Object& o)
{
const MWWorld::Class& cls = o.ptr().getClass();
return cls.getMaxSpeed(o.ptr()) > 0;
};
actor["runSpeed"] = [](const Object& o)
{
const MWWorld::Class& cls = o.ptr().getClass();
return cls.getRunSpeed(o.ptr());
};
actor["walkSpeed"] = [](const Object& o)
{
const MWWorld::Class& cls = o.ptr().getClass();
return cls.getWalkSpeed(o.ptr());
};
actor["currentSpeed"] = [](const Object& o)
{
const MWWorld::Class& cls = o.ptr().getClass();
return cls.getCurrentSpeed(o.ptr());
};
actor["isOnGround"] = [](const LObject& o)
{
return MWBase::Environment::get().getWorld()->isOnGround(o.ptr());
};
actor["isSwimming"] = [](const LObject& o)
{
return MWBase::Environment::get().getWorld()->isSwimming(o.ptr());
};
actor["inventory"] = sol::overload(
[](const LObject& o) { return Inventory<LObject>{o}; },
[](const GObject& o) { return Inventory<GObject>{o}; }
);
actor["equipment"] = [context](const Object& o)
{
const MWWorld::Ptr& ptr = o.ptr();
sol::table equipment(context.mLua->sol(), sol::create);
if (!ptr.getClass().hasInventoryStore(ptr))
return equipment;
MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr);
for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
{
auto it = store.getSlot(slot);
if (it == store.end())
continue;
context.mWorldView->getObjectRegistry()->registerPtr(*it);
equipment[slot] = o.getObject(context.mLua->sol(), getId(*it));
}
return equipment;
};
actor["hasEquipped"] = [](const Object& o, const Object& item)
{
const MWWorld::Ptr& ptr = o.ptr();
if (!ptr.getClass().hasInventoryStore(ptr))
return false;
MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr);
return store.isEquipped(item.ptr());
};
actor["setEquipment"] = [context](const SelfObject& obj, const sol::table& equipment)
{
if (!obj.ptr().getClass().hasInventoryStore(obj.ptr()))
{
if (!equipment.empty())
throw std::runtime_error(ptrToString(obj.ptr()) + " has no equipment slots");
return;
}
SetEquipmentAction::Equipment eqp;
for (auto& [key, value] : equipment)
{
int slot = key.as<int>();
if (value.is<Object>())
eqp[slot] = value.as<Object>().id();
else
eqp[slot] = value.as<std::string>();
}
context.mLuaManager->addAction(std::make_unique<SetEquipmentAction>(context.mLua, obj.id(), std::move(eqp)));
};
}
}

@ -0,0 +1,18 @@
#include "types.hpp"
#include "../luabindings.hpp"
namespace MWLua
{
static const MWWorld::Ptr& containerPtr(const Object& o) { return verifyType(ESM::REC_CONT, o.ptr()); }
void addContainerBindings(sol::table container, const Context& context)
{
container["content"] = sol::overload(
[](const LObject& o) { containerPtr(o); return Inventory<LObject>{o}; },
[](const GObject& o) { containerPtr(o); return Inventory<GObject>{o}; }
);
}
}

@ -0,0 +1,34 @@
#include "types.hpp"
#include "../luabindings.hpp"
namespace MWLua
{
static const MWWorld::Ptr& doorPtr(const Object& o) { return verifyType(ESM::REC_DOOR, o.ptr()); }
void addDoorBindings(sol::table door, const Context& context)
{
door["isTeleport"] = [](const Object& o) { return doorPtr(o).getCellRef().getTeleport(); };
door["destPosition"] = [](const Object& o) -> osg::Vec3f
{
return doorPtr(o).getCellRef().getDoorDest().asVec3();
};
door["destRotation"] = [](const Object& o) -> osg::Vec3f
{
return doorPtr(o).getCellRef().getDoorDest().asRotationVec3();
};
door["destCell"] = [worldView=context.mWorldView](sol::this_state lua, const Object& o) -> sol::object
{
const MWWorld::CellRef& cellRef = doorPtr(o).getCellRef();
if (!cellRef.getTeleport())
return sol::nil;
MWWorld::CellStore* cell = worldView->findCell(cellRef.getDestCell(), cellRef.getDoorDest().asVec3());
if (cell)
return o.getCell(lua, cell);
else
return sol::nil;
};
}
}

@ -0,0 +1,193 @@
#include "types.hpp"
#include <components/lua/luastate.hpp>
namespace MWLua
{
namespace ObjectTypeName
{
// Names of object types in Lua.
// These names are part of OpenMW Lua API.
constexpr std::string_view Actor = "Actor"; // base type for NPC, Creature, Player
constexpr std::string_view Item = "Item"; // base type for all items
constexpr std::string_view Activator = "Activator";
constexpr std::string_view Armor = "Armor";
constexpr std::string_view Book = "Book";
constexpr std::string_view Clothing = "Clothing";
constexpr std::string_view Container = "Container";
constexpr std::string_view Creature = "Creature";
constexpr std::string_view Door = "Door";
constexpr std::string_view Ingredient = "Ingredient";
constexpr std::string_view Light = "Light";
constexpr std::string_view MiscItem = "Miscellaneous";
constexpr std::string_view NPC = "NPC";
constexpr std::string_view Player = "Player";
constexpr std::string_view Potion = "Potion";
constexpr std::string_view Static = "Static";
constexpr std::string_view Weapon = "Weapon";
constexpr std::string_view Apparatus = "Apparatus";
constexpr std::string_view Lockpick = "Lockpick";
constexpr std::string_view Probe = "Probe";
constexpr std::string_view Repair = "Repair";
constexpr std::string_view Marker = "Marker";
}
namespace
{
struct LuaObjectTypeInfo
{
std::string_view mName;
ESM::LuaScriptCfg::Flags mFlag = 0;
};
const static std::unordered_map<ESM::RecNameInts, LuaObjectTypeInfo> luaObjectTypeInfo = {
{ESM::REC_INTERNAL_PLAYER, {ObjectTypeName::Player, ESM::LuaScriptCfg::sPlayer}},
{ESM::REC_INTERNAL_MARKER, {ObjectTypeName::Marker}},
{ESM::REC_ACTI, {ObjectTypeName::Activator, ESM::LuaScriptCfg::sActivator}},
{ESM::REC_ARMO, {ObjectTypeName::Armor, ESM::LuaScriptCfg::sArmor}},
{ESM::REC_BOOK, {ObjectTypeName::Book, ESM::LuaScriptCfg::sBook}},
{ESM::REC_CLOT, {ObjectTypeName::Clothing, ESM::LuaScriptCfg::sClothing}},
{ESM::REC_CONT, {ObjectTypeName::Container, ESM::LuaScriptCfg::sContainer}},
{ESM::REC_CREA, {ObjectTypeName::Creature, ESM::LuaScriptCfg::sCreature}},
{ESM::REC_DOOR, {ObjectTypeName::Door, ESM::LuaScriptCfg::sDoor}},
{ESM::REC_INGR, {ObjectTypeName::Ingredient, ESM::LuaScriptCfg::sIngredient}},
{ESM::REC_LIGH, {ObjectTypeName::Light, ESM::LuaScriptCfg::sLight}},
{ESM::REC_MISC, {ObjectTypeName::MiscItem, ESM::LuaScriptCfg::sMiscItem}},
{ESM::REC_NPC_, {ObjectTypeName::NPC, ESM::LuaScriptCfg::sNPC}},
{ESM::REC_ALCH, {ObjectTypeName::Potion, ESM::LuaScriptCfg::sPotion}},
{ESM::REC_STAT, {ObjectTypeName::Static}},
{ESM::REC_WEAP, {ObjectTypeName::Weapon, ESM::LuaScriptCfg::sWeapon}},
{ESM::REC_APPA, {ObjectTypeName::Apparatus}},
{ESM::REC_LOCK, {ObjectTypeName::Lockpick}},
{ESM::REC_PROB, {ObjectTypeName::Probe}},
{ESM::REC_REPA, {ObjectTypeName::Repair}},
};
}
std::string_view getLuaObjectTypeName(ESM::RecNameInts type, std::string_view fallback)
{
auto it = luaObjectTypeInfo.find(type);
if (it != luaObjectTypeInfo.end())
return it->second.mName;
else
return fallback;
}
std::string_view getLuaObjectTypeName(const MWWorld::Ptr& ptr)
{
return getLuaObjectTypeName(static_cast<ESM::RecNameInts>(ptr.getLuaType()), /*fallback=*/ptr.getTypeDescription());
}
ESM::LuaScriptCfg::Flags getLuaScriptFlag(const MWWorld::Ptr& ptr)
{
auto it = luaObjectTypeInfo.find(static_cast<ESM::RecNameInts>(ptr.getLuaType()));
if (it != luaObjectTypeInfo.end())
return it->second.mFlag;
else
return 0;
}
const MWWorld::Ptr& verifyType(ESM::RecNameInts recordType, const MWWorld::Ptr& ptr)
{
if (ptr.getType() != recordType)
{
std::string msg = "Requires type '";
msg.append(getLuaObjectTypeName(recordType));
msg.append("', but applied to ");
msg.append(ptrToString(ptr));
throw std::runtime_error(msg);
}
return ptr;
}
sol::table getTypeToPackageTable(lua_State* L)
{
constexpr std::string_view key = "typeToPackage";
sol::state_view lua(L);
if (lua[key] == sol::nil)
lua[key] = sol::table(lua, sol::create);
return lua[key];
}
sol::table getPackageToTypeTable(lua_State* L)
{
constexpr std::string_view key = "packageToType";
sol::state_view lua(L);
if (lua[key] == sol::nil)
lua[key] = sol::table(lua, sol::create);
return lua[key];
}
sol::table initTypesPackage(const Context& context)
{
auto* lua = context.mLua;
sol::table types(lua->sol(), sol::create);
auto addType = [&](std::string_view name, std::vector<ESM::RecNameInts> recTypes,
std::optional<std::string_view> base = std::nullopt) -> sol::table
{
sol::table t(lua->sol(), sol::create);
sol::table ro = LuaUtil::makeReadOnly(t);
sol::table meta = ro[sol::metatable_key];
meta[sol::meta_function::to_string] = [name]() { return name; };
if (base)
{
t["baseType"] = types[*base];
sol::table baseMeta(lua->sol(), sol::create);
baseMeta[sol::meta_function::index] = LuaUtil::getMutableFromReadOnly(types[*base]);
t[sol::metatable_key] = baseMeta;
}
t["objectIsInstance"] = [types=recTypes](const Object& o)
{
unsigned int type = o.ptr().getLuaType();
for (ESM::RecNameInts t : types)
if (t == type)
return true;
return false;
};
types[name] = ro;
return t;
};
addActorBindings(addType(ObjectTypeName::Actor, {ESM::REC_INTERNAL_PLAYER, ESM::REC_CREA, ESM::REC_NPC_}), context);
addType(ObjectTypeName::Item, {ESM::REC_ARMO, ESM::REC_BOOK, ESM::REC_CLOT, ESM::REC_INGR,
ESM::REC_LIGH, ESM::REC_MISC, ESM::REC_ALCH, ESM::REC_WEAP,
ESM::REC_APPA, ESM::REC_LOCK, ESM::REC_PROB, ESM::REC_REPA});
addType(ObjectTypeName::Creature, {ESM::REC_CREA}, ObjectTypeName::Actor);
addType(ObjectTypeName::NPC, {ESM::REC_INTERNAL_PLAYER, ESM::REC_NPC_}, ObjectTypeName::Actor);
addType(ObjectTypeName::Player, {ESM::REC_INTERNAL_PLAYER}, ObjectTypeName::NPC);
addType(ObjectTypeName::Armor, {ESM::REC_ARMO}, ObjectTypeName::Item);
addType(ObjectTypeName::Book, {ESM::REC_BOOK}, ObjectTypeName::Item);
addType(ObjectTypeName::Clothing, {ESM::REC_CLOT}, ObjectTypeName::Item);
addType(ObjectTypeName::Ingredient, {ESM::REC_INGR}, ObjectTypeName::Item);
addType(ObjectTypeName::Light, {ESM::REC_LIGH}, ObjectTypeName::Item);
addType(ObjectTypeName::MiscItem, {ESM::REC_MISC}, ObjectTypeName::Item);
addType(ObjectTypeName::Potion, {ESM::REC_ALCH}, ObjectTypeName::Item);
addType(ObjectTypeName::Weapon, {ESM::REC_WEAP}, ObjectTypeName::Item);
addType(ObjectTypeName::Apparatus, {ESM::REC_APPA}, ObjectTypeName::Item);
addType(ObjectTypeName::Lockpick, {ESM::REC_LOCK}, ObjectTypeName::Item);
addType(ObjectTypeName::Probe, {ESM::REC_PROB}, ObjectTypeName::Item);
addType(ObjectTypeName::Repair, {ESM::REC_REPA}, ObjectTypeName::Item);
addType(ObjectTypeName::Activator, {ESM::REC_ACTI});
addContainerBindings(addType(ObjectTypeName::Container, {ESM::REC_CONT}), context);
addDoorBindings(addType(ObjectTypeName::Door, {ESM::REC_DOOR}), context);
addType(ObjectTypeName::Static, {ESM::REC_STAT});
sol::table typeToPackage = getTypeToPackageTable(context.mLua->sol());
sol::table packageToType = getPackageToTypeTable(context.mLua->sol());
for (const auto& [type, v] : luaObjectTypeInfo)
{
sol::object t = types[v.mName];
if (t == sol::nil)
continue;
typeToPackage[type] = t;
packageToType[t] = type;
}
return LuaUtil::makeReadOnly(types);
}
}

@ -0,0 +1,32 @@
#ifndef MWLUA_TYPES_H
#define MWLUA_TYPES_H
#include <sol/sol.hpp>
#include <components/esm/defs.hpp>
#include <components/esm/luascripts.hpp>
#include "../context.hpp"
namespace MWLua
{
std::string_view getLuaObjectTypeName(ESM::RecNameInts type, std::string_view fallback = "Unknown");
std::string_view getLuaObjectTypeName(const MWWorld::Ptr& ptr);
const MWWorld::Ptr& verifyType(ESM::RecNameInts type, const MWWorld::Ptr& ptr);
sol::table getTypeToPackageTable(lua_State* L);
sol::table getPackageToTypeTable(lua_State* L);
// Each script has a set of flags that controls to which objects the script should be
// automatically attached. This function maps each object types to one of the flags.
ESM::LuaScriptCfg::Flags getLuaScriptFlag(const MWWorld::Ptr& ptr);
sol::table initTypesPackage(const Context& context);
// used in initTypesPackage
void addContainerBindings(sol::table door, const Context& context);
void addDoorBindings(sol::table door, const Context& context);
void addActorBindings(sol::table actor, const Context& context);
}
#endif // MWLUA_TYPES_H

@ -47,6 +47,23 @@ namespace MWWorld
throw std::runtime_error("Can't get type name from an empty object.");
}
// `getType()` is not exactly what we usually mean by "type" because some refids have special meaning.
// This function handles these special refids (and by this adds some performance overhead).
// We use this "fixed" type in Lua because we don't want to expose the weirdness of Morrowind internals to our API.
// TODO: Implement https://gitlab.com/OpenMW/openmw/-/issues/6617 and make `getType` work the same as `getLuaType`.
unsigned int getLuaType() const
{
if(mRef == nullptr)
throw std::runtime_error("Can't get type name from an empty object.");
std::string_view id = mRef->mRef.getRefId();
if (id == "player")
return ESM::REC_INTERNAL_PLAYER;
else if (id == "prisonmarker" || id == "divinemarker" || id == "templemarker" || id == "northmarker")
return ESM::REC_INTERNAL_MARKER;
else
return mRef->getType();
}
std::string_view getTypeDescription() const
{
return mRef ? mRef->getTypeDescription() : "nullptr";

@ -21,7 +21,6 @@ if (GTEST_FOUND AND GMOCK_FOUND)
lua/test_scriptscontainer.cpp
lua/test_utilpackage.cpp
lua/test_serialization.cpp
lua/test_querypackage.cpp
lua/test_configuration.cpp
lua/test_i18n.cpp
lua/test_storage.cpp

@ -1,29 +0,0 @@
#include "gmock/gmock.h"
#include <gtest/gtest.h>
#include <components/queries/luabindings.hpp>
namespace
{
using namespace testing;
TEST(LuaQueryPackageTest, basic)
{
sol::state lua;
lua.open_libraries(sol::lib::base, sol::lib::string);
Queries::registerQueryBindings(lua);
lua["query"] = Queries::Query("test");
lua["fieldX"] = Queries::Field({ "x" }, typeid(std::string));
lua["fieldY"] = Queries::Field({ "y" }, typeid(int));
lua.safe_script("t = query:where(fieldX:eq('abc') + fieldX:like('%abcd%'))");
lua.safe_script("t = t:where(fieldY:gt(5))");
lua.safe_script("t = t:orderBy(fieldX)");
lua.safe_script("t = t:orderByDesc(fieldY)");
lua.safe_script("t = t:groupBy(fieldY)");
lua.safe_script("t = t:limit(10):offset(5)");
EXPECT_EQ(
lua.safe_script("return tostring(t)").get<std::string>(),
"SELECT test WHERE ((x == \"abc\") OR (x LIKE \"%abcd%\")) AND (y > 5) ORDER BY x, y DESC GROUP BY y LIMIT 10 OFFSET 5");
}
}

@ -253,10 +253,6 @@ add_component_dir (fallback
fallback validate
)
add_component_dir (queries
query luabindings
)
add_component_dir (lua_ui
registerscriptsettings scriptsettings
properties widget element util layers content alignment resources

@ -94,6 +94,11 @@ constexpr unsigned int fourCC(const char(&name)[len]) {
enum RecNameInts : unsigned int
{
// Special values. Can not be used in any ESM.
// Added to this enum to guarantee that the values don't collide with any records.
REC_INTERNAL_PLAYER = 0,
REC_INTERNAL_MARKER = 1,
// format 0 / legacy
REC_ACTI = fourCC("ACTI"),
REC_ALCH = fourCC("ALCH"),

@ -1,118 +0,0 @@
#include "luabindings.hpp"
namespace sol
{
template <>
struct is_automagical<Queries::Field> : std::false_type {};
template <>
struct is_automagical<Queries::Filter> : std::false_type {};
template <>
struct is_automagical<Queries::Query> : std::false_type {};
}
namespace Queries
{
template <Condition::Type type>
struct CondBuilder
{
Filter operator()(const Field& field, const sol::object& o)
{
FieldValue value;
if (field.type() == typeid(bool) && o.is<bool>())
value = o.as<bool>();
else if (field.type() == typeid(int32_t) && o.is<int32_t>())
value = o.as<int32_t>();
else if (field.type() == typeid(int64_t) && o.is<int64_t>())
value = o.as<int64_t>();
else if (field.type() == typeid(float) && o.is<float>())
value = o.as<float>();
else if (field.type() == typeid(double) && o.is<double>())
value = o.as<double>();
else if (field.type() == typeid(std::string) && o.is<std::string>())
value = o.as<std::string>();
else
throw std::logic_error("Invalid value for field " + field.toString());
Filter filter;
filter.add({&field, type, value});
return filter;
}
};
void registerQueryBindings(sol::state& lua)
{
sol::usertype<Field> field = lua.new_usertype<Field>("QueryField");
sol::usertype<Filter> filter = lua.new_usertype<Filter>("QueryFilter");
sol::usertype<Query> query = lua.new_usertype<Query>("Query");
field[sol::meta_function::to_string] = [](const Field& f) { return f.toString(); };
field["eq"] = CondBuilder<Condition::EQUAL>();
field["neq"] = CondBuilder<Condition::NOT_EQUAL>();
field["lt"] = CondBuilder<Condition::LESSER>();
field["lte"] = CondBuilder<Condition::LESSER_OR_EQUAL>();
field["gt"] = CondBuilder<Condition::GREATER>();
field["gte"] = CondBuilder<Condition::GREATER_OR_EQUAL>();
field["like"] = CondBuilder<Condition::LIKE>();
filter[sol::meta_function::to_string] = [](const Filter& filter) { return filter.toString(); };
filter[sol::meta_function::multiplication] = [](const Filter& a, const Filter& b)
{
Filter res = a;
res.add(b, Operation::AND);
return res;
};
filter[sol::meta_function::addition] = [](const Filter& a, const Filter& b)
{
Filter res = a;
res.add(b, Operation::OR);
return res;
};
filter[sol::meta_function::unary_minus] = [](const Filter& a)
{
Filter res = a;
if (!a.mConditions.empty())
res.mOperations.push_back({Operation::NOT, 0});
return res;
};
query[sol::meta_function::to_string] = [](const Query& q) { return q.toString(); };
query["where"] = [](const Query& q, const Filter& filter)
{
Query res = q;
res.mFilter.add(filter, Operation::AND);
return res;
};
query["orderBy"] = [](const Query& q, const Field& field)
{
Query res = q;
res.mOrderBy.push_back({&field, false});
return res;
};
query["orderByDesc"] = [](const Query& q, const Field& field)
{
Query res = q;
res.mOrderBy.push_back({&field, true});
return res;
};
query["groupBy"] = [](const Query& q, const Field& field)
{
Query res = q;
res.mGroupBy.push_back(&field);
return res;
};
query["offset"] = [](const Query& q, int64_t offset)
{
Query res = q;
res.mOffset = offset;
return res;
};
query["limit"] = [](const Query& q, int64_t limit)
{
Query res = q;
res.mLimit = limit;
return res;
};
}
}

@ -1,9 +0,0 @@
#include <limits> // missing from sol/sol.hpp
#include <sol/sol.hpp>
#include "query.hpp"
namespace Queries
{
void registerQueryBindings(sol::state& lua);
}

@ -1,185 +0,0 @@
#include "query.hpp"
#include <sstream>
#include <iomanip>
namespace Queries
{
Field::Field(std::vector<std::string> path, std::type_index type)
: mPath(std::move(path))
, mType(type) {}
std::string Field::toString() const
{
std::string result;
for (const std::string& segment : mPath)
{
if (!result.empty())
result += ".";
result += segment;
}
return result;
}
std::string toString(const FieldValue& value)
{
return std::visit([](auto&& arg) -> std::string
{
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, std::string>)
{
std::ostringstream oss;
oss << std::quoted(arg);
return oss.str();
}
else if constexpr (std::is_same_v<T, bool>)
return arg ? "true" : "false";
else
return std::to_string(arg);
}, value);
}
std::string Condition::toString() const
{
std::string res;
res += mField->toString();
switch (mType)
{
case Condition::EQUAL: res += " == "; break;
case Condition::NOT_EQUAL: res += " != "; break;
case Condition::LESSER: res += " < "; break;
case Condition::LESSER_OR_EQUAL: res += " <= "; break;
case Condition::GREATER: res += " > "; break;
case Condition::GREATER_OR_EQUAL: res += " >= "; break;
case Condition::LIKE: res += " LIKE "; break;
}
res += Queries::toString(mValue);
return res;
}
void Filter::add(const Condition& c, Operation::Type op)
{
mOperations.push_back({Operation::PUSH, mConditions.size()});
mConditions.push_back(c);
if (mConditions.size() > 1)
mOperations.push_back({op, 0});
}
void Filter::add(const Filter& f, Operation::Type op)
{
size_t conditionOffset = mConditions.size();
size_t operationsBefore = mOperations.size();
mConditions.insert(mConditions.end(), f.mConditions.begin(), f.mConditions.end());
mOperations.insert(mOperations.end(), f.mOperations.begin(), f.mOperations.end());
for (size_t i = operationsBefore; i < mOperations.size(); ++i)
mOperations[i].mConditionIndex += conditionOffset;
if (conditionOffset > 0 && !f.mConditions.empty())
mOperations.push_back({op, 0});
}
std::string Filter::toString() const
{
if(mOperations.empty())
return "";
std::vector<std::string> stack;
auto pop = [&stack](){ auto v = stack.back(); stack.pop_back(); return v; };
auto push = [&stack](const std::string& s) { stack.push_back(s); };
for (const Operation& op : mOperations)
{
if(op.mType == Operation::PUSH)
push(mConditions[op.mConditionIndex].toString());
else if(op.mType == Operation::AND)
{
auto rhs = pop();
auto lhs = pop();
std::string res;
res += "(";
res += lhs;
res += ") AND (";
res += rhs;
res += ")";
push(res);
}
else if (op.mType == Operation::OR)
{
auto rhs = pop();
auto lhs = pop();
std::string res;
res += "(";
res += lhs;
res += ") OR (";
res += rhs;
res += ")";
push(res);
}
else if (op.mType == Operation::NOT)
{
std::string res;
res += "NOT (";
res += pop();
res += ")";
push(res);
}
else
throw std::logic_error("Unknown operation type!");
}
return pop();
}
std::string Query::toString() const
{
std::string res;
res += "SELECT ";
res += mQueryType;
std::string filter = mFilter.toString();
if(!filter.empty())
{
res += " WHERE ";
res += filter;
}
std::string order;
for(const OrderBy& ord : mOrderBy)
{
if(!order.empty())
order += ", ";
order += ord.mField->toString();
if(ord.mDescending)
order += " DESC";
}
if (!order.empty())
{
res += " ORDER BY ";
res += order;
}
std::string group;
for (const Field* f: mGroupBy)
{
if (!group.empty())
group += " ,";
group += f->toString();
}
if (!group.empty())
{
res += " GROUP BY ";
res += group;
}
if (mLimit != sNoLimit)
{
res += " LIMIT ";
res += std::to_string(mLimit);
}
if (mOffset != 0)
{
res += " OFFSET ";
res += std::to_string(mOffset);
}
return res;
}
}

@ -1,99 +0,0 @@
#ifndef COMPONENTS_QUERIES_QUERY
#define COMPONENTS_QUERIES_QUERY
#include <string>
#include <vector>
#include <typeindex>
#include <variant>
#include <stdexcept>
namespace Queries
{
class Field
{
public:
Field(std::vector<std::string> path, std::type_index type);
const std::vector<std::string>& path() const { return mPath; }
const std::type_index type() const { return mType; }
std::string toString() const;
private:
std::vector<std::string> mPath;
std::type_index mType;
};
struct OrderBy
{
const Field* mField;
bool mDescending;
};
using FieldValue = std::variant<bool, int32_t, int64_t, float, double, std::string>;
std::string toString(const FieldValue& value);
struct Condition
{
enum Type
{
EQUAL = 0,
NOT_EQUAL = 1,
GREATER = 2,
GREATER_OR_EQUAL = 3,
LESSER = 4,
LESSER_OR_EQUAL = 5,
LIKE = 6,
};
std::string toString() const;
const Field* mField;
Type mType;
FieldValue mValue;
};
struct Operation
{
enum Type
{
PUSH = 0, // push condition on stack
NOT = 1, // invert top condition on stack
AND = 2, // logic AND for two top conditions
OR = 3, // logic OR for two top conditions
};
Type mType;
size_t mConditionIndex; // used only if mType == PUSH
};
struct Filter
{
std::string toString() const;
// combines with given condition or filter using operation `AND` or `OR`.
void add(const Condition& c, Operation::Type op = Operation::AND);
void add(const Filter& f, Operation::Type op = Operation::AND);
std::vector<Condition> mConditions;
std::vector<Operation> mOperations; // operations on conditions in reverse polish notation
};
struct Query
{
static constexpr int64_t sNoLimit = -1;
Query(std::string type) : mQueryType(std::move(type)) {}
std::string toString() const;
std::string mQueryType;
Filter mFilter;
std::vector<OrderBy> mOrderBy;
std::vector<const Field*> mGroupBy;
int64_t mOffset = 0;
int64_t mLimit = sNoLimit;
};
}
#endif // !COMPONENTS_QUERIES_QUERY

@ -12,8 +12,8 @@ Lua API reference
openmw_util
openmw_storage
openmw_core
openmw_types
openmw_async
openmw_query
openmw_world
openmw_self
openmw_nearby
@ -56,9 +56,9 @@ Player scripts are local scripts that are attached to a player.
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.core <Package openmw.core>` | everywhere | | Functions that are common for both global and local scripts |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.async <Package openmw.async>` | everywhere | | Timers (implemented) and coroutine utils (not implemented) |
|:ref:`openmw.types <Package openmw.types>` | everywhere | | Functions for specific types of game objects. |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.query <Package openmw.query>` | everywhere | | Tools for constructing queries: base queries and fields. |
|:ref:`openmw.async <Package openmw.async>` | everywhere | | Timers (implemented) and coroutine utils (not implemented) |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.world <Package openmw.world>` | by global scripts | | Read-write access to the game world. |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+

@ -1,5 +0,0 @@
Package openmw.query
====================
.. raw:: html
:file: generated_html/openmw_query.html

@ -0,0 +1,5 @@
Package openmw.types
====================
.. raw:: html
:file: generated_html/openmw_types.html

@ -353,9 +353,9 @@ Player scripts are local scripts that are attached to a player.
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.core <Package openmw.core>` | everywhere | | Functions that are common for both global and local scripts |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.async <Package openmw.async>` | everywhere | | Timers (implemented) and coroutine utils (not implemented) |
|:ref:`openmw.types <Package openmw.types>` | everywhere | | Functions for specific types of game objects. |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.query <Package openmw.query>` | everywhere | | Tools for constructing queries: base queries and fields. |
|:ref:`openmw.async <Package openmw.async>` | everywhere | | Timers (implemented) and coroutine utils (not implemented) |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.world <Package openmw.world>` | by global scripts | | Read-write access to the game world. |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
@ -630,98 +630,6 @@ Also in `openmw_aux`_ is the helper function ``runRepeatedly``, it is implemente
})
Queries
=======
`openmw.query` contains base queries of each type (e.g. `query.doors`, `query.containers`...), which return all of the objects of given type in no particular order. You can then modify that query to filter the results, sort them, group them, etc. Queries are immutable, so any operations on them return a new copy, leaving the original unchanged.
`openmw.world.selectObjects` and `openmw.nearby.selectObjects` both accept a query and return objects that match it. However, `nearby.selectObjects` is only available in local scripts, and returns only objects from currently active cells, while `world.selectObjects` is only available in global scripts, and returns objects regardless of them being in active cells.
**TODO:** describe how to filter out inactive objects from world queries
An example of an object query:
.. code-block:: Lua
local query = require('openmw.query')
local nearby = require('openmw.nearby')
local ui = require('openmw.ui')
local function selectDoors(namePattern)
local query = query.doors:where(query.DOOR.destCell.name:like(namePattern))
return nearby.selectObjects(query)
end
local function showGuildDoors()
ui.showMessage('Here are all the entrances to guilds!')
for _, door in selectDoors("%Guild%"):ipairs() do
local pos = door.position
local message = string.format("%.0f;%.0f;%.0f", pos.x, pos.y, pos.z)
ui.showMessage(message)
end
end
return {
engineHandlers = {
onKeyPress = function(key)
if key.symbol == 'e' then
showGuildDoors()
end
end
}
}
.. warning::
The example above uses operation `like` that is not implemented yet.
**TODO:** add non-object queries, explain how relations work, and define what a field is
Queries are constructed through the following method calls: (if you've used SQL before, you will find them familiar)
- `:where(filter)` - filters the results to match the combination of conditions passed as the argument
- `:orderBy(field)` and `:orderByDesc(field)` sort the result by the `field` argument. Sorts in descending order in case of `:orderByDesc`. Multiple calls can be chained, with the first call having priority. (i. e. if the first field is equal, objects are sorted by the second one...) **(not implemented yet)**
- `:groupBy(field)` returns only one result for each value of the `field` argument. The choice of the result is arbitrary. Useful for counting only unique objects, or checking if certain objects exist. **(not implemented yet)**
- `:limit(number)` will only return `number` of results (or fewer)
- `:offset(number)` skips the first `number` results. Particularly useful in combination with `:limit` **(not implemented yet)**
Filters consist of conditions, which are combined with "and" (operator `*`), "or" (operator `+`), "not" (operator `-`) and braces `()`.
To make a condition, take a field from the `openmw.query` package and call any of the following methods:
- `:eq` equal to
- `:neq` not equal to
- `:gt` greater than
- `:gte` greater or equal to
- `:lt` less than
- `:lte` less or equal to
- `:like` matches a pattern. Only applicable to text (strings) **(not implemented yet)**
**TODO:** describe the pattern format
All the condition methods are type sensitive, and will throw an error if you pass a value of the wrong type into them.
A few examples of filters:
.. warning::
`openmw.query.ACTOR` is not implemented yet
.. code-block:: Lua
local query = require('openmw.query')
local ACTOR = query.ACTOR
local strong_guys_from_capital = (ACTOR.stats.level:gt(10) + ACTOR.stats.strength:gt(70))
* ACTOR.cell.name:eq("Default city")
-- could also write like this:
local strong_guys = ACTOR.stats.level:gt(10) + ACTOR.stats.strength:gt(70)
local guys_from_capital = ACTOR.cell.name:eq("Default city")
local strong_guys_from_capital_2 = strong_guys * guys_from_capital
local DOOR = query.DOOR
local interestingDoors = -DOOR.name:eq("") * DOOR.isTeleport:eq(true) * Door.destCell.isExterior:eq(false)
Using IDE for Lua scripting
===========================

@ -91,7 +91,7 @@ local function formatGameTime(formatStr, timestamp)
error('Unknown tag "'..tag..'"')
end
res, _ = string.gsub(formatStr or '%c', '%%.', replFn)
local res, _ = string.gsub(formatStr or '%c', '%%.', replFn)
return res
end

@ -5,6 +5,8 @@ local settings = require('openmw.settings')
local util = require('openmw.util')
local self = require('openmw.self')
local Actor = require('openmw.types').Actor
local head_bobbing = require('scripts.omw.head_bobbing')
local third_person = require('scripts.omw.third_person')
@ -77,7 +79,7 @@ local function updateVanity(dt)
end
local function updateSmoothedSpeed(dt)
local speed = self:getCurrentSpeed()
local speed = Actor.currentSpeed(self)
speed = speed / (1 + speed / 500)
local maxDelta = 300 * dt
smoothedSpeed = smoothedSpeed + util.clamp(speed - smoothedSpeed, -maxDelta, maxDelta)
@ -126,7 +128,7 @@ local function updateStandingPreview()
third_person.standingPreview = false
return
end
local standingStill = self:getCurrentSpeed() == 0 and not self:isInWeaponStance() and not self:isInMagicStance()
local standingStill = Actor.currentSpeed(self) == 0 and Actor.stance(self) == Actor.STANCE.Nothing
if standingStill and mode == MODE.ThirdPerson then
third_person.standingPreview = true
camera.setMode(MODE.Preview)

@ -3,6 +3,8 @@ local self = require('openmw.self')
local settings = require('openmw.settings')
local util = require('openmw.util')
local Actor = require('openmw.types').Actor
local doubleStepLength = settings._getFloatFromSettingsCfg('Camera', 'head bobbing step') * 2
local stepHeight = settings._getFloatFromSettingsCfg('Camera', 'head bobbing height')
local maxRoll = math.rad(settings._getFloatFromSettingsCfg('Camera', 'head bobbing roll'))
@ -20,14 +22,14 @@ local sampleArc = function(x) return 1 - math.cos(x * halfArc) end
local arcHeight = sampleArc(1)
function M.update(dt, smoothedSpeed)
local speed = self:getCurrentSpeed()
local speed = Actor.currentSpeed(self)
speed = speed / (1 + speed / 500) -- limit bobbing frequency if the speed is very high
totalMovement = totalMovement + speed * dt
if not M.enabled or camera.getMode() ~= camera.MODE.FirstPerson then
effectWeight = 0
return
end
if self:isOnGround() then
if Actor.isOnGround(self) then
effectWeight = math.min(1, effectWeight + dt * 5)
else
effectWeight = math.max(0, effectWeight - dt * 5)

@ -4,6 +4,8 @@ local util = require('openmw.util')
local self = require('openmw.self')
local nearby = require('openmw.nearby')
local Actor = require('openmw.types').Actor
local MODE = camera.MODE
local STATE = { RightShoulder = 0, LeftShoulder = 1, Combat = 2, Swimming = 3 }
@ -70,9 +72,9 @@ local noThirdPersonLastFrame = true
local function updateState()
local mode = camera.getMode()
local oldState = state
if (self:isInWeaponStance() or self:isInMagicStance()) and mode == MODE.ThirdPerson then
if Actor.stance(self) ~= Actor.STANCE.Nothing and mode == MODE.ThirdPerson then
state = STATE.Combat
elseif self:isSwimming() then
elseif Actor.isSwimming(self) then
state = STATE.Swimming
elseif oldState == STATE.Combat or oldState == STATE.Swimming then
state = defaultShoulder

@ -8,11 +8,11 @@ set(LUA_API_FILES
openmw/async.lua
openmw/core.lua
openmw/nearby.lua
openmw/query.lua
openmw/self.lua
openmw/ui.lua
openmw/util.lua
openmw/world.lua
openmw/types.lua
)
foreach (f ${LUA_API_FILES})

@ -86,59 +86,6 @@
-- print( myMsg('Hello %{name}!', {name='World'}) )
---
-- @type OBJECT_TYPE
-- @field #string Activator "Activator"
-- @field #string Armor "Armor"
-- @field #string Book "Book"
-- @field #string Clothing "Clothing"
-- @field #string Container "Container"
-- @field #string Creature "Creature"
-- @field #string Door "Door"
-- @field #string Ingredient "Ingredient"
-- @field #string Light "Light"
-- @field #string Miscellaneous "Miscellaneous"
-- @field #string NPC "NPC"
-- @field #string Player "Player"
-- @field #string Potion "Potion"
-- @field #string Static "Static"
-- @field #string Apparatus "Apparatus"
-- @field #string Lockpick "Lockpick"
-- @field #string Probe "Probe"
-- @field #string Repair "Repair"
---
-- Possible `object.type` values.
-- @field [parent=#core] #OBJECT_TYPE OBJECT_TYPE
---
-- @type EQUIPMENT_SLOT
-- @field #number Helmet
-- @field #number Cuirass
-- @field #number Greaves
-- @field #number LeftPauldron
-- @field #number RightPauldron
-- @field #number LeftGauntlet
-- @field #number RightGauntlet
-- @field #number Boots
-- @field #number Shirt
-- @field #number Pants
-- @field #number Skirt
-- @field #number Robe
-- @field #number LeftRing
-- @field #number RightRing
-- @field #number Amulet
-- @field #number Belt
-- @field #number CarriedRight
-- @field #number CarriedLeft
-- @field #number Ammunition
---
-- Available equipment slots. Used in `object:getEquipment` and `object:setEquipment`.
-- @field [parent=#core] #EQUIPMENT_SLOT EQUIPMENT_SLOT
---
-- Any object that exists in the game world and has a specific location.
-- Player, actors, items, and statics are game objects.
@ -146,73 +93,18 @@
-- @field openmw.util#Vector3 position Object position.
-- @field openmw.util#Vector3 rotation Object rotation (ZXY order).
-- @field #Cell cell The cell where the object currently is. During loading a game and for objects in an inventory or a container `cell` is nil.
-- @field #string type Type of the object (see @{openmw.core#OBJECT_TYPE}).
-- @field #number count Count (makes sense if holded in a container).
-- @field #table type Type of the object (one of the tables from the package @{openmw.types#types}).
-- @field #number count Count (makes sense if stored in a container).
-- @field #string recordId Record ID.
-- @field #Inventory inventory Inventory of an Player/NPC or content of an container.
-- @field #boolean isTeleport `True` if it is a teleport door (only if `object.type` == "Door").
-- @field openmw.util#Vector3 destPosition Destination (only if a teleport door).
-- @field openmw.util#Vector3 destRotation Destination rotation (only if a teleport door).
-- @field #string destCell Destination cell (only if a teleport door).
---
-- Is the object still exists/available.
-- Does the object still exist and is available.
-- Returns true if the object exists and loaded, and false otherwise. If false, then every
-- access to the object will raise an error.
-- @function [parent=#GameObject] isValid
-- @param self
-- @return #boolean
---
-- Returns true if the object is an actor and is able to move. For dead, paralized,
-- or knocked down actors in returns false.
-- access to the object will raise an error.
-- @function [parent=#GameObject] canMove
-- @param self
-- @return #boolean
---
-- Speed of running. Returns 0 if not an actor, but for dead actors it still returns a positive value.
-- @function [parent=#GameObject] getRunSpeed
-- @param self
-- @return #number
---
-- Speed of walking. Returns 0 if not an actor, but for dead actors it still returns a positive value.
-- @function [parent=#GameObject] getWalkSpeed
-- @param self
-- @return #number
---
-- Current speed. Can be called only from a local script.
-- @function [parent=#GameObject] getCurrentSpeed
-- @param self
-- @return #number
---
-- Is the actor standing on ground. Can be called only from a local script.
-- @function [parent=#GameObject] isOnGround
-- @param self
-- @return #boolean
---
-- Is the actor in water. Can be called only from a local script.
-- @function [parent=#GameObject] isSwimming
-- @param self
-- @return #boolean
---
-- Is the actor in weapon stance. Can be called only from a local script.
-- @function [parent=#GameObject] isInWeaponStance
-- @param self
-- @return #boolean
---
-- Is the actor in magic stance. Can be called only from a local script.
-- @function [parent=#GameObject] isInMagicStance
-- @param self
-- @return #boolean
---
-- Send local event to the object.
-- @function [parent=#GameObject] sendEvent
@ -228,36 +120,10 @@
-- @usage local self = require('openmw.self')
-- object:activateBy(self)
---
-- Returns `true` if the item is equipped on the object.
-- @function [parent=#GameObject] isEquipped
-- @param self
-- @param #GameObject item
-- @return #boolean
---
-- Get equipment.
-- Returns a table `slot` -> `GameObject` of currently equipped items.
-- See @{openmw.core#EQUIPMENT_SLOT}. Returns empty table if the object doesn't have
-- equipment slots.
-- @function [parent=#GameObject] getEquipment
-- @param self
-- @return #map<#number,#GameObject>
---
-- Set equipment.
-- Keys in the table are equipment slots (see @{openmw.core#EQUIPMENT_SLOT}). Each
-- value can be either a `GameObject` or recordId. Raises an error if
-- the object doesn't have equipment slots and table is not empty. Can be
-- called only on self or from a global script.
-- @function [parent=#GameObject] setEquipment
-- @param self
-- @param equipment
---
-- Add new local script to the object.
-- Can be called only from a global script. Script should be specified in a content
-- file (omwgame/omwaddon/omwscripts) with a CUSTOM flag.
-- file (omwgame/omwaddon/omwscripts) with a CUSTOM flag. Scripts can not be attached to Statics.
-- @function [parent=#GameObject] addScript
-- @param self
-- @param #string scriptPath Path to the script in OpenMW virtual filesystem.
@ -293,13 +159,6 @@
-- @type ObjectList
-- @list <#GameObject>
---
-- Filter list with a Query.
-- @function [parent=#ObjectList] select
-- @param self
-- @param openmw.query#Query query
-- @return #ObjectList
---
-- A cell of the game world.
@ -325,12 +184,15 @@
-- end
---
-- Select objects from the cell with a Query (only in global scripts).
-- Returns an empty list if the cell is not loaded.
-- @function [parent=#Cell] selectObjects
-- Get all objects of given type from the cell.
-- @function [parent=#Cell] getAll
-- @param self
-- @param openmw.query#Query query
-- @param type (optional) object type (see @{openmw.types#types})
-- @return #ObjectList
-- @usage
-- local type = require('openmw.types')
-- local all = cell:getAll()
-- local weapons = cell:getAll(types.Weapon)
---
@ -348,10 +210,12 @@
-- Get all items of given type from the inventory.
-- @function [parent=#Inventory] getAll
-- @param self
-- @param type (optional) items type (see @{openmw.core#OBJECT_TYPE})
-- @param type (optional) items type (see @{openmw.types#types})
-- @return #ObjectList
-- @usage local all = inventory:getAll()
-- local weapons = inventory:getAll(core.OBJECT_TYPE.Weapon)
-- @usage
-- local type = require('openmw.types')
-- local all = inventory:getAll()
-- local weapons = inventory:getAll(types.Weapon)
return nil

@ -26,12 +26,6 @@
-- Everything that can be picked up in the nearby.
-- @field [parent=#nearby] openmw.core#ObjectList items
---
-- Evaluates a Query.
-- @function [parent=#nearby] selectObjects
-- @param openmw.query#Query query
-- @return openmw.core#ObjectList
---
-- @type COLLISION_TYPE
-- @field [parent=#COLLISION_TYPE] #number World

@ -1,116 +0,0 @@
---
-- `openmw.query` constructs queries that can be used in `world.selectObjects` and `nearby.selectObjects`.
-- @module query
-- @usage local query = require('openmw.query')
---
-- Query. A Query itself can no return objects. It only holds search conditions.
-- @type Query
---
-- Add condition.
-- @function [parent=#Query] where
-- @param self
-- @param condition
-- @return #Query
---
-- Limit result size.
-- @function [parent=#Query] limit
-- @param self
-- @param #number maxCount
-- @return #Query
---
-- A field that can be used in a condition
-- @type Field
---
-- Equal
-- @function [parent=#Field] eq
-- @param self
-- @param value
---
-- Not equal
-- @function [parent=#Field] neq
-- @param self
-- @param value
---
-- Lesser
-- @function [parent=#Field] lt
-- @param self
-- @param value
---
-- Lesser or equal
-- @function [parent=#Field] lte
-- @param self
-- @param value
---
-- Greater
-- @function [parent=#Field] gt
-- @param self
-- @param value
---
-- Greater or equal
-- @function [parent=#Field] gte
-- @param self
-- @param value
---
-- @type OBJECT
-- @field [parent=#OBJECT] #Field type
-- @field [parent=#OBJECT] #Field recordId
-- @field [parent=#OBJECT] #Field count
-- @field [parent=#OBJECT] #CellFields cell
---
-- Fields that can be used with any object.
-- @field [parent=#query] #OBJECT OBJECT
---
-- @type DOOR
-- @field [parent=#DOOR] #Field isTeleport
-- @field [parent=#DOOR] #CellFields destCell
---
-- Fields that can be used only when search for doors.
-- @field [parent=#query] #DOOR DOOR
---
-- @type CellFields
-- @field [parent=#CellFields] #Field name
-- @field [parent=#CellFields] #Field region
-- @field [parent=#CellFields] #Field isExterior
---
-- Base Query to select activators.
-- @field [parent=#query] #Query activators
---
-- Base Query to select actors.
-- @field [parent=#query] #Query actors
---
-- Base Query to select containers.
-- @field [parent=#query] #Query containers
---
-- Base Query to select doors.
-- @field [parent=#query] #Query doors
---
-- Base Query to select items.
-- @field [parent=#query] #Query items
return nil

@ -0,0 +1,456 @@
---
-- `openmw.types` defines functions for specific types of game objects.
-- @module types
-- @usage local types = require('openmw.types')
--- Common @{#Actor} functions for Creature, NPC, and Player.
-- @field [parent=#types] #Actor Actor
--- Common functions for Creature, NPC, and Player.
-- @type Actor
---
-- Whether the object is an actor.
-- @function [parent=#Actor] objectIsInstance
-- @param openmw.core#GameObject object
-- @return #boolean
---
-- Actor inventory.
-- @function [parent=#Actor] inventory
-- @param openmw.core#GameObject actor
-- @return openmw.core#Inventory
---
-- @type EQUIPMENT_SLOT
-- @field #number Helmet
-- @field #number Cuirass
-- @field #number Greaves
-- @field #number LeftPauldron
-- @field #number RightPauldron
-- @field #number LeftGauntlet
-- @field #number RightGauntlet
-- @field #number Boots
-- @field #number Shirt
-- @field #number Pants
-- @field #number Skirt
-- @field #number Robe
-- @field #number LeftRing
-- @field #number RightRing
-- @field #number Amulet
-- @field #number Belt
-- @field #number CarriedRight
-- @field #number CarriedLeft
-- @field #number Ammunition
---
-- Available @{#EQUIPMENT_SLOT} values. Used in `Actor.equipment(obj)` and `Actor.setEquipment(obj, eqp)`.
-- @field [parent=#Actor] #EQUIPMENT_SLOT EQUIPMENT_SLOT
---
-- @type STANCE
-- @field #number Nothing Default stance
-- @field #number Weapon Weapon stance
-- @field #number Spell Magic stance
--- @{#STANCE}
-- @field [parent=#Actor] #STANCE STANCE
---
-- Returns true if the object is an actor and is able to move. For dead, paralyzed,
-- or knocked down actors it returns false.
-- @function [parent=#Actor] canMove
-- @param openmw.core#GameObject object
-- @return #boolean
---
-- Speed of running. For dead actors it still returns a positive value.
-- @function [parent=#Actor] runSpeed
-- @param openmw.core#GameObject actor
-- @return #number
---
-- Speed of walking. For dead actors it still returns a positive value.
-- @function [parent=#Actor] walkSpeed
-- @param openmw.core#GameObject actor
-- @return #number
---
-- Current speed.
-- @function [parent=#Actor] currentSpeed
-- @param openmw.core#GameObject actor
-- @return #number
---
-- Is the actor standing on ground. Can be called only from a local script.
-- @function [parent=#Actor] isOnGround
-- @param openmw.core#GameObject actor
-- @return #boolean
---
-- Is the actor in water. Can be called only from a local script.
-- @function [parent=#Actor] isSwimming
-- @param openmw.core#GameObject actor
-- @return #boolean
---
-- Returns the current stance (whether a weapon/spell is readied), see the list of @{#STANCE} values.
-- @function [parent=#Actor] stance
-- @param openmw.core#GameObject actor
-- @return #number
---
-- Returns `true` if the item is equipped on the actor.
-- @function [parent=#Actor] isEquipped
-- @param openmw.core#GameObject actor
-- @param openmw.core#GameObject item
-- @return #boolean
---
-- Get equipment.
-- Returns a table `slot` -> @{openmw.core#GameObject} of currently equipped items.
-- See @{#EQUIPMENT_SLOT}. Returns empty table if the actor doesn't have
-- equipment slots.
-- @function [parent=#Actor] equipment
-- @param openmw.core#GameObject actor
-- @return #map<#number,openmw.core#GameObject>
---
-- Set equipment.
-- Keys in the table are equipment slots (see @{#EQUIPMENT_SLOT}). Each
-- value can be either a `GameObject` or recordId. Raises an error if
-- the actor doesn't have equipment slots and table is not empty. Can be
-- used only in local scripts and only on self.
-- @function [parent=#Actor] setEquipment
-- @param openmw.core#GameObject actor
-- @param equipment
-- @usage local self = require('openmw.self')
-- local Actor = require('openmw.types').Actor
-- Actor.setEquipment(self, {}) -- unequip all
--- @{#Item} functions (all pickable items that can be placed to an inventory or container)
-- @field [parent=#types] #Item Item
--- Functions for pickable items that can be placed to an inventory or container
-- @type Item
---
-- Whether the object is an item.
-- @function [parent=#Item] objectIsInstance
-- @param openmw.core#GameObject object
-- @return #boolean
--- @{#Creature} functions
-- @field [parent=#types] #Creature Creature
---
-- @type Creature
-- @extends #Actor
-- @field #Actor baseType @{#Actor}
---
-- Whether the object is a creature.
-- @function [parent=#Creature] objectIsInstance
-- @param openmw.core#GameObject object
-- @return #boolean
--- @{#NPC} functions
-- @field [parent=#types] #NPC NPC
---
-- @type NPC
-- @extends #Actor
-- @field #Actor baseType @{#Actor}
---
-- Whether the object is an NPC or a Player.
-- @function [parent=#NPC] objectIsInstance
-- @param openmw.core#GameObject object
-- @return #boolean
--- @{#Player} functions
-- @field [parent=#types] #Player Player
---
-- @type Player
-- @extends #NPC
-- @field #NPC baseType @{#NPC}
---
-- Whether the object is a player.
-- @function [parent=#Player] objectIsInstance
-- @param openmw.core#GameObject object
-- @return #boolean
--- @{#Armor} functions
-- @field [parent=#types] #Armor Armor
---
-- @type Armor
-- @extends #Item
-- @field #Item baseType @{#Item}
---
-- Whether the object is an Armor.
-- @function [parent=#Armor] objectIsInstance
-- @param openmw.core#GameObject object
-- @return #boolean
--- @{#Book} functions
-- @field [parent=#types] #Book Book
---
-- @type Book
-- @extends #Item
-- @field #Item baseType @{#Item}
---
-- Whether the object is a Book.
-- @function [parent=#Book] objectIsInstance
-- @param openmw.core#GameObject object
-- @return #boolean
--- @{#Clothing} functions
-- @field [parent=#types] #Clothing Clothing
---
-- @type Clothing
-- @extends #Item
-- @field #Item baseType @{#Item}
---
-- Whether the object is a Clothing.
-- @function [parent=#Clothing] objectIsInstance
-- @param openmw.core#GameObject object
-- @return #boolean
--- @{#Ingredient} functions
-- @field [parent=#types] #Ingredient Ingredient
---
-- @type Ingredient
-- @extends #Item
-- @field #Item baseType @{#Item}
---
-- Whether the object is an Ingredient.
-- @function [parent=#Ingredient] objectIsInstance
-- @param openmw.core#GameObject object
-- @return #boolean
--- @{#Light} functions
-- @field [parent=#types] #Light Light
---
-- @type Light
-- @extends #Item
-- @field #Item baseType @{#Item}
---
-- Whether the object is a Light.
-- @function [parent=#Light] objectIsInstance
-- @param openmw.core#GameObject object
-- @return #boolean
--- Functions for @{#Miscellaneous} objects
-- @field [parent=#types] #Miscellaneous Miscellaneous
---
-- @type Miscellaneous
-- @extends #Item
-- @field #Item baseType @{#Item}
---
-- Whether the object is a Miscellaneous.
-- @function [parent=#Miscellaneous] objectIsInstance
-- @param openmw.core#GameObject object
-- @return #boolean
--- @{#Potion} functions
-- @field [parent=#types] #Potion Potion
---
-- @type Potion
-- @extends #Item
-- @field #Item baseType @{#Item}
---
-- Whether the object is a Potion.
-- @function [parent=#Potion] objectIsInstance
-- @param openmw.core#GameObject object
-- @return #boolean
--- @{#Weapon} functions
-- @field [parent=#types] #Weapon Weapon
---
-- @type Weapon
-- @extends #Item
-- @field #Item baseType @{#Item}
---
-- Whether the object is a Weapon.
-- @function [parent=#Weapon] objectIsInstance
-- @param openmw.core#GameObject object
-- @return #boolean
--- @{#Apparatus} functions
-- @field [parent=#types] #Apparatus Apparatus
---
-- @type Apparatus
-- @extends #Item
-- @field #Item baseType @{#Item}
---
-- Whether the object is an Apparatus.
-- @function [parent=#Apparatus] objectIsInstance
-- @param openmw.core#GameObject object
-- @return #boolean
--- @{#Lockpick} functions
-- @field [parent=#types] #Lockpick Lockpick
---
-- @type Lockpick
-- @extends #Item
-- @field #Item baseType @{#Item}
---
-- Whether the object is a Lockpick.
-- @function [parent=#Lockpick] objectIsInstance
-- @param openmw.core#GameObject object
-- @return #boolean
--- @{#Probe} functions
-- @field [parent=#types] #Probe Probe
---
-- @type Probe
-- @extends #Item
-- @field #Item baseType @{#Item}
---
-- Whether the object is a Probe.
-- @function [parent=#Probe] objectIsInstance
-- @param openmw.core#GameObject object
-- @return #boolean
--- @{#Repair} functions
-- @field [parent=#types] #Repair Repair
---
-- @type Repair
-- @extends #Item
-- @field #Item baseType @{#Item}
---
-- Whether the object is a Repair.
-- @function [parent=#Repair] objectIsInstance
-- @param openmw.core#GameObject object
-- @return #boolean
--- @{#Activator} functions
-- @field [parent=#types] #Activator Activator
---
-- @type Activator
---
-- Whether the object is an Activator.
-- @function [parent=#Activator] objectIsInstance
-- @param openmw.core#GameObject object
-- @return #boolean
--- @{#Container} functions
-- @field [parent=#types] #Container Container
---
-- @type Container
---
-- Container content.
-- @function [parent=#Container] content
-- @param openmw.core#GameObject object
-- @return openmw.core#Inventory
---
-- Whether the object is a Container.
-- @function [parent=#Container] objectIsInstance
-- @param openmw.core#GameObject object
-- @return #boolean
--- @{#Door} functions
-- @field [parent=#types] #Door Door
---
-- @type Door
---
-- Whether the object is a Door.
-- @function [parent=#Door] objectIsInstance
-- @param openmw.core#GameObject object
-- @return #boolean
---
-- Whether the door is a teleport.
-- @function [parent=#Door] isTeleport
-- @param openmw.core#GameObject object
-- @return #boolean
---
-- Destination (only if a teleport door).
-- @function [parent=#Door] destPosition
-- @param openmw.core#GameObject object
-- @return openmw.util#Vector3
---
-- Destination rotation (only if a teleport door).
-- @function [parent=#Door] destRotation
-- @param openmw.core#GameObject object
-- @return openmw.util#Vector3
---
-- Destination cell (only if a teleport door).
-- @function [parent=#Door] destCell
-- @param openmw.core#GameObject object
-- @return openmw.core#Cell
--- Functions for @{#Static} objects
-- @field [parent=#types] #Static Static
---
-- @type Static
---
-- Whether the object is a Static.
-- @function [parent=#Static] objectIsInstance
-- @param openmw.core#GameObject object
-- @return #boolean
return nil

@ -10,12 +10,6 @@
-- List of currently active actors.
-- @field [parent=#world] openmw.core#ObjectList activeActors
---
-- Evaluates a Query.
-- @function [parent=#world] selectObjects
-- @param openmw.query#Query query
-- @return openmw.core#ObjectList
---
-- Loads a named cell
-- @function [parent=#world] getCellByName

Loading…
Cancel
Save