diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 240d4a8e42..be80584882 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -57,7 +57,7 @@ add_openmw_dir (mwscript add_openmw_dir (mwlua luamanagerimp localscripts object worldview luabindings userdataserializer eventqueue - objectbindings asyncbindings + objectbindings asyncbindings camerabindings uibindings ) add_openmw_dir (mwsound diff --git a/apps/openmw/mwlua/camerabindings.cpp b/apps/openmw/mwlua/camerabindings.cpp new file mode 100644 index 0000000000..68f2331b9e --- /dev/null +++ b/apps/openmw/mwlua/camerabindings.cpp @@ -0,0 +1,13 @@ +#include "luabindings.hpp" + +namespace MWLua +{ + + sol::table initCameraPackage(const Context& context) + { + sol::table api(context.mLua->sol(), sol::create); + // TODO + return context.mLua->makeReadOnly(api); + } + +} diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index 1c35b60131..573188c004 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -1,5 +1,10 @@ #include "localscripts.hpp" +#include "../mwworld/ptr.hpp" +#include "../mwworld/class.hpp" +#include "../mwmechanics/aisequence.hpp" +#include "../mwmechanics/aicombat.hpp" + namespace sol { template <> @@ -27,6 +32,28 @@ namespace MWLua selfAPI["object"] = sol::readonly_property([](SelfObject& self) -> LObject { return LObject(self); }); selfAPI["controls"] = sol::readonly_property([](SelfObject& self) { return &self.mControls; }); selfAPI["setDirectControl"] = [](SelfObject& self, bool v) { self.mControls.controlledFromLua = v; }; + selfAPI["getCombatTarget"] = [worldView=context.mWorldView](SelfObject& self) -> sol::optional + { + const MWWorld::Ptr& ptr = self.ptr(); + MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); + MWWorld::Ptr target; + if (ai.getCombatTarget(target)) + return LObject(getId(target), worldView->getObjectRegistry()); + else + return {}; + }; + selfAPI["stopCombat"] = [](SelfObject& self) + { + const MWWorld::Ptr& ptr = self.ptr(); + MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); + ai.stopCombat(); + }; + selfAPI["startCombat"] = [](SelfObject& self, const LObject& target) + { + const MWWorld::Ptr& ptr = self.ptr(); + MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); + ai.stack(MWMechanics::AiCombat(target.ptr()), ptr); + }; } std::unique_ptr LocalScripts::create(LuaUtil::LuaState* lua, const LObject& obj) diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 5ebb6670f8..f8d7812c61 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -8,6 +8,14 @@ namespace MWLua { + static sol::table definitionList(LuaUtil::LuaState& lua, std::initializer_list values) + { + sol::table res(lua.sol(), sol::create); + for (const std::string& v : values) + res[v] = v; + return lua.makeReadOnly(res); + } + sol::table initCorePackage(const Context& context) { sol::table api(context.mLua->sol(), sol::create); @@ -17,6 +25,11 @@ namespace MWLua }; api["getGameTimeInSeconds"] = [world=context.mWorldView]() { return world->getGameTimeInSeconds(); }; api["getGameTimeInHours"] = [world=context.mWorldView]() { return world->getGameTimeInHours(); }; + api["OBJECT_TYPE"] = definitionList(*context.mLua, + { + "Activator", "Armor", "Book", "Clothing", "Creature", "Door", "Ingredient", + "Light", "Miscellaneous", "NPC", "Player", "Potion", "Static", "Weapon" + }); return context.mLua->makeReadOnly(api); } @@ -32,7 +45,10 @@ namespace MWLua { sol::table api(context.mLua->sol(), sol::create); WorldView* worldView = context.mWorldView; + api["activators"] = LObjectList{worldView->getActivatorsInScene()}; api["actors"] = LObjectList{worldView->getActorsInScene()}; + api["containers"] = LObjectList{worldView->getContainersInScene()}; + api["doors"] = LObjectList{worldView->getDoorsInScene()}; api["items"] = LObjectList{worldView->getItemsInScene()}; return context.mLua->makeReadOnly(api); } diff --git a/apps/openmw/mwlua/luabindings.hpp b/apps/openmw/mwlua/luabindings.hpp index 2eae569ef0..251ed3998b 100644 --- a/apps/openmw/mwlua/luabindings.hpp +++ b/apps/openmw/mwlua/luabindings.hpp @@ -40,6 +40,12 @@ namespace MWLua }; sol::function getAsyncPackageInitializer(const Context&); + // Implemented in camerabindings.cpp + sol::table initCameraPackage(const Context&); + + // Implemented in uibindings.cpp + sol::table initUserInterfacePackage(const Context&); + // openmw.self package is implemented in localscripts.cpp } diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index c725c0f936..b629e22ab2 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -8,6 +8,8 @@ #include +#include "../mwbase/windowmanager.hpp" + #include "../mwworld/class.hpp" #include "../mwworld/ptr.hpp" @@ -47,6 +49,8 @@ namespace MWLua mLua.addCommonPackage("openmw.util", LuaUtil::initUtilPackage(mLua.sol())); mLua.addCommonPackage("openmw.core", initCorePackage(context)); mGlobalScripts.addPackage("openmw.world", initWorldPackage(context)); + mCameraPackage = initCameraPackage(localContext); + mUserInterfacePackage = initUserInterfacePackage(localContext); mNearbyPackage = initNearbyPackage(localContext); auto endsWith = [](std::string_view s, std::string_view suffix) @@ -151,6 +155,10 @@ namespace MWLua void LuaManager::applyQueuedChanges() { + MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager(); + for (const std::string& message : mUIMessages) + windowManager->messageBox(message); + mUIMessages.clear(); } void LuaManager::clear() @@ -238,8 +246,8 @@ namespace MWLua { mPlayerScripts = new PlayerScripts(&mLua, LObject(getId(ptr), mWorldView.getObjectRegistry())); scripts = std::unique_ptr(mPlayerScripts); - // TODO: scripts->addPackage("openmw.ui", ...); - // TODO: scripts->addPackage("openmw.camera", ...); + scripts->addPackage("openmw.ui", mUserInterfacePackage); + scripts->addPackage("openmw.camera", mCameraPackage); } else scripts = LocalScripts::create(&mLua, LObject(getId(ptr), mWorldView.getObjectRegistry())); diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 833b22cf0f..e8babc9695 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -48,6 +48,7 @@ namespace MWLua // Used only in luabindings.cpp void addLocalScript(const MWWorld::Ptr&, const std::string& scriptPath); + void addUIMessage(std::string_view message) { mUIMessages.emplace_back(message); } // Saving void write(ESM::ESMWriter& writer, Loading::Listener& progress) override; @@ -63,6 +64,8 @@ namespace MWLua LuaUtil::LuaState mLua; sol::table mNearbyPackage; + sol::table mUserInterfacePackage; + sol::table mCameraPackage; std::vector mGlobalScriptList; GlobalScripts mGlobalScripts{&mLua}; @@ -85,6 +88,9 @@ namespace MWLua std::vector mKeyPressEvents; std::vector mActorAddedEvents; + + // Queued actions that should be done in main thread. Processed by applyQueuedChanges(). + std::vector mUIMessages; }; } diff --git a/apps/openmw/mwlua/object.cpp b/apps/openmw/mwlua/object.cpp index fa0f9daffc..2eb8d16f3c 100644 --- a/apps/openmw/mwlua/object.cpp +++ b/apps/openmw/mwlua/object.cpp @@ -1,7 +1,19 @@ #include "object.hpp" -#include -#include +#include "../mwclass/activator.hpp" +#include "../mwclass/armor.hpp" +#include "../mwclass/book.hpp" +#include "../mwclass/clothing.hpp" +#include "../mwclass/container.hpp" +#include "../mwclass/creature.hpp" +#include "../mwclass/door.hpp" +#include "../mwclass/ingredient.hpp" +#include "../mwclass/light.hpp" +#include "../mwclass/misc.hpp" +#include "../mwclass/npc.hpp" +#include "../mwclass/potion.hpp" +#include "../mwclass/static.hpp" +#include "../mwclass/weapon.hpp" namespace MWLua { @@ -11,33 +23,58 @@ namespace MWLua return std::to_string(id.mIndex) + "_" + std::to_string(id.mContentFile); } - std::string Object::toString() const + const static std::map classNames = { + {typeid(MWClass::Activator), "Activator"}, + {typeid(MWClass::Armor), "Armor"}, + {typeid(MWClass::Book), "Book"}, + {typeid(MWClass::Clothing), "Clothing"}, + {typeid(MWClass::Container), "Container"}, + {typeid(MWClass::Creature), "Creature"}, + {typeid(MWClass::Door), "Door"}, + {typeid(MWClass::Ingredient), "Ingredient"}, + {typeid(MWClass::Light), "Light"}, + {typeid(MWClass::Miscellaneous), "Miscellaneous"}, + {typeid(MWClass::Npc), "NPC"}, + {typeid(MWClass::Potion), "Potion"}, + {typeid(MWClass::Static), "Static"}, + {typeid(MWClass::Weapon), "Weapon"}, + }; + + std::string_view getMWClassName(const std::type_index& cls_type, std::string_view fallback) { - std::string res = idToString(mId); - if (isValid()) - { - res.append(" ("); - res.append(type()); - res.append(", "); - res.append(*ptr().getCellRef().getRefIdPtr()); - res.append(")"); - } + auto it = classNames.find(cls_type); + if (it != classNames.end()) + return it->second; else - res.append(" (not found)"); - return res; + return fallback; } - std::string_view Object::type() const + std::string_view getMWClassName(const MWWorld::Ptr& ptr) { - if (*ptr().getCellRef().getRefIdPtr() == "player") + if (*ptr.getCellRef().getRefIdPtr() == "player") return "Player"; - const std::string& typeName = ptr().getTypeName(); - if (typeName == typeid(ESM::NPC).name()) - return "NPC"; - else if (typeName == typeid(ESM::Creature).name()) - return "Creature"; else - return typeName; + return getMWClassName(typeid(ptr.getClass()), ptr.getTypeName()); + } + + std::string ptrToString(const MWWorld::Ptr& ptr) + { + std::string res = "object"; + res.append(idToString(getId(ptr))); + res.append(" ("); + res.append(getMWClassName(ptr)); + res.append(", "); + res.append(*ptr.getCellRef().getRefIdPtr()); + res.append(")"); + return res; + } + + std::string Object::toString() const + { + if (isValid()) + return ptrToString(ptr()); + else + return "object" + idToString(mId) + " (not found)"; } bool Object::isValid() const diff --git a/apps/openmw/mwlua/object.hpp b/apps/openmw/mwlua/object.hpp index 59e6534166..ac079fb38f 100644 --- a/apps/openmw/mwlua/object.hpp +++ b/apps/openmw/mwlua/object.hpp @@ -1,6 +1,8 @@ #ifndef MWLUA_OBJECT_H #define MWLUA_OBJECT_H +#include + #include #include "../mwbase/environment.hpp" @@ -15,6 +17,9 @@ namespace MWLua using ObjectId = ESM::RefNum; inline const ObjectId& getId(const MWWorld::Ptr& ptr) { return ptr.getCellRef().getRefNum(); } std::string idToString(const ObjectId& id); + std::string ptrToString(const MWWorld::Ptr& ptr); + std::string_view getMWClassName(const std::type_index& cls_type, std::string_view fallback = "Unknown"); + std::string_view getMWClassName(const MWWorld::Ptr& ptr); // Holds a mapping ObjectId -> MWWord::Ptr. class ObjectRegistry @@ -60,7 +65,7 @@ namespace MWLua ObjectId id() const { return mId; } std::string toString() const; - std::string_view type() const; + std::string_view type() const { return getMWClassName(ptr()); } // Updates and returns the underlying Ptr. Throws an exception if object is not available. const MWWorld::Ptr& ptr() const; diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 806907ef35..dbe0a54480 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -2,6 +2,8 @@ #include +#include "../mwclass/door.hpp" + #include "eventqueue.hpp" #include "luamanagerimp.hpp" @@ -20,6 +22,20 @@ namespace sol namespace MWLua { + template + static const MWWorld::Ptr& requireClass(const MWWorld::Ptr& ptr) + { + if (typeid(Class) != typeid(ptr.getClass())) + { + std::string msg = "Requires type '"; + msg.append(getMWClassName(typeid(Class))); + 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) { @@ -83,11 +99,35 @@ namespace MWLua } } + template + static void addDoorBindings(sol::usertype& objectT, const Context& context) + { + auto ptr = [](const ObjectT& o) -> const MWWorld::Ptr& { return requireClass(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](const ObjectT& o) -> std::string_view + { + return ptr(o).getCellRef().getDestCell(); + }); + } + 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); registerObjectList(prefix, context); } diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp new file mode 100644 index 0000000000..cb14c41621 --- /dev/null +++ b/apps/openmw/mwlua/uibindings.cpp @@ -0,0 +1,18 @@ +#include "luabindings.hpp" + +#include "luamanagerimp.hpp" + +namespace MWLua +{ + + sol::table initUserInterfacePackage(const Context& context) + { + sol::table api(context.mLua->sol(), sol::create); + api["showMessage"] = [luaManager=context.mLuaManager](std::string_view message) + { + luaManager->addUIMessage(message); + }; + return context.mLua->makeReadOnly(api); + } + +} diff --git a/apps/openmw/mwlua/worldview.cpp b/apps/openmw/mwlua/worldview.cpp index fc08b60037..6e54efac39 100644 --- a/apps/openmw/mwlua/worldview.cpp +++ b/apps/openmw/mwlua/worldview.cpp @@ -3,6 +3,8 @@ #include #include +#include "../mwclass/container.hpp" + #include "../mwworld/class.hpp" #include "../mwworld/timestamp.hpp" @@ -12,31 +14,52 @@ namespace MWLua void WorldView::update() { mObjectRegistry.update(); + mActivatorsInScene.updateList(); mActorsInScene.updateList(); + mContainersInScene.updateList(); + mDoorsInScene.updateList(); mItemsInScene.updateList(); } void WorldView::clear() { mObjectRegistry.clear(); + mActivatorsInScene.clear(); mActorsInScene.clear(); + mContainersInScene.clear(); + mDoorsInScene.clear(); mItemsInScene.clear(); } + WorldView::ObjectGroup* WorldView::chooseGroup(const MWWorld::Ptr& ptr) + { + const MWWorld::Class& cls = ptr.getClass(); + if (cls.isActivator()) + return &mActivatorsInScene; + if (cls.isActor()) + return &mActorsInScene; + if (cls.isDoor()) + return &mDoorsInScene; + if (typeid(cls) == typeid(MWClass::Container)) + return &mContainersInScene; + if (cls.hasToolTip(ptr)) + return &mItemsInScene; + return nullptr; + } + void WorldView::objectAddedToScene(const MWWorld::Ptr& ptr) { - if (ptr.getClass().isActor()) - addToGroup(mActorsInScene, ptr); - else - addToGroup(mItemsInScene, ptr); + mObjectRegistry.registerPtr(ptr); + ObjectGroup* group = chooseGroup(ptr); + if (group) + addToGroup(*group, ptr); } void WorldView::objectRemovedFromScene(const MWWorld::Ptr& ptr) { - if (ptr.getClass().isActor()) - removeFromGroup(mActorsInScene, ptr); - else - removeFromGroup(mItemsInScene, ptr); + ObjectGroup* group = chooseGroup(ptr); + if (group) + removeFromGroup(*group, ptr); } double WorldView::getGameTimeInHours() const diff --git a/apps/openmw/mwlua/worldview.hpp b/apps/openmw/mwlua/worldview.hpp index 45046c0b73..89f5afa0c3 100644 --- a/apps/openmw/mwlua/worldview.hpp +++ b/apps/openmw/mwlua/worldview.hpp @@ -27,7 +27,10 @@ namespace MWLua // Note that the number of seconds in a game hour is not fixed. double getGameTimeInHours() const; + ObjectIdList getActivatorsInScene() const { return mActivatorsInScene.mList; } ObjectIdList getActorsInScene() const { return mActorsInScene.mList; } + ObjectIdList getContainersInScene() const { return mContainersInScene.mList; } + ObjectIdList getDoorsInScene() const { return mDoorsInScene.mList; } ObjectIdList getItemsInScene() const { return mItemsInScene.mList; } ObjectRegistry* getObjectRegistry() { return &mObjectRegistry; } @@ -51,11 +54,15 @@ namespace MWLua std::set mSet; }; + ObjectGroup* chooseGroup(const MWWorld::Ptr& ptr); void addToGroup(ObjectGroup& group, const MWWorld::Ptr& ptr); void removeFromGroup(ObjectGroup& group, const MWWorld::Ptr& ptr); ObjectRegistry mObjectRegistry; + ObjectGroup mActivatorsInScene; ObjectGroup mActorsInScene; + ObjectGroup mContainersInScene; + ObjectGroup mDoorsInScene; ObjectGroup mItemsInScene; double mGameSeconds = 0;