1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2026-02-10 21:08:28 +00:00

Merge branch 'luascriptid' into 'master'

Save Lua script ids instead of paths

See merge request OpenMW/openmw!5092
This commit is contained in:
Alexei Kotov 2026-02-09 13:53:12 +03:00
commit aecb07e159
18 changed files with 158 additions and 52 deletions

View file

@ -48,7 +48,7 @@ namespace
EXPECT_EQ(LuaUtil::scriptCfgToString(cfg.mScripts[5]), "CUSTOM CREATURE : my_mod/creature.lua");
LuaUtil::ScriptsConfiguration conf;
conf.init(std::move(cfg));
conf.init(std::move(cfg), false);
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]
@ -68,7 +68,7 @@ namespace
// Check that initialization cleans old data
cfg = ESM::LuaScriptsCfg();
conf.init(std::move(cfg));
conf.init(std::move(cfg), false);
EXPECT_EQ(conf.size(), 0);
}
@ -82,7 +82,7 @@ namespace
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");
EXPECT_ERROR(conf.init(std::move(cfg), false), "Global script can not have local flags");
}
TEST(LuaConfigurationTest, ConfInit)
@ -112,7 +112,7 @@ namespace
script1Extra.mRefs.push_back({ false, 2, 3, "" });
LuaUtil::ScriptsConfiguration conf;
conf.init(cfg);
conf.init(cfg, false);
ASSERT_EQ(conf.size(), 2);
EXPECT_EQ(LuaUtil::scriptCfgToString(conf[0]),
"CUSTOM PLAYER CREATURE NPC : script1.lua ; data 5 bytes ; 3 records ; 4 objects");
@ -141,7 +141,7 @@ namespace
ESM::LuaScriptCfg& script3 = cfg.mScripts.emplace_back();
script3.mScriptPath = VFS::Path::Normalized("script1.lua");
script3.mFlags = ESM::LuaScriptCfg::sGlobal;
EXPECT_ERROR(conf.init(cfg), "Flags mismatch for script1.lua");
EXPECT_ERROR(conf.init(cfg, false), "Flags mismatch for script1.lua");
}
TEST(LuaConfigurationTest, Serialization)

View file

@ -225,7 +225,7 @@ CUSTOM, PLAYER: useInterface.lua
CUSTOM: unload.lua
CUSTOM: customdata.lua
)X");
mCfg.init(std::move(cfg));
mCfg.init(std::move(cfg), false);
}
int getId(VFS::Path::NormalizedView path) const
@ -650,4 +650,33 @@ CUSTOM: customdata.lua
EXPECT_FALSE(true);
});
}
TEST_F(LuaScriptsContainerTest, ScriptOrderChange)
{
LuaUtil::ScriptsContainer scripts1(&mLua, "Test");
scripts1.setAutoStartConf(mCfg.getLocalConf(ESM::REC_NPC_, ESM::RefId(), ESM::RefNum()));
scripts1.addAutoStartedScripts();
const int scriptId = getId(test1Path);
EXPECT_TRUE(scripts1.addCustomScript(scriptId));
ESM::LuaScripts data;
scripts1.save(data);
ESM::LuaScriptsCfg cfg;
LuaUtil::parseOMWScripts(cfg, R"X(
CUSTOM, NPC: loadSave2.lua
NPC: loadSave1.lua
CUSTOM: test1.lua
)X");
mCfg.init(std::move(cfg), true);
LuaUtil::ScriptsContainer scripts2(&mLua, "Test");
scripts2.setAutoStartConf(mCfg.getLocalConf(ESM::REC_NPC_, ESM::RefId(), ESM::RefNum()));
scripts2.load(data);
const int newScriptId = getId(test1Path);
EXPECT_NE(scriptId, newScriptId);
EXPECT_EQ(newScriptId, 2);
EXPECT_TRUE(scripts2.hasScript(newScriptId));
}
}

View file

