diff --git a/apps/openmw/mwlua/mwscriptbindings.cpp b/apps/openmw/mwlua/mwscriptbindings.cpp index 21ce73479a..d0e8a2dff7 100644 --- a/apps/openmw/mwlua/mwscriptbindings.cpp +++ b/apps/openmw/mwlua/mwscriptbindings.cpp @@ -1,5 +1,6 @@ #include "mwscriptbindings.hpp" +#include #include #include #include @@ -94,18 +95,75 @@ namespace MWLua }); mwscript["player"] = sol::readonly_property( [](const MWScriptRef&) { return GObject(MWBase::Environment::get().getWorld()->getPlayerPtr()); }); - mwscriptVars[sol::meta_function::index] - = [](MWScriptVariables& s, std::string_view var) -> sol::optional { - if (s.mRef.getLocals().hasVar(s.mRef.mId, var)) - return s.mRef.getLocals().getVarAsDouble(s.mRef.mId, Misc::StringUtils::lowerCase(var)); - else + mwscriptVars[sol::meta_function::length] + = [](MWScriptVariables& s) { return s.mRef.getLocals().getSize(s.mRef.mId); }; + mwscriptVars[sol::meta_function::index] = sol::overload( + [](MWScriptVariables& s, std::string_view var) -> sol::optional { + if (s.mRef.getLocals().hasVar(s.mRef.mId, var)) + return s.mRef.getLocals().getVarAsDouble(s.mRef.mId, Misc::StringUtils::lowerCase(var)); + else + return sol::nullopt; + }, + [](MWScriptVariables& s, std::size_t index) -> sol::optional { + auto& locals = s.mRef.getLocals(); + if (index < 1 || locals.getSize(s.mRef.mId) < index) + return sol::nullopt; + if (index <= locals.mShorts.size()) + return locals.mShorts[index - 1]; + index -= locals.mShorts.size(); + if (index <= locals.mLongs.size()) + return locals.mLongs[index - 1]; + index -= locals.mLongs.size(); + if (index <= locals.mFloats.size()) + return locals.mFloats[index - 1]; return sol::nullopt; - }; - mwscriptVars[sol::meta_function::new_index] = [](MWScriptVariables& s, std::string_view var, double val) { - MWScript::Locals& locals = s.mRef.getLocals(); - if (!locals.setVar(s.mRef.mId, Misc::StringUtils::lowerCase(var), val)) - throw std::runtime_error( - "No variable \"" + std::string(var) + "\" in mwscript " + s.mRef.mId.toDebugString()); + }); + mwscriptVars[sol::meta_function::new_index] = sol::overload( + [](MWScriptVariables& s, std::string_view var, double val) { + MWScript::Locals& locals = s.mRef.getLocals(); + if (!locals.setVar(s.mRef.mId, Misc::StringUtils::lowerCase(var), val)) + throw std::runtime_error( + "No variable \"" + std::string(var) + "\" in mwscript " + s.mRef.mId.toDebugString()); + }, + [](MWScriptVariables& s, std::size_t index, double val) { + auto& locals = s.mRef.getLocals(); + if (index < 1 || locals.getSize(s.mRef.mId) < index) + throw std::runtime_error("Index out of range in mwscript " + s.mRef.mId.toDebugString()); + if (index <= locals.mShorts.size()) + { + locals.mShorts[index - 1] = static_cast(val); + return; + } + index -= locals.mShorts.size(); + if (index <= locals.mLongs.size()) + { + locals.mLongs[index - 1] = static_cast(val); + return; + } + index -= locals.mLongs.size(); + if (index <= locals.mFloats.size()) + locals.mFloats[index - 1] = static_cast(val); + }); + mwscriptVars[sol::meta_function::pairs] = [](MWScriptVariables& s) { + std::size_t index = 0; + const auto& compilerLocals = MWBase::Environment::get().getScriptManager()->getLocals(s.mRef.mId); + auto& locals = s.mRef.getLocals(); + std::size_t size = locals.getSize(s.mRef.mId); + return sol::as_function( + [&, index, size](sol::this_state ts) mutable -> sol::optional> { + if (index >= size) + return sol::nullopt; + auto i = index++; + if (i < locals.mShorts.size()) + return std::make_tuple(compilerLocals.get('s')[i], locals.mShorts[i]); + i -= locals.mShorts.size(); + if (i < locals.mLongs.size()) + return std::make_tuple(compilerLocals.get('l')[i], locals.mLongs[i]); + i -= locals.mLongs.size(); + if (i < locals.mFloats.size()) + return std::make_tuple(compilerLocals.get('f')[i], locals.mFloats[i]); + return sol::nullopt; + }); }; using GlobalStore = MWWorld::Store; @@ -173,5 +231,4 @@ namespace MWLua }; return LuaUtil::makeReadOnly(api); } - } diff --git a/apps/openmw/mwscript/locals.cpp b/apps/openmw/mwscript/locals.cpp index db5dda5204..3bd4e82059 100644 --- a/apps/openmw/mwscript/locals.cpp +++ b/apps/openmw/mwscript/locals.cpp @@ -121,6 +121,12 @@ namespace MWScript return true; } + std::size_t Locals::getSize(const ESM::RefId& script) + { + ensure(script); + return mShorts.size() + mLongs.size() + mFloats.size(); + } + bool Locals::write(ESM::Locals& locals, const ESM::RefId& script) const { if (!mInitialised) diff --git a/apps/openmw/mwscript/locals.hpp b/apps/openmw/mwscript/locals.hpp index 1cc8fecb9b..76b582b78c 100644 --- a/apps/openmw/mwscript/locals.hpp +++ b/apps/openmw/mwscript/locals.hpp @@ -67,6 +67,8 @@ namespace MWScript return static_cast(getVarAsDouble(script, var)); } + std::size_t getSize(const ESM::RefId& script); + /// \note If locals have not been configured yet, no data is written. /// /// \return Locals written? diff --git a/scripts/data/morrowind_tests/global.lua b/scripts/data/morrowind_tests/global.lua index cdc10c0059..fb7113d1b1 100644 --- a/scripts/data/morrowind_tests/global.lua +++ b/scripts/data/morrowind_tests/global.lua @@ -24,6 +24,7 @@ end local testModules = { 'global_issues', 'global_dialogues', + 'global_mwscript', } return { diff --git a/scripts/data/morrowind_tests/global_mwscript.lua b/scripts/data/morrowind_tests/global_mwscript.lua new file mode 100644 index 0000000000..a4347ea66e --- /dev/null +++ b/scripts/data/morrowind_tests/global_mwscript.lua @@ -0,0 +1,51 @@ +local testing = require('testing_util') +local core = require('openmw.core') +local world = require('openmw.world') + +function iterateOverVariables(variables) + local first = nil + local last = nil + local count = 0 + for k, _ in pairs(variables) do + first = first or k + last = k + count = count + 1 + end + return first, last, count +end + +return { + {'Should support iteration over an empty set of script variables', function() + local mainVars = world.mwscript.getGlobalScript('main').variables + local first, last, count = iterateOverVariables(mainVars) + testing.expectEqual(first, nil) + testing.expectEqual(last, nil) + testing.expectEqual(count, 0) + testing.expectEqual(count, #mainVars) + end}, + {'Should support iteration of script variables', function() + local jiub = world.getObjectByFormId(core.getFormId('Morrowind.esm', 172867)) + local jiubVars = world.mwscript.getLocalScript(jiub).variables + local first, last, count = iterateOverVariables(jiubVars) + + testing.expectEqual(first, 'state') + testing.expectEqual(last, 'timer') + testing.expectEqual(count, 3) + testing.expectEqual(count, #jiubVars) + end}, + {'Should support numeric and string indices for getting and setting', function() + local jiub = world.getObjectByFormId(core.getFormId('Morrowind.esm', 172867)) + local jiubVars = world.mwscript.getLocalScript(jiub).variables + + testing.expectEqual(jiubVars[1], jiubVars.state) + testing.expectEqual(jiubVars[2], jiubVars.wandering) + testing.expectEqual(jiubVars[3], jiubVars.timer) + + jiubVars[1] = 123; + testing.expectEqual(jiubVars.state, 123) + jiubVars.wandering = 42; + testing.expectEqual(jiubVars[2], 42) + jiubVars[3] = 1.25; + testing.expectEqual(jiubVars.timer, 1.25) + end}, +}