1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-02-10 12:39:53 +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
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

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());
}
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 "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;
@ -149,7 +167,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()};

View File

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

View File

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

View File

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

View File

@ -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,