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:
commit
5aa8e475a4
16 changed files with 344 additions and 289 deletions
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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();
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()};
|
||||||
|
|
|
@ -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()};
|
||||||
|
|
|
@ -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>;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue