1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-06-19 06:41:36 +00:00

Reorganize delayed Lua actions

This commit is contained in:
Petr Mikheev 2022-04-11 01:04:55 +02:00
parent fd6899e91d
commit a65f8ebbc6
9 changed files with 267 additions and 283 deletions

View file

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

View file

@ -1,180 +0,0 @@
#include "actions.hpp"
#include "localscripts.hpp"
#include <cstring>
#include <components/debug/debuglog.hpp>
#include <components/lua/luastate.hpp>
#include <components/settings/settings.hpp>
#include <apps/openmw/mwbase/luamanager.hpp>
#include <apps/openmw/mwworld/action.hpp>
#include <apps/openmw/mwworld/cellstore.hpp>
#include <apps/openmw/mwworld/class.hpp>
#include <apps/openmw/mwworld/inventorystore.hpp>
#include <apps/openmw/mwworld/player.hpp>
namespace MWLua
{
Action::Action(LuaUtil::LuaState* state)
{
static const bool luaDebug = Settings::Manager::getBool("lua debug", "Lua");
if (luaDebug)
mCallerTraceback = state->debugTraceback();
}
void Action::safeApply(WorldView& w) const
{
try
{
apply(w);
}
catch (const std::exception& e)
{
Log(Debug::Error) << "Error in " << this->toString() << ": " << e.what();
if (mCallerTraceback.empty())
Log(Debug::Error) << "Set 'lua_debug=true' in settings.cfg to enable action tracebacks";
else
Log(Debug::Error) << "Caller " << mCallerTraceback;
}
}
void TeleportAction::apply(WorldView& worldView) const
{
MWWorld::CellStore* cell = worldView.findCell(mCell, mPos);
if (!cell)
throw std::runtime_error(std::string("cell not found: '") + mCell + "'");
MWBase::World* world = MWBase::Environment::get().getWorld();
MWWorld::Ptr obj = worldView.getObjectRegistry()->getPtr(mObject, false);
const MWWorld::Class& cls = obj.getClass();
bool isPlayer = obj == world->getPlayerPtr();
if (cls.isActor())
cls.getCreatureStats(obj).land(isPlayer);
if (isPlayer)
{
ESM::Position esmPos;
static_assert(sizeof(esmPos) == sizeof(osg::Vec3f) * 2);
std::memcpy(esmPos.pos, &mPos, sizeof(osg::Vec3f));
std::memcpy(esmPos.rot, &mRot, sizeof(osg::Vec3f));
world->getPlayer().setTeleported(true);
if (cell->isExterior())
world->changeToExteriorCell(esmPos, true);
else
world->changeToInteriorCell(mCell, esmPos, true);
}
else
{
MWWorld::Ptr newObj = world->moveObject(obj, cell, mPos);
world->rotateObject(newObj, mRot);
}
}
void SetEquipmentAction::apply(WorldView& worldView) const
{
MWWorld::Ptr actor = worldView.getObjectRegistry()->getPtr(mActor, false);
MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor);
std::array<bool, MWWorld::InventoryStore::Slots> usedSlots;
std::fill(usedSlots.begin(), usedSlots.end(), false);
static constexpr int anySlot = -1;
auto tryEquipToSlot = [&actor, &store, &usedSlots, &worldView](int slot, const Item& item) -> bool
{
auto old_it = slot != anySlot ? store.getSlot(slot) : store.end();
MWWorld::Ptr itemPtr;
if (std::holds_alternative<ObjectId>(item))
{
itemPtr = worldView.getObjectRegistry()->getPtr(std::get<ObjectId>(item), false);
if (old_it != store.end() && *old_it == itemPtr)
return true; // already equipped
if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0 ||
itemPtr.getContainerStore() != static_cast<const MWWorld::ContainerStore*>(&store))
{
Log(Debug::Warning) << "Object" << idToString(std::get<ObjectId>(item)) << " is not in inventory";
return false;
}
}
else
{
const std::string& recordId = std::get<std::string>(item);
if (old_it != store.end() && old_it->getCellRef().getRefId() == recordId)
return true; // already equipped
itemPtr = store.search(recordId);
if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0)
{
Log(Debug::Warning) << "There is no object with recordId='" << recordId << "' in inventory";
return false;
}
}
auto [allowedSlots, _] = itemPtr.getClass().getEquipmentSlots(itemPtr);
bool requestedSlotIsAllowed = std::find(allowedSlots.begin(), allowedSlots.end(), slot) != allowedSlots.end();
if (!requestedSlotIsAllowed)
{
auto firstAllowed = std::find_if(allowedSlots.begin(), allowedSlots.end(), [&](int s) { return !usedSlots[s]; });
if (firstAllowed == allowedSlots.end())
{
Log(Debug::Warning) << "No suitable slot for " << ptrToString(itemPtr);
return false;
}
slot = *firstAllowed;
}
// TODO: Refactor InventoryStore to accept Ptr and get rid of this linear search.
MWWorld::ContainerStoreIterator it = std::find(store.begin(), store.end(), itemPtr);
if (it == store.end()) // should never happen
throw std::logic_error("Item not found in container");
store.equip(slot, it, actor);
return requestedSlotIsAllowed; // return true if equipped to requested slot and false if slot was changed
};
for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
{
auto old_it = store.getSlot(slot);
auto new_it = mEquipment.find(slot);
if (new_it == mEquipment.end())
{
if (old_it != store.end())
store.unequipSlot(slot, actor);
continue;
}
if (tryEquipToSlot(slot, new_it->second))
usedSlots[slot] = true;
}
for (const auto& [slot, item] : mEquipment)
if (slot >= MWWorld::InventoryStore::Slots)
tryEquipToSlot(anySlot, item);
}
void ActivateAction::apply(WorldView& worldView) const
{
MWWorld::Ptr object = worldView.getObjectRegistry()->getPtr(mObject, true);
if (object.isEmpty())
throw std::runtime_error(std::string("Object not found: " + idToString(mObject)));
MWWorld::Ptr actor = worldView.getObjectRegistry()->getPtr(mActor, true);
if (actor.isEmpty())
throw std::runtime_error(std::string("Actor not found: " + idToString(mActor)));
MWBase::Environment::get().getLuaManager()->objectActivated(object, actor);
std::unique_ptr<MWWorld::Action> action = object.getClass().activate(object, actor);
action->execute(actor);
}
std::string ActivateAction::toString() const
{
return std::string("ActivateAction object=") + idToString(mObject) +
std::string(" actor=") + idToString(mActor);
}
void StatUpdateAction::apply(WorldView& worldView) const
{
LObject obj(mId, worldView.getObjectRegistry());
LocalScripts* scripts = obj.ptr().getRefData().getLuaScripts();
if (scripts)
scripts->applyStatsCache();
}
}

