mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-16 17:59:56 +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
|
||||
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
|
||||
|
|
|
@ -58,6 +58,7 @@ namespace ESM
|
|||
|
||||
namespace MWPhysics
|
||||
{
|
||||
class RayCastingResult;
|
||||
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 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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
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();
|
||||
|
|
|
@ -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<PlayerScripts*>(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)
|
||||
|
@ -501,4 +503,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<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 "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>&& 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); }
|
||||
|
||||
// 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
|
||||
void write(ESM::ESMWriter& writer, Loading::Listener& progress) override;
|
||||
|
@ -93,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;
|
||||
|
@ -149,7 +170,7 @@ namespace MWLua
|
|||
|
||||
// Queued actions that should be done in main thread. Processed by applyQueuedChanges().
|
||||
std::vector<std::unique_ptr<Action>> mActionQueue;
|
||||
std::unique_ptr<TeleportAction> mTeleportPlayerAction;
|
||||
std::unique_ptr<Action> mTeleportPlayerAction;
|
||||
std::vector<std::string> mUIMessages;
|
||||
|
||||
LuaUtil::LuaStorage mGlobalStorage{mLua.sol()};
|
||||
|
|
|
@ -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<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["actors"] = LObjectList{worldView->getActorsInScene()};
|
||||
|
|
|
@ -2,9 +2,12 @@
|
|||
|
||||
#include <components/lua/luastate.hpp>
|
||||
|
||||
#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<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>
|
||||
using Cell = std::conditional_t<std::is_same_v<ObjT, LObject>, LCell, GCell>;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
#include <apps/openmw/mwworld/inventorystore.hpp>
|
||||
#include <apps/openmw/mwworld/class.hpp>
|
||||
|
||||
#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<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;
|
||||
|
||||
void addActorBindings(sol::table actor, const Context& context)
|
||||
|
|
|
@ -10,14 +10,13 @@
|
|||
#include <components/misc/stringops.hpp>
|
||||
|
||||
#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<LuaUi::Element> mElement;
|
||||
};
|
||||
|
||||
class InsertLayerAction final : public Action
|
||||
class InsertLayerAction final : public LuaManager::Action
|
||||
{
|
||||
public:
|
||||
InsertLayerAction(std::string_view name, size_t index,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in a new issue