1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-01-29 08:15:35 +00:00

Merge branch 'rendering_raycast' into 'master'

Rendering raycasts in Lua

See merge request OpenMW/openmw!1768
This commit is contained in:
uramer 2022-04-12 19:15:28 +00:00
commit 5aa8e475a4
16 changed files with 344 additions and 289 deletions

View file

@ -57,7 +57,7 @@ add_openmw_dir (mwscript
) )
add_openmw_dir (mwlua add_openmw_dir (mwlua
luamanagerimp actions object worldview userdataserializer eventqueue luamanagerimp object worldview userdataserializer eventqueue
luabindings localscripts playerscripts objectbindings cellbindings asyncbindings settingsbindings luabindings localscripts playerscripts objectbindings cellbindings asyncbindings settingsbindings
camerabindings uibindings inputbindings nearbybindings stats camerabindings uibindings inputbindings nearbybindings stats
types/types types/door types/actor types/container types/weapon types/npc types/types types/door types/actor types/container types/weapon types/npc

View file

@ -58,6 +58,7 @@ namespace ESM
namespace MWPhysics namespace MWPhysics
{ {
class RayCastingResult;
class RayCastingInterface; class RayCastingInterface;
} }
@ -332,6 +333,9 @@ namespace MWBase
virtual bool castRay(const osg::Vec3f& from, const osg::Vec3f& to, int mask, const MWWorld::ConstPtr& ignore) = 0; 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 void setActorCollisionMode(const MWWorld::Ptr& ptr, bool internal, bool external) = 0;
virtual bool isActorCollisionEnabled(const MWWorld::Ptr& ptr) = 0; virtual bool isActorCollisionEnabled(const MWWorld::Ptr& ptr) = 0;

View file

@ -1,180 +0,0 @@
#include "actions.hpp"
#include "localscripts.hpp"
#include <cstring>
#include <components/debug/debuglog.hpp>
#include <components/lua/luastate.hpp>
#include <components/settings/settings.hpp>
#include <apps/openmw/mwbase/luamanager.hpp>
#include <apps/openmw/mwworld/action.hpp>
#include <apps/openmw/mwworld/cellstore.hpp>
#include <apps/openmw/mwworld/class.hpp>
#include <apps/openmw/mwworld/inventorystore.hpp>
#include <apps/openmw/mwworld/player.hpp>
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<bool, MWWorld::InventoryStore::Slots> 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<ObjectId>(item))
{
itemPtr = worldView.getObjectRegistry()->getPtr(std::get<ObjectId>(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<const MWWorld::ContainerStore*>(&store))
{
Log(Debug::Warning) << "Object" << idToString(std::get<ObjectId>(item)) << " is not in inventory";
return false;
}
}
else
{
const std::string& recordId = std::get<std::string>(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<MWWorld::Action> 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();
}
}

View file

@ -1,94 +0,0 @@
#ifndef MWLUA_ACTIONS_H
#define MWLUA_ACTIONS_H
#include <variant>
#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<std::string, ObjectId>; // recordId or ObjectId
using Equipment = std::map<int, Item>; // 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

View file

@ -41,7 +41,7 @@ namespace MWLua
{ {
auto* lua = context.mLua; auto* lua = context.mLua;
sol::table api(lua->sol(), sol::create); sol::table api(lua->sol(), sol::create);
api["API_REVISION"] = 20; api["API_REVISION"] = 21;
api["quit"] = [lua]() api["quit"] = [lua]()
{ {
Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback(); Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback();

View file

@ -226,6 +226,7 @@ namespace MWLua
return; // The game is not started yet. return; // The game is not started yet.
// We apply input events in `synchronizedUpdate` rather than in `update` in order to reduce input latency. // We apply input events in `synchronizedUpdate` rather than in `update` in order to reduce input latency.
mProcessingInputEvents = true;
PlayerScripts* playerScripts = dynamic_cast<PlayerScripts*>(mPlayer.getRefData().getLuaScripts()); PlayerScripts* playerScripts = dynamic_cast<PlayerScripts*>(mPlayer.getRefData().getLuaScripts());
if (playerScripts && !MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu)) if (playerScripts && !MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu))
{ {
@ -235,6 +236,7 @@ namespace MWLua
mInputEvents.clear(); mInputEvents.clear();
if (playerScripts && !mWorldView.isPaused()) if (playerScripts && !mWorldView.isPaused())
playerScripts->inputUpdate(MWBase::Environment::get().getFrameDuration()); playerScripts->inputUpdate(MWBase::Environment::get().getFrameDuration());
mProcessingInputEvents = false;
MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager(); MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager();
for (const std::string& message : mUIMessages) for (const std::string& message : mUIMessages)
@ -501,4 +503,50 @@ namespace MWLua
scripts->receiveEngineEvent(LocalScripts::OnActive()); 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<void()> 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<void()> mFn;
std::string mName;
};
}
void LuaManager::addAction(std::function<void()> action, std::string_view name)
{
mActionQueue.push_back(std::make_unique<FunctionAction>(&mLua, std::move(action), name));
}
} }

View file

@ -12,7 +12,6 @@
#include "../mwbase/luamanager.hpp" #include "../mwbase/luamanager.hpp"
#include "actions.hpp"
#include "object.hpp" #include "object.hpp"
#include "eventqueue.hpp" #include "eventqueue.hpp"
#include "globalscripts.hpp" #include "globalscripts.hpp"
@ -60,9 +59,28 @@ namespace MWLua
// Used only in Lua bindings // Used only in Lua bindings
void addCustomLocalScript(const MWWorld::Ptr&, int scriptId); void addCustomLocalScript(const MWWorld::Ptr&, int scriptId);
void addAction(std::unique_ptr<Action>&& action) { mActionQueue.push_back(std::move(action)); }
void addTeleportPlayerAction(std::unique_ptr<TeleportAction>&& action) { mTeleportPlayerAction = std::move(action); }
void addUIMessage(std::string_view message) { mUIMessages.emplace_back(message); } 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<void()> action, std::string_view name = "");
void addAction(std::unique_ptr<Action>&& action) { mActionQueue.push_back(std::move(action)); }
void addTeleportPlayerAction(std::unique_ptr<Action>&& action) { mTeleportPlayerAction = std::move(action); }
// Saving // Saving
void write(ESM::ESMWriter& writer, Loading::Listener& progress) override; void write(ESM::ESMWriter& writer, Loading::Listener& progress) override;
@ -93,12 +111,15 @@ namespace MWLua
LuaUi::ResourceManager* uiResourceManager() { return &mUiResourceManager; } LuaUi::ResourceManager* uiResourceManager() { return &mUiResourceManager; }
bool isProcessingInputEvents() const { return mProcessingInputEvents; }
private: private:
void initConfiguration(); void initConfiguration();
LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScriptCfg::Flags); LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScriptCfg::Flags);
bool mInitialized = false; bool mInitialized = false;
bool mGlobalScriptsStarted = false; bool mGlobalScriptsStarted = false;
bool mProcessingInputEvents = false;
LuaUtil::ScriptsConfiguration mConfiguration; LuaUtil::ScriptsConfiguration mConfiguration;
LuaUtil::LuaState mLua; LuaUtil::LuaState mLua;
LuaUi::ResourceManager mUiResourceManager; LuaUi::ResourceManager mUiResourceManager;
@ -149,7 +170,7 @@ namespace MWLua
// Queued actions that should be done in main thread. Processed by applyQueuedChanges(). // Queued actions that should be done in main thread. Processed by applyQueuedChanges().
std::vector<std::unique_ptr<Action>> mActionQueue; std::vector<std::unique_ptr<Action>> mActionQueue;
std::unique_ptr<TeleportAction> mTeleportPlayerAction; std::unique_ptr<Action> mTeleportPlayerAction;
std::vector<std::string> mUIMessages; std::vector<std::string> mUIMessages;
LuaUtil::LuaStorage mGlobalStorage{mLua.sol()}; LuaUtil::LuaStorage mGlobalStorage{mLua.sol()};