View file

@ -1,94 +0,0 @@
#ifndef MWLUA_ACTIONS_H
#define MWLUA_ACTIONS_H
#include <variant>
#include "object.hpp"
#include "worldview.hpp"
namespace LuaUtil
{
class LuaState;
}
namespace MWLua
{
// Some changes to the game world can not be done from the scripting thread (because it runs in parallel with OSG Cull),
// so we need to queue it and apply from the main thread. All such changes should be implemented as classes inherited
// from MWLua::Action.
class Action
{
public:
Action(LuaUtil::LuaState* state);
virtual ~Action() {}
void safeApply(WorldView&) const;
virtual void apply(WorldView&) const = 0;
virtual std::string toString() const = 0;
private:
std::string mCallerTraceback;
};
class TeleportAction final : public Action
{
public:
TeleportAction(LuaUtil::LuaState* state, ObjectId object, std::string cell, const osg::Vec3f& pos, const osg::Vec3f& rot)
: Action(state), mObject(object), mCell(std::move(cell)), mPos(pos), mRot(rot) {}
void apply(WorldView&) const override;
std::string toString() const override { return "TeleportAction"; }
private:
ObjectId mObject;
std::string mCell;
osg::Vec3f mPos;
osg::Vec3f mRot;
};
class SetEquipmentAction final : public Action
{
public:
using Item = std::variant<std::string, ObjectId>; // recordId or ObjectId
using Equipment = std::map<int, Item>; // slot to item
SetEquipmentAction(LuaUtil::LuaState* state, ObjectId actor, Equipment equipment)
: Action(state), mActor(actor), mEquipment(std::move(equipment)) {}
void apply(WorldView&) const override;
std::string toString() const override { return "SetEquipmentAction"; }
private:
ObjectId mActor;
Equipment mEquipment;
};
class ActivateAction final : public Action
{
public:
ActivateAction(LuaUtil::LuaState* state, ObjectId object, ObjectId actor)
: Action(state), mObject(object), mActor(actor) {}
void apply(WorldView&) const override;
std::string toString() const override;
private:
ObjectId mObject;
ObjectId mActor;
};
class StatUpdateAction final : public Action
{
ObjectId mId;
public:
StatUpdateAction(LuaUtil::LuaState* state, ObjectId id) : Action(state), mId(id) {}
void apply(WorldView& worldView) const override;
std::string toString() const override { return "StatUpdateAction"; }
};
}
#endif // MWLUA_ACTIONS_H