@ -86,9 +86,9 @@ namespace MWLua
LuaUi::clearSettings();
}
void LuaManager::initConfiguration()
void LuaManager::initConfiguration(bool reload)
{
mConfiguration.init(MWBase::Environment::get().getESMStore()->getLuaScriptsCfg());
mConfiguration.init(MWBase::Environment::get().getESMStore()->getLuaScriptsCfg(), reload);
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]);
@ -137,7 +137,7 @@ namespace MWLua
mPlayerStorage.setActive(true);
mGlobalStorage.setActive(false);
initConfiguration();
initConfiguration(false);
mInitialized = true;
mMenuScripts.addAutoStartedScripts();
});
@ -661,6 +661,7 @@ namespace MWLua
writer.writeHNT<double>("LUAW", MWBase::Environment::get().getWorld()->getTimeManager()->getSimulationTime());
writer.writeFormId(MWBase::Environment::get().getWorldModel()->getLastGeneratedRefNum(), true);
mConfiguration.write(writer);
ESM::LuaScripts globalScripts;
mGlobalScripts.save(globalScripts);
globalScripts.save(writer);
@ -682,6 +683,8 @@ namespace MWLua
throw std::runtime_error("Last generated RefNum is invalid");
MWBase::Environment::get().getWorldModel()->setLastGeneratedRefNum(lastGenerated);
mConfiguration.read(reader);
// TODO: don't execute scripts right away, it will be necessary in multiplayer where global storage requires
// initialization. For now just set global storage as active slightly before it would be set by gameLoaded()
mGlobalStorage.setActive(true);
@ -735,7 +738,6 @@ namespace MWLua
mLua.dropScriptCache();
mInputActions.clear(true);
mInputTriggers.clear(true);
initConfiguration();
ESM::LuaScripts globalData;
@ -759,6 +761,8 @@ namespace MWLua
localData[id] = std::move(data);
}
initConfiguration(true);
mMenuScripts.removeAllScripts();
mPlayerStorage.clearTemporaryAndRemoveCallbacks();

View file

@ -177,7 +177,7 @@ namespace MWLua
bool isSynchronizedUpdateRunning() const { return mRunningSynchronizedUpdates; }
private:
void initConfiguration();
void initConfiguration(bool reload);
LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr,
std::optional<LuaUtil::ScriptIdsWithInitializationData> autoStartConf = std::nullopt);
void reloadAllScriptsImpl();

View file

@ -471,7 +471,7 @@ void MWState::StateManager::loadGame(const Character* character, const std::file
ESM::ActorIdConverter actorIdConverter;
if (version <= ESM::MaxActorIdSaveGameFormatVersion)
reader.setActorIdConverter(&actorIdConverter);
reader.mActorIdConverter = &actorIdConverter;
Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen();

View file

@ -259,10 +259,10 @@ namespace
}
if constexpr (std::is_same_v<T, ESM::Creature> || std::is_same_v<T, ESM::NPC>)
{
if (reader.getActorIdConverter() && state.mHasCustomState)
if (reader.mActorIdConverter && state.mHasCustomState)
{
MWBase::Environment::get().getWorldModel()->assignSaveFileRefNum(state.mRef);
reader.getActorIdConverter()->mMappings.emplace(state.mCreatureStats.mActorId, state.mRef.mRefNum);
reader.mActorIdConverter->mMappings.emplace(state.mCreatureStats.mActorId, state.mRef.mRefNum);
}
}

View file

@ -345,8 +345,9 @@ namespace MWWorld
MWBase::Environment::get().getWorldModel()->deregisterLiveCellRef(mPlayer);
mPlayer.load(player.mObject);
MWBase::Environment::get().getWorldModel()->registerPtr(getPlayer());
if (ESM::ActorIdConverter* converter = reader.getActorIdConverter())
converter->mMappings.emplace(player.mObject.mCreatureStats.mActorId, mPlayer.mRef.getRefNum());
if (reader.mActorIdConverter)
reader.mActorIdConverter->mMappings.emplace(
player.mObject.mCreatureStats.mActorId, mPlayer.mRef.getRefNum());
for (size_t i = 0; i < mSaveAttributes.size(); ++i)
mSaveAttributes[i] = player.mSaveAttributes[i];

View file

@ -813,12 +813,12 @@ namespace MWWorld
void ProjectileManager::saveLoaded(const ESM::ESMReader& reader)
{
// Can't do this in readRecord because the vectors might get reallocated as they grow
if (reader.getActorIdConverter())
if (reader.mActorIdConverter)
{
for (ProjectileState& projectile : mProjectiles)
reader.getActorIdConverter()->convert(projectile.mCaster, projectile.mCaster.mIndex);
reader.mActorIdConverter->convert(projectile.mCaster, projectile.mCaster.mIndex);
for (MagicBoltState& bolt : mMagicBolts)
reader.getActorIdConverter()->convert(bolt.mCaster, bolt.mCaster.mIndex);
reader.mActorIdConverter->convert(bolt.mCaster, bolt.mCaster.mIndex);
}
}

