mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-30 16:15:33 +00:00
Lua scripts configuration in omwaddon
This commit is contained in:
parent
58fd560ce9
commit
a70d5831c5
26 changed files with 700 additions and 236 deletions
|
@ -53,7 +53,10 @@ void CSMDoc::WriteHeaderStage::perform (int stage, Messages& messages)
|
|||
mState.getWriter().setAuthor ("");
|
||||
mState.getWriter().setDescription ("");
|
||||
mState.getWriter().setRecordCount (0);
|
||||
mState.getWriter().setFormat (ESM::Header::CurrentFormat);
|
||||
|
||||
// ESM::Header::CurrentFormat is `1` but since new records are not yet used in opencs
|
||||
// we use the format `0` for compatibility with old versions.
|
||||
mState.getWriter().setFormat(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -6,7 +6,9 @@
|
|||
|
||||
void CSMWorld::MetaData::blank()
|
||||
{
|
||||
mFormat = ESM::Header::CurrentFormat;
|
||||
// ESM::Header::CurrentFormat is `1` but since new records are not yet used in opencs
|
||||
// we use the format `0` for compatibility with old versions.
|
||||
mFormat = 0;
|
||||
mAuthor.clear();
|
||||
mDescription.clear();
|
||||
}
|
||||
|
|
|
@ -16,8 +16,7 @@ namespace MWLua
|
|||
class GlobalScripts : public LuaUtil::ScriptsContainer
|
||||
{
|
||||
public:
|
||||
GlobalScripts(LuaUtil::LuaState* lua) :
|
||||
LuaUtil::ScriptsContainer(lua, "Global", ESM::LuaScriptCfg::sGlobal)
|
||||
GlobalScripts(LuaUtil::LuaState* lua) : LuaUtil::ScriptsContainer(lua, "Global")
|
||||
{
|
||||
registerEngineHandlers({
|
||||
&mObjectActiveHandlers,
|
||||
|
|
|
@ -148,8 +148,8 @@ namespace MWLua
|
|||
};
|
||||
}
|
||||
|
||||
LocalScripts::LocalScripts(LuaUtil::LuaState* lua, const LObject& obj, ESM::LuaScriptCfg::Flags autoStartMode)
|
||||
: LuaUtil::ScriptsContainer(lua, "L" + idToString(obj.id()), autoStartMode), mData(obj)
|
||||
LocalScripts::LocalScripts(LuaUtil::LuaState* lua, const LObject& obj)
|
||||
: LuaUtil::ScriptsContainer(lua, "L" + idToString(obj.id())), mData(obj)
|
||||
{
|
||||
this->addPackage("openmw.self", sol::make_object(lua->sol(), &mData));
|
||||
registerEngineHandlers({&mOnActiveHandlers, &mOnInactiveHandlers, &mOnConsumeHandlers, &mOnActivatedHandlers});
|
||||
|
|
|
@ -20,7 +20,7 @@ namespace MWLua
|
|||
{
|
||||
public:
|
||||
static void initializeSelfPackage(const Context&);
|
||||
LocalScripts(LuaUtil::LuaState* lua, const LObject& obj, ESM::LuaScriptCfg::Flags autoStartMode);
|
||||
LocalScripts(LuaUtil::LuaState* lua, const LObject& obj);
|
||||
|
||||
MWBase::LuaManager::ActorControls* getActorControls() { return &mData.mControls; }
|
||||
|
||||
|
|
|
@ -52,6 +52,7 @@ namespace MWLua
|
|||
Log(Debug::Verbose) << "Lua scripts configuration (" << mConfiguration.size() << " scripts):";
|
||||
for (size_t i = 0; i < mConfiguration.size(); ++i)
|
||||
Log(Debug::Verbose) << "#" << i << " " << LuaUtil::scriptCfgToString(mConfiguration[i]);
|
||||
mGlobalScripts.setAutoStartConf(mConfiguration.getGlobalConf());
|
||||
}
|
||||
|
||||
void LuaManager::init()
|
||||
|
@ -306,7 +307,7 @@ namespace MWLua
|
|||
LocalScripts* localScripts = ptr.getRefData().getLuaScripts();
|
||||
if (!localScripts)
|
||||
{
|
||||
localScripts = createLocalScripts(ptr, ESM::LuaScriptCfg::sPlayer);
|
||||
localScripts = createLocalScripts(ptr);
|
||||
localScripts->addAutoStartedScripts();
|
||||
}
|
||||
mActiveLocalScripts.insert(localScripts);
|
||||
|
@ -336,10 +337,11 @@ namespace MWLua
|
|||
LocalScripts* localScripts = ptr.getRefData().getLuaScripts();
|
||||
if (!localScripts)
|
||||
{
|
||||
ESM::LuaScriptCfg::Flags flag = getLuaScriptFlag(ptr);
|
||||
if (!mConfiguration.getListByFlag(flag).empty())
|
||||
LuaUtil::ScriptIdsWithInitializationData autoStartConf =
|
||||
mConfiguration.getLocalConf(ptr.getLuaType(), ptr.getCellRef().getRefId(), getId(ptr));
|
||||
if (!autoStartConf.empty())
|
||||
{
|
||||
localScripts = createLocalScripts(ptr, flag);
|
||||
localScripts = createLocalScripts(ptr, std::move(autoStartConf));
|
||||
localScripts->addAutoStartedScripts(); // TODO: put to a queue and apply on next `update()`
|
||||
}
|
||||
}
|
||||
|
@ -398,7 +400,7 @@ namespace MWLua
|
|||
LocalScripts* localScripts = ptr.getRefData().getLuaScripts();
|
||||
if (!localScripts)
|
||||
{
|
||||
localScripts = createLocalScripts(ptr, getLuaScriptFlag(ptr));
|
||||
localScripts = createLocalScripts(ptr);
|
||||
localScripts->addAutoStartedScripts();
|
||||
if (ptr.isInCell() && MWBase::Environment::get().getWorld()->isCellActive(ptr.getCell()))
|
||||
mActiveLocalScripts.insert(localScripts);
|
||||
|
@ -406,16 +408,18 @@ namespace MWLua
|
|||
localScripts->addCustomScript(scriptId);
|
||||
}
|
||||
|
||||
LocalScripts* LuaManager::createLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScriptCfg::Flags flag)
|
||||
LocalScripts* LuaManager::createLocalScripts(const MWWorld::Ptr& ptr,
|
||||
std::optional<LuaUtil::ScriptIdsWithInitializationData> autoStartConf)
|
||||
{
|
||||
assert(mInitialized);
|
||||
assert(flag != ESM::LuaScriptCfg::sGlobal);
|
||||
assert(ptr.getType() != ESM::REC_STAT);
|
||||
std::shared_ptr<LocalScripts> scripts;
|
||||
if (flag == ESM::LuaScriptCfg::sPlayer)
|
||||
uint32_t type = ptr.getLuaType();
|
||||
if (type == ESM::REC_STAT)
|
||||
throw std::runtime_error("Lua scripts on static objects are not allowed");
|
||||
else if (type == ESM::REC_INTERNAL_PLAYER)
|
||||
{
|
||||
assert(ptr.getCellRef().getRefId() == "player");
|
||||
scripts = std::make_shared<PlayerScripts>(&mLua, LObject(getId(ptr), mWorldView.getObjectRegistry()));
|
||||
scripts->setAutoStartConf(mConfiguration.getPlayerConf());
|
||||
scripts->addPackage("openmw.ui", mUserInterfacePackage);
|
||||
scripts->addPackage("openmw.camera", mCameraPackage);
|
||||
scripts->addPackage("openmw.input", mInputPackage);
|
||||
|
@ -426,7 +430,10 @@ namespace MWLua
|
|||
}
|
||||
else
|
||||
{
|
||||
scripts = std::make_shared<LocalScripts>(&mLua, LObject(getId(ptr), mWorldView.getObjectRegistry()), flag);
|
||||
scripts = std::make_shared<LocalScripts>(&mLua, LObject(getId(ptr), mWorldView.getObjectRegistry()));
|
||||
if (!autoStartConf.has_value())
|
||||
autoStartConf = mConfiguration.getLocalConf(type, ptr.getCellRef().getRefId(), getId(ptr));
|
||||
scripts->setAutoStartConf(std::move(*autoStartConf));
|
||||
scripts->addPackage("openmw.settings", mLocalSettingsPackage);
|
||||
scripts->addPackage("openmw.storage", mLocalStoragePackage);
|
||||
}
|
||||
|
@ -461,9 +468,8 @@ namespace MWLua
|
|||
globalScripts.load(reader);
|
||||
loadEvents(mLua.sol(), reader, mGlobalEvents, mLocalEvents, mContentFileMapping, mGlobalLoader.get());
|
||||
|
||||
mGlobalScripts.setSerializer(mGlobalLoader.get());
|
||||
mGlobalScripts.setSavedDataDeserializer(mGlobalLoader.get());
|
||||
mGlobalScripts.load(globalScripts);
|
||||
mGlobalScripts.setSerializer(mGlobalSerializer.get());
|
||||
mGlobalScriptsStarted = true;
|
||||
}
|
||||
|
||||
|
@ -485,11 +491,11 @@ namespace MWLua
|
|||
}
|
||||
|
||||
mWorldView.getObjectRegistry()->registerPtr(ptr);
|
||||
LocalScripts* scripts = createLocalScripts(ptr, getLuaScriptFlag(ptr));
|
||||
LocalScripts* scripts = createLocalScripts(ptr);
|
||||
|
||||
scripts->setSerializer(mLocalLoader.get());
|
||||
scripts->load(data);
|
||||
scripts->setSerializer(mLocalSerializer.get());
|
||||
scripts->setSavedDataDeserializer(mLocalLoader.get());
|
||||
scripts->load(data);
|
||||
|
||||
// LiveCellRef is usually copied after loading, so this Ptr will become invalid and should be deregistered.
|
||||
mWorldView.getObjectRegistry()->deregisterPtr(ptr);
|
||||
|
@ -506,6 +512,7 @@ namespace MWLua
|
|||
initConfiguration();
|
||||
|
||||
{ // Reload global scripts
|
||||
mGlobalScripts.setSavedDataDeserializer(mGlobalSerializer.get());
|
||||
ESM::LuaScripts data;
|
||||
mGlobalScripts.save(data);
|
||||
mGlobalScripts.load(data);
|
||||
|
@ -516,6 +523,7 @@ namespace MWLua
|
|||
LocalScripts* scripts = ptr.getRefData().getLuaScripts();
|
||||
if (scripts == nullptr)
|
||||
continue;
|
||||
scripts->setSavedDataDeserializer(mLocalSerializer.get());
|
||||
ESM::LuaScripts data;
|
||||
scripts->save(data);
|
||||
scripts->load(data);
|
||||
|
|
|
@ -123,7 +123,8 @@ namespace MWLua
|
|||
|
||||
private:
|
||||
void initConfiguration();
|
||||
LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScriptCfg::Flags);
|
||||
LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr,
|
||||
std::optional<LuaUtil::ScriptIdsWithInitializationData> autoStartConf = std::nullopt);
|
||||
|
||||
bool mInitialized = false;
|
||||
bool mGlobalScriptsStarted = false;
|
||||
|
|
|
@ -222,8 +222,7 @@ namespace MWLua
|
|||
LocalScripts* localScripts = ptr.getRefData().getLuaScripts();
|
||||
if (!localScripts || !localScripts->hasScript(*scriptId))
|
||||
throw std::runtime_error("There is no script " + std::string(path) + " on " + ptrToString(ptr));
|
||||
ESM::LuaScriptCfg::Flags flags = cfg[*scriptId].mFlags;
|
||||
if ((flags & (localScripts->getAutoStartMode() | ESM::LuaScriptCfg::sCustom)) != ESM::LuaScriptCfg::sCustom)
|
||||
if (localScripts->getAutoStartConf().count(*scriptId) > 0)
|
||||
throw std::runtime_error("Autostarted script can not be removed: " + std::string(path));
|
||||
localScripts->removeScript(*scriptId);
|
||||
};
|
||||
|
|
|
@ -15,7 +15,7 @@ namespace MWLua
|
|||
class PlayerScripts : public LocalScripts
|
||||
{
|
||||
public:
|
||||
PlayerScripts(LuaUtil::LuaState* lua, const LObject& obj) : LocalScripts(lua, obj, ESM::LuaScriptCfg::sPlayer)
|
||||
PlayerScripts(LuaUtil::LuaState* lua, const LObject& obj) : LocalScripts(lua, obj)
|
||||
{
|
||||
registerEngineHandlers({
|
||||
&mConsoleCommandHandlers, &mKeyPressHandlers, &mKeyReleaseHandlers,
|
||||
|
|
|
@ -35,33 +35,27 @@ namespace MWLua
|
|||
|
||||
namespace
|
||||
{
|
||||
struct LuaObjectTypeInfo
|
||||
{
|
||||
std::string_view mName;
|
||||
ESM::LuaScriptCfg::Flags mFlag = 0;
|
||||
};
|
||||
|
||||
const static std::unordered_map<ESM::RecNameInts, LuaObjectTypeInfo> luaObjectTypeInfo = {
|
||||
{ESM::REC_INTERNAL_PLAYER, {ObjectTypeName::Player, ESM::LuaScriptCfg::sPlayer}},
|
||||
{ESM::REC_INTERNAL_MARKER, {ObjectTypeName::Marker}},
|
||||
{ESM::REC_ACTI, {ObjectTypeName::Activator, ESM::LuaScriptCfg::sActivator}},
|
||||
{ESM::REC_ARMO, {ObjectTypeName::Armor, ESM::LuaScriptCfg::sArmor}},
|
||||
{ESM::REC_BOOK, {ObjectTypeName::Book, ESM::LuaScriptCfg::sBook}},
|
||||
{ESM::REC_CLOT, {ObjectTypeName::Clothing, ESM::LuaScriptCfg::sClothing}},
|
||||
{ESM::REC_CONT, {ObjectTypeName::Container, ESM::LuaScriptCfg::sContainer}},
|
||||
{ESM::REC_CREA, {ObjectTypeName::Creature, ESM::LuaScriptCfg::sCreature}},
|
||||
{ESM::REC_DOOR, {ObjectTypeName::Door, ESM::LuaScriptCfg::sDoor}},
|
||||
{ESM::REC_INGR, {ObjectTypeName::Ingredient, ESM::LuaScriptCfg::sIngredient}},
|
||||
{ESM::REC_LIGH, {ObjectTypeName::Light, ESM::LuaScriptCfg::sLight}},
|
||||
{ESM::REC_MISC, {ObjectTypeName::MiscItem, ESM::LuaScriptCfg::sMiscItem}},
|
||||
{ESM::REC_NPC_, {ObjectTypeName::NPC, ESM::LuaScriptCfg::sNPC}},
|
||||
{ESM::REC_ALCH, {ObjectTypeName::Potion, ESM::LuaScriptCfg::sPotion}},
|
||||
{ESM::REC_STAT, {ObjectTypeName::Static}},
|
||||
{ESM::REC_WEAP, {ObjectTypeName::Weapon, ESM::LuaScriptCfg::sWeapon}},
|
||||
{ESM::REC_APPA, {ObjectTypeName::Apparatus}},
|
||||
{ESM::REC_LOCK, {ObjectTypeName::Lockpick}},
|
||||
{ESM::REC_PROB, {ObjectTypeName::Probe}},
|
||||
{ESM::REC_REPA, {ObjectTypeName::Repair}},
|
||||
const static std::unordered_map<ESM::RecNameInts, std::string_view> luaObjectTypeInfo = {
|
||||
{ESM::REC_INTERNAL_PLAYER, ObjectTypeName::Player},
|
||||
{ESM::REC_INTERNAL_MARKER, ObjectTypeName::Marker},
|
||||
{ESM::REC_ACTI, ObjectTypeName::Activator},
|
||||
{ESM::REC_ARMO, ObjectTypeName::Armor},
|
||||
{ESM::REC_BOOK, ObjectTypeName::Book},
|
||||
{ESM::REC_CLOT, ObjectTypeName::Clothing},
|
||||
{ESM::REC_CONT, ObjectTypeName::Container},
|
||||
{ESM::REC_CREA, ObjectTypeName::Creature},
|
||||
{ESM::REC_DOOR, ObjectTypeName::Door},
|
||||
{ESM::REC_INGR, ObjectTypeName::Ingredient},
|
||||
{ESM::REC_LIGH, ObjectTypeName::Light},
|
||||
{ESM::REC_MISC, ObjectTypeName::MiscItem},
|
||||
{ESM::REC_NPC_, ObjectTypeName::NPC},
|
||||
{ESM::REC_ALCH, ObjectTypeName::Potion},
|
||||
{ESM::REC_STAT, ObjectTypeName::Static},
|
||||
{ESM::REC_WEAP, ObjectTypeName::Weapon},
|
||||
{ESM::REC_APPA, ObjectTypeName::Apparatus},
|
||||
{ESM::REC_LOCK, ObjectTypeName::Lockpick},
|
||||
{ESM::REC_PROB, ObjectTypeName::Probe},
|
||||
{ESM::REC_REPA, ObjectTypeName::Repair},
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -70,7 +64,7 @@ namespace MWLua
|
|||
{
|
||||
auto it = luaObjectTypeInfo.find(type);
|
||||
if (it != luaObjectTypeInfo.end())
|
||||
return it->second.mName;
|
||||
return it->second;
|
||||
else
|
||||
return fallback;
|
||||
}
|
||||
|
@ -80,15 +74,6 @@ namespace MWLua
|
|||
return getLuaObjectTypeName(static_cast<ESM::RecNameInts>(ptr.getLuaType()), /*fallback=*/ptr.getTypeDescription());
|
||||
}
|
||||
|
||||
ESM::LuaScriptCfg::Flags getLuaScriptFlag(const MWWorld::Ptr& ptr)
|
||||
{
|
||||
auto it = luaObjectTypeInfo.find(static_cast<ESM::RecNameInts>(ptr.getLuaType()));
|
||||
if (it != luaObjectTypeInfo.end())
|
||||
return it->second.mFlag;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
const MWWorld::Ptr& verifyType(ESM::RecNameInts recordType, const MWWorld::Ptr& ptr)
|
||||
{
|
||||
if (ptr.getType() != recordType)
|
||||
|
@ -179,9 +164,9 @@ namespace MWLua
|
|||
|
||||
sol::table typeToPackage = getTypeToPackageTable(context.mLua->sol());
|
||||
sol::table packageToType = getPackageToTypeTable(context.mLua->sol());
|
||||
for (const auto& [type, v] : luaObjectTypeInfo)
|
||||
for (const auto& [type, name] : luaObjectTypeInfo)
|
||||
{
|
||||
sol::object t = types[v.mName];
|
||||
sol::object t = types[name];
|
||||
if (t == sol::nil)
|
||||
continue;
|
||||
typeToPackage[type] = t;
|
||||
|
|
|
@ -17,10 +17,6 @@ namespace MWLua
|
|||
sol::table getTypeToPackageTable(lua_State* L);
|
||||
sol::table getPackageToTypeTable(lua_State* L);
|
||||
|
||||
// Each script has a set of flags that controls to which objects the script should be
|
||||
// automatically attached. This function maps each object types to one of the flags.
|
||||
ESM::LuaScriptCfg::Flags getLuaScriptFlag(const MWWorld::Ptr& ptr);
|
||||
|
||||
sol::table initTypesPackage(const Context& context);
|
||||
|
||||
// used in initTypesPackage
|
||||
|
|
|
@ -21,11 +21,7 @@ namespace MWLua
|
|||
{
|
||||
if (data.is<GObject>() || data.is<LObject>())
|
||||
{
|
||||
ObjectId id = data.as<Object>().id();
|
||||
static_assert(sizeof(ObjectId) == 8);
|
||||
id.mIndex = Misc::toLittleEndian(id.mIndex);
|
||||
id.mContentFile = Misc::toLittleEndian(id.mContentFile);
|
||||
append(out, "o", &id, sizeof(ObjectId));
|
||||
appendRefNum(out, data.as<Object>().id());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -35,14 +31,9 @@ namespace MWLua
|
|||
// Returns false if this type is not supported by this serializer.
|
||||
bool deserialize(std::string_view typeName, std::string_view binaryData, lua_State* lua) const override
|
||||
{
|
||||
if (typeName == "o")
|
||||
if (typeName == sRefNumTypeName)
|
||||
{
|
||||
if (binaryData.size() != sizeof(ObjectId))
|
||||
throw std::runtime_error("Incorrect serialization format. Size of ObjectId doesn't match.");
|
||||
ObjectId id;
|
||||
std::memcpy(&id, binaryData.data(), sizeof(ObjectId));
|
||||
id.mIndex = Misc::fromLittleEndian(id.mIndex);
|
||||
id.mContentFile = Misc::fromLittleEndian(id.mContentFile);
|
||||
ObjectId id = loadRefNum(binaryData);
|
||||
if (id.hasContentFile() && mContentFileMapping)
|
||||
{
|
||||
auto iter = mContentFileMapping->find(id.mContentFile);
|
||||
|
|
|
@ -191,7 +191,7 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener, ESM::Dialo
|
|||
{
|
||||
ESM::LuaScriptsCfg cfg;
|
||||
cfg.load(esm);
|
||||
// TODO: update refnums in cfg.mScripts[].mInitializationData according to load order
|
||||
cfg.adjustRefNums(esm);
|
||||
mLuaContent.push_back(std::move(cfg));
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -1,14 +1,31 @@
|
|||
#include "gmock/gmock.h"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include <components/esm3/esmreader.hpp>
|
||||
#include <components/esm3/esmwriter.hpp>
|
||||
#include <components/esm3/readerscache.hpp>
|
||||
#include <components/lua/configuration.hpp>
|
||||
#include <components/lua/serialization.hpp>
|
||||
|
||||
#include "../testing_util.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
TEST(LuaConfigurationTest, ValidConfiguration)
|
||||
using testing::ElementsAre;
|
||||
using testing::Pair;
|
||||
|
||||
std::vector<std::pair<int, std::string>> asVector(const LuaUtil::ScriptIdsWithInitializationData& d)
|
||||
{
|
||||
std::vector<std::pair<int, std::string>> res;
|
||||
for (const auto& [k, v] : d)
|
||||
res.emplace_back(k, std::string(v));
|
||||
return res;
|
||||
}
|
||||
|
||||
TEST(LuaConfigurationTest, ValidOMWScripts)
|
||||
{
|
||||
ESM::LuaScriptsCfg cfg;
|
||||
LuaUtil::parseOMWScripts(cfg, R"X(
|
||||
|
@ -19,40 +36,208 @@ namespace
|
|||
PLAYER :my_mod/player.lua
|
||||
CUSTOM : my_mod/some_other_script.lua
|
||||
NPC , CREATURE PLAYER : my_mod/some_other_script.lua)X");
|
||||
LuaUtil::parseOMWScripts(cfg, ":my_mod/player.LUA \r\nCONTAINER,CUSTOM: my_mod/container.lua\r\n");
|
||||
LuaUtil::parseOMWScripts(cfg, ":my_mod/player.LUA \r\nCREATURE,CUSTOM: my_mod/creature.lua\r\n");
|
||||
|
||||
ASSERT_EQ(cfg.mScripts.size(), 6);
|
||||
EXPECT_EQ(LuaUtil::scriptCfgToString(cfg.mScripts[0]), "GLOBAL : my_mod/#some_global_script.lua");
|
||||
EXPECT_EQ(LuaUtil::scriptCfgToString(cfg.mScripts[1]), "PLAYER : my_mod/player.lua");
|
||||
EXPECT_EQ(LuaUtil::scriptCfgToString(cfg.mScripts[2]), "CUSTOM : my_mod/some_other_script.lua");
|
||||
EXPECT_EQ(LuaUtil::scriptCfgToString(cfg.mScripts[3]), "CREATURE NPC PLAYER : my_mod/some_other_script.lua");
|
||||
EXPECT_EQ(LuaUtil::scriptCfgToString(cfg.mScripts[3]), "PLAYER NPC CREATURE : my_mod/some_other_script.lua");
|
||||
EXPECT_EQ(LuaUtil::scriptCfgToString(cfg.mScripts[4]), ": my_mod/player.LUA");
|
||||
EXPECT_EQ(LuaUtil::scriptCfgToString(cfg.mScripts[5]), "CONTAINER CUSTOM : my_mod/container.lua");
|
||||
EXPECT_EQ(LuaUtil::scriptCfgToString(cfg.mScripts[5]), "CUSTOM CREATURE : my_mod/creature.lua");
|
||||
|
||||
LuaUtil::ScriptsConfiguration conf;
|
||||
conf.init(std::move(cfg));
|
||||
ASSERT_EQ(conf.size(), 3);
|
||||
ASSERT_EQ(conf.size(), 4);
|
||||
EXPECT_EQ(LuaUtil::scriptCfgToString(conf[0]), "GLOBAL : my_mod/#some_global_script.lua");
|
||||
// cfg.mScripts[1] is overridden by cfg.mScripts[4]
|
||||
// cfg.mScripts[2] is overridden by cfg.mScripts[3]
|
||||
EXPECT_EQ(LuaUtil::scriptCfgToString(conf[1]), "CREATURE NPC PLAYER : my_mod/some_other_script.lua");
|
||||
// cfg.mScripts[4] is removed because there are no flags
|
||||
EXPECT_EQ(LuaUtil::scriptCfgToString(conf[2]), "CONTAINER CUSTOM : my_mod/container.lua");
|
||||
EXPECT_EQ(LuaUtil::scriptCfgToString(conf[1]), "PLAYER NPC CREATURE : my_mod/some_other_script.lua");
|
||||
EXPECT_EQ(LuaUtil::scriptCfgToString(conf[2]), ": my_mod/player.LUA");
|
||||
EXPECT_EQ(LuaUtil::scriptCfgToString(conf[3]), "CUSTOM CREATURE : my_mod/creature.lua");
|
||||
|
||||
EXPECT_THAT(asVector(conf.getGlobalConf()), ElementsAre(Pair(0, "")));
|
||||
EXPECT_THAT(asVector(conf.getPlayerConf()), ElementsAre(Pair(1, "")));
|
||||
EXPECT_THAT(asVector(conf.getLocalConf(ESM::REC_CONT, "something", ESM::RefNum())), ElementsAre());
|
||||
EXPECT_THAT(asVector(conf.getLocalConf(ESM::REC_NPC_, "something", ESM::RefNum())), ElementsAre(Pair(1, "")));
|
||||
EXPECT_THAT(asVector(conf.getLocalConf(ESM::REC_CREA, "something", ESM::RefNum())), ElementsAre(Pair(1, ""), Pair(3, "")));
|
||||
|
||||
// Check that initialization cleans old data
|
||||
cfg = ESM::LuaScriptsCfg();
|
||||
conf.init(std::move(cfg));
|
||||
ASSERT_EQ(conf.size(), 0);
|
||||
EXPECT_EQ(conf.size(), 0);
|
||||
}
|
||||
|
||||
TEST(LuaConfigurationTest, Errors)
|
||||
TEST(LuaConfigurationTest, InvalidOMWScripts)
|
||||
{
|
||||
ESM::LuaScriptsCfg cfg;
|
||||
EXPECT_ERROR(LuaUtil::parseOMWScripts(cfg, "GLOBAL: something"),
|
||||
"Lua script should have suffix '.lua', got: GLOBAL: something");
|
||||
EXPECT_ERROR(LuaUtil::parseOMWScripts(cfg, "something.lua"),
|
||||
"No flags found in: something.lua");
|
||||
EXPECT_ERROR(LuaUtil::parseOMWScripts(cfg, "GLOBAL, PLAYER: something.lua"),
|
||||
"Global script can not have local flags");
|
||||
|
||||
cfg.mScripts.clear();
|
||||
EXPECT_NO_THROW(LuaUtil::parseOMWScripts(cfg, "GLOBAL, PLAYER: something.lua"));
|
||||
LuaUtil::ScriptsConfiguration conf;
|
||||
EXPECT_ERROR(conf.init(std::move(cfg)), "Global script can not have local flags");
|
||||
}
|
||||
|
||||
TEST(LuaConfigurationTest, ConfInit)
|
||||
{
|
||||
ESM::LuaScriptsCfg cfg;
|
||||
ESM::LuaScriptCfg& script1 = cfg.mScripts.emplace_back();
|
||||
script1.mScriptPath = "Script1.lua";
|
||||
script1.mInitializationData = "data1";
|
||||
script1.mFlags = ESM::LuaScriptCfg::sPlayer;
|
||||
script1.mTypes.push_back(ESM::REC_CREA);
|
||||
script1.mRecords.push_back({true, "record1", "dataRecord1"});
|
||||
script1.mRefs.push_back({true, 2, 3, ""});
|
||||
script1.mRefs.push_back({true, 2, 4, ""});
|
||||
|
||||
ESM::LuaScriptCfg& script2 = cfg.mScripts.emplace_back();
|
||||
script2.mScriptPath = "Script2.lua";
|
||||
script2.mFlags = ESM::LuaScriptCfg::sCustom;
|
||||
script2.mTypes.push_back(ESM::REC_CONT);
|
||||
|
||||
ESM::LuaScriptCfg& script1Extra = cfg.mScripts.emplace_back();
|
||||
script1Extra.mScriptPath = "script1.LUA";
|
||||
script1Extra.mFlags = ESM::LuaScriptCfg::sCustom | ESM::LuaScriptCfg::sMerge;
|
||||
script1Extra.mTypes.push_back(ESM::REC_NPC_);
|
||||
script1Extra.mRecords.push_back({false, "rat", ""});
|
||||
script1Extra.mRecords.push_back({true, "record2", ""});
|
||||
script1Extra.mRefs.push_back({true, 3, 5, "dataRef35"});
|
||||
script1Extra.mRefs.push_back({false, 2, 3, ""});
|
||||
|
||||
LuaUtil::ScriptsConfiguration conf;
|
||||
conf.init(cfg);
|
||||
ASSERT_EQ(conf.size(), 2);
|
||||
EXPECT_EQ(LuaUtil::scriptCfgToString(conf[0]),
|
||||
"CUSTOM PLAYER CREATURE NPC : Script1.lua ; data 5 bytes ; 3 records ; 4 objects");
|
||||
EXPECT_EQ(LuaUtil::scriptCfgToString(conf[1]), "CUSTOM CONTAINER : Script2.lua");
|
||||
|
||||
EXPECT_THAT(asVector(conf.getPlayerConf()), ElementsAre(Pair(0, "data1")));
|
||||
EXPECT_THAT(asVector(conf.getLocalConf(ESM::REC_CONT, "something", ESM::RefNum())), ElementsAre(Pair(1, "")));
|
||||
EXPECT_THAT(asVector(conf.getLocalConf(ESM::REC_CREA, "guar", ESM::RefNum())), ElementsAre(Pair(0, "data1")));
|
||||
EXPECT_THAT(asVector(conf.getLocalConf(ESM::REC_CREA, "rat", ESM::RefNum())), ElementsAre());
|
||||
EXPECT_THAT(asVector(conf.getLocalConf(ESM::REC_DOOR, "record1", ESM::RefNum())), ElementsAre(Pair(0, "dataRecord1")));
|
||||
EXPECT_THAT(asVector(conf.getLocalConf(ESM::REC_DOOR, "record2", ESM::RefNum())), ElementsAre(Pair(0, "data1")));
|
||||
EXPECT_THAT(asVector(conf.getLocalConf(ESM::REC_NPC_, "record3", {1, 1})), ElementsAre(Pair(0, "data1")));
|
||||
EXPECT_THAT(asVector(conf.getLocalConf(ESM::REC_NPC_, "record3", {2, 3})), ElementsAre());
|
||||
EXPECT_THAT(asVector(conf.getLocalConf(ESM::REC_NPC_, "record3", {3, 5})), ElementsAre(Pair(0, "dataRef35")));
|
||||
EXPECT_THAT(asVector(conf.getLocalConf(ESM::REC_CONT, "record4", {2, 4})), ElementsAre(Pair(0, "data1"), Pair(1, "")));
|
||||
|
||||
ESM::LuaScriptCfg& script3 = cfg.mScripts.emplace_back();
|
||||
script3.mScriptPath = "script1.lua";
|
||||
script3.mFlags = ESM::LuaScriptCfg::sGlobal;
|
||||
EXPECT_ERROR(conf.init(cfg), "Flags mismatch for script1.lua");
|
||||
}
|
||||
|
||||
TEST(LuaConfigurationTest, Serialization)
|
||||
{
|
||||
sol::state lua;
|
||||
LuaUtil::BasicSerializer serializer;
|
||||
|
||||
ESM::ESMWriter writer;
|
||||
writer.setAuthor("");
|
||||
writer.setDescription("");
|
||||
writer.setRecordCount(1);
|
||||
writer.setFormat(ESM::Header::CurrentFormat);
|
||||
writer.setVersion();
|
||||
writer.addMaster("morrowind.esm", 0);
|
||||
|
||||
ESM::LuaScriptsCfg cfg;
|
||||
std::string luaData;
|
||||
{
|
||||
sol::table data(lua, sol::create);
|
||||
data["number"] = 5;
|
||||
data["string"] = "some value";
|
||||
data["fargoth"] = ESM::RefNum{128964, 1};
|
||||
luaData = LuaUtil::serialize(data, &serializer);
|
||||
}
|
||||
{
|
||||
ESM::LuaScriptCfg& script = cfg.mScripts.emplace_back();
|
||||
script.mScriptPath = "test_global.lua";
|
||||
script.mFlags = ESM::LuaScriptCfg::sGlobal;
|
||||
script.mInitializationData = luaData;
|
||||
}
|
||||
{
|
||||
ESM::LuaScriptCfg& script = cfg.mScripts.emplace_back();
|
||||
script.mScriptPath = "test_local.lua";
|
||||
script.mFlags = ESM::LuaScriptCfg::sMerge;
|
||||
script.mTypes.push_back(ESM::REC_DOOR);
|
||||
script.mTypes.push_back(ESM::REC_MISC);
|
||||
script.mRecords.push_back({true, "rat", luaData});
|
||||
script.mRecords.push_back({false, "chargendoorjournal", ""});
|
||||
script.mRefs.push_back({true, 128964, 1, ""});
|
||||
script.mRefs.push_back({true, 128962, 1, luaData});
|
||||
}
|
||||
|
||||
std::stringstream stream;
|
||||
writer.save(stream);
|
||||
writer.startRecord(ESM::REC_LUAL);
|
||||
cfg.save(writer);
|
||||
writer.endRecord(ESM::REC_LUAL);
|
||||
writer.close();
|
||||
std::string serializedOMWAddon = stream.str();
|
||||
|
||||
{
|
||||
// Save for manual testing.
|
||||
std::ofstream f(TestingOpenMW::outputFilePath("lua_conf_test.omwaddon"), std::ios::binary);
|
||||
f << serializedOMWAddon;
|
||||
f.close();
|
||||
}
|
||||
|
||||
ESM::ESMReader reader;
|
||||
reader.open(std::make_unique<std::istringstream>(serializedOMWAddon), "lua_conf_test.omwaddon");
|
||||
ASSERT_EQ(reader.getRecordCount(), 1);
|
||||
ASSERT_EQ(reader.getRecName().toInt(), ESM::REC_LUAL);
|
||||
reader.getRecHeader();
|
||||
ESM::LuaScriptsCfg loadedCfg;
|
||||
loadedCfg.load(reader);
|
||||
|
||||
ASSERT_EQ(loadedCfg.mScripts.size(), cfg.mScripts.size());
|
||||
for (size_t i = 0; i < cfg.mScripts.size(); ++i)
|
||||
{
|
||||
EXPECT_EQ(loadedCfg.mScripts[i].mScriptPath, cfg.mScripts[i].mScriptPath);
|
||||
EXPECT_EQ(loadedCfg.mScripts[i].mFlags, cfg.mScripts[i].mFlags);
|
||||
EXPECT_EQ(loadedCfg.mScripts[i].mInitializationData, cfg.mScripts[i].mInitializationData);
|
||||
ASSERT_EQ(loadedCfg.mScripts[i].mTypes.size(), cfg.mScripts[i].mTypes.size());
|
||||
for (size_t j = 0; j < cfg.mScripts[i].mTypes.size(); ++j)
|
||||
EXPECT_EQ(loadedCfg.mScripts[i].mTypes[j], cfg.mScripts[i].mTypes[j]);
|
||||
ASSERT_EQ(loadedCfg.mScripts[i].mRecords.size(), cfg.mScripts[i].mRecords.size());
|
||||
for (size_t j = 0; j < cfg.mScripts[i].mRecords.size(); ++j)
|
||||
{
|
||||
EXPECT_EQ(loadedCfg.mScripts[i].mRecords[j].mAttach, cfg.mScripts[i].mRecords[j].mAttach);
|
||||
EXPECT_EQ(loadedCfg.mScripts[i].mRecords[j].mRecordId, cfg.mScripts[i].mRecords[j].mRecordId);
|
||||
EXPECT_EQ(loadedCfg.mScripts[i].mRecords[j].mInitializationData, cfg.mScripts[i].mRecords[j].mInitializationData);
|
||||
}
|
||||
ASSERT_EQ(loadedCfg.mScripts[i].mRefs.size(), cfg.mScripts[i].mRefs.size());
|
||||
for (size_t j = 0; j < cfg.mScripts[i].mRefs.size(); ++j)
|
||||
{
|
||||
EXPECT_EQ(loadedCfg.mScripts[i].mRefs[j].mAttach, cfg.mScripts[i].mRefs[j].mAttach);
|
||||
EXPECT_EQ(loadedCfg.mScripts[i].mRefs[j].mRefnumIndex, cfg.mScripts[i].mRefs[j].mRefnumIndex);
|
||||
EXPECT_EQ(loadedCfg.mScripts[i].mRefs[j].mRefnumContentFile, cfg.mScripts[i].mRefs[j].mRefnumContentFile);
|
||||
EXPECT_EQ(loadedCfg.mScripts[i].mRefs[j].mInitializationData, cfg.mScripts[i].mRefs[j].mInitializationData);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
ESM::ReadersCache readers(4);
|
||||
readers.get(0)->openRaw(std::make_unique<std::istringstream>("dummyData"), "a.omwaddon");
|
||||
readers.get(1)->openRaw(std::make_unique<std::istringstream>("dummyData"), "b.omwaddon");
|
||||
readers.get(2)->openRaw(std::make_unique<std::istringstream>("dummyData"), "Morrowind.esm");
|
||||
readers.get(3)->openRaw(std::make_unique<std::istringstream>("dummyData"), "c.omwaddon");
|
||||
reader.setIndex(3);
|
||||
reader.resolveParentFileIndices(readers);
|
||||
}
|
||||
loadedCfg.adjustRefNums(reader);
|
||||
EXPECT_EQ(loadedCfg.mScripts[1].mRefs[0].mRefnumIndex, cfg.mScripts[1].mRefs[0].mRefnumIndex);
|
||||
EXPECT_EQ(loadedCfg.mScripts[1].mRefs[0].mRefnumContentFile, 2);
|
||||
{
|
||||
sol::table data = LuaUtil::deserialize(lua.lua_state(), loadedCfg.mScripts[1].mRefs[1].mInitializationData, &serializer);
|
||||
ESM::RefNum adjustedRef = data["fargoth"].get<ESM::RefNum>();
|
||||
EXPECT_EQ(adjustedRef.mIndex, 128964);
|
||||
EXPECT_EQ(adjustedRef.mContentFile, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -135,17 +135,19 @@ return {
|
|||
LuaScriptsContainerTest()
|
||||
{
|
||||
ESM::LuaScriptsCfg cfg;
|
||||
cfg.mScripts.push_back({"invalid.lua", "", ESM::LuaScriptCfg::sCustom});
|
||||
cfg.mScripts.push_back({"incorrect.lua", "", ESM::LuaScriptCfg::sCustom});
|
||||
cfg.mScripts.push_back({"empty.lua", "", ESM::LuaScriptCfg::sCustom});
|
||||
cfg.mScripts.push_back({"test1.lua", "", ESM::LuaScriptCfg::sCustom});
|
||||
cfg.mScripts.push_back({"stopEvent.lua", "", ESM::LuaScriptCfg::sCustom});
|
||||
cfg.mScripts.push_back({"test2.lua", "", ESM::LuaScriptCfg::sCustom});
|
||||
cfg.mScripts.push_back({"loadSave1.lua", "", ESM::LuaScriptCfg::sNPC});
|
||||
cfg.mScripts.push_back({"loadSave2.lua", "", ESM::LuaScriptCfg::sCustom | ESM::LuaScriptCfg::sNPC});
|
||||
cfg.mScripts.push_back({"testInterface.lua", "", ESM::LuaScriptCfg::sCustom | ESM::LuaScriptCfg::sPlayer});
|
||||
cfg.mScripts.push_back({"overrideInterface.lua", "", ESM::LuaScriptCfg::sCustom | ESM::LuaScriptCfg::sPlayer});
|
||||
cfg.mScripts.push_back({"useInterface.lua", "", ESM::LuaScriptCfg::sCustom | ESM::LuaScriptCfg::sPlayer});
|
||||
LuaUtil::parseOMWScripts(cfg, R"X(
|
||||
CUSTOM: invalid.lua
|
||||
CUSTOM: incorrect.lua
|
||||
CUSTOM: empty.lua
|
||||
CUSTOM: test1.lua
|
||||
CUSTOM: stopEvent.lua
|
||||
CUSTOM: test2.lua
|
||||
NPC: loadSave1.lua
|
||||
CUSTOM, NPC: loadSave2.lua
|
||||
CUSTOM, PLAYER: testInterface.lua
|
||||
CUSTOM, PLAYER: overrideInterface.lua
|
||||
CUSTOM, PLAYER: useInterface.lua
|
||||
)X");
|
||||
mCfg.init(std::move(cfg));
|
||||
}
|
||||
};
|
||||
|
@ -278,7 +280,8 @@ return {
|
|||
|
||||
TEST_F(LuaScriptsContainerTest, AutoStart)
|
||||
{
|
||||
LuaUtil::ScriptsContainer scripts(&mLua, "Test", ESM::LuaScriptCfg::sPlayer);
|
||||
LuaUtil::ScriptsContainer scripts(&mLua, "Test");
|
||||
scripts.setAutoStartConf(mCfg.getPlayerConf());
|
||||
testing::internal::CaptureStdout();
|
||||
scripts.addAutoStartedScripts();
|
||||
scripts.update(1.5f);
|
||||
|
@ -291,7 +294,8 @@ return {
|
|||
|
||||
TEST_F(LuaScriptsContainerTest, Interface)
|
||||
{
|
||||
LuaUtil::ScriptsContainer scripts(&mLua, "Test", ESM::LuaScriptCfg::sCreature);
|
||||
LuaUtil::ScriptsContainer scripts(&mLua, "Test");
|
||||
scripts.setAutoStartConf(mCfg.getLocalConf(ESM::REC_CREA, "", ESM::RefNum()));
|
||||
int addIfaceId = *mCfg.findId("testInterface.lua");
|
||||
int overrideIfaceId = *mCfg.findId("overrideInterface.lua");
|
||||
int useIfaceId = *mCfg.findId("useInterface.lua");
|
||||
|
@ -318,9 +322,12 @@ return {
|
|||
|
||||
TEST_F(LuaScriptsContainerTest, LoadSave)
|
||||
{
|
||||
LuaUtil::ScriptsContainer scripts1(&mLua, "Test", ESM::LuaScriptCfg::sNPC);
|
||||
LuaUtil::ScriptsContainer scripts2(&mLua, "Test", ESM::LuaScriptCfg::sNPC);
|
||||
LuaUtil::ScriptsContainer scripts3(&mLua, "Test", ESM::LuaScriptCfg::sPlayer);
|
||||
LuaUtil::ScriptsContainer scripts1(&mLua, "Test");
|
||||
LuaUtil::ScriptsContainer scripts2(&mLua, "Test");
|
||||
LuaUtil::ScriptsContainer scripts3(&mLua, "Test");
|
||||
scripts1.setAutoStartConf(mCfg.getLocalConf(ESM::REC_NPC_, "", ESM::RefNum()));
|
||||
scripts2.setAutoStartConf(mCfg.getLocalConf(ESM::REC_NPC_, "", ESM::RefNum()));
|
||||
scripts3.setAutoStartConf(mCfg.getPlayerConf());
|
||||
|
||||
scripts1.addAutoStartedScripts();
|
||||
EXPECT_TRUE(scripts1.addCustomScript(*mCfg.findId("test1.lua")));
|
||||
|
|
|
@ -174,7 +174,7 @@ enum RecNameInts : unsigned int
|
|||
// format 1
|
||||
REC_FILT = fourCC("FILT"),
|
||||
REC_DBGP = fourCC("DBGP"), ///< only used in project files
|
||||
REC_LUAL = fourCC("LUAL"), // LuaScriptsCfg
|
||||
REC_LUAL = fourCC("LUAL"), // LuaScriptsCfg (only in omwgame or omwaddon)
|
||||
|
||||
// format 16 - Lua scripts in saved games
|
||||
REC_LUAM = fourCC("LUAM"), // LuaManager data
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
#include "components/esm3/esmreader.hpp"
|
||||
#include "components/esm3/esmwriter.hpp"
|
||||
|
||||
#include <components/lua/serialization.hpp>
|
||||
|
||||
// List of all records, that are related to Lua.
|
||||
//
|
||||
// Records:
|
||||
|
@ -10,13 +12,15 @@
|
|||
// LUAM - MWLua::LuaManager (in saves)
|
||||
//
|
||||
// Subrecords:
|
||||
// LUAF - LuaScriptCfg::mFlags
|
||||
// LUAF - LuaScriptCfg::mFlags and ESM::RecNameInts list
|
||||
// LUAW - Start of MWLua::WorldView data
|
||||
// LUAE - Start of MWLua::LocalEvent or MWLua::GlobalEvent (eventName)
|
||||
// LUAS - VFS path to a Lua script
|
||||
// LUAD - Serialized Lua variable
|
||||
// LUAT - MWLua::ScriptsContainer::Timer
|
||||
// LUAC - Name of a timer callback (string)
|
||||
// LUAR - Attach script to a specific record (LuaScriptCfg::PerRecordCfg)
|
||||
// LUAI - Attach script to a specific instance (LuaScriptCfg::PerRefCfg)
|
||||
|
||||
void ESM::saveLuaBinaryData(ESMWriter& esm, const std::string& data)
|
||||
{
|
||||
|
@ -39,16 +43,90 @@ std::string ESM::loadLuaBinaryData(ESMReader& esm)
|
|||
return data;
|
||||
}
|
||||
|
||||
static bool readBool(ESM::ESMReader& esm)
|
||||
{
|
||||
char c;
|
||||
esm.getT<char>(c);
|
||||
return c != 0;
|
||||
}
|
||||
|
||||
void ESM::LuaScriptsCfg::load(ESMReader& esm)
|
||||
{
|
||||
while (esm.isNextSub("LUAS"))
|
||||
{
|
||||
std::string name = esm.getHString();
|
||||
uint64_t flags;
|
||||
esm.getHNT(flags, "LUAF");
|
||||
std::string data = loadLuaBinaryData(esm);
|
||||
mScripts.push_back({std::move(name), std::move(data), flags});
|
||||
mScripts.emplace_back();
|
||||
ESM::LuaScriptCfg& script = mScripts.back();
|
||||
script.mScriptPath = esm.getHString();
|
||||
|
||||
esm.getSubNameIs("LUAF");
|
||||
esm.getSubHeader();
|
||||
if (esm.getSubSize() < 4 || (esm.getSubSize() % 4 != 0))
|
||||
esm.fail("Incorrect LUAF size");
|
||||
esm.getT(script.mFlags);
|
||||
script.mTypes.resize((esm.getSubSize() - 4) / 4);
|
||||
for (uint32_t& type : script.mTypes)
|
||||
esm.getT(type);
|
||||
|
||||
script.mInitializationData = loadLuaBinaryData(esm);
|
||||
|
||||
while (esm.isNextSub("LUAR"))
|
||||
{
|
||||
esm.getSubHeader();
|
||||
script.mRecords.emplace_back();
|
||||
ESM::LuaScriptCfg::PerRecordCfg& recordCfg = script.mRecords.back();
|
||||
recordCfg.mRecordId.resize(esm.getSubSize() - 1);
|
||||
recordCfg.mAttach = readBool(esm);
|
||||
esm.getExact(recordCfg.mRecordId.data(), static_cast<int>(recordCfg.mRecordId.size()));
|
||||
recordCfg.mInitializationData = loadLuaBinaryData(esm);
|
||||
}
|
||||
while (esm.isNextSub("LUAI"))
|
||||
{
|
||||
esm.getSubHeader();
|
||||
script.mRefs.emplace_back();
|
||||
ESM::LuaScriptCfg::PerRefCfg& refCfg = script.mRefs.back();
|
||||
refCfg.mAttach = readBool(esm);
|
||||
esm.getT<uint32_t>(refCfg.mRefnumIndex);
|
||||
esm.getT<int32_t>(refCfg.mRefnumContentFile);
|
||||
refCfg.mInitializationData = loadLuaBinaryData(esm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ESM::LuaScriptsCfg::adjustRefNums(const ESMReader& esm)
|
||||
{
|
||||
auto adjustRefNumFn = [&esm](int contentFile) -> int
|
||||
{
|
||||
if (contentFile == 0)
|
||||
return esm.getIndex();
|
||||
else if (contentFile > 0 && contentFile <= static_cast<int>(esm.getParentFileIndices().size()))
|
||||
return esm.getParentFileIndices()[contentFile - 1];
|
||||
else
|
||||
throw std::runtime_error("Incorrect contentFile index");
|
||||
};
|
||||
|
||||
lua_State* L = lua_open();
|
||||
LuaUtil::BasicSerializer serializer(adjustRefNumFn);
|
||||
|
||||
auto adjustLuaData = [&](std::string& data)
|
||||
{
|
||||
if (data.empty())
|
||||
return;
|
||||
sol::object luaData = LuaUtil::deserialize(L, data, &serializer);
|
||||
data = LuaUtil::serialize(luaData, &serializer);
|
||||
};
|
||||
|
||||
for (LuaScriptCfg& script : mScripts)
|
||||
{
|
||||
adjustLuaData(script.mInitializationData);
|
||||
for (LuaScriptCfg::PerRecordCfg& recordCfg : script.mRecords)
|
||||
adjustLuaData(recordCfg.mInitializationData);
|
||||
for (LuaScriptCfg::PerRefCfg& refCfg : script.mRefs)
|
||||
{
|
||||
adjustLuaData(refCfg.mInitializationData);
|
||||
refCfg.mRefnumContentFile = adjustRefNumFn(refCfg.mRefnumContentFile);
|
||||
}
|
||||
}
|
||||
lua_close(L);
|
||||
}
|
||||
|
||||
void ESM::LuaScriptsCfg::save(ESMWriter& esm) const
|
||||
|
@ -56,8 +134,29 @@ void ESM::LuaScriptsCfg::save(ESMWriter& esm) const
|
|||
for (const LuaScriptCfg& script : mScripts)
|
||||
{
|
||||
esm.writeHNString("LUAS", script.mScriptPath);
|
||||
esm.writeHNT("LUAF", script.mFlags);
|
||||
esm.startSubRecord("LUAF");
|
||||
esm.writeT<uint32_t>(script.mFlags);
|
||||
for (uint32_t type : script.mTypes)
|
||||
esm.writeT<uint32_t>(type);
|
||||
esm.endRecord("LUAF");
|
||||
saveLuaBinaryData(esm, script.mInitializationData);
|
||||
for (const LuaScriptCfg::PerRecordCfg& recordCfg : script.mRecords)
|
||||
{
|
||||
esm.startSubRecord("LUAR");
|
||||
esm.writeT<char>(recordCfg.mAttach ? 1 : 0);
|
||||
esm.write(recordCfg.mRecordId.data(), recordCfg.mRecordId.size());
|
||||
esm.endRecord("LUAR");
|
||||
saveLuaBinaryData(esm, recordCfg.mInitializationData);
|
||||
}
|
||||
for (const LuaScriptCfg::PerRefCfg& refCfg : script.mRefs)
|
||||
{
|
||||
esm.startSubRecord("LUAI");
|
||||
esm.writeT<char>(refCfg.mAttach ? 1 : 0);
|
||||
esm.writeT<uint32_t>(refCfg.mRefnumIndex);
|
||||
esm.writeT<int32_t>(refCfg.mRefnumContentFile);
|
||||
esm.endRecord("LUAI");
|
||||
saveLuaBinaryData(esm, refCfg.mInitializationData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,28 +13,41 @@ namespace ESM
|
|||
|
||||
struct LuaScriptCfg
|
||||
{
|
||||
using Flags = uint64_t;
|
||||
static constexpr Flags sGlobal = 1ull << 0;
|
||||
using Flags = uint32_t;
|
||||
static constexpr Flags sGlobal = 1ull << 0; // start as a global script
|
||||
static constexpr Flags sCustom = 1ull << 1; // local; can be attached/detached by a global script
|
||||
static constexpr Flags sPlayer = 1ull << 2; // auto attach to players
|
||||
// auto attach for other classes:
|
||||
static constexpr Flags sActivator = 1ull << 3;
|
||||
static constexpr Flags sArmor = 1ull << 4;
|
||||
static constexpr Flags sBook = 1ull << 5;
|
||||
static constexpr Flags sClothing = 1ull << 6;
|
||||
static constexpr Flags sContainer = 1ull << 7;
|
||||
static constexpr Flags sCreature = 1ull << 8;
|
||||
static constexpr Flags sDoor = 1ull << 9;
|
||||
static constexpr Flags sIngredient = 1ull << 10;
|
||||
static constexpr Flags sLight = 1ull << 11;
|
||||
static constexpr Flags sMiscItem = 1ull << 12;
|
||||
static constexpr Flags sNPC = 1ull << 13;
|
||||
static constexpr Flags sPotion = 1ull << 14;
|
||||
static constexpr Flags sWeapon = 1ull << 15;
|
||||
|
||||
std::string mScriptPath;
|
||||
static constexpr Flags sMerge = 1ull << 3; // merge with configuration for this script from previous content files.
|
||||
|
||||
std::string mScriptPath; // VFS path to the script.
|
||||
std::string mInitializationData; // Serialized Lua table. It is a binary data. Can contain '\0'.
|
||||
Flags mFlags; // bitwise OR of Flags.
|
||||
|
||||
// Auto attach as a local script to objects of specific types (i.e. Container, Door, Activator, etc.)
|
||||
std::vector<uint32_t> mTypes; // values are ESM::RecNameInts
|
||||
|
||||
// Auto attach as a local script to objects with specific recordIds (i.e. specific door type, or an unique NPC)
|
||||
struct PerRecordCfg
|
||||
{
|
||||
bool mAttach; // true - attach, false - don't attach (overrides previous attach)
|
||||
std::string mRecordId;
|
||||
// Initialization data for this specific record. If empty than LuaScriptCfg::mInitializationData is used.
|
||||
std::string mInitializationData;
|
||||
};
|
||||
std::vector<PerRecordCfg> mRecords;
|
||||
|
||||
// Auto attach as a local script to specific objects by their Refnums. The reference must be defined in the same
|
||||
// content file as this LuaScriptCfg or in one of its deps.
|
||||
struct PerRefCfg
|
||||
{
|
||||
bool mAttach; // true - attach, false - don't attach (overrides previous attach)
|
||||
uint32_t mRefnumIndex;
|
||||
int32_t mRefnumContentFile;
|
||||
// Initialization data for this specific refnum. If empty than LuaScriptCfg::mInitializationData is used.
|
||||
std::string mInitializationData;
|
||||
};
|
||||
std::vector<PerRefCfg> mRefs;
|
||||
};
|
||||
|
||||
struct LuaScriptsCfg
|
||||
|
@ -42,6 +55,8 @@ namespace ESM
|
|||
std::vector<LuaScriptCfg> mScripts;
|
||||
|
||||
void load(ESMReader &esm);
|
||||
void adjustRefNums(const ESMReader &esm);
|
||||
|
||||
void save(ESMWriter &esm) const;
|
||||
};
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ namespace ESM
|
|||
/// \brief File header record
|
||||
struct Header
|
||||
{
|
||||
static const int CurrentFormat = 0; // most recent known format
|
||||
static constexpr int CurrentFormat = 1; // most recent known format
|
||||
|
||||
// Defines another files (esm or esp) that this file depends upon.
|
||||
struct MasterData
|
||||
|
|
|
@ -16,19 +16,26 @@ namespace LuaUtil
|
|||
{"GLOBAL", ESM::LuaScriptCfg::sGlobal},
|
||||
{"CUSTOM", ESM::LuaScriptCfg::sCustom},
|
||||
{"PLAYER", ESM::LuaScriptCfg::sPlayer},
|
||||
{"ACTIVATOR", ESM::LuaScriptCfg::sActivator},
|
||||
{"ARMOR", ESM::LuaScriptCfg::sArmor},
|
||||
{"BOOK", ESM::LuaScriptCfg::sBook},
|
||||
{"CLOTHING", ESM::LuaScriptCfg::sClothing},
|
||||
{"CONTAINER", ESM::LuaScriptCfg::sContainer},
|
||||
{"CREATURE", ESM::LuaScriptCfg::sCreature},
|
||||
{"DOOR", ESM::LuaScriptCfg::sDoor},
|
||||
{"INGREDIENT", ESM::LuaScriptCfg::sIngredient},
|
||||
{"LIGHT", ESM::LuaScriptCfg::sLight},
|
||||
{"MISC_ITEM", ESM::LuaScriptCfg::sMiscItem},
|
||||
{"NPC", ESM::LuaScriptCfg::sNPC},
|
||||
{"POTION", ESM::LuaScriptCfg::sPotion},
|
||||
{"WEAPON", ESM::LuaScriptCfg::sWeapon},
|
||||
};
|
||||
|
||||
const std::map<std::string, ESM::RecNameInts, std::less<>> typeTagsByName{
|
||||
{"ACTIVATOR", ESM::REC_ACTI},
|
||||
{"ARMOR", ESM::REC_ARMO},
|
||||
{"BOOK", ESM::REC_BOOK},
|
||||
{"CLOTHING", ESM::REC_CLOT},
|
||||
{"CONTAINER", ESM::REC_CONT},
|
||||
{"CREATURE", ESM::REC_CREA},
|
||||
{"DOOR", ESM::REC_DOOR},
|
||||
{"INGREDIENT", ESM::REC_INGR},
|
||||
{"LIGHT", ESM::REC_LIGH},
|
||||
{"MISC_ITEM", ESM::REC_MISC},
|
||||
{"NPC", ESM::REC_NPC_},
|
||||
{"POTION", ESM::REC_ALCH},
|
||||
{"WEAPON", ESM::REC_WEAP},
|
||||
{"APPARATUS", ESM::REC_APPA},
|
||||
{"LOCKPICK", ESM::REC_LOCK},
|
||||
{"PROBE", ESM::REC_PROB},
|
||||
{"REPAIR", ESM::REC_REPA},
|
||||
};
|
||||
|
||||
bool isSpace(char c)
|
||||
|
@ -37,43 +44,69 @@ namespace LuaUtil
|
|||
}
|
||||
}
|
||||
|
||||
const std::vector<int> ScriptsConfiguration::sEmpty;
|
||||
|
||||
void ScriptsConfiguration::init(ESM::LuaScriptsCfg cfg)
|
||||
{
|
||||
mScripts.clear();
|
||||
mScriptsByFlag.clear();
|
||||
mPathToIndex.clear();
|
||||
|
||||
// Find duplicates; only the last occurrence will be used.
|
||||
// Find duplicates; only the last occurrence will be used (unless `sMerge` flag is used).
|
||||
// Search for duplicates is case insensitive.
|
||||
std::vector<bool> skip(cfg.mScripts.size(), false);
|
||||
for (int i = cfg.mScripts.size() - 1; i >= 0; --i)
|
||||
{
|
||||
auto [_, inserted] = mPathToIndex.insert_or_assign(
|
||||
Misc::StringUtils::lowerCase(cfg.mScripts[i].mScriptPath), -1);
|
||||
if (!inserted || cfg.mScripts[i].mFlags == 0)
|
||||
skip[i] = true;
|
||||
}
|
||||
mPathToIndex.clear();
|
||||
int index = 0;
|
||||
for (size_t i = 0; i < cfg.mScripts.size(); ++i)
|
||||
{
|
||||
if (skip[i])
|
||||
const ESM::LuaScriptCfg& script = cfg.mScripts[i];
|
||||
bool global = script.mFlags & ESM::LuaScriptCfg::sGlobal;
|
||||
if (global && (script.mFlags & ~ESM::LuaScriptCfg::sMerge) != ESM::LuaScriptCfg::sGlobal)
|
||||
throw std::runtime_error(std::string("Global script can not have local flags: ") + script.mScriptPath);
|
||||
if (global && (!script.mTypes.empty() || !script.mRecords.empty() || !script.mRefs.empty()))
|
||||
throw std::runtime_error(std::string(
|
||||
"Global script can not have per-type and per-object configuration") + script.mScriptPath);
|
||||
auto [it, inserted] = mPathToIndex.emplace(
|
||||
Misc::StringUtils::lowerCase(script.mScriptPath), i);
|
||||
if (inserted)
|
||||
continue;
|
||||
ESM::LuaScriptCfg& s = cfg.mScripts[i];
|
||||
mPathToIndex[s.mScriptPath] = index; // Stored paths are case sensitive.
|
||||
ESM::LuaScriptCfg::Flags flags = s.mFlags;
|
||||
ESM::LuaScriptCfg::Flags flag = 1;
|
||||
while (flags != 0)
|
||||
ESM::LuaScriptCfg& oldScript = cfg.mScripts[it->second];
|
||||
if (global != bool(oldScript.mFlags & ESM::LuaScriptCfg::sGlobal))
|
||||
throw std::runtime_error(std::string("Flags mismatch for ") + script.mScriptPath);
|
||||
if (script.mFlags & ESM::LuaScriptCfg::sMerge)
|
||||
{
|
||||
if (flags & flag)
|
||||
mScriptsByFlag[flag].push_back(index);
|
||||
flags &= ~flag;
|
||||
flag = flag << 1;
|
||||
oldScript.mFlags |= (script.mFlags & ~ESM::LuaScriptCfg::sMerge);
|
||||
if (!script.mInitializationData.empty())
|
||||
oldScript.mInitializationData = script.mInitializationData;
|
||||
oldScript.mTypes.insert(oldScript.mTypes.end(), script.mTypes.begin(), script.mTypes.end());
|
||||
oldScript.mRecords.insert(oldScript.mRecords.end(), script.mRecords.begin(), script.mRecords.end());
|
||||
oldScript.mRefs.insert(oldScript.mRefs.end(), script.mRefs.begin(), script.mRefs.end());
|
||||
skip[i] = true;
|
||||
}
|
||||
else
|
||||
skip[it->second] = true;
|
||||
}
|
||||
|
||||
// Filter duplicates
|
||||
for (size_t i = 0; i < cfg.mScripts.size(); ++i)
|
||||
{
|
||||
if (!skip[i])
|
||||
mScripts.push_back(std::move(cfg.mScripts[i]));
|
||||
}
|
||||
|
||||
// Initialize mappings
|
||||
mPathToIndex.clear();
|
||||
for (int i = 0; i < static_cast<int>(mScripts.size()); ++i)
|
||||
{
|
||||
const ESM::LuaScriptCfg& s = mScripts[i];
|
||||
mPathToIndex[s.mScriptPath] = i; // Stored paths are case sensitive.
|
||||
for (uint32_t t : s.mTypes)
|
||||
mScriptsPerType[t].push_back(i);
|
||||
for (const ESM::LuaScriptCfg::PerRecordCfg& r : s.mRecords)
|
||||
{
|
||||
std::string_view data = r.mInitializationData.empty() ? s.mInitializationData : r.mInitializationData;
|
||||
mScriptsPerRecordId[r.mRecordId].push_back(DetailedConf{i, r.mAttach, data});
|
||||
}
|
||||
for (const ESM::LuaScriptCfg::PerRefCfg& r : s.mRefs)
|
||||
{
|
||||
std::string_view data = r.mInitializationData.empty() ? s.mInitializationData : r.mInitializationData;
|
||||
mScriptsPerRefNum[ESM::RefNum{r.mRefnumIndex, r.mRefnumContentFile}].push_back(DetailedConf{i, r.mAttach, data});
|
||||
}
|
||||
mScripts.push_back(std::move(s));
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,14 +119,50 @@ namespace LuaUtil
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
const std::vector<int>& ScriptsConfiguration::getListByFlag(ESM::LuaScriptCfg::Flags type) const
|
||||
ScriptIdsWithInitializationData ScriptsConfiguration::getConfByFlag(ESM::LuaScriptCfg::Flags flag) const
|
||||
{
|
||||
assert(std::bitset<64>(type).count() <= 1);
|
||||
auto it = mScriptsByFlag.find(type);
|
||||
if (it != mScriptsByFlag.end())
|
||||
return it->second;
|
||||
ScriptIdsWithInitializationData res;
|
||||
for (size_t id = 0; id < mScripts.size(); ++id)
|
||||
{
|
||||
const ESM::LuaScriptCfg& script = mScripts[id];
|
||||
if (script.mFlags & flag)
|
||||
res[id] = script.mInitializationData;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
ScriptIdsWithInitializationData ScriptsConfiguration::getLocalConf(
|
||||
uint32_t type, std::string_view recordId, ESM::RefNum refnum) const
|
||||
{
|
||||
ScriptIdsWithInitializationData res;
|
||||
auto typeIt = mScriptsPerType.find(type);
|
||||
if (typeIt != mScriptsPerType.end())
|
||||
for (int scriptId : typeIt->second)
|
||||
res[scriptId] = mScripts[scriptId].mInitializationData;
|
||||
auto recordIt = mScriptsPerRecordId.find(recordId);
|
||||
if (recordIt != mScriptsPerRecordId.end())
|
||||
{
|
||||
for (const DetailedConf& d : recordIt->second)
|
||||
{
|
||||
if (d.mAttach)
|
||||
res[d.mScriptId] = d.mInitializationData;
|
||||
else
|
||||
return sEmpty;
|
||||
res.erase(d.mScriptId);
|
||||
}
|
||||
}
|
||||
if (!refnum.hasContentFile())
|
||||
return res;
|
||||
auto refIt = mScriptsPerRefNum.find(refnum);
|
||||
if (refIt == mScriptsPerRefNum.end())
|
||||
return res;
|
||||
for (const DetailedConf& d : refIt->second)
|
||||
{
|
||||
if (d.mAttach)
|
||||
res[d.mScriptId] = d.mInitializationData;
|
||||
else
|
||||
res.erase(d.mScriptId);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void parseOMWScripts(ESM::LuaScriptsCfg& cfg, std::string_view data)
|
||||
|
@ -117,53 +186,69 @@ namespace LuaUtil
|
|||
throw std::runtime_error(Misc::StringUtils::format(
|
||||
"Lua script should have suffix '.lua', got: %s", std::string(line.substr(0, 300))));
|
||||
|
||||
// Split flags and script path
|
||||
// Split tags and script path
|
||||
size_t semicolonPos = line.find(':');
|
||||
if (semicolonPos == std::string::npos)
|
||||
throw std::runtime_error(Misc::StringUtils::format("No flags found in: %s", std::string(line)));
|
||||
std::string_view flagsStr = line.substr(0, semicolonPos);
|
||||
std::string_view tagsStr = line.substr(0, semicolonPos);
|
||||
std::string_view scriptPath = line.substr(semicolonPos + 1);
|
||||
while (isSpace(scriptPath[0]))
|
||||
scriptPath = scriptPath.substr(1);
|
||||
|
||||
// Parse flags
|
||||
ESM::LuaScriptCfg::Flags flags = 0;
|
||||
size_t flagsPos = 0;
|
||||
ESM::LuaScriptCfg& script = cfg.mScripts.emplace_back();
|
||||
script.mScriptPath = std::string(scriptPath);
|
||||
script.mFlags = 0;
|
||||
|
||||
// Parse tags
|
||||
size_t tagsPos = 0;
|
||||
while (true)
|
||||
{
|
||||
while (flagsPos < flagsStr.size() && (isSpace(flagsStr[flagsPos]) || flagsStr[flagsPos] == ','))
|
||||
flagsPos++;
|
||||
size_t startPos = flagsPos;
|
||||
while (flagsPos < flagsStr.size() && !isSpace(flagsStr[flagsPos]) && flagsStr[flagsPos] != ',')
|
||||
flagsPos++;
|
||||
if (startPos == flagsPos)
|
||||
while (tagsPos < tagsStr.size() && (isSpace(tagsStr[tagsPos]) || tagsStr[tagsPos] == ','))
|
||||
tagsPos++;
|
||||
size_t startPos = tagsPos;
|
||||
while (tagsPos < tagsStr.size() && !isSpace(tagsStr[tagsPos]) && tagsStr[tagsPos] != ',')
|
||||
tagsPos++;
|
||||
if (startPos == tagsPos)
|
||||
break;
|
||||
std::string_view flagName = flagsStr.substr(startPos, flagsPos - startPos);
|
||||
auto it = flagsByName.find(flagName);
|
||||
std::string_view tagName = tagsStr.substr(startPos, tagsPos - startPos);
|
||||
auto it = flagsByName.find(tagName);
|
||||
auto typesIt = typeTagsByName.find(tagName);
|
||||
if (it != flagsByName.end())
|
||||
flags |= it->second;
|
||||
script.mFlags |= it->second;
|
||||
else if (typesIt != typeTagsByName.end())
|
||||
script.mTypes.push_back(typesIt->second);
|
||||
else
|
||||
throw std::runtime_error(Misc::StringUtils::format("Unknown flag '%s' in: %s",
|
||||
std::string(flagName), std::string(line)));
|
||||
throw std::runtime_error(Misc::StringUtils::format("Unknown tag '%s' in: %s",
|
||||
std::string(tagName), std::string(line)));
|
||||
}
|
||||
if ((flags & ESM::LuaScriptCfg::sGlobal) && flags != ESM::LuaScriptCfg::sGlobal)
|
||||
throw std::runtime_error("Global script can not have local flags");
|
||||
|
||||
cfg.mScripts.push_back(ESM::LuaScriptCfg{std::string(scriptPath), "", flags});
|
||||
}
|
||||
}
|
||||
|
||||
std::string scriptCfgToString(const ESM::LuaScriptCfg& script)
|
||||
{
|
||||
std::stringstream ss;
|
||||
if (script.mFlags & ESM::LuaScriptCfg::sMerge)
|
||||
ss << "+ ";
|
||||
for (const auto& [flagName, flag] : flagsByName)
|
||||
{
|
||||
if (script.mFlags & flag)
|
||||
ss << flagName << " ";
|
||||
}
|
||||
for (uint32_t type : script.mTypes)
|
||||
{
|
||||
for (const auto& [tagName, t] : typeTagsByName)
|
||||
{
|
||||
if (type == t)
|
||||
ss << tagName << " ";
|
||||
}
|
||||
}
|
||||
ss << ": " << script.mScriptPath;
|
||||
if (!script.mInitializationData.empty())
|
||||
ss << " (with data, " << script.mInitializationData.size() << " bytes)";
|
||||
ss << " ; data " << script.mInitializationData.size() << " bytes";
|
||||
if (!script.mRecords.empty())
|
||||
ss << " ; " << script.mRecords.size() << " records";
|
||||
if (!script.mRefs.empty())
|
||||
ss << " ; " << script.mRefs.size() << " objects";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
|
|
|
@ -5,9 +5,11 @@
|
|||
#include <optional>
|
||||
|
||||
#include "components/esm/luascripts.hpp"
|
||||
#include "components/esm3/cellref.hpp"
|
||||
|
||||
namespace LuaUtil
|
||||
{
|
||||
using ScriptIdsWithInitializationData = std::map<int, std::string_view>;
|
||||
|
||||
class ScriptsConfiguration
|
||||
{
|
||||
|
@ -18,13 +20,27 @@ namespace LuaUtil
|
|||
const ESM::LuaScriptCfg& operator[](int id) const { return mScripts[id]; }
|
||||
|
||||
std::optional<int> findId(std::string_view path) const;
|
||||
const std::vector<int>& getListByFlag(ESM::LuaScriptCfg::Flags type) const;
|
||||
bool isCustomScript(int id) const { return mScripts[id].mFlags & ESM::LuaScriptCfg::sCustom; }
|
||||
|
||||
ScriptIdsWithInitializationData getGlobalConf() const { return getConfByFlag(ESM::LuaScriptCfg::sGlobal); }
|
||||
ScriptIdsWithInitializationData getPlayerConf() const { return getConfByFlag(ESM::LuaScriptCfg::sPlayer); }
|
||||
ScriptIdsWithInitializationData getLocalConf(uint32_t type, std::string_view recordId, ESM::RefNum refnum) const;
|
||||
|
||||
private:
|
||||
ScriptIdsWithInitializationData getConfByFlag(ESM::LuaScriptCfg::Flags flag) const;
|
||||
|
||||
std::vector<ESM::LuaScriptCfg> mScripts;
|
||||
std::map<std::string, int, std::less<>> mPathToIndex;
|
||||
std::map<ESM::LuaScriptCfg::Flags, std::vector<int>> mScriptsByFlag;
|
||||
static const std::vector<int> sEmpty;
|
||||
|
||||
struct DetailedConf
|
||||
{
|
||||
int mScriptId;
|
||||
bool mAttach;
|
||||
std::string_view mInitializationData;
|
||||
};
|
||||
std::map<uint32_t, std::vector<int>> mScriptsPerType;
|
||||
std::map<std::string, std::vector<DetailedConf>, std::less<>> mScriptsPerRecordId;
|
||||
std::map<ESM::RefNum, std::vector<DetailedConf>> mScriptsPerRefNum;
|
||||
};
|
||||
|
||||
// Parse ESM::LuaScriptsCfg from text and add to `cfg`.
|
||||
|
|
|
@ -15,8 +15,8 @@ namespace LuaUtil
|
|||
static constexpr std::string_view HANDLER_LOAD = "onLoad";
|
||||
static constexpr std::string_view HANDLER_INTERFACE_OVERRIDE = "onInterfaceOverride";
|
||||
|
||||
ScriptsContainer::ScriptsContainer(LuaUtil::LuaState* lua, std::string_view namePrefix, ESM::LuaScriptCfg::Flags autoStartMode)
|
||||
: mNamePrefix(namePrefix), mLua(*lua), mAutoStartMode(autoStartMode)
|
||||
ScriptsContainer::ScriptsContainer(LuaUtil::LuaState* lua, std::string_view namePrefix)
|
||||
: mNamePrefix(namePrefix), mLua(*lua)
|
||||
{
|
||||
registerEngineHandlers({&mUpdateHandlers});
|
||||
mPublicInterfaces = sol::table(lua->sol(), sol::create);
|
||||
|
@ -35,22 +35,23 @@ namespace LuaUtil
|
|||
|
||||
bool ScriptsContainer::addCustomScript(int scriptId)
|
||||
{
|
||||
assert(mLua.getConfiguration()[scriptId].mFlags & ESM::LuaScriptCfg::sCustom);
|
||||
const ScriptsConfiguration& conf = mLua.getConfiguration();
|
||||
assert(conf.isCustomScript(scriptId));
|
||||
std::optional<sol::function> onInit, onLoad;
|
||||
bool ok = addScript(scriptId, onInit, onLoad);
|
||||
if (ok && onInit)
|
||||
callOnInit(scriptId, *onInit);
|
||||
callOnInit(scriptId, *onInit, conf[scriptId].mInitializationData);
|
||||
return ok;
|
||||
}
|
||||
|
||||
void ScriptsContainer::addAutoStartedScripts()
|
||||
{
|
||||
for (int scriptId : mLua.getConfiguration().getListByFlag(mAutoStartMode))
|
||||
for (const auto& [scriptId, data] : mAutoStartScripts)
|
||||
{
|
||||
std::optional<sol::function> onInit, onLoad;
|
||||
bool ok = addScript(scriptId, onInit, onLoad);
|
||||
if (ok && onInit)
|
||||
callOnInit(scriptId, *onInit);
|
||||
callOnInit(scriptId, *onInit, data);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -296,11 +297,10 @@ namespace LuaUtil
|
|||
mEngineHandlers[h->mName] = h;
|
||||
}
|
||||
|
||||
void ScriptsContainer::callOnInit(int scriptId, const sol::function& onInit)
|
||||
void ScriptsContainer::callOnInit(int scriptId, const sol::function& onInit, std::string_view data)
|
||||
{
|
||||
try
|
||||
{
|
||||
const std::string& data = mLua.getConfiguration()[scriptId].mInitializationData;
|
||||
LuaUtil::call(onInit, deserialize(mLua.sol(), data, mSerializer));
|
||||
}
|
||||
catch (std::exception& e) { printError(scriptId, "onInit failed", e); }
|
||||
|
@ -352,9 +352,14 @@ namespace LuaUtil
|
|||
removeAllScripts();
|
||||
const ScriptsConfiguration& cfg = mLua.getConfiguration();
|
||||
|
||||
std::map<int, const ESM::LuaScript*> scripts;
|
||||
for (int scriptId : mLua.getConfiguration().getListByFlag(mAutoStartMode))
|
||||
scripts[scriptId] = nullptr;
|
||||
struct ScriptInfo
|
||||
{
|
||||
std::string_view mInitData;
|
||||
const ESM::LuaScript* mSavedData;
|
||||
};
|
||||
std::map<int, ScriptInfo> scripts;
|
||||
for (const auto& [scriptId, initData] : mAutoStartScripts)
|
||||
scripts[scriptId] = {initData, nullptr};
|
||||
for (const ESM::LuaScript& s : data.mScripts)
|
||||
{
|
||||
std::optional<int> scriptId = cfg.findId(s.mScriptPath);
|
||||
|
@ -363,37 +368,38 @@ namespace LuaUtil
|
|||
Log(Debug::Verbose) << "Ignoring " << mNamePrefix << "[" << s.mScriptPath << "]; script not registered";
|
||||
continue;
|
||||
}
|
||||
if (!(cfg[*scriptId].mFlags & (ESM::LuaScriptCfg::sCustom | mAutoStartMode)))
|
||||
{
|
||||
auto it = scripts.find(*scriptId);
|
||||
if (it != scripts.end())
|
||||
it->second.mSavedData = &s;
|
||||
else if (cfg.isCustomScript(*scriptId))
|
||||
scripts[*scriptId] = {cfg[*scriptId].mInitializationData, &s};
|
||||
else
|
||||
Log(Debug::Verbose) << "Ignoring " << mNamePrefix << "[" << s.mScriptPath << "]; this script is not allowed here";
|
||||
continue;
|
||||
}
|
||||
scripts[*scriptId] = &s;
|
||||
}
|
||||
|
||||
for (const auto& [scriptId, savedScript] : scripts)
|
||||
for (const auto& [scriptId, scriptInfo] : scripts)
|
||||
{
|
||||
std::optional<sol::function> onInit, onLoad;
|
||||
if (!addScript(scriptId, onInit, onLoad))
|
||||
continue;
|
||||
if (savedScript == nullptr)
|
||||
if (scriptInfo.mSavedData == nullptr)
|
||||
{
|
||||
if (onInit)
|
||||
callOnInit(scriptId, *onInit);
|
||||
callOnInit(scriptId, *onInit, scriptInfo.mInitData);
|
||||
continue;
|
||||
}
|
||||
if (onLoad)
|
||||
{
|
||||
try
|
||||
{
|
||||
sol::object state = deserialize(mLua.sol(), savedScript->mData, mSerializer);
|
||||
sol::object state = deserialize(mLua.sol(), scriptInfo.mSavedData->mData, mSavedDataDeserializer);
|
||||
sol::object initializationData =
|
||||
deserialize(mLua.sol(), mLua.getConfiguration()[scriptId].mInitializationData, mSerializer);
|
||||
deserialize(mLua.sol(), scriptInfo.mInitData, mSerializer);
|
||||
LuaUtil::call(*onLoad, state, initializationData);
|
||||
}
|
||||
catch (std::exception& e) { printError(scriptId, "onLoad failed", e); }
|
||||
}
|
||||
for (const ESM::LuaTimer& savedTimer : savedScript->mTimers)
|
||||
for (const ESM::LuaTimer& savedTimer : scriptInfo.mSavedData->mTimers)
|
||||
{
|
||||
Timer timer;
|
||||
timer.mCallback = savedTimer.mCallbackName;
|
||||
|
@ -403,7 +409,7 @@ namespace LuaUtil
|
|||
|
||||
try
|
||||
{
|
||||
timer.mArg = deserialize(mLua.sol(), savedTimer.mCallbackArgument, mSerializer);
|
||||
timer.mArg = deserialize(mLua.sol(), savedTimer.mCallbackArgument, mSavedDataDeserializer);
|
||||
// It is important if the order of content files was changed. The deserialize-serialize procedure
|
||||
// updates refnums, so timer.mSerializedArg may be not equal to savedTimer.mCallbackArgument.
|
||||
timer.mSerializedArg = serialize(timer.mArg, mSerializer);
|
||||
|
|
|
@ -76,15 +76,16 @@ namespace LuaUtil
|
|||
using TimerType = ESM::LuaTimer::Type;
|
||||
|
||||
// `namePrefix` is a common prefix for all scripts in the container. Used in logs for error messages and `print` output.
|
||||
// `autoStartMode` specifies the list of scripts that should be autostarted in this container; the list itself is
|
||||
// stored in ScriptsConfiguration: lua->getConfiguration().getListByFlag(autoStartMode).
|
||||
ScriptsContainer(LuaState* lua, std::string_view namePrefix, ESM::LuaScriptCfg::Flags autoStartMode = 0);
|
||||
// `autoStartScripts` specifies the list of scripts that should be autostarted in this container;
|
||||
// the script names themselves are stored in ScriptsConfiguration.
|
||||
ScriptsContainer(LuaState* lua, std::string_view namePrefix);
|
||||
|
||||
ScriptsContainer(const ScriptsContainer&) = delete;
|
||||
ScriptsContainer(ScriptsContainer&&) = delete;
|
||||
virtual ~ScriptsContainer();
|
||||
|
||||
ESM::LuaScriptCfg::Flags getAutoStartMode() const { return mAutoStartMode; }
|
||||
void setAutoStartConf(ScriptIdsWithInitializationData conf) { mAutoStartScripts = std::move(conf); }
|
||||
const ScriptIdsWithInitializationData& getAutoStartConf() const { return mAutoStartScripts; }
|
||||
|
||||
// Adds package that will be available (via `require`) for all scripts in the container.
|
||||
// Automatically applies LuaUtil::makeReadOnly to the package.
|
||||
|
@ -115,6 +116,9 @@ namespace LuaUtil
|
|||
// only built-in types and types from util package can be serialized.
|
||||
void setSerializer(const UserdataSerializer* serializer) { mSerializer = serializer; }
|
||||
|
||||
// Special deserializer to use when load data from saves. Can be used to remap content files in Refnums.
|
||||
void setSavedDataDeserializer(const UserdataSerializer* serializer) { mSavedDataDeserializer = serializer; }
|
||||
|
||||
// Starts scripts according to `autoStartMode` and calls `onInit` for them. Not needed if `load` is used.
|
||||
void addAutoStartedScripts();
|
||||
|
||||
|
@ -216,7 +220,7 @@ namespace LuaUtil
|
|||
|
||||
void printError(int scriptId, std::string_view msg, const std::exception& e);
|
||||
const std::string& scriptPath(int scriptId) const { return mLua.getConfiguration()[scriptId].mScriptPath; }
|
||||
void callOnInit(int scriptId, const sol::function& onInit);
|
||||
void callOnInit(int scriptId, const sol::function& onInit, std::string_view data);
|
||||
void callTimer(const Timer& t);
|
||||
void updateTimerQueue(std::vector<Timer>& timerQueue, double time);
|
||||
static void insertTimer(std::vector<Timer>& timerQueue, Timer&& t);
|
||||
|
@ -225,8 +229,9 @@ namespace LuaUtil
|
|||
void insertInterface(int scriptId, const Script& script);
|
||||
void removeInterface(int scriptId, const Script& script);
|
||||
|
||||
ESM::LuaScriptCfg::Flags mAutoStartMode;
|
||||
ScriptIdsWithInitializationData mAutoStartScripts;
|
||||
const UserdataSerializer* mSerializer = nullptr;
|
||||
const UserdataSerializer* mSavedDataDeserializer = nullptr;
|
||||
std::map<std::string, sol::object> mAPI;
|
||||
|
||||
std::map<int, Script> mScripts;
|
||||
|
|
|
@ -96,6 +96,42 @@ namespace LuaUtil
|
|||
appendData(out, data, dataSize);
|
||||
}
|
||||
|
||||
void UserdataSerializer::appendRefNum(BinaryData& out, ESM::RefNum refnum)
|
||||
{
|
||||
static_assert(sizeof(ESM::RefNum) == 8);
|
||||
refnum.mIndex = Misc::toLittleEndian(refnum.mIndex);
|
||||
refnum.mContentFile = Misc::toLittleEndian(refnum.mContentFile);
|
||||
append(out, sRefNumTypeName, &refnum, sizeof(ESM::RefNum));
|
||||
}
|
||||
|
||||
bool BasicSerializer::serialize(BinaryData& out, const sol::userdata& data) const
|
||||
{
|
||||
appendRefNum(out, data.as<ESM::RefNum>());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BasicSerializer::deserialize(std::string_view typeName, std::string_view binaryData, lua_State* lua) const
|
||||
{
|
||||
if (typeName != sRefNumTypeName)
|
||||
return false;
|
||||
ESM::RefNum refnum = loadRefNum(binaryData);
|
||||
if (mAdjustContentFilesIndexFn)
|
||||
refnum.mContentFile = mAdjustContentFilesIndexFn(refnum.mContentFile);
|
||||
sol::stack::push<ESM::RefNum>(lua, refnum);
|
||||
return true;
|
||||
}
|
||||
|
||||
ESM::RefNum UserdataSerializer::loadRefNum(std::string_view data)
|
||||
{
|
||||
if (data.size() != sizeof(ESM::RefNum))
|
||||
throw std::runtime_error("Incorrect serialization format. Size of RefNum doesn't match.");
|
||||
ESM::RefNum refnum;
|
||||
std::memcpy(&refnum, data.data(), sizeof(ESM::RefNum));
|
||||
refnum.mIndex = Misc::fromLittleEndian(refnum.mIndex);
|
||||
refnum.mContentFile = Misc::fromLittleEndian(refnum.mContentFile);
|
||||
return refnum;
|
||||
}
|
||||
|
||||
static void serializeUserdata(BinaryData& out, const sol::userdata& data, const UserdataSerializer* customSerializer)
|
||||
{
|
||||
if (data.is<osg::Vec2f>())
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
#include <sol/sol.hpp>
|
||||
|
||||
#include <components/esm3/cellref.hpp>
|
||||
|
||||
namespace LuaUtil
|
||||
{
|
||||
|
||||
|
@ -24,6 +26,26 @@ namespace LuaUtil
|
|||
|
||||
protected:
|
||||
static void append(BinaryData&, std::string_view typeName, const void* data, size_t dataSize);
|
||||
|
||||
static constexpr std::string_view sRefNumTypeName = "o";
|
||||
static void appendRefNum(BinaryData&, ESM::RefNum);
|
||||
static ESM::RefNum loadRefNum(std::string_view data);
|
||||
};
|
||||
|
||||
// Serializer that can load Lua data from content files and saved games, but doesn't depend on apps/openmw.
|
||||
// Instead of LObject/GObject (that are defined in apps/openmw) it loads refnums directly as ESM::RefNum.
|
||||
class BasicSerializer final : public UserdataSerializer
|
||||
{
|
||||
public:
|
||||
BasicSerializer() = default;
|
||||
explicit BasicSerializer(std::function<int(int)> adjustContentFileIndexFn) :
|
||||
mAdjustContentFilesIndexFn(std::move(adjustContentFileIndexFn)) {}
|
||||
|
||||
private:
|
||||
bool serialize(LuaUtil::BinaryData& out, const sol::userdata& data) const override;
|
||||
bool deserialize(std::string_view typeName, std::string_view binaryData, lua_State* lua) const override;
|
||||
|
||||
std::function<int(int)> mAdjustContentFilesIndexFn;
|
||||
};
|
||||
|
||||
BinaryData serialize(const sol::object&, const UserdataSerializer* customSerializer = nullptr);
|
||||
|
|
|
@ -165,7 +165,11 @@ Possible flags are:
|
|||
- ``MISC_ITEM`` - a local script that will be automatically attached to any miscellaneous item;
|
||||
- ``NPC`` - a local script that will be automatically attached to any NPC;
|
||||
- ``POTION`` - a local script that will be automatically attached to any potion;
|
||||
- ``WEAPON`` - a local script that will be automatically attached to any weapon.
|
||||
- ``WEAPON`` - a local script that will be automatically attached to any weapon;
|
||||
- ``APPARATUS`` - a local script that will be automatically attached to any apparatus;
|
||||
- ``LOCKPICK`` - a local script that will be automatically attached to any lockpick;
|
||||
- ``PROBE`` - a local script that will be automatically attached to any probe tool;
|
||||
- ``REPAIR`` - a local script that will be automatically attached to any repair tool.
|
||||
|
||||
Several flags (except ``GLOBAL``) can be used with a single script. Use space or comma as a separator.
|
||||
|
||||
|
|
Loading…
Reference in a new issue