View file

@ -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));
}
} }

View file

@ -12,7 +12,6 @@
#include "../mwbase/luamanager.hpp" #include "../mwbase/luamanager.hpp"
#include "actions.hpp"
#include "object.hpp" #include "object.hpp"
#include "eventqueue.hpp" #include "eventqueue.hpp"
#include "globalscripts.hpp" #include "globalscripts.hpp"
@ -60,9 +59,28 @@ namespace MWLua
// Used only in Lua bindings // Used only in Lua bindings
void addCustomLocalScript(const MWWorld::Ptr&, int scriptId); void addCustomLocalScript(const MWWorld::Ptr&, int scriptId);
void addAction(std::unique_ptr<Action>&& action) { mActionQueue.push_back(std::move(action)); }
void addTeleportPlayerAction(std::unique_ptr<TeleportAction>&& action) { mTeleportPlayerAction = std::move(action); }
void addUIMessage(std::string_view message) { mUIMessages.emplace_back(message); } void addUIMessage(std::string_view message) { mUIMessages.emplace_back(message); }
// Some changes to the game world can not be done from the scripting thread (because it runs in parallel with OSG Cull),
// so we need to queue it and apply from the main thread. All such changes should be implemented as classes inherited
// from MWLua::Action.
class Action
{
public:
Action(LuaUtil::LuaState* state);
virtual ~Action() {}
void safeApply(WorldView&) const;
virtual void apply(WorldView&) const = 0;
virtual std::string toString() const = 0;
private:
std::string mCallerTraceback;
};
void addAction(std::function<void()> action, std::string_view name = "");
void addAction(std::unique_ptr<Action>&& action) { mActionQueue.push_back(std::move(action)); }
void addTeleportPlayerAction(std::unique_ptr<Action>&& action) { mTeleportPlayerAction = std::move(action); }
// Saving // Saving
void write(ESM::ESMWriter& writer, Loading::Listener& progress) override; void write(ESM::ESMWriter& writer, Loading::Listener& progress) override;
@ -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()};

View file

@ -2,9 +2,12 @@
#include <components/lua/luastate.hpp> #include <components/lua/luastate.hpp>
#include "../mwworld/action.hpp"
#include "../mwworld/cellstore.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/containerstore.hpp" #include "../mwworld/containerstore.hpp"
#include "../mwworld/inventorystore.hpp" #include "../mwworld/inventorystore.hpp"
#include "../mwworld/player.hpp"
#include "eventqueue.hpp" #include "eventqueue.hpp"
#include "luamanagerimp.hpp" #include "luamanagerimp.hpp"
@ -31,6 +34,83 @@ namespace MWLua
namespace { namespace {
class TeleportAction final : public LuaManager::Action
{
public:
TeleportAction(LuaUtil::LuaState* state, ObjectId object, std::string cell, const osg::Vec3f& pos, const osg::Vec3f& rot)
: Action(state), mObject(object), mCell(std::move(cell)), mPos(pos), mRot(rot) {}
void apply(WorldView& worldView) const override
{
MWWorld::CellStore* cell = worldView.findCell(mCell, mPos);
if (!cell)
throw std::runtime_error(std::string("cell not found: '") + mCell + "'");
MWBase::World* world = MWBase::Environment::get().getWorld();
MWWorld::Ptr obj = worldView.getObjectRegistry()->getPtr(mObject, false);
const MWWorld::Class& cls = obj.getClass();
bool isPlayer = obj == world->getPlayerPtr();
if (cls.isActor())
cls.getCreatureStats(obj).land(isPlayer);
if (isPlayer)
{
ESM::Position esmPos;
static_assert(sizeof(esmPos) == sizeof(osg::Vec3f) * 2);
std::memcpy(esmPos.pos, &mPos, sizeof(osg::Vec3f));
std::memcpy(esmPos.rot, &mRot, sizeof(osg::Vec3f));
world->getPlayer().setTeleported(true);
if (cell->isExterior())
world->changeToExteriorCell(esmPos, true);
else
world->changeToInteriorCell(mCell, esmPos, true);
}
else
{
MWWorld::Ptr newObj = world->moveObject(obj, cell, mPos);
world->rotateObject(newObj, mRot);
}
}
std::string toString() const override { return "TeleportAction"; }
private:
ObjectId mObject;
std::string mCell;
osg::Vec3f mPos;
osg::Vec3f mRot;
};
class ActivateAction final : public LuaManager::Action
{
public:
ActivateAction(LuaUtil::LuaState* state, ObjectId object, ObjectId actor)
: Action(state), mObject(object), mActor(actor) {}
void apply(WorldView& worldView) const override
{
MWWorld::Ptr object = worldView.getObjectRegistry()->getPtr(mObject, true);
if (object.isEmpty())
throw std::runtime_error(std::string("Object not found: " + idToString(mObject)));
MWWorld::Ptr actor = worldView.getObjectRegistry()->getPtr(mActor, true);
if (actor.isEmpty())
throw std::runtime_error(std::string("Actor not found: " + idToString(mActor)));
MWBase::Environment::get().getLuaManager()->objectActivated(object, actor);
std::unique_ptr<MWWorld::Action> action = object.getClass().activate(object, actor);
action->execute(actor);
}
std::string toString() const override
{
return std::string("ActivateAction object=") + idToString(mObject) +
std::string(" actor=") + idToString(mActor);
}
private:
ObjectId mObject;
ObjectId mActor;
};
template <typename ObjT> template <typename ObjT>
using Cell = std::conditional_t<std::is_same_v<ObjT, LObject>, LCell, GCell>; using Cell = std::conditional_t<std::is_same_v<ObjT, LObject>, LCell, GCell>;

View file

@ -79,6 +79,26 @@ namespace
namespace MWLua namespace MWLua
{ {
namespace
{
class StatUpdateAction final : public LuaManager::Action
{
ObjectId mId;
public:
StatUpdateAction(LuaUtil::LuaState* state, ObjectId id) : Action(state), mId(id) {}
void apply(WorldView& worldView) const override
{
LObject obj(mId, worldView.getObjectRegistry());
LocalScripts* scripts = obj.ptr().getRefData().getLuaScripts();
if (scripts)
scripts->applyStatsCache();
}
std::string toString() const override { return "StatUpdateAction"; }
};
}
class LevelStat class LevelStat
{ {
StatObject mObject; StatObject mObject;

View file

@ -6,7 +6,6 @@
#include <apps/openmw/mwworld/inventorystore.hpp> #include <apps/openmw/mwworld/inventorystore.hpp>
#include <apps/openmw/mwworld/class.hpp> #include <apps/openmw/mwworld/class.hpp>
#include "../actions.hpp"
#include "../luabindings.hpp" #include "../luabindings.hpp"
#include "../localscripts.hpp" #include "../localscripts.hpp"
#include "../luamanagerimp.hpp" #include "../luamanagerimp.hpp"
@ -14,6 +13,102 @@
namespace MWLua namespace MWLua
{ {
namespace
{
class SetEquipmentAction final : public LuaManager::Action
{
public:
using Item = std::variant<std::string, ObjectId>; // recordId or ObjectId
using Equipment = std::map<int, Item>; // slot to item
SetEquipmentAction(LuaUtil::LuaState* state, ObjectId actor, Equipment equipment)
: Action(state), mActor(actor), mEquipment(std::move(equipment)) {}
void apply(WorldView& worldView) const override
{
MWWorld::Ptr actor = worldView.getObjectRegistry()->getPtr(mActor, false);
MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor);
std::array<bool, MWWorld::InventoryStore::Slots> usedSlots;
std::fill(usedSlots.begin(), usedSlots.end(), false);
static constexpr int anySlot = -1;
auto tryEquipToSlot = [&actor, &store, &usedSlots, &worldView](int slot, const Item& item) -> bool
{
auto old_it = slot != anySlot ? store.getSlot(slot) : store.end();
MWWorld::Ptr itemPtr;
if (std::holds_alternative<ObjectId>(item))
{
itemPtr = worldView.getObjectRegistry()->getPtr(std::get<ObjectId>(item), false);
if (old_it != store.end() && *old_it == itemPtr)
return true; // already equipped
if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0 ||
itemPtr.getContainerStore() != static_cast<const MWWorld::ContainerStore*>(&store))
{
Log(Debug::Warning) << "Object" << idToString(std::get<ObjectId>(item)) << " is not in inventory";
return false;
}
}
else
{
const std::string& recordId = std::get<std::string>(item);
if (old_it != store.end() && old_it->getCellRef().getRefId() == recordId)
return true; // already equipped
itemPtr = store.search(recordId);
if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0)
{
Log(Debug::Warning) << "There is no object with recordId='" << recordId << "' in inventory";
return false;
}
}
auto [allowedSlots, _] = itemPtr.getClass().getEquipmentSlots(itemPtr);
bool requestedSlotIsAllowed = std::find(allowedSlots.begin(), allowedSlots.end(), slot) != allowedSlots.end();
if (!requestedSlotIsAllowed)
{
auto firstAllowed = std::find_if(allowedSlots.begin(), allowedSlots.end(), [&](int s) { return !usedSlots[s]; });
if (firstAllowed == allowedSlots.end())
{
Log(Debug::Warning) << "No suitable slot for " << ptrToString(itemPtr);
return false;
}
slot = *firstAllowed;
}
// TODO: Refactor InventoryStore to accept Ptr and get rid of this linear search.
MWWorld::ContainerStoreIterator it = std::find(store.begin(), store.end(), itemPtr);
if (it == store.end()) // should never happen
throw std::logic_error("Item not found in container");
store.equip(slot, it, actor);
return requestedSlotIsAllowed; // return true if equipped to requested slot and false if slot was changed
};
for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
{
auto old_it = store.getSlot(slot);
auto new_it = mEquipment.find(slot);
if (new_it == mEquipment.end())
{
if (old_it != store.end())
store.unequipSlot(slot, actor);
continue;
}
if (tryEquipToSlot(slot, new_it->second))
usedSlots[slot] = true;
}
for (const auto& [slot, item] : mEquipment)
if (slot >= MWWorld::InventoryStore::Slots)
tryEquipToSlot(anySlot, item);
}
std::string toString() const override { return "SetEquipmentAction"; }
private:
ObjectId mActor;
Equipment mEquipment;
};
}
using SelfObject = LocalScripts::SelfObject; using SelfObject = LocalScripts::SelfObject;
void addActorBindings(sol::table actor, const Context& context) void addActorBindings(sol::table actor, const Context& context)

View file

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