mirror of
https://github.com/OpenMW/openmw.git
synced 2025-06-19 19:41:33 +00:00
Reorganize delayed Lua actions
This commit is contained in:
parent
fd6899e91d
commit
a65f8ebbc6
9 changed files with 267 additions and 283 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
|
||||||
|
|
|
@ -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
|
|
|
@ -501,4 +501,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,10 +59,29 @@ 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;
|
||||||
void saveLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScripts& data) override;
|
void saveLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScripts& data) override;
|
||||||
|
@ -149,7 +167,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()};
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue