diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 71c138eee9..9a83a5cdb7 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -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 diff --git a/apps/openmw/mwlua/cellbindings.cpp b/apps/openmw/mwlua/cellbindings.cpp index dbcbbab644..3d0c4d26df 100644 --- a/apps/openmw/mwlua/cellbindings.cpp +++ b/apps/openmw/mwlua/cellbindings.cpp @@ -3,6 +3,7 @@ #include #include "../mwworld/cellstore.hpp" +#include "types/types.hpp" namespace sol { @@ -50,9 +51,64 @@ namespace MWLua if constexpr (std::is_same_v) { // 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 type) { - return GObjectList{selectObjectsFromCellStore(query, cell.mStore, context)}; + ObjectIdList res = std::make_shared>(); + 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 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(visitor); break; + case ESM::REC_NPC_: cell.mStore->template forEachType(visitor); break; + case ESM::REC_ACTI: cell.mStore->template forEachType(visitor); break; + case ESM::REC_DOOR: cell.mStore->template forEachType(visitor); break; + case ESM::REC_CONT: cell.mStore->template forEachType(visitor); break; + + case ESM::REC_ALCH: cell.mStore->template forEachType(visitor); break; + case ESM::REC_ARMO: cell.mStore->template forEachType(visitor); break; + case ESM::REC_BOOK: cell.mStore->template forEachType(visitor); break; + case ESM::REC_CLOT: cell.mStore->template forEachType(visitor); break; + case ESM::REC_INGR: cell.mStore->template forEachType(visitor); break; + case ESM::REC_LIGH: cell.mStore->template forEachType(visitor); break; + case ESM::REC_MISC: cell.mStore->template forEachType(visitor); break; + case ESM::REC_WEAP: cell.mStore->template forEachType(visitor); break; + case ESM::REC_APPA: cell.mStore->template forEachType(visitor); break; + case ESM::REC_LOCK: cell.mStore->template forEachType(visitor); break; + case ESM::REC_PROB: cell.mStore->template forEachType(visitor); break; + case ESM::REC_REPA: cell.mStore->template forEachType(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}; }; } } diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index c95eae43a7..7cdb31f518 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -43,31 +43,12 @@ namespace MWLua #undef CONTROL sol::usertype selfAPI = - context.mLua->sol().new_usertype("SelfObject", sol::base_classes, sol::bases()); + context.mLua->sol().new_usertype("SelfObject", sol::base_classes, sol::bases()); 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(); - if (value.is()) - eqp[slot] = value.as().id(); - else - eqp[slot] = value.as(); - } - context.mLuaManager->addAction(std::make_unique(context.mLua, obj.id(), std::move(eqp))); - }; using AiPackage = MWMechanics::AiPackage; sol::usertype aiPackage = context.mLua->sol().new_usertype("AiPackage"); diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 06ba6d5c5a..a46435b2ca 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -2,7 +2,6 @@ #include #include -#include #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 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({ - {"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* gmst = &MWBase::Environment::get().getWorld()->getStore().get(); 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); diff --git a/apps/openmw/mwlua/luabindings.hpp b/apps/openmw/mwlua/luabindings.hpp index 377d6d1e4f..41bc8b29ae 100644 --- a/apps/openmw/mwlua/luabindings.hpp +++ b/apps/openmw/mwlua/luabindings.hpp @@ -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&); diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 716612f745..df49862df6 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -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 scripts; if (flag == ESM::LuaScriptCfg::sPlayer) { diff --git a/apps/openmw/mwlua/nearbybindings.cpp b/apps/openmw/mwlua/nearbybindings.cpp index 624cf69da6..05bc52c5cc 100644 --- a/apps/openmw/mwlua/nearbybindings.cpp +++ b/apps/openmw/mwlua/nearbybindings.cpp @@ -1,7 +1,6 @@ #include "luabindings.hpp" #include -#include #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); } } diff --git a/apps/openmw/mwlua/object.cpp b/apps/openmw/mwlua/object.cpp index 816ec6712b..e81a6fbcc6 100644 --- a/apps/openmw/mwlua/object.cpp +++ b/apps/openmw/mwlua/object.cpp @@ -2,6 +2,8 @@ #include +#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 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(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(ptr.getType())); - if (it != luaObjectTypeInfo.end()) - return it->second.mFlag; - else - return 0; - } - std::string ptrToString(const MWWorld::Ptr& ptr) { std::string res = "object"; diff --git a/apps/openmw/mwlua/object.hpp b/apps/openmw/mwlua/object.hpp index 0b51da17c3..499c21c7e7 100644 --- a/apps/openmw/mwlua/object.hpp +++ b/apps/openmw/mwlua/object.hpp @@ -3,6 +3,8 @@ #include +#include + #include #include #include @@ -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(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(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>; @@ -133,6 +118,9 @@ namespace MWLua using GObjectList = ObjectList; using LObjectList = ObjectList; + template + struct Inventory { Obj mObj; }; + } #endif // MWLUA_OBJECT_H diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 3ae9035271..08435583df 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -1,24 +1,14 @@ #include "luabindings.hpp" #include -#include - -#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 - struct Inventory - { - ObjectT mObj; - }; -} +#include "types/types.hpp" namespace sol { @@ -39,364 +29,245 @@ namespace sol namespace MWLua { - template - using Cell = std::conditional_t, 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 + using Cell = std::conditional_t, LCell, GCell>; - template - static void registerObjectList(const std::string& prefix, const Context& context) - { - using ListT = ObjectList; - sol::state& lua = context.mLua->sol(); - ObjectRegistry* registry = context.mWorldView->getObjectRegistry(); - sol::usertype listT = lua.new_usertype(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 + void registerObjectList(const std::string& prefix, const Context& context) { - auto iter = [registry](const ListT& l, int64_t i) -> sol::optional> + using ListT = ObjectList; + sol::state& lua = context.mLua->sol(); + ObjectRegistry* registry = context.mWorldView->getObjectRegistry(); + sol::usertype listT = lua.new_usertype(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(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 - static void addBasicBindings(sol::usertype& 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> - { - const MWWorld::Ptr& ptr = o.ptr(); - if (ptr.isInCell()) - return Cell{ptr.getCell()}; + if constexpr (std::is_same_v) + { + // 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> + { + if (i >= 0 && i < static_cast(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> + { + if (i >= 0 && i < static_cast(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 + void addBasicBindings(sol::usertype& 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(context.mLua, o.id(), actor.id())); - }; - - if constexpr (std::is_same_v) - { // 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 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> { - const LuaUtil::ScriptsConfiguration& cfg = lua->getConfiguration(); - std::optional 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{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 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& optRot) - { - MWWorld::Ptr ptr = object.ptr(); - osg::Vec3f rot = optRot ? *optRot : ptr.getRefData().getPosition().asRotationVec3(); - auto action = std::make_unique(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(context.mLua, o.id(), actor.id())); }; - } - } - - template - static void addDoorBindings(sol::usertype& 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> - { - 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{cell}; - else - return sol::nullopt; - }); - } + if constexpr (std::is_same_v) + { // 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 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 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 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(); - if (value.is()) - eqp[slot] = value.as().id(); - else - eqp[slot] = value.as(); + objectT["teleport"] = [context](const GObject& object, std::string_view cell, + const osg::Vec3f& pos, const sol::optional& optRot) + { + MWWorld::Ptr ptr = object.ptr(); + osg::Vec3f rot = optRot ? *optRot : ptr.getRefData().getPosition().asRotationVec3(); + auto action = std::make_unique(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 - static void addInventoryBindings(sol::usertype& objectT, const std::string& prefix, const Context& context) - { - using InventoryT = Inventory; - sol::usertype inventoryT = context.mLua->sol().new_usertype(prefix + "Inventory"); - objectT["getEquipment"] = [context](const ObjectT& o) + template + void addInventoryBindings(sol::usertype& 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; + sol::usertype inventoryT = context.mLua->sol().new_usertype(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 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>(); - auto it = store.begin(mask); - while (it.getType() != -1) + inventoryT["getAll"] = [worldView=context.mWorldView, ids=getPackageToTypeTable(context.mLua->sol())]( + const InventoryT& inventory, sol::optional type) { - const MWWorld::Ptr& item = *(it++); - worldView->getObjectRegistry()->registerPtr(item); - list->push_back(getId(item)); - } - return ObjectList{list}; - }; + int mask = -1; + sol::optional 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) - { // 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>(); + 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( - context.mLua, obj.id(), parseEquipmentTable(equipment))); + return ObjectList{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) + { // 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 - static void initObjectBindings(const std::string& prefix, const Context& context) - { - sol::usertype objectT = context.mLua->sol().new_usertype(prefix + "Object"); - addBasicBindings(objectT, context); - addDoorBindings(objectT, context); - addInventoryBindings(objectT, prefix, context); + template + void initObjectBindings(const std::string& prefix, const Context& context) + { + sol::usertype objectT = context.mLua->sol().new_usertype( + prefix + "Object", sol::base_classes, sol::bases()); + addBasicBindings(objectT, context); + addInventoryBindings(objectT, prefix, context); - registerObjectList(prefix, context); - } + registerObjectList(prefix, context); + } + } // namespace void initObjectBindingsForLocalScripts(const Context& context) { diff --git a/apps/openmw/mwlua/query.cpp b/apps/openmw/mwlua/query.cpp deleted file mode 100644 index c357e56884..0000000000 --- a/apps/openmw/mwlua/query.cpp +++ /dev/null @@ -1,191 +0,0 @@ -#include "query.hpp" - -#include - -#include - -#include "../mwclass/container.hpp" -#include "../mwworld/cellstore.hpp" - -#include "worldview.hpp" - -namespace MWLua -{ - - static std::vector initBasicFieldGroups() - { - auto createGroup = [](std::string name, const auto& arr) -> QueryFieldGroup - { - std::vector 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{ - createGroup("OBJECT", objectFields), - createGroup("DOOR", doorFields), - }; - } - - const std::vector& getBasicQueryFieldGroups() - { - static std::vector 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 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::get(cond.mValue), cond.mType); - else if (cond.mField->type() == typeid(float)) - c = compareFn(fieldObj.as(), std::get(cond.mValue), cond.mType); - else if (cond.mField->type() == typeid(double)) - c = compareFn(fieldObj.as(), std::get(cond.mValue), cond.mType); - else if (cond.mField->type() == typeid(bool)) - c = compareFn(fieldObj.as(), std::get(cond.mValue), cond.mType); - else if (cond.mField->type() == typeid(int32_t)) - c = compareFn(fieldObj.as(), std::get(cond.mValue), cond.mType); - else if (cond.mField->type() == typeid(int64_t)) - c = compareFn(fieldObj.as(), std::get(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>(); - for (const ObjectId& id : *list) - { - if (static_cast(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>(); - auto visitor = [&](const MWWorld::Ptr& ptr) - { - if (static_cast(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(res->size()) != query.mLimit; - }; - store->forEach(std::move(visitor)); // TODO: maybe use store->forEachType depending on query.mType - return res; - } - -} diff --git a/apps/openmw/mwlua/query.hpp b/apps/openmw/mwlua/query.hpp deleted file mode 100644 index 65bf0c5105..0000000000 --- a/apps/openmw/mwlua/query.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef MWLUA_QUERY_H -#define MWLUA_QUERY_H - -#include - -#include - -#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 mFields; - }; - const std::vector& 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 diff --git a/apps/openmw/mwlua/types/actor.cpp b/apps/openmw/mwlua/types/actor.cpp new file mode 100644 index 0000000000..d620472095 --- /dev/null +++ b/apps/openmw/mwlua/types/actor.cpp @@ -0,0 +1,137 @@ +#include "types.hpp" + +#include + +#include +#include +#include + +#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({ + {"Nothing", MWMechanics::DrawState_Nothing}, + {"Weapon", MWMechanics::DrawState_Weapon}, + {"Spell", MWMechanics::DrawState_Spell}, + })); + actor["EQUIPMENT_SLOT"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs({ + {"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{o}; }, + [](const GObject& o) { return Inventory{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(); + if (value.is()) + eqp[slot] = value.as().id(); + else + eqp[slot] = value.as(); + } + context.mLuaManager->addAction(std::make_unique(context.mLua, obj.id(), std::move(eqp))); + }; + } + +} diff --git a/apps/openmw/mwlua/types/container.cpp b/apps/openmw/mwlua/types/container.cpp new file mode 100644 index 0000000000..b603b6d3f4 --- /dev/null +++ b/apps/openmw/mwlua/types/container.cpp @@ -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{o}; }, + [](const GObject& o) { containerPtr(o); return Inventory{o}; } + ); + } + +} diff --git a/apps/openmw/mwlua/types/door.cpp b/apps/openmw/mwlua/types/door.cpp new file mode 100644 index 0000000000..efcf638360 --- /dev/null +++ b/apps/openmw/mwlua/types/door.cpp @@ -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; + }; + } + +} diff --git a/apps/openmw/mwlua/types/types.cpp b/apps/openmw/mwlua/types/types.cpp new file mode 100644 index 0000000000..556c13963f --- /dev/null +++ b/apps/openmw/mwlua/types/types.cpp @@ -0,0 +1,193 @@ +#include "types.hpp" + +#include + +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 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(ptr.getLuaType()), /*fallback=*/ptr.getTypeDescription()); + } + + ESM::LuaScriptCfg::Flags getLuaScriptFlag(const MWWorld::Ptr& ptr) + { + auto it = luaObjectTypeInfo.find(static_cast(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 recTypes, + std::optional 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); + } +} diff --git a/apps/openmw/mwlua/types/types.hpp b/apps/openmw/mwlua/types/types.hpp new file mode 100644 index 0000000000..966e32ccf2 --- /dev/null +++ b/apps/openmw/mwlua/types/types.hpp @@ -0,0 +1,32 @@ +#ifndef MWLUA_TYPES_H +#define MWLUA_TYPES_H + +#include + +#include +#include + +#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 diff --git a/apps/openmw/mwworld/ptr.hpp b/apps/openmw/mwworld/ptr.hpp index 4dbdfa5545..33e062df90 100644 --- a/apps/openmw/mwworld/ptr.hpp +++ b/apps/openmw/mwworld/ptr.hpp @@ -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"; diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 73e7af1d9e..767eebe6c8 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -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 diff --git a/apps/openmw_test_suite/lua/test_querypackage.cpp b/apps/openmw_test_suite/lua/test_querypackage.cpp deleted file mode 100644 index aeaf992db0..0000000000 --- a/apps/openmw_test_suite/lua/test_querypackage.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "gmock/gmock.h" -#include - -#include - -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(), - "SELECT test WHERE ((x == \"abc\") OR (x LIKE \"%abcd%\")) AND (y > 5) ORDER BY x, y DESC GROUP BY y LIMIT 10 OFFSET 5"); - } -} - diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index e183d54a95..efc80081e3 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -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 diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index f587fc6690..45a8cf0f56 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -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"), diff --git a/components/queries/luabindings.cpp b/components/queries/luabindings.cpp deleted file mode 100644 index c830a140f7..0000000000 --- a/components/queries/luabindings.cpp +++ /dev/null @@ -1,118 +0,0 @@ -#include "luabindings.hpp" - -namespace sol -{ - template <> - struct is_automagical : std::false_type {}; - - template <> - struct is_automagical : std::false_type {}; - - template <> - struct is_automagical : std::false_type {}; -} - -namespace Queries -{ - template - struct CondBuilder - { - Filter operator()(const Field& field, const sol::object& o) - { - FieldValue value; - if (field.type() == typeid(bool) && o.is()) - value = o.as(); - else if (field.type() == typeid(int32_t) && o.is()) - value = o.as(); - else if (field.type() == typeid(int64_t) && o.is()) - value = o.as(); - else if (field.type() == typeid(float) && o.is()) - value = o.as(); - else if (field.type() == typeid(double) && o.is()) - value = o.as(); - else if (field.type() == typeid(std::string) && o.is()) - value = o.as(); - 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 = lua.new_usertype("QueryField"); - sol::usertype filter = lua.new_usertype("QueryFilter"); - sol::usertype query = lua.new_usertype("Query"); - - field[sol::meta_function::to_string] = [](const Field& f) { return f.toString(); }; - field["eq"] = CondBuilder(); - field["neq"] = CondBuilder(); - field["lt"] = CondBuilder(); - field["lte"] = CondBuilder(); - field["gt"] = CondBuilder(); - field["gte"] = CondBuilder(); - field["like"] = CondBuilder(); - - 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; - }; - } -} - diff --git a/components/queries/luabindings.hpp b/components/queries/luabindings.hpp deleted file mode 100644 index 16d1a822be..0000000000 --- a/components/queries/luabindings.hpp +++ /dev/null @@ -1,9 +0,0 @@ -#include // missing from sol/sol.hpp -#include - -#include "query.hpp" - -namespace Queries -{ - void registerQueryBindings(sol::state& lua); -} diff --git a/components/queries/query.cpp b/components/queries/query.cpp deleted file mode 100644 index 3c7f1517ee..0000000000 --- a/components/queries/query.cpp +++ /dev/null @@ -1,185 +0,0 @@ -#include "query.hpp" - -#include -#include - -namespace Queries -{ - Field::Field(std::vector 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; - if constexpr (std::is_same_v) - { - std::ostringstream oss; - oss << std::quoted(arg); - return oss.str(); - } - else if constexpr (std::is_same_v) - 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 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; - } -} - diff --git a/components/queries/query.hpp b/components/queries/query.hpp deleted file mode 100644 index 45144fed62..0000000000 --- a/components/queries/query.hpp +++ /dev/null @@ -1,99 +0,0 @@ -#ifndef COMPONENTS_QUERIES_QUERY -#define COMPONENTS_QUERIES_QUERY - -#include -#include -#include -#include -#include - -namespace Queries -{ - class Field - { - public: - Field(std::vector path, std::type_index type); - - const std::vector& path() const { return mPath; } - const std::type_index type() const { return mType; } - - std::string toString() const; - - private: - std::vector mPath; - std::type_index mType; - }; - - struct OrderBy - { - const Field* mField; - bool mDescending; - }; - - using FieldValue = std::variant; - 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 mConditions; - std::vector 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 mOrderBy; - std::vector mGroupBy; - int64_t mOffset = 0; - int64_t mLimit = sNoLimit; - }; -} - -#endif // !COMPONENTS_QUERIES_QUERY - diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index 0681c63dea..cf071534d9 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -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 ` | everywhere | | Functions that are common for both global and local scripts | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ -|:ref:`openmw.async ` | everywhere | | Timers (implemented) and coroutine utils (not implemented) | +|:ref:`openmw.types ` | everywhere | | Functions for specific types of game objects. | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ -|:ref:`openmw.query ` | everywhere | | Tools for constructing queries: base queries and fields. | +|:ref:`openmw.async ` | everywhere | | Timers (implemented) and coroutine utils (not implemented) | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.world ` | by global scripts | | Read-write access to the game world. | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ diff --git a/docs/source/reference/lua-scripting/openmw_query.rst b/docs/source/reference/lua-scripting/openmw_query.rst deleted file mode 100644 index ea0932e8f0..0000000000 --- a/docs/source/reference/lua-scripting/openmw_query.rst +++ /dev/null @@ -1,5 +0,0 @@ -Package openmw.query -==================== - -.. raw:: html - :file: generated_html/openmw_query.html diff --git a/docs/source/reference/lua-scripting/openmw_types.rst b/docs/source/reference/lua-scripting/openmw_types.rst new file mode 100644 index 0000000000..1819b1a6ce --- /dev/null +++ b/docs/source/reference/lua-scripting/openmw_types.rst @@ -0,0 +1,5 @@ +Package openmw.types +==================== + +.. raw:: html + :file: generated_html/openmw_types.html diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index 8fa255bac0..91c0a071e0 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -353,9 +353,9 @@ Player scripts are local scripts that are attached to a player. +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.core ` | everywhere | | Functions that are common for both global and local scripts | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ -|:ref:`openmw.async ` | everywhere | | Timers (implemented) and coroutine utils (not implemented) | +|:ref:`openmw.types ` | everywhere | | Functions for specific types of game objects. | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ -|:ref:`openmw.query ` | everywhere | | Tools for constructing queries: base queries and fields. | +|:ref:`openmw.async ` | everywhere | | Timers (implemented) and coroutine utils (not implemented) | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`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 =========================== diff --git a/files/builtin_scripts/openmw_aux/calendar.lua b/files/builtin_scripts/openmw_aux/calendar.lua index 58e7298f1d..181b133b83 100644 --- a/files/builtin_scripts/openmw_aux/calendar.lua +++ b/files/builtin_scripts/openmw_aux/calendar.lua @@ -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 diff --git a/files/builtin_scripts/scripts/omw/camera.lua b/files/builtin_scripts/scripts/omw/camera.lua index 4b5911930e..40b25c6d13 100644 --- a/files/builtin_scripts/scripts/omw/camera.lua +++ b/files/builtin_scripts/scripts/omw/camera.lua @@ -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) diff --git a/files/builtin_scripts/scripts/omw/head_bobbing.lua b/files/builtin_scripts/scripts/omw/head_bobbing.lua index fe809fca8a..8e6ea0660f 100644 --- a/files/builtin_scripts/scripts/omw/head_bobbing.lua +++ b/files/builtin_scripts/scripts/omw/head_bobbing.lua @@ -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) diff --git a/files/builtin_scripts/scripts/omw/third_person.lua b/files/builtin_scripts/scripts/omw/third_person.lua index ca00ef5ee9..95f872b15f 100644 --- a/files/builtin_scripts/scripts/omw/third_person.lua +++ b/files/builtin_scripts/scripts/omw/third_person.lua @@ -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 diff --git a/files/lua_api/CMakeLists.txt b/files/lua_api/CMakeLists.txt index 8c5b46e8db..88efc8195c 100644 --- a/files/lua_api/CMakeLists.txt +++ b/files/lua_api/CMakeLists.txt @@ -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}) diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index d57b6a405e..43b2d29a70 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -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 diff --git a/files/lua_api/openmw/nearby.lua b/files/lua_api/openmw/nearby.lua index c3403ded0c..3ba3d7e6fb 100644 --- a/files/lua_api/openmw/nearby.lua +++ b/files/lua_api/openmw/nearby.lua @@ -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 diff --git a/files/lua_api/openmw/query.lua b/files/lua_api/openmw/query.lua deleted file mode 100644 index b5cea061ff..0000000000 --- a/files/lua_api/openmw/query.lua +++ /dev/null @@ -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 - diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua new file mode 100644 index 0000000000..82dbded2e8 --- /dev/null +++ b/files/lua_api/openmw/types.lua @@ -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 + diff --git a/files/lua_api/openmw/world.lua b/files/lua_api/openmw/world.lua index 127c41c064..1f5d9a539b 100644 --- a/files/lua_api/openmw/world.lua +++ b/files/lua_api/openmw/world.lua @@ -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