View file

@ -6,6 +6,7 @@
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwphysics/raycasting.hpp" #include "../mwphysics/raycasting.hpp"
#include "luamanagerimp.hpp"
#include "worldview.hpp" #include "worldview.hpp"
namespace sol namespace sol
@ -91,6 +92,27 @@ namespace MWLua
// and use this callback from the main thread at the beginning of the next frame processing. // and use this callback from the main thread at the beginning of the next frame processing.
rayCasting->asyncCastRay(callback, from, to, ignore, std::vector<MWWorld::Ptr>(), collisionType); rayCasting->asyncCastRay(callback, from, to, ignore, std::vector<MWWorld::Ptr>(), 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["activators"] = LObjectList{worldView->getActivatorsInScene()};
api["actors"] = LObjectList{worldView->getActorsInScene()}; api["actors"] = LObjectList{worldView->getActorsInScene()};

View file

@ -2,9 +2,12 @@
#include <components/lua/luastate.hpp> #include <components/lua/luastate.hpp>
#include "../mwworld/action.hpp"
#include "../mwworld/cellstore.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/containerstore.hpp" #include "../mwworld/containerstore.hpp"
#include "../mwworld/inventorystore.hpp" #include "../mwworld/inventorystore.hpp"
#include "../mwworld/player.hpp"
#include "eventqueue.hpp" #include "eventqueue.hpp"
#include "luamanagerimp.hpp" #include "luamanagerimp.hpp"
@ -31,6 +34,83 @@ namespace MWLua
namespace { 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<MWWorld::Action> 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 <typename ObjT> template <typename ObjT>
using Cell = std::conditional_t<std::is_same_v<ObjT, LObject>, LCell, GCell>; using Cell = std::conditional_t<std::is_same_v<ObjT, LObject>, LCell, GCell>;

View file

@ -79,6 +79,26 @@ namespace
namespace MWLua 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 class LevelStat
{ {
StatObject mObject; StatObject mObject;

View file

@ -6,7 +6,6 @@
#include <apps/openmw/mwworld/inventorystore.hpp> #include <apps/openmw/mwworld/inventorystore.hpp>
#include <apps/openmw/mwworld/class.hpp> #include <apps/openmw/mwworld/class.hpp>
#include "../actions.hpp"
#include "../luabindings.hpp" #include "../luabindings.hpp"
#include "../localscripts.hpp" #include "../localscripts.hpp"
#include "../luamanagerimp.hpp" #include "../luamanagerimp.hpp"
@ -14,6 +13,102 @@
namespace MWLua namespace MWLua
{ {
namespace
{
class SetEquipmentAction final : public LuaManager::Action
{
public:
using Item = std::variant<std::string, ObjectId>; // recordId or ObjectId
using Equipment = std::map<int, Item>; // 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<bool, MWWorld::InventoryStore::Slots> 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<ObjectId>(item))
{
itemPtr = worldView.getObjectRegistry()->getPtr(std::get<ObjectId>(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<const MWWorld::ContainerStore*>(&store))
{
Log(Debug::Warning) << "Object" << idToString(std::get<ObjectId>(item)) << " is not in inventory";
return false;
}
}
else
{
const std::string& recordId = std::get<std::string>(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; using SelfObject = LocalScripts::SelfObject;
void addActorBindings(sol::table actor, const Context& context) void addActorBindings(sol::table actor, const Context& context)

View file

@ -10,14 +10,13 @@
#include <components/misc/stringops.hpp> #include <components/misc/stringops.hpp>
#include "context.hpp" #include "context.hpp"
#include "actions.hpp"
#include "luamanagerimp.hpp" #include "luamanagerimp.hpp"
namespace MWLua namespace MWLua
{ {
namespace namespace
{ {
class UiAction final : public Action class UiAction final : public LuaManager::Action
{ {
public: public:
enum Type enum Type
@ -81,7 +80,7 @@ namespace MWLua
std::shared_ptr<LuaUi::Element> mElement; std::shared_ptr<LuaUi::Element> mElement;
}; };
class InsertLayerAction final : public Action class InsertLayerAction final : public LuaManager::Action
{ {
public: public:
InsertLayerAction(std::string_view name, size_t index, InsertLayerAction(std::string_view name, size_t index,

View file

@ -9,12 +9,13 @@
namespace MWPhysics namespace MWPhysics
{ {
struct RayCastingResult class RayCastingResult
{ {
bool mHit; public:
osg::Vec3f mHitPos; bool mHit;
osg::Vec3f mHitNormal; osg::Vec3f mHitPos;
MWWorld::Ptr mHitObject; osg::Vec3f mHitNormal;
MWWorld::Ptr mHitObject;
}; };
class RayCastingInterface class RayCastingInterface

View file

@ -2042,6 +2042,25 @@ namespace MWWorld
return facedObject; 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 bool World::isCellExterior() const
{ {
const CellStore *currentCell = mWorldScene->getCurrentCell(); const CellStore *currentCell = mWorldScene->getCurrentCell();

View file

@ -421,6 +421,9 @@ namespace MWWorld
bool castRay(const osg::Vec3f& from, const osg::Vec3f& to, int mask, const MWWorld::ConstPtr& ignore) override; 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; void setActorCollisionMode(const Ptr& ptr, bool internal, bool external) override;
bool isActorCollisionEnabled(const Ptr& ptr) override; bool isActorCollisionEnabled(const Ptr& ptr) override;

View file

@ -68,5 +68,22 @@
-- radius = 10, -- 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 return nil