View file

@ -3,6 +3,7 @@
#include <components/esm3/esmreader.hpp>
#include <components/esm3/esmwriter.hpp>
#include <components/lua/configuration.hpp>
#include <components/lua/luastateptr.hpp>
#include <components/lua/serialization.hpp>
@ -164,7 +165,15 @@ void ESM::LuaScripts::load(ESMReader& esm)
{
while (esm.isNextSub("LUAS"))
{
VFS::Path::Normalized name(esm.getHString());
int32_t id = -1;
if (esm.getFormatVersion() <= ESM::MaxLuaScriptPathFormatVersion)
{
VFS::Path::Normalized name(esm.getHString());
if (esm.mScriptsConfiguration)
id = esm.mScriptsConfiguration->findId(name).value_or(-1);
}
else
esm.getHT(id);
std::string data = loadLuaBinaryData(esm);
std::vector<LuaTimer> timers;
while (esm.isNextSub("LUAT"))
@ -177,7 +186,7 @@ void ESM::LuaScripts::load(ESMReader& esm)
timer.mCallbackArgument = loadLuaBinaryData(esm);
timers.push_back(std::move(timer));
}
mScripts.push_back({ std::move(name), std::move(data), std::move(timers) });
mScripts.push_back({ id, std::move(data), std::move(timers) });
}
}
@ -185,7 +194,7 @@ void ESM::LuaScripts::save(ESMWriter& esm) const
{
for (const LuaScript& script : mScripts)
{
esm.writeHNString("LUAS", script.mScriptPath);
esm.writeHNT("LUAS", script.mScriptId);
saveLuaBinaryData(esm, script.mData);
for (const LuaTimer& timer : script.mTimers)
{

View file

@ -87,7 +87,7 @@ namespace ESM
struct LuaScript
{
VFS::Path::Normalized mScriptPath;
int32_t mScriptId;
std::string mData; // Serialized Lua table. It is a binary data. Can contain '\0'.
std::vector<LuaTimer> mTimers;
};

View file

@ -236,7 +236,7 @@ namespace ESM
void ActiveSpells::load(ESMReader& esm)
{
mActorIdConverter = esm.getActorIdConverter();
mActorIdConverter = esm.mActorIdConverter;
loadImpl(esm, mSpells, "ID__");
loadImpl(esm, mQueue, "QID_");
}

View file

@ -230,7 +230,7 @@ namespace ESM
void AiSequence::load(ESMReader& esm)
{
mActorIdConverter = esm.getActorIdConverter();
mActorIdConverter = esm.mActorIdConverter;
int count = 0;
while (esm.isNextSub("AIPK"))
{

View file

@ -18,6 +18,11 @@
#include "loadtes3.hpp"
namespace LuaUtil
{
class ScriptsConfiguration;
}
namespace ESM
{
template <class T>
@ -123,9 +128,6 @@ namespace ESM
// Returns false if content file not found.
bool applyContentFileMapping(FormId& id);
void setActorIdConverter(ActorIdConverter* converter) { mActorIdConverter = converter; }
ActorIdConverter* getActorIdConverter() const { return mActorIdConverter; }
/*************************************************************************
*
* Medium-level reading shortcuts
@ -377,8 +379,6 @@ namespace ESM
uint32_t mRecordFlags;
// Special file signifier (see SpecialFile enum above)
// Buffer for ESM strings
std::vector<char> mBuffer;
@ -390,7 +390,10 @@ namespace ESM
const std::map<int, int>* mContentFileMapping = nullptr;
public:
ActorIdConverter* mActorIdConverter = nullptr;
const LuaUtil::ScriptsConfiguration* mScriptsConfiguration = nullptr;
};
}
#endif

View file

@ -30,7 +30,8 @@ namespace ESM
inline constexpr FormatVersion MaxPlayerBeforeCellDataFormatVersion = 32;
inline constexpr FormatVersion MaxActorIdSaveGameFormatVersion = 34;
inline constexpr FormatVersion MaxSerializeEffectRefIdFormatVersion = 35;
inline constexpr FormatVersion CurrentSaveGameFormatVersion = 36;
inline constexpr FormatVersion MaxLuaScriptPathFormatVersion = 36;
inline constexpr FormatVersion CurrentSaveGameFormatVersion = 37;
inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 5;
inline constexpr FormatVersion OpenMW0_49MinSaveGameFormatVersion = 5;

View file

@ -14,7 +14,7 @@ namespace ESM
void ObjectState::load(ESMReader& esm)
{
mVersion = esm.getFormatVersion();
mActorIdConverter = esm.getActorIdConverter();
mActorIdConverter = esm.mActorIdConverter;
bool isDeleted;
mRef.loadData(esm, isDeleted);

View file

@ -6,6 +6,8 @@
#include <format>
#include <sstream>
#include <components/esm3/esmreader.hpp>
#include <components/esm3/esmwriter.hpp>
#include <components/misc/strings/algorithm.hpp>
#include <components/misc/strings/lower.hpp>
@ -47,8 +49,14 @@ namespace LuaUtil
}
}
void ScriptsConfiguration::init(ESM::LuaScriptsCfg cfg)
void ScriptsConfiguration::init(ESM::LuaScriptsCfg cfg, bool remap)
{
std::vector<VFS::Path::Normalized> oldPaths;
if (remap)
{
for (ESM::LuaScriptCfg& script : mScripts)
oldPaths.emplace_back(std::move(script.mScriptPath));
}
mScripts.clear();
mPathToIndex.clear();
@ -94,6 +102,9 @@ namespace LuaUtil
// Initialize mappings
mPathToIndex.clear();
mScriptsPerType.clear();
mScriptsPerRecordId.clear();
mScriptsPerRefNum.clear();
for (int i = 0; i < static_cast<int>(mScripts.size()); ++i)
{
const ESM::LuaScriptCfg& s = mScripts[i];
@ -112,6 +123,16 @@ namespace LuaUtil
DetailedConf{ i, r.mAttach, data });
}
}
if (remap)
{
mScriptIdMapping.clear();
for (size_t i = 0; i < oldPaths.size(); ++i)
{
if (std::optional<int> id = findId(oldPaths[i]))
mScriptIdMapping[static_cast<int>(i)] = *id;
}
}
}
std::optional<int> ScriptsConfiguration::findId(VFS::Path::NormalizedView path) const
@ -169,6 +190,40 @@ namespace LuaUtil
return res;
}
void ScriptsConfiguration::read(ESM::ESMReader& reader)
{
reader.mScriptsConfiguration = this;
mScriptIdMapping.clear();
int index = 0;
while (reader.isNextSub("LUAP"))
{
VFS::Path::Normalized path(reader.getHString());
if (std::optional<int> id = findId(path))
mScriptIdMapping[index] = *id;
++index;
}
}
void ScriptsConfiguration::write(ESM::ESMWriter& writer) const
{
for (const ESM::LuaScriptCfg& script : mScripts)
writer.writeHNString("LUAP", script.mScriptPath);
}
std::optional<int> ScriptsConfiguration::mapId(int savedId) const
{
if (mScriptIdMapping.empty())
{
if (savedId == -1)
return {};
return savedId;
}
auto it = mScriptIdMapping.find(savedId);
if (it == mScriptIdMapping.end())
return {};
return it->second;
}
void parseOMWScripts(ESM::LuaScriptsCfg& cfg, std::string_view data)
{
while (!data.empty())

View file

@ -8,6 +8,12 @@
#include <components/esm3/refnum.hpp>
#include <components/vfs/pathutil.hpp>
namespace ESM
{
class ESMReader;
class ESMWriter;
}
namespace LuaUtil
{
using ScriptIdsWithInitializationData = std::map<int, std::string_view>;
@ -15,12 +21,13 @@ namespace LuaUtil
class ScriptsConfiguration
{
public:
void init(ESM::LuaScriptsCfg);
void init(ESM::LuaScriptsCfg, bool);
size_t size() const { return mScripts.size(); }
const ESM::LuaScriptCfg& operator[](size_t id) const { return mScripts[id]; }
std::optional<int> findId(VFS::Path::NormalizedView path) const;
std::optional<int> mapId(int savedId) const;
bool isCustomScript(int id) const { return mScripts[id].mFlags & ESM::LuaScriptCfg::sCustom; }
@ -30,6 +37,9 @@ namespace LuaUtil
ScriptIdsWithInitializationData getLocalConf(
uint32_t type, const ESM::RefId& recordId, ESM::RefNum refnum) const;
void read(ESM::ESMReader&);
void write(ESM::ESMWriter&) const;
private:
ScriptIdsWithInitializationData getConfByFlag(ESM::LuaScriptCfg::Flags flag) const;
@ -45,6 +55,7 @@ namespace LuaUtil
std::map<uint32_t, std::vector<int>> mScriptsPerType;
std::map<ESM::RefId, std::vector<DetailedConf>, std::less<>> mScriptsPerRecordId;
std::map<ESM::RefNum, std::vector<DetailedConf>> mScriptsPerRefNum;
std::map<int, int> mScriptIdMapping;
};
// Parse ESM::LuaScriptsCfg from text and add to `cfg`.

View file

@ -205,15 +205,10 @@ namespace LuaUtil
using T = std::decay_t<decltype(variant)>;
if constexpr (std::is_same_v<T, UnloadedData>)
{
const auto& conf = mLua.getConfiguration();
if (scriptId >= 0 && static_cast<size_t>(scriptId) < conf.size())
for (const ESM::LuaScript& script : variant.mScripts)
{
const auto& path = conf[scriptId].mScriptPath;
for (const ESM::LuaScript& script : variant.mScripts)
{
if (script.mScriptPath == path)
return true;
}
if (script.mScriptId == scriptId)
return true;
}
return false;
}
@ -443,9 +438,7 @@ namespace LuaUtil
for (auto& [scriptId, script] : loadedData.mScripts)
{
ESM::LuaScript savedScript;
// Note: We can not use `scriptPath(scriptId)` here because `save` can be called during
// evaluating "reloadlua" command when ScriptsConfiguration is already changed.
savedScript.mScriptPath = script.mPath;
savedScript.mScriptId = scriptId;
if (script.mOnSave)
{
try
@ -475,10 +468,10 @@ namespace LuaUtil
scripts[scriptId] = { initData, nullptr };
for (const ESM::LuaScript& s : data.mScripts)
{
std::optional<int> scriptId = cfg.findId(s.mScriptPath);
std::optional<int> scriptId = cfg.mapId(s.mScriptId);
if (!scriptId)
{
Log(Debug::Verbose) << "Ignoring " << mNamePrefix << "[" << s.mScriptPath << "]; script not registered";
Log(Debug::Verbose) << "Ignoring " << mNamePrefix << "[" << s.mScriptId << "]; script not registered";
continue;
}
auto it = scripts.find(*scriptId);
@ -487,7 +480,7 @@ namespace LuaUtil
else if (cfg.isCustomScript(*scriptId))
scripts[*scriptId] = { cfg[*scriptId].mInitializationData, &s };
else
Log(Debug::Verbose) << "Ignoring " << mNamePrefix << "[" << s.mScriptPath
Log(Debug::Verbose) << "Ignoring " << mNamePrefix << "[" << cfg[*scriptId].mScriptPath
<< "]; this script is not allowed here";
}
@ -499,6 +492,7 @@ namespace LuaUtil
if (scriptInfo.mSavedData == nullptr)
continue;
ESM::LuaScript& script = container.mScripts.emplace_back(*scriptInfo.mSavedData);
script.mScriptId = scriptId;
if (!script.mData.empty())
{
try
@ -549,12 +543,11 @@ namespace LuaUtil
scripts[scriptId] = { initData, nullptr };
for (const ESM::LuaScript& s : savedScripts)
{
std::optional<int> scriptId = cfg.findId(s.mScriptPath);
auto it = scripts.find(*scriptId);
auto it = scripts.find(s.mScriptId);
if (it != scripts.end())
it->second.mSavedData = &s;
else if (cfg.isCustomScript(*scriptId))
scripts[*scriptId] = { cfg[*scriptId].mInitializationData, &s };
else if (cfg.isCustomScript(s.mScriptId))
scripts[s.mScriptId] = { cfg[s.mScriptId].mInitializationData, &s };
}
mLua.protectedCall([&](LuaView& view) {