From af93ebf4334d01a72d49e68f78773597f8712de3 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 15 Feb 2022 19:38:47 +0100 Subject: [PATCH] [Lua] Move class-specific functions to `openmw.types` --- apps/openmw/CMakeLists.txt | 1 + apps/openmw/mwlua/localscripts.cpp | 21 +-- apps/openmw/mwlua/luabindings.cpp | 74 ++++++--- apps/openmw/mwlua/luabindings.hpp | 9 +- apps/openmw/mwlua/luamanagerimp.cpp | 1 + apps/openmw/mwlua/object.cpp | 15 +- apps/openmw/mwlua/object.hpp | 18 +++ apps/openmw/mwlua/objectbindings.cpp | 142 +----------------- apps/openmw/mwlua/types/actor.cpp | 137 +++++++++++++++++ apps/openmw/mwlua/types/door.cpp | 34 +++++ apps/openmw/mwlua/types/types.hpp | 14 ++ files/builtin_scripts/openmw_aux/calendar.lua | 2 +- files/builtin_scripts/scripts/omw/camera.lua | 6 +- .../scripts/omw/head_bobbing.lua | 6 +- .../scripts/omw/third_person.lua | 6 +- 15 files changed, 286 insertions(+), 200 deletions(-) create mode 100644 apps/openmw/mwlua/types/actor.cpp create mode 100644 apps/openmw/mwlua/types/door.cpp create mode 100644 apps/openmw/mwlua/types/types.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 71c138eee9..6b439bde80 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -60,6 +60,7 @@ add_openmw_dir (mwlua luamanagerimp actions object worldview userdataserializer eventqueue query luabindings localscripts playerscripts objectbindings cellbindings asyncbindings settingsbindings camerabindings uibindings inputbindings nearbybindings + types/types types/door types/actor ) add_openmw_dir (mwsound 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..2319872974 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -12,6 +12,7 @@ #include "eventqueue.hpp" #include "worldview.hpp" +#include "types/types.hpp" namespace MWLua { @@ -45,11 +46,57 @@ namespace MWLua // api["resume"] = []() {}; } + sol::table initTypesPackage(const Context& context) + { + auto* lua = context.mLua; + sol::table types(lua->sol(), sol::create); + auto addType = [&](std::string_view name, 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[sol::metatable_key] = LuaUtil::getMutableFromReadOnly(types[*base]); + t["baseType"] = types[*base]; + } + types[name] = ro; + return t; + }; + + addActorBindings(addType("Actor"), context); + addType("Item"); + + addType(ObjectTypeName::Creature, "Actor"); + addType(ObjectTypeName::NPC, "Actor"); + addType(ObjectTypeName::Player, ObjectTypeName::NPC); + + addType(ObjectTypeName::Armor, "Item"); + addType(ObjectTypeName::Book, "Item"); + addType(ObjectTypeName::Clothing, "Item"); + addType(ObjectTypeName::Ingredient, "Item"); + addType(ObjectTypeName::Light, "Item"); + addType(ObjectTypeName::MiscItem, "Item"); + addType(ObjectTypeName::Potion, "Item"); + addType(ObjectTypeName::Weapon, "Item"); + addType(ObjectTypeName::Apparatus, "Item"); + addType(ObjectTypeName::Lockpick, "Item"); + addType(ObjectTypeName::Probe, "Item"); + addType(ObjectTypeName::Repair, "Item"); + + addType(ObjectTypeName::Activator); + addDoorBindings(addType(ObjectTypeName::Door), context); + addType(ObjectTypeName::Static); + + return LuaUtil::makeReadOnly(types); + } + sol::table initCorePackage(const Context& context) { 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 +107,14 @@ namespace MWLua context.mGlobalEventQueue->push_back({std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer)}); }; addTimeBindings(api, context, false); - api["OBJECT_TYPE"] = definitionList(*lua, + api["OBJECT_TYPE"] = definitionList(*lua, // TODO: remove, use require('openmw.types') instead { 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::Static, ObjectTypeName::Weapon, ObjectTypeName::Apparatus, 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 diff --git a/apps/openmw/mwlua/luabindings.hpp b/apps/openmw/mwlua/luabindings.hpp index 377d6d1e4f..d80c649f4c 100644 --- a/apps/openmw/mwlua/luabindings.hpp +++ b/apps/openmw/mwlua/luabindings.hpp @@ -23,6 +23,7 @@ namespace MWLua sol::table initCorePackage(const Context&); sol::table initWorldPackage(const Context&); sol::table initQueryPackage(const Context&); + sol::table initTypesPackage(const Context&); sol::table initFieldGroup(const Context&, const QueryFieldGroup&); @@ -38,14 +39,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..cf1c69aa11 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -82,6 +82,7 @@ namespace MWLua 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)); diff --git a/apps/openmw/mwlua/object.cpp b/apps/openmw/mwlua/object.cpp index 816ec6712b..dc903845f4 100644 --- a/apps/openmw/mwlua/object.cpp +++ b/apps/openmw/mwlua/object.cpp @@ -60,7 +60,7 @@ namespace MWLua // 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"; + return ObjectTypeName::Player; if (isMarker(ptr)) return "Marker"; return getLuaObjectTypeName(static_cast(ptr.getType()), /*fallback=*/ptr.getTypeDescription()); @@ -79,6 +79,19 @@ namespace MWLua 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; + } + 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..36744b8f93 100644 --- a/apps/openmw/mwlua/object.hpp +++ b/apps/openmw/mwlua/object.hpp @@ -3,6 +3,8 @@ #include +#include + #include #include #include @@ -48,6 +50,7 @@ namespace MWLua 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); + const MWWorld::Ptr& verifyType(ESM::RecNameInts recordType, 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. @@ -103,6 +106,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 +120,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>; diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 3ae9035271..0310541eee 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -42,19 +42,6 @@ namespace MWLua template using Cell = std::conditional_t, LCell, GCell>; - 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 static void registerObjectList(const std::string& prefix, const Context& context) { @@ -122,21 +109,6 @@ namespace MWLua context.mLocalEventQueue->push_back({dest.id(), std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer)}); }; - objectT["canMove"] = [](const ObjectT& o) - { - 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(); @@ -199,112 +171,14 @@ namespace MWLua context.mLuaManager->addAction(std::move(action)); }; } - else - { // Only for local scripts - objectT["isOnGround"] = [](const ObjectT& o) - { - return MWBase::Environment::get().getWorld()->isOnGround(o.ptr()); - }; - objectT["isSwimming"] = [](const ObjectT& o) - { - return MWBase::Environment::get().getWorld()->isSwimming(o.ptr()); - }; - objectT["isInWeaponStance"] = [](const ObjectT& o) - { - const MWWorld::Class& cls = o.ptr().getClass(); - return cls.isActor() && cls.getCreatureStats(o.ptr()).getDrawState() == MWMechanics::DrawState_Weapon; - }; - objectT["isInMagicStance"] = [](const ObjectT& o) - { - const MWWorld::Class& cls = o.ptr().getClass(); - return cls.isActor() && cls.getCreatureStats(o.ptr()).getDrawState() == MWMechanics::DrawState_Spell; - }; - objectT["getCurrentSpeed"] = [](const ObjectT& o) - { - const MWWorld::Class& cls = o.ptr().getClass(); - return cls.getCurrentSpeed(o.ptr()); - }; - } } - 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; - }); - } - - 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(); - } - 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) - { - 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] = 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() + "]"; }; @@ -363,18 +237,6 @@ namespace MWLua 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 (!equipment.empty()) - throw std::runtime_error(ptrToString(obj.ptr()) + " has no equipment slots"); - return; - } - context.mLuaManager->addAction(std::make_unique( - context.mLua, obj.id(), parseEquipmentTable(equipment))); - }; - // TODO // obj.inventory:drop(obj2, [count]) // obj.inventory:drop(recordId, [count]) @@ -390,9 +252,9 @@ namespace MWLua template static void initObjectBindings(const std::string& prefix, const Context& context) { - sol::usertype objectT = context.mLua->sol().new_usertype(prefix + "Object"); + sol::usertype objectT = context.mLua->sol().new_usertype( + prefix + "Object", sol::base_classes, sol::bases()); addBasicBindings(objectT, context); - addDoorBindings(objectT, context); addInventoryBindings(objectT, prefix, context); registerObjectList(prefix, context); diff --git a/apps/openmw/mwlua/types/actor.cpp b/apps/openmw/mwlua/types/actor.cpp new file mode 100644 index 0000000000..ba2c05a52d --- /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["getEquipment"] = [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 sol::object& luaObj, const sol::table& equipment) + { + if (!luaObj.is() && !luaObj.is()) + throw std::runtime_error("Incorrect type of the first argument. " + "Can be either self (in local scripts) or game object (in global scripts)"); + const Object& obj = luaObj.as(); + 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/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.hpp b/apps/openmw/mwlua/types/types.hpp new file mode 100644 index 0000000000..20fd208462 --- /dev/null +++ b/apps/openmw/mwlua/types/types.hpp @@ -0,0 +1,14 @@ +#ifndef MWLUA_TYPES_H +#define MWLUA_TYPES_H + +#include + +#include "../context.hpp" + +namespace MWLua +{ + void addDoorBindings(sol::table door, const Context& context); + void addActorBindings(sol::table actor, const Context& context); +} + +#endif // MWLUA_TYPES_H 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