#include "luabindings.hpp" #include #include #include "../mwclass/door.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/inventorystore.hpp" #include "eventqueue.hpp" #include "luamanagerimp.hpp" namespace MWLua { template struct Inventory { ObjectT mObj; }; } namespace sol { template <> struct is_automagical : std::false_type {}; template <> struct is_automagical : std::false_type {}; template <> struct is_automagical : std::false_type {}; template <> struct is_automagical : std::false_type {}; template <> struct is_automagical> : std::false_type {}; template <> struct is_automagical> : std::false_type {}; } namespace MWLua { 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) { auto iter = [registry](const ListT& l, int64_t i) -> sol::optional> { if (i >= 0 && i < static_cast(l.mIds->size())) return std::make_tuple(i + 1, ObjectT((*l.mIds)[i], registry)); else return sol::nullopt; }; 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()}; 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)}); }; 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) { 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) { 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); }; 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)); }; } } 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["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))); 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) { const MWWorld::Ptr& item = *(it++); worldView->getObjectRegistry()->registerPtr(item); list->push_back(getId(item)); } 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); }; 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", sol::base_classes, sol::bases()); addBasicBindings(objectT, context); addInventoryBindings(objectT, prefix, context); registerObjectList(prefix, context); } void initObjectBindingsForLocalScripts(const Context& context) { initObjectBindings("L", context); } void initObjectBindingsForGlobalScripts(const Context& context) { initObjectBindings("G", context); } }