From a65f8ebbc611667e71b10d10d1fcafc9d6151599 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Mon, 11 Apr 2022 01:04:55 +0200 Subject: [PATCH 1/2] Reorganize delayed Lua actions --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwlua/actions.cpp | 180 --------------------------- apps/openmw/mwlua/actions.hpp | 94 -------------- apps/openmw/mwlua/luamanagerimp.cpp | 46 +++++++ apps/openmw/mwlua/luamanagerimp.hpp | 26 +++- apps/openmw/mwlua/objectbindings.cpp | 80 ++++++++++++ apps/openmw/mwlua/stats.cpp | 20 +++ apps/openmw/mwlua/types/actor.cpp | 97 ++++++++++++++- apps/openmw/mwlua/uibindings.cpp | 5 +- 9 files changed, 267 insertions(+), 283 deletions(-) delete mode 100644 apps/openmw/mwlua/actions.cpp delete mode 100644 apps/openmw/mwlua/actions.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 3d6469fa33..f67c991958 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -57,7 +57,7 @@ add_openmw_dir (mwscript ) add_openmw_dir (mwlua - luamanagerimp actions object worldview userdataserializer eventqueue + luamanagerimp object worldview userdataserializer eventqueue luabindings localscripts playerscripts objectbindings cellbindings asyncbindings settingsbindings camerabindings uibindings inputbindings nearbybindings stats types/types types/door types/actor types/container types/weapon types/npc diff --git a/apps/openmw/mwlua/actions.cpp b/apps/openmw/mwlua/actions.cpp deleted file mode 100644 index 0934b92226..0000000000 --- a/apps/openmw/mwlua/actions.cpp +++ /dev/null @@ -1,180 +0,0 @@ -#include "actions.hpp" - -#include "localscripts.hpp" - -#include - -#include -#include -#include - -#include - -#include -#include -#include -#include -#include - -namespace MWLua -{ - Action::Action(LuaUtil::LuaState* state) - { - static const bool luaDebug = Settings::Manager::getBool("lua debug", "Lua"); - if (luaDebug) - mCallerTraceback = state->debugTraceback(); - } - - void Action::safeApply(WorldView& w) const - { - try - { - apply(w); - } - catch (const std::exception& e) - { - Log(Debug::Error) << "Error in " << this->toString() << ": " << e.what(); - - if (mCallerTraceback.empty()) - Log(Debug::Error) << "Set 'lua_debug=true' in settings.cfg to enable action tracebacks"; - else - Log(Debug::Error) << "Caller " << mCallerTraceback; - } - } - - void TeleportAction::apply(WorldView& worldView) const - { - MWWorld::CellStore* cell = worldView.findCell(mCell, mPos); - if (!cell) - throw std::runtime_error(std::string("cell not found: '") + mCell + "'"); - - MWBase::World* world = MWBase::Environment::get().getWorld(); - MWWorld::Ptr obj = worldView.getObjectRegistry()->getPtr(mObject, false); - const MWWorld::Class& cls = obj.getClass(); - bool isPlayer = obj == world->getPlayerPtr(); - if (cls.isActor()) - cls.getCreatureStats(obj).land(isPlayer); - if (isPlayer) - { - ESM::Position esmPos; - static_assert(sizeof(esmPos) == sizeof(osg::Vec3f) * 2); - std::memcpy(esmPos.pos, &mPos, sizeof(osg::Vec3f)); - std::memcpy(esmPos.rot, &mRot, sizeof(osg::Vec3f)); - world->getPlayer().setTeleported(true); - if (cell->isExterior()) - world->changeToExteriorCell(esmPos, true); - else - world->changeToInteriorCell(mCell, esmPos, true); - } - else - { - MWWorld::Ptr newObj = world->moveObject(obj, cell, mPos); - world->rotateObject(newObj, mRot); - } - } - - void SetEquipmentAction::apply(WorldView& worldView) const - { - MWWorld::Ptr actor = worldView.getObjectRegistry()->getPtr(mActor, false); - MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); - std::array usedSlots; - std::fill(usedSlots.begin(), usedSlots.end(), false); - - static constexpr int anySlot = -1; - auto tryEquipToSlot = [&actor, &store, &usedSlots, &worldView](int slot, const Item& item) -> bool - { - auto old_it = slot != anySlot ? store.getSlot(slot) : store.end(); - MWWorld::Ptr itemPtr; - if (std::holds_alternative(item)) - { - itemPtr = worldView.getObjectRegistry()->getPtr(std::get(item), false); - if (old_it != store.end() && *old_it == itemPtr) - return true; // already equipped - if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0 || - itemPtr.getContainerStore() != static_cast(&store)) - { - Log(Debug::Warning) << "Object" << idToString(std::get(item)) << " is not in inventory"; - return false; - } - } - else - { - const std::string& recordId = std::get(item); - if (old_it != store.end() && old_it->getCellRef().getRefId() == recordId) - return true; // already equipped - itemPtr = store.search(recordId); - if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0) - { - Log(Debug::Warning) << "There is no object with recordId='" << recordId << "' in inventory"; - return false; - } - } - - auto [allowedSlots, _] = itemPtr.getClass().getEquipmentSlots(itemPtr); - bool requestedSlotIsAllowed = std::find(allowedSlots.begin(), allowedSlots.end(), slot) != allowedSlots.end(); - if (!requestedSlotIsAllowed) - { - auto firstAllowed = std::find_if(allowedSlots.begin(), allowedSlots.end(), [&](int s) { return !usedSlots[s]; }); - if (firstAllowed == allowedSlots.end()) - { - Log(Debug::Warning) << "No suitable slot for " << ptrToString(itemPtr); - return false; - } - slot = *firstAllowed; - } - - // TODO: Refactor InventoryStore to accept Ptr and get rid of this linear search. - MWWorld::ContainerStoreIterator it = std::find(store.begin(), store.end(), itemPtr); - if (it == store.end()) // should never happen - throw std::logic_error("Item not found in container"); - - store.equip(slot, it, actor); - return requestedSlotIsAllowed; // return true if equipped to requested slot and false if slot was changed - }; - - for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) - { - auto old_it = store.getSlot(slot); - auto new_it = mEquipment.find(slot); - if (new_it == mEquipment.end()) - { - if (old_it != store.end()) - store.unequipSlot(slot, actor); - continue; - } - if (tryEquipToSlot(slot, new_it->second)) - usedSlots[slot] = true; - } - for (const auto& [slot, item] : mEquipment) - if (slot >= MWWorld::InventoryStore::Slots) - tryEquipToSlot(anySlot, item); - } - - void ActivateAction::apply(WorldView& worldView) const - { - MWWorld::Ptr object = worldView.getObjectRegistry()->getPtr(mObject, true); - if (object.isEmpty()) - throw std::runtime_error(std::string("Object not found: " + idToString(mObject))); - MWWorld::Ptr actor = worldView.getObjectRegistry()->getPtr(mActor, true); - if (actor.isEmpty()) - throw std::runtime_error(std::string("Actor not found: " + idToString(mActor))); - - MWBase::Environment::get().getLuaManager()->objectActivated(object, actor); - std::unique_ptr action = object.getClass().activate(object, actor); - action->execute(actor); - } - - std::string ActivateAction::toString() const - { - return std::string("ActivateAction object=") + idToString(mObject) + - std::string(" actor=") + idToString(mActor); - } - - void StatUpdateAction::apply(WorldView& worldView) const - { - LObject obj(mId, worldView.getObjectRegistry()); - LocalScripts* scripts = obj.ptr().getRefData().getLuaScripts(); - if (scripts) - scripts->applyStatsCache(); - } -} diff --git a/apps/openmw/mwlua/actions.hpp b/apps/openmw/mwlua/actions.hpp deleted file mode 100644 index 30211b6e53..0000000000 --- a/apps/openmw/mwlua/actions.hpp +++ /dev/null @@ -1,94 +0,0 @@ -#ifndef MWLUA_ACTIONS_H -#define MWLUA_ACTIONS_H - -#include - -#include "object.hpp" -#include "worldview.hpp" - -namespace LuaUtil -{ - class LuaState; -} - -namespace MWLua -{ - - // Some changes to the game world can not be done from the scripting thread (because it runs in parallel with OSG Cull), - // so we need to queue it and apply from the main thread. All such changes should be implemented as classes inherited - // from MWLua::Action. - - class Action - { - public: - Action(LuaUtil::LuaState* state); - virtual ~Action() {} - - void safeApply(WorldView&) const; - virtual void apply(WorldView&) const = 0; - virtual std::string toString() const = 0; - - private: - std::string mCallerTraceback; - }; - - class TeleportAction final : public Action - { - public: - TeleportAction(LuaUtil::LuaState* state, ObjectId object, std::string cell, const osg::Vec3f& pos, const osg::Vec3f& rot) - : Action(state), mObject(object), mCell(std::move(cell)), mPos(pos), mRot(rot) {} - - void apply(WorldView&) const override; - std::string toString() const override { return "TeleportAction"; } - - private: - ObjectId mObject; - std::string mCell; - osg::Vec3f mPos; - osg::Vec3f mRot; - }; - - class SetEquipmentAction final : public Action - { - public: - using Item = std::variant; // recordId or ObjectId - using Equipment = std::map; // slot to item - - SetEquipmentAction(LuaUtil::LuaState* state, ObjectId actor, Equipment equipment) - : Action(state), mActor(actor), mEquipment(std::move(equipment)) {} - - void apply(WorldView&) const override; - std::string toString() const override { return "SetEquipmentAction"; } - - private: - ObjectId mActor; - Equipment mEquipment; - }; - - class ActivateAction final : public Action - { - public: - ActivateAction(LuaUtil::LuaState* state, ObjectId object, ObjectId actor) - : Action(state), mObject(object), mActor(actor) {} - - void apply(WorldView&) const override; - std::string toString() const override; - - private: - ObjectId mObject; - ObjectId mActor; - }; - - class StatUpdateAction final : public Action - { - ObjectId mId; - public: - StatUpdateAction(LuaUtil::LuaState* state, ObjectId id) : Action(state), mId(id) {} - - void apply(WorldView& worldView) const override; - - std::string toString() const override { return "StatUpdateAction"; } - }; -} - -#endif // MWLUA_ACTIONS_H diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index e2037049d8..692b089bdb 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -501,4 +501,50 @@ namespace MWLua scripts->receiveEngineEvent(LocalScripts::OnActive()); } + LuaManager::Action::Action(LuaUtil::LuaState* state) + { + static const bool luaDebug = Settings::Manager::getBool("lua debug", "Lua"); + if (luaDebug) + mCallerTraceback = state->debugTraceback(); + } + + void LuaManager::Action::safeApply(WorldView& w) const + { + try + { + apply(w); + } + catch (const std::exception& e) + { + Log(Debug::Error) << "Error in " << this->toString() << ": " << e.what(); + + if (mCallerTraceback.empty()) + Log(Debug::Error) << "Set 'lua_debug=true' in settings.cfg to enable action tracebacks"; + else + Log(Debug::Error) << "Caller " << mCallerTraceback; + } + } + + namespace + { + class FunctionAction final : public LuaManager::Action + { + public: + FunctionAction(LuaUtil::LuaState* state, std::function fn, std::string_view name) + : Action(state), mFn(std::move(fn)), mName(name) {} + + void apply(WorldView&) const override { mFn(); } + std::string toString() const override { return "FunctionAction " + mName; } + + private: + std::function mFn; + std::string mName; + }; + } + + void LuaManager::addAction(std::function action, std::string_view name) + { + mActionQueue.push_back(std::make_unique(&mLua, std::move(action), name)); + } + } diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index e6f5af78be..43f5d9c3b6 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -12,7 +12,6 @@ #include "../mwbase/luamanager.hpp" -#include "actions.hpp" #include "object.hpp" #include "eventqueue.hpp" #include "globalscripts.hpp" @@ -60,9 +59,28 @@ namespace MWLua // Used only in Lua bindings void addCustomLocalScript(const MWWorld::Ptr&, int scriptId); - void addAction(std::unique_ptr&& action) { mActionQueue.push_back(std::move(action)); } - void addTeleportPlayerAction(std::unique_ptr&& action) { mTeleportPlayerAction = std::move(action); } void addUIMessage(std::string_view message) { mUIMessages.emplace_back(message); } + + // Some changes to the game world can not be done from the scripting thread (because it runs in parallel with OSG Cull), + // so we need to queue it and apply from the main thread. All such changes should be implemented as classes inherited + // from MWLua::Action. + class Action + { + public: + Action(LuaUtil::LuaState* state); + virtual ~Action() {} + + void safeApply(WorldView&) const; + virtual void apply(WorldView&) const = 0; + virtual std::string toString() const = 0; + + private: + std::string mCallerTraceback; + }; + + void addAction(std::function action, std::string_view name = ""); + void addAction(std::unique_ptr&& action) { mActionQueue.push_back(std::move(action)); } + void addTeleportPlayerAction(std::unique_ptr&& action) { mTeleportPlayerAction = std::move(action); } // Saving void write(ESM::ESMWriter& writer, Loading::Listener& progress) override; @@ -149,7 +167,7 @@ namespace MWLua // Queued actions that should be done in main thread. Processed by applyQueuedChanges(). std::vector> mActionQueue; - std::unique_ptr mTeleportPlayerAction; + std::unique_ptr mTeleportPlayerAction; std::vector mUIMessages; LuaUtil::LuaStorage mGlobalStorage{mLua.sol()}; diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 08435583df..0bf3fc99c5 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -2,9 +2,12 @@ #include +#include "../mwworld/action.hpp" +#include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/inventorystore.hpp" +#include "../mwworld/player.hpp" #include "eventqueue.hpp" #include "luamanagerimp.hpp" @@ -31,6 +34,83 @@ namespace MWLua namespace { + class TeleportAction final : public LuaManager::Action + { + public: + TeleportAction(LuaUtil::LuaState* state, ObjectId object, std::string cell, const osg::Vec3f& pos, const osg::Vec3f& rot) + : Action(state), mObject(object), mCell(std::move(cell)), mPos(pos), mRot(rot) {} + + void apply(WorldView& worldView) const override + { + MWWorld::CellStore* cell = worldView.findCell(mCell, mPos); + if (!cell) + throw std::runtime_error(std::string("cell not found: '") + mCell + "'"); + + MWBase::World* world = MWBase::Environment::get().getWorld(); + MWWorld::Ptr obj = worldView.getObjectRegistry()->getPtr(mObject, false); + const MWWorld::Class& cls = obj.getClass(); + bool isPlayer = obj == world->getPlayerPtr(); + if (cls.isActor()) + cls.getCreatureStats(obj).land(isPlayer); + if (isPlayer) + { + ESM::Position esmPos; + static_assert(sizeof(esmPos) == sizeof(osg::Vec3f) * 2); + std::memcpy(esmPos.pos, &mPos, sizeof(osg::Vec3f)); + std::memcpy(esmPos.rot, &mRot, sizeof(osg::Vec3f)); + world->getPlayer().setTeleported(true); + if (cell->isExterior()) + world->changeToExteriorCell(esmPos, true); + else + world->changeToInteriorCell(mCell, esmPos, true); + } + else + { + MWWorld::Ptr newObj = world->moveObject(obj, cell, mPos); + world->rotateObject(newObj, mRot); + } + } + + std::string toString() const override { return "TeleportAction"; } + + private: + ObjectId mObject; + std::string mCell; + osg::Vec3f mPos; + osg::Vec3f mRot; + }; + + class ActivateAction final : public LuaManager::Action + { + public: + ActivateAction(LuaUtil::LuaState* state, ObjectId object, ObjectId actor) + : Action(state), mObject(object), mActor(actor) {} + + void apply(WorldView& worldView) const override + { + MWWorld::Ptr object = worldView.getObjectRegistry()->getPtr(mObject, true); + if (object.isEmpty()) + throw std::runtime_error(std::string("Object not found: " + idToString(mObject))); + MWWorld::Ptr actor = worldView.getObjectRegistry()->getPtr(mActor, true); + if (actor.isEmpty()) + throw std::runtime_error(std::string("Actor not found: " + idToString(mActor))); + + MWBase::Environment::get().getLuaManager()->objectActivated(object, actor); + std::unique_ptr action = object.getClass().activate(object, actor); + action->execute(actor); + } + + std::string toString() const override + { + return std::string("ActivateAction object=") + idToString(mObject) + + std::string(" actor=") + idToString(mActor); + } + + private: + ObjectId mObject; + ObjectId mActor; + }; + template using Cell = std::conditional_t, LCell, GCell>; diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp index 36d7cf6538..f81293513d 100644 --- a/apps/openmw/mwlua/stats.cpp +++ b/apps/openmw/mwlua/stats.cpp @@ -79,6 +79,26 @@ namespace namespace MWLua { + namespace + { + class StatUpdateAction final : public LuaManager::Action + { + ObjectId mId; + public: + StatUpdateAction(LuaUtil::LuaState* state, ObjectId id) : Action(state), mId(id) {} + + void apply(WorldView& worldView) const override + { + LObject obj(mId, worldView.getObjectRegistry()); + LocalScripts* scripts = obj.ptr().getRefData().getLuaScripts(); + if (scripts) + scripts->applyStatsCache(); + } + + std::string toString() const override { return "StatUpdateAction"; } + }; + } + class LevelStat { StatObject mObject; diff --git a/apps/openmw/mwlua/types/actor.cpp b/apps/openmw/mwlua/types/actor.cpp index 335ba517d5..98ff5148d5 100644 --- a/apps/openmw/mwlua/types/actor.cpp +++ b/apps/openmw/mwlua/types/actor.cpp @@ -6,7 +6,6 @@ #include #include -#include "../actions.hpp" #include "../luabindings.hpp" #include "../localscripts.hpp" #include "../luamanagerimp.hpp" @@ -14,6 +13,102 @@ namespace MWLua { + namespace + { + class SetEquipmentAction final : public LuaManager::Action + { + public: + using Item = std::variant; // recordId or ObjectId + using Equipment = std::map; // slot to item + + SetEquipmentAction(LuaUtil::LuaState* state, ObjectId actor, Equipment equipment) + : Action(state), mActor(actor), mEquipment(std::move(equipment)) {} + + void apply(WorldView& worldView) const override + { + MWWorld::Ptr actor = worldView.getObjectRegistry()->getPtr(mActor, false); + MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); + std::array usedSlots; + std::fill(usedSlots.begin(), usedSlots.end(), false); + + static constexpr int anySlot = -1; + auto tryEquipToSlot = [&actor, &store, &usedSlots, &worldView](int slot, const Item& item) -> bool + { + auto old_it = slot != anySlot ? store.getSlot(slot) : store.end(); + MWWorld::Ptr itemPtr; + if (std::holds_alternative(item)) + { + itemPtr = worldView.getObjectRegistry()->getPtr(std::get(item), false); + if (old_it != store.end() && *old_it == itemPtr) + return true; // already equipped + if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0 || + itemPtr.getContainerStore() != static_cast(&store)) + { + Log(Debug::Warning) << "Object" << idToString(std::get(item)) << " is not in inventory"; + return false; + } + } + else + { + const std::string& recordId = std::get(item); + if (old_it != store.end() && old_it->getCellRef().getRefId() == recordId) + return true; // already equipped + itemPtr = store.search(recordId); + if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0) + { + Log(Debug::Warning) << "There is no object with recordId='" << recordId << "' in inventory"; + return false; + } + } + + auto [allowedSlots, _] = itemPtr.getClass().getEquipmentSlots(itemPtr); + bool requestedSlotIsAllowed = std::find(allowedSlots.begin(), allowedSlots.end(), slot) != allowedSlots.end(); + if (!requestedSlotIsAllowed) + { + auto firstAllowed = std::find_if(allowedSlots.begin(), allowedSlots.end(), [&](int s) { return !usedSlots[s]; }); + if (firstAllowed == allowedSlots.end()) + { + Log(Debug::Warning) << "No suitable slot for " << ptrToString(itemPtr); + return false; + } + slot = *firstAllowed; + } + + // TODO: Refactor InventoryStore to accept Ptr and get rid of this linear search. + MWWorld::ContainerStoreIterator it = std::find(store.begin(), store.end(), itemPtr); + if (it == store.end()) // should never happen + throw std::logic_error("Item not found in container"); + + store.equip(slot, it, actor); + return requestedSlotIsAllowed; // return true if equipped to requested slot and false if slot was changed + }; + + for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) + { + auto old_it = store.getSlot(slot); + auto new_it = mEquipment.find(slot); + if (new_it == mEquipment.end()) + { + if (old_it != store.end()) + store.unequipSlot(slot, actor); + continue; + } + if (tryEquipToSlot(slot, new_it->second)) + usedSlots[slot] = true; + } + for (const auto& [slot, item] : mEquipment) + if (slot >= MWWorld::InventoryStore::Slots) + tryEquipToSlot(anySlot, item); + } + + std::string toString() const override { return "SetEquipmentAction"; } + + private: + ObjectId mActor; + Equipment mEquipment; + }; + } + using SelfObject = LocalScripts::SelfObject; void addActorBindings(sol::table actor, const Context& context) diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index fdad57b13d..f82dd3db00 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -10,14 +10,13 @@ #include #include "context.hpp" -#include "actions.hpp" #include "luamanagerimp.hpp" namespace MWLua { namespace { - class UiAction final : public Action + class UiAction final : public LuaManager::Action { public: enum Type @@ -81,7 +80,7 @@ namespace MWLua std::shared_ptr mElement; }; - class InsertLayerAction final : public Action + class InsertLayerAction final : public LuaManager::Action { public: InsertLayerAction(std::string_view name, size_t index, From 51845e95536bdb29c86569c3c6481c06446215d4 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 18 Mar 2022 00:27:16 +0100 Subject: [PATCH 2/2] Rendering raycasts in Lua --- apps/openmw/mwbase/world.hpp | 4 ++++ apps/openmw/mwlua/luabindings.cpp | 2 +- apps/openmw/mwlua/luamanagerimp.cpp | 2 ++ apps/openmw/mwlua/luamanagerimp.hpp | 3 +++ apps/openmw/mwlua/nearbybindings.cpp | 22 ++++++++++++++++++++++ apps/openmw/mwphysics/raycasting.hpp | 11 ++++++----- apps/openmw/mwworld/worldimp.cpp | 19 +++++++++++++++++++ apps/openmw/mwworld/worldimp.hpp | 3 +++ files/lua_api/openmw/nearby.lua | 17 +++++++++++++++++ 9 files changed, 77 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 625758d640..f90794fc52 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -57,6 +57,7 @@ namespace ESM namespace MWPhysics { + class RayCastingResult; class RayCastingInterface; } @@ -331,6 +332,9 @@ namespace MWBase virtual bool castRay(const osg::Vec3f& from, const osg::Vec3f& to, int mask, const MWWorld::ConstPtr& ignore) = 0; + virtual bool castRenderingRay(MWPhysics::RayCastingResult& res, const osg::Vec3f& from, const osg::Vec3f& to, + bool ignorePlayer, bool ignoreActors) = 0; + virtual void setActorCollisionMode(const MWWorld::Ptr& ptr, bool internal, bool external) = 0; virtual bool isActorCollisionEnabled(const MWWorld::Ptr& ptr) = 0; diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index c88045ae84..3c2023ff5f 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -41,7 +41,7 @@ namespace MWLua { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); - api["API_REVISION"] = 20; + api["API_REVISION"] = 21; api["quit"] = [lua]() { Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback(); diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 692b089bdb..aa2d5baa6f 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -226,6 +226,7 @@ namespace MWLua return; // The game is not started yet. // We apply input events in `synchronizedUpdate` rather than in `update` in order to reduce input latency. + mProcessingInputEvents = true; PlayerScripts* playerScripts = dynamic_cast(mPlayer.getRefData().getLuaScripts()); if (playerScripts && !MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu)) { @@ -235,6 +236,7 @@ namespace MWLua mInputEvents.clear(); if (playerScripts && !mWorldView.isPaused()) playerScripts->inputUpdate(MWBase::Environment::get().getFrameDuration()); + mProcessingInputEvents = false; MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager(); for (const std::string& message : mUIMessages) diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 43f5d9c3b6..fd9ec8b172 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -111,12 +111,15 @@ namespace MWLua LuaUi::ResourceManager* uiResourceManager() { return &mUiResourceManager; } + bool isProcessingInputEvents() const { return mProcessingInputEvents; } + private: void initConfiguration(); LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScriptCfg::Flags); bool mInitialized = false; bool mGlobalScriptsStarted = false; + bool mProcessingInputEvents = false; LuaUtil::ScriptsConfiguration mConfiguration; LuaUtil::LuaState mLua; LuaUi::ResourceManager mUiResourceManager; diff --git a/apps/openmw/mwlua/nearbybindings.cpp b/apps/openmw/mwlua/nearbybindings.cpp index 05bc52c5cc..6ce78e569f 100644 --- a/apps/openmw/mwlua/nearbybindings.cpp +++ b/apps/openmw/mwlua/nearbybindings.cpp @@ -6,6 +6,7 @@ #include "../mwbase/world.hpp" #include "../mwphysics/raycasting.hpp" +#include "luamanagerimp.hpp" #include "worldview.hpp" namespace sol @@ -91,6 +92,27 @@ namespace MWLua // and use this callback from the main thread at the beginning of the next frame processing. rayCasting->asyncCastRay(callback, from, to, ignore, std::vector(), collisionType); };*/ + api["castRenderingRay"] = [manager=context.mLuaManager](const osg::Vec3f& from, const osg::Vec3f& to) + { + if (!manager->isProcessingInputEvents()) + { + throw std::logic_error("castRenderingRay can be used only in player scripts during processing of input events; " + "use asyncCastRenderingRay instead."); + } + MWPhysics::RayCastingResult res; + MWBase::Environment::get().getWorld()->castRenderingRay(res, from, to, false, false); + return res; + }; + api["asyncCastRenderingRay"] = + [manager=context.mLuaManager](const LuaUtil::Callback& callback, const osg::Vec3f& from, const osg::Vec3f& to) + { + manager->addAction([manager, callback, from, to] + { + MWPhysics::RayCastingResult res; + MWBase::Environment::get().getWorld()->castRenderingRay(res, from, to, false, false); + manager->queueCallback(callback, sol::make_object(callback.mFunc.lua_state(), res)); + }); + }; api["activators"] = LObjectList{worldView->getActivatorsInScene()}; api["actors"] = LObjectList{worldView->getActorsInScene()}; diff --git a/apps/openmw/mwphysics/raycasting.hpp b/apps/openmw/mwphysics/raycasting.hpp index d00f23e2c4..848f17a01a 100644 --- a/apps/openmw/mwphysics/raycasting.hpp +++ b/apps/openmw/mwphysics/raycasting.hpp @@ -9,12 +9,13 @@ namespace MWPhysics { - struct RayCastingResult + class RayCastingResult { - bool mHit; - osg::Vec3f mHitPos; - osg::Vec3f mHitNormal; - MWWorld::Ptr mHitObject; + public: + bool mHit; + osg::Vec3f mHitPos; + osg::Vec3f mHitNormal; + MWWorld::Ptr mHitObject; }; class RayCastingInterface diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 7f652d9520..4d3f4aa7ec 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2042,6 +2042,25 @@ namespace MWWorld return facedObject; } + bool World::castRenderingRay(MWPhysics::RayCastingResult& res, const osg::Vec3f& from, const osg::Vec3f& to, + bool ignorePlayer, bool ignoreActors) + { + MWRender::RenderingManager::RayResult rayRes = mRendering->castRay(from, to, ignorePlayer, ignoreActors); + res.mHit = rayRes.mHit; + res.mHitPos = rayRes.mHitPointWorld; + res.mHitNormal = rayRes.mHitNormalWorld; + res.mHitObject = rayRes.mHitObject; + if (res.mHitObject.isEmpty() && rayRes.mHitRefnum.isSet()) + { + for (CellStore* cellstore : mWorldScene->getActiveCells()) + { + res.mHitObject = cellstore->searchViaRefNum(rayRes.mHitRefnum); + if (!res.mHitObject.isEmpty()) break; + } + } + return res.mHit; + } + bool World::isCellExterior() const { const CellStore *currentCell = mWorldScene->getCurrentCell(); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 399b7232de..8cf83eb3aa 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -421,6 +421,9 @@ namespace MWWorld bool castRay(const osg::Vec3f& from, const osg::Vec3f& to, int mask, const MWWorld::ConstPtr& ignore) override; + bool castRenderingRay(MWPhysics::RayCastingResult& res, const osg::Vec3f& from, const osg::Vec3f& to, + bool ignorePlayer, bool ignoreActors) override; + void setActorCollisionMode(const Ptr& ptr, bool internal, bool external) override; bool isActorCollisionEnabled(const Ptr& ptr) override; diff --git a/files/lua_api/openmw/nearby.lua b/files/lua_api/openmw/nearby.lua index 3ba3d7e6fb..ba27269700 100644 --- a/files/lua_api/openmw/nearby.lua +++ b/files/lua_api/openmw/nearby.lua @@ -68,5 +68,22 @@ -- radius = 10, -- }) +--- +-- Cast ray from one point to another and find the first visual intersection with anything in the scene. +-- As opposite to `castRay` can find an intersection with an object without collisions. +-- In order to avoid threading issues can be used only in player scripts only in `onInputUpdate` or +-- in engine handlers for user input. In other cases use `asyncCastRenderingRay` instead. +-- @function [parent=#nearby] castRenderingRay +-- @param openmw.util#Vector3 from Start point of the ray. +-- @param openmw.util#Vector3 to End point of the ray. +-- @return #RayCastingResult + +--- +-- Asynchronously cast ray from one point to another and find the first visual intersection with anything in the scene. +-- @function [parent=#nearby] asyncCastRenderingRay +-- @param openmw.async#Callback callback The callback to pass the result to (should accept a single argument @{openmw.nearby#RayCastingResult}). +-- @param openmw.util#Vector3 from Start point of the ray. +-- @param openmw.util#Vector3 to End point of the ray. + return nil