#include "luabindings.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/statemanager.hpp" #include "../mwworld/action.hpp" #include "../mwworld/class.hpp" #include "../mwworld/datetimemanager.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/manualref.hpp" #include "../mwworld/store.hpp" #include "../mwworld/worldmodel.hpp" #include "luaevents.hpp" #include "luamanagerimp.hpp" #include "mwscriptbindings.hpp" #include "objectlists.hpp" #include "camerabindings.hpp" #include "cellbindings.hpp" #include "debugbindings.hpp" #include "inputbindings.hpp" #include "magicbindings.hpp" #include "nearbybindings.hpp" #include "objectbindings.hpp" #include "postprocessingbindings.hpp" #include "soundbindings.hpp" #include "types/types.hpp" #include "uibindings.hpp" namespace MWLua { struct CellsStore { }; } namespace sol { template <> struct is_automagical : std::false_type { }; } namespace MWLua { static void addTimeBindings(sol::table& api, const Context& context, bool global) { MWWorld::DateTimeManager* timeManager = MWBase::Environment::get().getWorld()->getTimeManager(); api["getSimulationTime"] = [timeManager]() { return timeManager->getSimulationTime(); }; api["getSimulationTimeScale"] = [timeManager]() { return timeManager->getSimulationTimeScale(); }; api["getGameTime"] = [timeManager]() { return timeManager->getGameTime(); }; api["getGameTimeScale"] = [timeManager]() { return timeManager->getGameTimeScale(); }; api["isWorldPaused"] = [timeManager]() { return timeManager->isPaused(); }; api["getRealTime"] = []() { return std::chrono::duration(std::chrono::steady_clock::now().time_since_epoch()).count(); }; if (!global) return; api["setGameTimeScale"] = [timeManager](double scale) { timeManager->setGameTimeScale(scale); }; api["setSimulationTimeScale"] = [context, timeManager](float scale) { context.mLuaManager->addAction([scale, timeManager] { timeManager->setSimulationTimeScale(scale); }); }; api["pause"] = [timeManager](sol::optional tag) { timeManager->pause(tag.value_or("paused")); }; api["unpause"] = [timeManager](sol::optional tag) { timeManager->unpause(tag.value_or("paused")); }; api["getPausedTags"] = [timeManager](sol::this_state lua) { sol::table res(lua, sol::create); for (const std::string& tag : timeManager->getPausedTags()) res[tag] = tag; return res; }; } static sol::table initContentFilesBindings(sol::state_view& lua) { const std::vector& contentList = MWBase::Environment::get().getWorld()->getContentFiles(); sol::table list(lua, sol::create); for (size_t i = 0; i < contentList.size(); ++i) list[i + 1] = Misc::StringUtils::lowerCase(contentList[i]); sol::table res(lua, sol::create); res["list"] = LuaUtil::makeReadOnly(list); res["indexOf"] = [&contentList](std::string_view contentFile) -> sol::optional { for (size_t i = 0; i < contentList.size(); ++i) if (Misc::StringUtils::ciEqual(contentList[i], contentFile)) return i + 1; return sol::nullopt; }; res["has"] = [&contentList](std::string_view contentFile) -> bool { for (size_t i = 0; i < contentList.size(); ++i) if (Misc::StringUtils::ciEqual(contentList[i], contentFile)) return true; return false; }; return LuaUtil::makeReadOnly(res); } static sol::table initCorePackage(const Context& context) { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); api["API_REVISION"] = 43; api["quit"] = [lua]() { Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback(); MWBase::Environment::get().getStateManager()->requestQuit(); }; api["sendGlobalEvent"] = [context](std::string eventName, const sol::object& eventData) { context.mLuaEvents->addGlobalEvent( { std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer) }); }; api["contentFiles"] = initContentFilesBindings(lua->sol()); api["sound"] = initCoreSoundBindings(context); api["getFormId"] = [](std::string_view contentFile, unsigned int index) -> std::string { const std::vector& contentList = MWBase::Environment::get().getWorld()->getContentFiles(); for (size_t i = 0; i < contentList.size(); ++i) if (Misc::StringUtils::ciEqual(contentList[i], contentFile)) return ESM::RefId(ESM::FormId{ index, int(i) }).serializeText(); throw std::runtime_error("Content file not found: " + std::string(contentFile)); }; addTimeBindings(api, context, false); api["magic"] = initCoreMagicBindings(context); api["l10n"] = LuaUtil::initL10nLoader(lua->sol(), MWBase::Environment::get().getL10nManager()); const MWWorld::Store* gmstStore = &MWBase::Environment::get().getESMStore()->get(); api["getGMST"] = [lua = context.mLua, gmstStore](const std::string& setting) -> sol::object { const ESM::GameSetting* gmst = gmstStore->search(setting); if (gmst == nullptr) return sol::nil; const ESM::Variant& value = gmst->mValue; switch (value.getType()) { case ESM::VT_Float: return sol::make_object(lua->sol(), value.getFloat()); case ESM::VT_Short: case ESM::VT_Long: case ESM::VT_Int: return sol::make_object(lua->sol(), value.getInteger()); case ESM::VT_String: return sol::make_object(lua->sol(), value.getString()); case ESM::VT_Unknown: case ESM::VT_None: break; } return sol::nil; }; // TODO: deprecate this and provide access to the store instead sol::table skills(context.mLua->sol(), sol::create); api["SKILL"] = LuaUtil::makeStrictReadOnly(skills); for (int i = 0; i < ESM::Skill::Length; ++i) { ESM::RefId skill = ESM::Skill::indexToRefId(i); std::string id = skill.serializeText(); std::string key = Misc::StringUtils::lowerCase(skill.getRefIdString()); // force first character to uppercase for backwards compatability key[0] += 'A' - 'a'; skills[key] = id; } // TODO: deprecate this and provide access to the store instead sol::table attributes(context.mLua->sol(), sol::create); api["ATTRIBUTE"] = LuaUtil::makeStrictReadOnly(attributes); for (int i = 0; i < ESM::Attribute::Length; ++i) { ESM::RefId attribute = ESM::Attribute::indexToRefId(i); std::string id = attribute.serializeText(); std::string key = Misc::StringUtils::lowerCase(attribute.getRefIdString()); // force first character to uppercase for backwards compatability key[0] += 'A' - 'a'; attributes[key] = id; } return LuaUtil::makeReadOnly(api); } static void addCellGetters(sol::table& api, const Context& context) { api["getCellByName"] = [](std::string_view name) { return GCell{ &MWBase::Environment::get().getWorldModel()->getCell(name, /*forceLoad=*/false) }; }; api["getExteriorCell"] = [](int x, int y, sol::object cellOrName) { ESM::RefId worldspace; if (cellOrName.is()) worldspace = cellOrName.as().mStore->getCell()->getWorldSpace(); else if (cellOrName.is() && !cellOrName.as().empty()) worldspace = MWBase::Environment::get() .getWorldModel() ->getCell(cellOrName.as()) .getCell() ->getWorldSpace(); else worldspace = ESM::Cell::sDefaultWorldspaceId; return GCell{ &MWBase::Environment::get().getWorldModel()->getExterior( ESM::ExteriorCellLocation(x, y, worldspace), /*forceLoad=*/false) }; }; const MWWorld::Store* cells3Store = &MWBase::Environment::get().getESMStore()->get(); const MWWorld::Store* cells4Store = &MWBase::Environment::get().getESMStore()->get(); sol::usertype cells = context.mLua->sol().new_usertype("Cells"); cells[sol::meta_function::length] = [cells3Store, cells4Store](const CellsStore&) { return cells3Store->getSize() + cells4Store->getSize(); }; cells[sol::meta_function::index] = [cells3Store, cells4Store](const CellsStore&, size_t index) -> GCell { index--; // Translate from Lua's 1-based indexing. if (index < cells3Store->getSize()) { const ESM::Cell* cellRecord = cells3Store->at(index); return GCell{ &MWBase::Environment::get().getWorldModel()->getCell( cellRecord->mId, /*forceLoad=*/false) }; } else { const ESM4::Cell* cellRecord = cells4Store->at(index - cells3Store->getSize()); return GCell{ &MWBase::Environment::get().getWorldModel()->getCell( cellRecord->mId, /*forceLoad=*/false) }; } }; cells[sol::meta_function::pairs] = context.mLua->sol()["ipairsForArray"].template get(); cells[sol::meta_function::ipairs] = context.mLua->sol()["ipairsForArray"].template get(); api["cells"] = CellsStore{}; } static sol::table initWorldPackage(const Context& context) { sol::table api(context.mLua->sol(), sol::create); ObjectLists* objectLists = context.mObjectLists; addTimeBindings(api, context, true); addCellGetters(api, context); api["mwscript"] = initMWScriptBindings(context); api["activeActors"] = GObjectList{ objectLists->getActorsInScene() }; api["players"] = GObjectList{ objectLists->getPlayers() }; api["createObject"] = [](std::string_view recordId, sol::optional count) -> GObject { MWWorld::ManualRef mref(*MWBase::Environment::get().getESMStore(), ESM::RefId::deserializeText(recordId)); const MWWorld::Ptr& ptr = mref.getPtr(); ptr.getRefData().disable(); MWWorld::CellStore& cell = MWBase::Environment::get().getWorldModel()->getDraftCell(); MWWorld::Ptr newPtr = ptr.getClass().copyToCell(ptr, cell, count.value_or(1)); return GObject(newPtr); }; api["getObjectByFormId"] = [](std::string_view formIdStr) -> GObject { ESM::RefId refId = ESM::RefId::deserializeText(formIdStr); if (!refId.is()) throw std::runtime_error("FormId expected, got " + std::string(formIdStr) + "; use core.getFormId"); return GObject(refId.getIf()->getValue()); }; // Creates a new record in the world database. api["createRecord"] = sol::overload( [](const ESM::Activator& activator) -> const ESM::Activator* { return MWBase::Environment::get().getESMStore()->insert(activator); }, [](const ESM::Armor& armor) -> const ESM::Armor* { return MWBase::Environment::get().getESMStore()->insert(armor); }, [](const ESM::Clothing& clothing) -> const ESM::Clothing* { return MWBase::Environment::get().getESMStore()->insert(clothing); }, [](const ESM::Book& book) -> const ESM::Book* { return MWBase::Environment::get().getESMStore()->insert(book); }, [](const ESM::Miscellaneous& misc) -> const ESM::Miscellaneous* { return MWBase::Environment::get().getESMStore()->insert(misc); }, [](const ESM::Potion& potion) -> const ESM::Potion* { return MWBase::Environment::get().getESMStore()->insert(potion); }, [](const ESM::Weapon& weapon) -> const ESM::Weapon* { return MWBase::Environment::get().getESMStore()->insert(weapon); }); api["_runStandardActivationAction"] = [context](const GObject& object, const GObject& actor) { if (!object.ptr().getRefData().activate()) return; context.mLuaManager->addAction( [object, actor] { const MWWorld::Ptr& objPtr = object.ptr(); const MWWorld::Ptr& actorPtr = actor.ptr(); objPtr.getClass().activate(objPtr, actorPtr)->execute(actorPtr); }, "_runStandardActivationAction"); }; return LuaUtil::makeReadOnly(api); } std::map initCommonPackages(const Context& context) { sol::state_view lua = context.mLua->sol(); MWWorld::DateTimeManager* tm = MWBase::Environment::get().getWorld()->getTimeManager(); return { { "openmw.async", LuaUtil::getAsyncPackageInitializer( lua, [tm] { return tm->getSimulationTime(); }, [tm] { return tm->getGameTime(); }) }, { "openmw.core", initCorePackage(context) }, { "openmw.types", initTypesPackage(context) }, { "openmw.util", LuaUtil::initUtilPackage(lua) }, }; } std::map initGlobalPackages(const Context& context) { initObjectBindingsForGlobalScripts(context); initCellBindingsForGlobalScripts(context); return { { "openmw.world", initWorldPackage(context) }, }; } std::map initLocalPackages(const Context& context) { initObjectBindingsForLocalScripts(context); initCellBindingsForLocalScripts(context); LocalScripts::initializeSelfPackage(context); return { { "openmw.nearby", initNearbyPackage(context) }, }; } std::map initPlayerPackages(const Context& context) { return { { "openmw.ambient", initAmbientPackage(context) }, { "openmw.camera", initCameraPackage(context.mLua->sol()) }, { "openmw.debug", initDebugPackage(context) }, { "openmw.input", initInputPackage(context) }, { "openmw.postprocessing", initPostprocessingPackage(context) }, { "openmw.ui", initUserInterfacePackage(context) }, }; } }