diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 3c4f4c45a7..4de11d7967 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -104,7 +104,6 @@ jobs: fail-fast: true matrix: image: - - "2019" - "2022" uses: ./.github/workflows/windows.yml diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index d2a7d37990..81d83451a6 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -4,9 +4,13 @@ on: workflow_call: inputs: image: - description: MSVC image (2019/2022) + description: Window Server image required: true type: string + msvc: + description: MSVC version (2019/2022) + default: "2022" + type: string vcpkg-deps-tag: description: Git tag of our deps required: true @@ -45,7 +49,7 @@ jobs: - name: Download prebuilt vcpkg packages working-directory: ${{ github.workspace }}/deps run: | - $MANIFEST = "vcpkg-x64-${{ inputs.image }}-${{ inputs.vcpkg-deps-tag }}.txt" + $MANIFEST = "vcpkg-x64-${{ inputs.msvc }}-${{ inputs.vcpkg-deps-tag }}.txt" curl --fail --retry 3 -L -o "$MANIFEST" "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/$MANIFEST" $lines = Get-Content "$MANIFEST" $URL = $lines[0] @@ -61,7 +65,7 @@ jobs: - name: Extract archived prebuilt vcpkg packages working-directory: ${{ github.workspace }}/deps - run: 7z x -y -ovcpkg-x64-${{ inputs.image }}-${{ inputs.vcpkg-deps-tag }} $env:archive + run: 7z x -y -ovcpkg-x64-${{ inputs.msvc }}-${{ inputs.vcpkg-deps-tag }} $env:archive - name: Cache Qt id: qt-cache @@ -94,12 +98,12 @@ jobs: -B ${{ github.workspace }}/build -G Ninja -D CMAKE_BUILD_TYPE=${{ inputs.build-type }} - -D CMAKE_TOOLCHAIN_FILE='${{ github.workspace }}/deps/vcpkg-x64-${{ inputs.image }}-${{ inputs.vcpkg-deps-tag }}/scripts/buildsystems/vcpkg.cmake' + -D CMAKE_TOOLCHAIN_FILE='${{ github.workspace }}/deps/vcpkg-x64-${{ inputs.msvc }}-${{ inputs.vcpkg-deps-tag }}/scripts/buildsystems/vcpkg.cmake' -D CMAKE_PREFIX_PATH='${{ github.workspace }}/deps/Qt/6.6.3/msvc2019_64' ${{ inputs.package && '-D CMAKE_CXX_FLAGS_RELEASE="/O2 /Ob2 /DNDEBUG /Zi"' || '' }} ${{ inputs.package && '-D "CMAKE_EXE_LINKER_FLAGS_RELEASE=/DEBUG /INCREMENTAL:NO"' || '' }} - -D LuaJit_INCLUDE_DIR='${{ github.workspace }}/deps/vcpkg-x64-${{ inputs.image }}-${{ inputs.vcpkg-deps-tag }}/installed/x64-windows/include/luajit' - -D LuaJit_LIBRARY='${{ github.workspace }}/deps/vcpkg-x64-${{ inputs.image }}-${{ inputs.vcpkg-deps-tag }}/installed/x64-windows/lib/lua51.lib' + -D LuaJit_INCLUDE_DIR='${{ github.workspace }}/deps/vcpkg-x64-${{ inputs.msvc }}-${{ inputs.vcpkg-deps-tag }}/installed/x64-windows/include/luajit' + -D LuaJit_LIBRARY='${{ github.workspace }}/deps/vcpkg-x64-${{ inputs.msvc }}-${{ inputs.vcpkg-deps-tag }}/installed/x64-windows/lib/lua51.lib' -D BUILD_BENCHMARKS=${{ ! inputs.package }} -D BUILD_COMPONENTS_TESTS=${{ ! inputs.package }} -D BUILD_OPENMW_TESTS=${{ ! inputs.package }} @@ -114,9 +118,9 @@ jobs: - name: Copy missing DLLs run: | - cp ${{ github.workspace }}/deps/vcpkg-x64-${{ inputs.image }}-${{ inputs.vcpkg-deps-tag }}/installed/x64-windows/bin/Release/MyGUIEngine.dll ${{ github.workspace }}/build - cp -Filter *.dll -Recurse ${{ github.workspace }}/deps/vcpkg-x64-${{ inputs.image }}-${{ inputs.vcpkg-deps-tag }}/installed/x64-windows/bin/osgPlugins-3.6.5 ${{ github.workspace }}/build - cp ${{ github.workspace }}/deps/vcpkg-x64-${{ inputs.image }}-${{ inputs.vcpkg-deps-tag }}/installed/x64-windows/bin/*.dll ${{ github.workspace }}/build + cp ${{ github.workspace }}/deps/vcpkg-x64-${{ inputs.msvc }}-${{ inputs.vcpkg-deps-tag }}/installed/x64-windows/bin/Release/MyGUIEngine.dll ${{ github.workspace }}/build + cp -Filter *.dll -Recurse ${{ github.workspace }}/deps/vcpkg-x64-${{ inputs.msvc }}-${{ inputs.vcpkg-deps-tag }}/installed/x64-windows/bin/osgPlugins-3.6.5 ${{ github.workspace }}/build + cp ${{ github.workspace }}/deps/vcpkg-x64-${{ inputs.msvc }}-${{ inputs.vcpkg-deps-tag }}/installed/x64-windows/bin/*.dll ${{ github.workspace }}/build - name: Copy Qt DLLs working-directory: ${{ github.workspace }}/deps/Qt/6.6.3/msvc2019_64 @@ -172,7 +176,7 @@ jobs: env: GH_TOKEN: ${{ github.token }} run: | - job_url=$(gh run --repo ${{ github.repository }} view ${{ github.run_id }} --json jobs --jq '.jobs[] | select(.name == "windows-${{ inputs.image }}") | .url') + job_url=$(gh run --repo ${{ github.repository }} view ${{ github.run_id }} --json jobs --jq '.jobs[] | select(.name == "windows-${{ inputs.msvc }}") | .url') printf "Ref ${{ github.ref }}\nJob ${job_url}\nCommit ${{ github.sha }}\n" > install/CI-ID.txt cp install/CI-ID.txt pdb/CI-ID.txt cp install/CI-ID.txt SymStore/CI-ID.txt @@ -180,19 +184,19 @@ jobs: - name: Store OpenMW archived pdb files uses: actions/upload-artifact@v4 with: - name: openmw-windows-${{ inputs.image }}-pdb-${{ github.sha }} + name: openmw-windows-${{ inputs.msvc }}-pdb-${{ github.sha }} path: ${{ github.workspace }}/pdb/* - name: Store OpenMW build artifacts uses: actions/upload-artifact@v4 with: - name: openmw-windows-${{ inputs.image }}-${{ github.sha }} + name: openmw-windows-${{ inputs.msvc }}-${{ github.sha }} path: ${{ github.workspace }}/install/* - name: Store symbol server artifacts uses: actions/upload-artifact@v4 with: - name: openmw-windows-${{ inputs.image }}-sym-store-${{ github.sha }} + name: openmw-windows-${{ inputs.msvc }}-sym-store-${{ github.sha }} path: ${{ github.workspace }}/SymStore/* - name: Upload to symbol server diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 52fa51d299..dc638729c4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -467,7 +467,7 @@ Ubuntu_Clang_tests_Debug: stage: test variables: PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" - EXAMPLE_SUITE_REVISION: f51b832e033429a7cdc520e0e48d7dfdb9141caa + EXAMPLE_SUITE_REVISION: 599987b52bd2d064e26299d3317b11049499f32b cache: paths: - .cache/pip diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 962b34f516..926dd3dbf6 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -10,4 +10,4 @@ python: build: os: ubuntu-22.04 tools: - python: "3.8" + python: "3.9" diff --git a/AUTHORS.md b/AUTHORS.md index 156d526917..e327bc29d6 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -53,6 +53,7 @@ Programmers Carl Maxwell cc9cii Cédric Mocquillon + Charles Horn Chris Boyce (slothlife) Chris Robinson (KittyCat) Chris Vigil diff --git a/CHANGELOG.md b/CHANGELOG.md index 8310cb8c32..cbffc23bc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ 0.50.0 ------ + Feature #8285: Expose list of active shaders in postprocessing API 0.49.0 ------ diff --git a/CMakeLists.txt b/CMakeLists.txt index a7d7bf9b3f..7301496172 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,7 +82,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 50) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 76) +set(OPENMW_LUA_API_REVISION 78) set(OPENMW_POSTPROCESSING_API_REVISION 2) set(OPENMW_VERSION_COMMITHASH "") diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 8c7c238b4a..a8dee709da 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -11,6 +11,23 @@ namespace sfs = std::filesystem; +namespace +{ + // from configfileparser.cpp + std::string trim_ws(const std::string& s) + { + std::string::size_type n, n2; + n = s.find_first_not_of(" \t\r\n"); + if (n == std::string::npos) + return std::string(); + else + { + n2 = s.find_last_not_of(" \t\r\n"); + return s.substr(n, n2 - n + 1); + } + } +} + MwIniImporter::MwIniImporter() : mVerbose(false) , mEncoding(ToUTF8::WINDOWS_1250) @@ -352,12 +369,10 @@ MwIniImporter::multistrmap MwIniImporter::loadCfgFile(const std::filesystem::pat std::string line; while (std::getline(file, line)) { - - // we cant say comment by only looking at first char anymore - int comment_pos = static_cast(line.find('#')); - if (comment_pos > 0) + // ignore comments - keep in sync with configfileparser.cpp + if (line.find('#') == line.find_first_not_of(" \t\r\n")) { - line = line.substr(0, comment_pos); + continue; } if (line.empty()) @@ -373,6 +388,8 @@ MwIniImporter::multistrmap MwIniImporter::loadCfgFile(const std::filesystem::pat std::string key(line.substr(0, pos)); std::string value(line.substr(pos + 1)); + key = trim_ws(key); + value = trim_ws(value); if (map.find(key) == map.end()) { diff --git a/apps/mwiniimporter/main.cpp b/apps/mwiniimporter/main.cpp index 0beb2b38cc..6e4242cb4e 100644 --- a/apps/mwiniimporter/main.cpp +++ b/apps/mwiniimporter/main.cpp @@ -126,12 +126,20 @@ int wmain(int argc, wchar_t* wargv[]) MwIniImporter importer; importer.setVerbose(vm.count("verbose") != 0); + MwIniImporter::multistrmap cfg = importer.loadCfgFile(cfgFile); + // Font encoding settings - std::string encoding(vm["encoding"].as()); + std::string encoding; + if (vm["encoding"].defaulted() && cfg.contains("encoding") && !cfg["encoding"].empty()) + encoding = cfg["encoding"].back(); + else + { + encoding = vm["encoding"].as(); + cfg["encoding"] = { encoding }; + } importer.setInputEncoding(ToUTF8::calculateEncoding(encoding)); MwIniImporter::multistrmap ini = importer.loadIniFile(iniFile); - MwIniImporter::multistrmap cfg = importer.loadCfgFile(cfgFile); if (!vm.count("fonts")) { diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 330551f91a..39fe9c4f7a 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -60,9 +60,9 @@ add_openmw_dir (mwscript add_openmw_dir (mwlua luamanagerimp object objectlists userdataserializer luaevents engineevents objectvariant - context menuscripts globalscripts localscripts playerscripts luabindings objectbindings cellbindings + context menuscripts globalscripts localscripts playerscripts luabindings objectbindings cellbindings coremwscriptbindings mwscriptbindings camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings dialoguebindings - postprocessingbindings stats recordstore debugbindings corebindings worldbindings worker magicbindings factionbindings + postprocessingbindings stats recordstore debugbindings corebindings worldbindings worker landbindings magicbindings factionbindings classbindings itemdata inputprocessor animationbindings birthsignbindings racebindings markupbindings types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus diff --git a/apps/openmw/mwdialogue/keywordsearch.hpp b/apps/openmw/mwdialogue/keywordsearch.hpp index 2c98eac218..7032925321 100644 --- a/apps/openmw/mwdialogue/keywordsearch.hpp +++ b/apps/openmw/mwdialogue/keywordsearch.hpp @@ -3,6 +3,7 @@ #include // std::reverse #include +#include #include #include #include @@ -66,6 +67,8 @@ namespace MWDialogue return false; } + static bool isWordSeparator(char c) { return std::strchr("\n\r \t'\"", c) != nullptr; } + static bool sortMatches(const Match& left, const Match& right) { return left.mBeg < right.mBeg; } void highlightKeywords(Point beg, Point end, std::vector& out) const @@ -73,6 +76,14 @@ namespace MWDialogue std::vector matches; for (Point i = beg; i != end; ++i) { + if (i != beg) + { + Point prev = i; + --prev; + if (!isWordSeparator(*prev)) + continue; + } + // check first character typename Entry::childen_t::const_iterator candidate = mRoot.mChildren.find(Misc::StringUtils::toLower(*i)); @@ -86,7 +97,6 @@ namespace MWDialogue // some keywords might be longer variations of other keywords, so we definitely need a list of // candidates the first element in the pair is length of the match, i.e. depth from the first character - // on std::vector> candidates; while ((j + 1) != end) diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 6508622bba..18963ea50a 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -653,10 +653,13 @@ namespace MWGui if (mTopicsList->getItemCount() > 0) mTopicsList->addSeparator(); + // Morrowind uses 3 px invisible borders for padding topics + constexpr int verticalPadding = 3; + for (const auto& keyword : mKeywords) { std::string topicId = Misc::StringUtils::lowerCase(keyword); - mTopicsList->addItem(keyword); + mTopicsList->addItem(keyword, verticalPadding); auto t = std::make_unique(keyword); mKeywordSearch.seed(topicId, intptr_t(t.get())); diff --git a/apps/openmw/mwgui/postprocessorhud.cpp b/apps/openmw/mwgui/postprocessorhud.cpp index 8f5b20ba98..7712594c54 100644 --- a/apps/openmw/mwgui/postprocessorhud.cpp +++ b/apps/openmw/mwgui/postprocessorhud.cpp @@ -301,7 +301,7 @@ namespace MWGui auto technique = processor->loadTechnique(name); - if (!technique || technique->getStatus() == fx::Technique::Status::File_Not_exists) + if (technique->getStatus() == fx::Technique::Status::File_Not_exists) return; while (mConfigArea->getChildCount() > 0) @@ -432,9 +432,6 @@ namespace MWGui { auto technique = processor->loadTechnique(name); - if (!technique) - continue; - if (!technique->getHidden() && !processor->isTechniqueEnabled(technique)) { std::string lowerName = Utf8Stream::lowerCaseUtf8(name); diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index b69b9535ea..e0b10f6b37 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -232,12 +232,11 @@ namespace MWGui << MyGUI::TextIterator::toTagsString(MyGUI::UString(className)) << ")"; const MyGUI::UString playerDesc = MyGUI::LanguageManager::getInstance().replaceTags(title.str()); - mCharacterSelection->addItem(playerDesc, signature.mPlayerName); + mCharacterSelection->addItem(playerDesc, &*it); if (mCurrentCharacter == &*it || (!mCurrentCharacter && !mSaving - && Misc::StringUtils::ciEqual( - directory, Files::pathToUnicodeString(it->begin()->mPath.parent_path().filename())))) + && Misc::StringUtils::ciEqual(directory, Files::pathToUnicodeString(it->getPath().filename())))) { mCurrentCharacter = &*it; selectedIndex = mCharacterSelection->getItemCount() - 1; @@ -245,6 +244,11 @@ namespace MWGui } } + if (selectedIndex == MyGUI::ITEM_NONE && !mSaving && mCharacterSelection->getItemCount() != 0) + { + selectedIndex = 0; + mCurrentCharacter = *mCharacterSelection->getItemDataAt(0); + } mCharacterSelection->setIndexSelected(selectedIndex); if (selectedIndex == MyGUI::ITEM_NONE) mCharacterSelection->setCaptionWithReplacing("#{OMWEngine:SelectCharacter}..."); @@ -356,16 +360,7 @@ namespace MWGui void SaveGameDialog::onCharacterSelected(MyGUI::ComboBox* sender, size_t pos) { - MWBase::StateManager* mgr = MWBase::Environment::get().getStateManager(); - - unsigned int i = 0; - const MWState::Character* character = nullptr; - for (MWBase::StateManager::CharacterIterator it = mgr->characterBegin(); it != mgr->characterEnd(); ++it, ++i) - { - if (i == pos) - character = &*it; - } - assert(character && "Can't find selected character"); + const MWState::Character* character = *mCharacterSelection->getItemDataAt(pos); mCurrentCharacter = character; mCurrentSlot = nullptr; @@ -436,7 +431,7 @@ namespace MWGui mSaveNameEdit->setCaption(sender->getItemNameAt(pos)); mCurrentSlot = nullptr; - unsigned int i = 0; + size_t i = 0; for (MWState::Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); ++it, ++i) { @@ -450,8 +445,9 @@ namespace MWGui const size_t profileIndex = mCharacterSelection->getIndexSelected(); const std::string& slotPlayerName = mCurrentSlot->mProfile.mPlayerName; - const std::string& profilePlayerName = *mCharacterSelection->getItemDataAt(profileIndex); - if (slotPlayerName != profilePlayerName) + const ESM::SavedGame& profileSavedGame + = (*mCharacterSelection->getItemDataAt(profileIndex))->getSignature(); + if (slotPlayerName != profileSavedGame.mPlayerName) text << slotPlayerName << "\n"; text << "#{OMWEngine:Level} " << mCurrentSlot->mProfile.mPlayerLevel << "\n"; diff --git a/apps/openmw/mwlua/README.md b/apps/openmw/mwlua/README.md index ed911eb01d..a1cfeb1f1b 100644 --- a/apps/openmw/mwlua/README.md +++ b/apps/openmw/mwlua/README.md @@ -3,7 +3,7 @@ This folder contains the C++ implementation of the Lua scripting system. For user-facing documentation, see -[OpenMW Lua scripting](https://openmw.readthedocs.io/en/latest/reference/lua-scripting/index.html). +[Lua scripting](https://openmw.readthedocs.io/en/latest/reference/lua-scripting/index.html). The documentation is generated from [/docs/source/reference/lua-scripting](/docs/source/reference/lua-scripting). You can find instructions for generating the documentation at the diff --git a/apps/openmw/mwlua/corebindings.cpp b/apps/openmw/mwlua/corebindings.cpp index 9df435c00d..b85c14578c 100644 --- a/apps/openmw/mwlua/corebindings.cpp +++ b/apps/openmw/mwlua/corebindings.cpp @@ -19,8 +19,10 @@ #include "../mwworld/datetimemanager.hpp" #include "../mwworld/esmstore.hpp" +#include "coremwscriptbindings.hpp" #include "dialoguebindings.hpp" #include "factionbindings.hpp" +#include "landbindings.hpp" #include "luaevents.hpp" #include "magicbindings.hpp" #include "soundbindings.hpp" @@ -97,6 +99,11 @@ namespace MWLua api["stats"] = context.cachePackage("openmw_core_stats", [context]() { return initCoreStatsBindings(context); }); + api["mwscripts"] + = context.cachePackage("openmw_core_mwscripts", [context]() { return initCoreMwScriptBindings(context); }); + + api["land"] = context.cachePackage("openmw_core_land", [context]() { return initCoreLandBindings(context); }); + api["factions"] = context.cachePackage("openmw_core_factions", [context]() { return initCoreFactionBindings(context); }); api["dialogue"] diff --git a/apps/openmw/mwlua/coremwscriptbindings.cpp b/apps/openmw/mwlua/coremwscriptbindings.cpp new file mode 100644 index 0000000000..6bf01a4b3d --- /dev/null +++ b/apps/openmw/mwlua/coremwscriptbindings.cpp @@ -0,0 +1,28 @@ +#include "coremwscriptbindings.hpp" + +#include + +#include "../mwworld/esmstore.hpp" + +#include "context.hpp" +#include "recordstore.hpp" + +namespace MWLua +{ + sol::table initCoreMwScriptBindings(const Context& context) + { + sol::state_view lua = context.sol(); + sol::table api(lua, sol::create); + + auto recordBindingsClass = lua.new_usertype("ESM3_Script"); + recordBindingsClass[sol::meta_function::to_string] + = [](const ESM::Script& rec) { return "ESM3_Script[" + rec.mId.toDebugString() + "]"; }; + recordBindingsClass["id"] + = sol::readonly_property([](const ESM::Script& rec) { return rec.mId.serializeText(); }); + recordBindingsClass["text"] = sol::readonly_property([](const ESM::Script& rec) { return rec.mScriptText; }); + + addRecordFunctionBinding(api, context); + + return LuaUtil::makeReadOnly(api); + } +} diff --git a/apps/openmw/mwlua/coremwscriptbindings.hpp b/apps/openmw/mwlua/coremwscriptbindings.hpp new file mode 100644 index 0000000000..0f69b7dcb7 --- /dev/null +++ b/apps/openmw/mwlua/coremwscriptbindings.hpp @@ -0,0 +1,13 @@ +#ifndef MWLUA_COREMWSCRIPTBINDINGS_H +#define MWLUA_COREMWSCRIPTBINDINGS_H + +#include + +namespace MWLua +{ + struct Context; + + sol::table initCoreMwScriptBindings(const Context& context); +} + +#endif // MWLUA_COREMWSCRIPTBINDINGS_H diff --git a/apps/openmw/mwlua/landbindings.cpp b/apps/openmw/mwlua/landbindings.cpp new file mode 100644 index 0000000000..3f85e2b066 --- /dev/null +++ b/apps/openmw/mwlua/landbindings.cpp @@ -0,0 +1,124 @@ +#include "landbindings.hpp" + +#include +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwworld/cellstore.hpp" +#include "../mwworld/esmstore.hpp" +#include "../mwworld/worldmodel.hpp" +#include "object.hpp" + +namespace +{ + // Takes in a corrected world pos to match the visuals. + ESMTerrain::UniqueTextureId getTextureAt(const std::span landData, const int plugin, + const osg::Vec3f& correctedWorldPos, const float cellSize) + { + int cellX = static_cast(std::floor(correctedWorldPos.x() / cellSize)); + int cellY = static_cast(std::floor(correctedWorldPos.y() / cellSize)); + + // Normalized position in the cell + float nX = (correctedWorldPos.x() - (cellX * cellSize)) / cellSize; + float nY = (correctedWorldPos.y() - (cellY * cellSize)) / cellSize; + + int startX = static_cast(nX * ESM::Land::LAND_TEXTURE_SIZE); + int startY = static_cast(nY * ESM::Land::LAND_TEXTURE_SIZE); + + assert(startX < ESM::Land::LAND_TEXTURE_SIZE); + assert(startY < ESM::Land::LAND_TEXTURE_SIZE); + + const std::uint16_t tex = landData[startY * ESM::Land::LAND_TEXTURE_SIZE + startX]; + if (tex == 0) + return { 0, 0 }; // vtex 0 is always the base texture, regardless of plugin + + return { tex, plugin }; + } + + const ESM::RefId worldspaceAt(sol::object cellOrId) + { + const MWWorld::Cell* cell = nullptr; + if (cellOrId.is()) + cell = cellOrId.as().mStore->getCell(); + else if (cellOrId.is()) + cell = cellOrId.as().mStore->getCell(); + else if (cellOrId.is() && !cellOrId.as().empty()) + cell = MWBase::Environment::get() + .getWorldModel() + ->getCell(ESM::RefId::deserializeText(cellOrId.as())) + .getCell(); + if (cell == nullptr) + throw std::runtime_error("Invalid cell"); + else if (!cell->isExterior()) + throw std::runtime_error("Cell cannot be interior"); + + return cell->getWorldSpace(); + } +} + +namespace MWLua +{ + sol::table initCoreLandBindings(const Context& context) + { + sol::state_view lua = context.sol(); + sol::table landApi(lua, sol::create); + + landApi["getHeightAt"] = [](const osg::Vec3f& pos, sol::object cellOrId) { + ESM::RefId worldspace = worldspaceAt(cellOrId); + return MWBase::Environment::get().getWorld()->getTerrainHeightAt(pos, worldspace); + }; + + landApi["getTextureAt"] = [lua = lua](const osg::Vec3f& pos, sol::object cellOrId) { + sol::variadic_results values; + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + const MWWorld::Store& landStore = store.get(); + ESM::RefId worldspace = worldspaceAt(cellOrId); + + if (worldspace != ESM::Cell::sDefaultWorldspaceId) + return values; + + const float cellSize = ESM::getCellSize(worldspace); + const float offset = (cellSize / ESM::LandRecordData::sLandTextureSize) * 0.25; + const osg::Vec3f correctedPos = pos + osg::Vec3f{ -offset, +offset, 0.0f }; + + const ESM::Land* land = nullptr; + const ESM::Land::LandData* landData = nullptr; + + int cellX = static_cast(std::floor(correctedPos.x() / cellSize)); + int cellY = static_cast(std::floor(correctedPos.y() / cellSize)); + + land = landStore.search(cellX, cellY); + + if (land != nullptr) + landData = land->getLandData(ESM::Land::DATA_VTEX); + + // If we fail to preload land data, return, we need to be able to get *any* land to know how to correct + // the position used to sample terrain + if (landData == nullptr) + return values; + + const ESMTerrain::UniqueTextureId textureId + = getTextureAt(landData->mTextures, land->getPlugin(), correctedPos, cellSize); + + // Need to check for 0, 0 so that we can safely subtract 1 later, as per documentation on UniqueTextureId + if (textureId.first != 0) + { + const MWWorld::Store& textureStore = store.get(); + const std::string* textureString = textureStore.search(textureId.first - 1, textureId.second); + if (!textureString) + return values; + + values.push_back(sol::make_object(lua, *textureString)); + const std::vector& contentList = MWBase::Environment::get().getWorld()->getContentFiles(); + if (textureId.second >= 0 && static_cast(textureId.second) < contentList.size()) + values.push_back(sol::make_object(lua, contentList[textureId.second])); + } + + return values; + }; + + return LuaUtil::makeReadOnly(landApi); + } +} diff --git a/apps/openmw/mwlua/landbindings.hpp b/apps/openmw/mwlua/landbindings.hpp new file mode 100644 index 0000000000..8cdf30046b --- /dev/null +++ b/apps/openmw/mwlua/landbindings.hpp @@ -0,0 +1,11 @@ +#ifndef MWLUA_LANDBINDINGS_H +#define MWLUA_LANDBINDINGS_H + +#include "context.hpp" + +namespace MWLua +{ + sol::table initCoreLandBindings(const Context& context); +} + +#endif // MWLUA_LANDBINDINGS_H diff --git a/apps/openmw/mwlua/postprocessingbindings.cpp b/apps/openmw/mwlua/postprocessingbindings.cpp index e64bf0fa9e..6127cb4b27 100644 --- a/apps/openmw/mwlua/postprocessingbindings.cpp +++ b/apps/openmw/mwlua/postprocessingbindings.cpp @@ -1,5 +1,7 @@ #include "postprocessingbindings.hpp" +#include "MyGUI_LanguageManager.h" + #include #include "../mwbase/environment.hpp" @@ -8,6 +10,14 @@ #include "luamanagerimp.hpp" +namespace +{ + std::string getLocalizedMyGUIString(std::string_view unlocalized) + { + return MyGUI::LanguageManager::getInstance().replaceTags(std::string(unlocalized)).asUTF8(); + } +} + namespace MWLua { struct Shader; @@ -37,7 +47,7 @@ namespace MWLua if (!mShader) return "Shader(nil)"; - return Misc::StringUtils::format("Shader(%s, %s)", mShader->getName(), mShader->getFileName()); + return Misc::StringUtils::format("Shader(%s, %s)", mShader->getName(), mShader->getFileName().value()); } enum @@ -139,6 +149,15 @@ namespace MWLua return MWBase::Environment::get().getWorld()->getPostProcessor()->isTechniqueEnabled(shader.mShader); }; + shader["name"] = sol::readonly_property( + [](const Shader& shader) { return getLocalizedMyGUIString(shader.mShader->getName()); }); + shader["author"] = sol::readonly_property( + [](const Shader& shader) { return getLocalizedMyGUIString(shader.mShader->getAuthor()); }); + shader["description"] = sol::readonly_property( + [](const Shader& shader) { return getLocalizedMyGUIString(shader.mShader->getDescription()); }); + shader["version"] = sol::readonly_property( + [](const Shader& shader) { return getLocalizedMyGUIString(shader.mShader->getVersion()); }); + shader["setBool"] = getSetter(context); shader["setFloat"] = getSetter(context); shader["setInt"] = getSetter(context); @@ -158,12 +177,23 @@ namespace MWLua if (!shader.mShader || !shader.mShader->isValid()) throw std::runtime_error(Misc::StringUtils::format("Failed loading shader '%s'", name)); - if (!shader.mShader->getDynamic()) - throw std::runtime_error(Misc::StringUtils::format("Shader '%s' is not marked as dynamic", name)); - return shader; }; + api["getChain"] = [context]() { + sol::table chain(context.sol(), sol::create); + + for (const auto& shader : MWBase::Environment::get().getWorld()->getPostProcessor()->getChain()) + { + // Don't expose internal shaders to the API, they should be invisible to the user + if (shader->getInternal()) + continue; + chain.add(Shader(shader)); + } + + return chain; + }; + return LuaUtil::makeReadOnly(api); } diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index 7035c7f61c..91e24f946f 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -174,7 +174,7 @@ namespace return -1; } - void addBoundItem(const ESM::RefId& itemId, const MWWorld::Ptr& actor) + bool addBoundItem(const ESM::RefId& itemId, const MWWorld::Ptr& actor) { MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); MWWorld::Ptr boundPtr = *store.MWWorld::ContainerStore::add(itemId, 1); @@ -185,9 +185,6 @@ namespace MWWorld::ActionEquip action(boundPtr); action.execute(actor); - if (actor != MWMechanics::getPlayer()) - return; - MWWorld::Ptr newItem; auto it = slot >= 0 ? store.getSlot(slot) : store.end(); // Equip can fail because beast races cannot equip boots/helmets @@ -195,16 +192,21 @@ namespace newItem = *it; if (newItem.isEmpty() || boundPtr != newItem) - return; + return false; - MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); + if (actor == MWMechanics::getPlayer()) + { + MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); - // change draw state only if the item is in player's right hand - if (slot == MWWorld::InventoryStore::Slot_CarriedRight) - player.setDrawState(MWMechanics::DrawState::Weapon); + // change draw state only if the item is in player's right hand + if (slot == MWWorld::InventoryStore::Slot_CarriedRight) + player.setDrawState(MWMechanics::DrawState::Weapon); - if (prevItem != store.end()) - player.setPreviousItem(itemId, prevItem->getCellRef().getRefId()); + if (prevItem != store.end()) + player.setPreviousItem(itemId, prevItem->getCellRef().getRefId()); + } + + return true; } void removeBoundItem(const ESM::RefId& itemId, const MWWorld::Ptr& actor) @@ -626,16 +628,19 @@ namespace MWMechanics case ESM::MagicEffect::BoundHelm: case ESM::MagicEffect::BoundBoots: case ESM::MagicEffect::BoundShield: + { if (!target.getClass().hasInventoryStore(target)) - invalid = true; - else { - const std::string& item = sBoundItemsMap.at(effect.mEffectId); - addBoundItem(ESM::RefId::stringRefId( - world->getStore().get().find(item)->mValue.getString()), - target); + invalid = true; + break; } + const std::string& item = sBoundItemsMap.at(effect.mEffectId); + const MWWorld::Store& gmst = world->getStore().get(); + const ESM::RefId itemId = ESM::RefId::stringRefId(gmst.find(item)->mValue.getString()); + if (!addBoundItem(itemId, target)) + effect.mTimeLeft = 0.f; break; + } case ESM::MagicEffect::FireDamage: case ESM::MagicEffect::ShockDamage: case ESM::MagicEffect::FrostDamage: diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index 8b835a4d47..1f1b7258b3 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -348,7 +348,7 @@ namespace MWRender for (auto& technique : mTechniques) { - if (!technique || technique->getStatus() == fx::Technique::Status::File_Not_exists) + if (technique->getStatus() == fx::Technique::Status::File_Not_exists) continue; const auto lastWriteTime = std::filesystem::last_write_time(mTechniqueFileMap[technique->getName()]); @@ -570,7 +570,7 @@ namespace MWRender for (const auto& technique : mTechniques) { - if (!technique || !technique->isValid()) + if (!technique->isValid()) continue; if (technique->getGLSLVersion() > mGLSLVersion) @@ -712,7 +712,7 @@ namespace MWRender PostProcessor::Status PostProcessor::enableTechnique( std::shared_ptr technique, std::optional location) { - if (!technique || technique->getLocked() || (location.has_value() && location.value() < 0)) + if (technique->getLocked() || (location.has_value() && location.value() < 0)) return Status_Error; disableTechnique(technique, false); @@ -727,7 +727,7 @@ namespace MWRender PostProcessor::Status PostProcessor::disableTechnique(std::shared_ptr technique, bool dirty) { - if (!technique || technique->getLocked()) + if (technique->getLocked()) return Status_Error; auto it = std::find(mTechniques.begin(), mTechniques.end(), technique); @@ -743,9 +743,6 @@ namespace MWRender bool PostProcessor::isTechniqueEnabled(const std::shared_ptr& technique) const { - if (!technique) - return false; - if (auto it = std::find(mTechniques.begin(), mTechniques.end(), technique); it == mTechniques.end()) return false; @@ -786,6 +783,11 @@ namespace MWRender return mTemplates.back(); } + PostProcessor::TechniqueList PostProcessor::getChain() + { + return mTechniques; + } + void PostProcessor::loadChain() { mTechniques.clear(); @@ -812,7 +814,7 @@ namespace MWRender for (const auto& technique : mTechniques) { - if (!technique || technique->getDynamic() || technique->getInternal()) + if (technique->getDynamic() || technique->getInternal()) continue; chain.push_back(technique->getName()); } @@ -830,9 +832,10 @@ namespace MWRender void PostProcessor::disableDynamicShaders() { - for (auto& technique : mTechniques) - if (technique && technique->getDynamic()) - disableTechnique(technique); + auto erased = std::erase_if(mTechniques, [](const auto& technique) { return technique->getDynamic(); }); + + if (erased) + dirtyTechniques(); } int PostProcessor::renderWidth() const diff --git a/apps/openmw/mwrender/postprocessor.hpp b/apps/openmw/mwrender/postprocessor.hpp index af6eeae62b..6b1f4612f1 100644 --- a/apps/openmw/mwrender/postprocessor.hpp +++ b/apps/openmw/mwrender/postprocessor.hpp @@ -178,6 +178,8 @@ namespace MWRender std::shared_ptr loadTechnique(const std::string& name, bool loadNextFrame = false); + TechniqueList getChain(); + bool isEnabled() const { return mUsePostProcessing; } void disable(); diff --git a/apps/openmw/mwscript/globalscripts.hpp b/apps/openmw/mwscript/globalscripts.hpp index 083695b51b..47c3f1ef6c 100644 --- a/apps/openmw/mwscript/globalscripts.hpp +++ b/apps/openmw/mwscript/globalscripts.hpp @@ -64,6 +64,8 @@ namespace MWScript void removeScript(const ESM::RefId& name); + const std::unordered_map>& getScripts() const { return mScripts; } + bool isRunning(const ESM::RefId& name) const; void run(); diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 95238d4299..06abbe5bb2 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -1192,6 +1192,10 @@ namespace MWScript MWBase::World* world = MWBase::Environment::get().getWorld(); std::vector names = runtime.getContext().getGlobals(); + + // sort for user convenience + std::sort(names.begin(), names.end()); + for (size_t i = 0; i < names.size(); ++i) { char type = world->getGlobalVariableType(names[i]); @@ -1223,6 +1227,50 @@ namespace MWScript runtime.getContext().report(str.str()); } + void printGlobalScriptsVars(Interpreter::Runtime& runtime) + { + std::stringstream str; + str << std::endl << "Global Scripts:"; + + const auto& scripts = MWBase::Environment::get().getScriptManager()->getGlobalScripts().getScripts(); + + // sort for user convenience + std::map> globalScripts(scripts.begin(), scripts.end()); + + auto printVariables + = [&str](const ESM::RefId& scptName, const auto& names, const auto& values, std::string_view type) { + size_t size = std::min(names.size(), values.size()); + for (size_t i = 0; i < size; ++i) + { + str << std::endl + << " " << scptName << "->" << names[i] << " = " << values[i] << " (" << type << ")"; + } + }; + + for (const auto& [refId, script] : globalScripts) + { + // Skip dormant global scripts + if (!script->mRunning) + continue; + + const Compiler::Locals& complocals + = MWBase::Environment::get().getScriptManager()->getLocals(refId); + const Locals& locals + = MWBase::Environment::get().getScriptManager()->getGlobalScripts().getLocals(refId); + + if (locals.isEmpty()) + str << std::endl << " No variables in script " << refId; + else + { + printVariables(refId, complocals.get('s'), locals.mShorts, "short"); + printVariables(refId, complocals.get('l'), locals.mLongs, "long"); + printVariables(refId, complocals.get('f'), locals.mFloats, "float"); + } + } + + runtime.getContext().report(str.str()); + } + public: void execute(Interpreter::Runtime& runtime) override { @@ -1233,6 +1281,7 @@ namespace MWScript { // No reference, no problem. printGlobalVars(runtime); + printGlobalScriptsVars(runtime); } } }; diff --git a/apps/openmw/mwstate/character.cpp b/apps/openmw/mwstate/character.cpp index 3c02311458..22d7e7ba1e 100644 --- a/apps/openmw/mwstate/character.cpp +++ b/apps/openmw/mwstate/character.cpp @@ -18,14 +18,25 @@ bool MWState::operator<(const Slot& left, const Slot& right) return left.mTimeStamp < right.mTimeStamp; } -std::string MWState::getFirstGameFile(const std::vector& contentFiles) +bool MWState::operator<(const Character& left, const Character& right) +{ + if (left.mSlots.empty() && right.mSlots.empty()) + return left.mPath < right.mPath; + else if (left.mSlots.empty()) + return false; + else if (right.mSlots.empty()) + return true; + return right.mSlots.back() < left.mSlots.back(); +} + +std::string_view MWState::getFirstGameFile(const std::vector& contentFiles) { for (const std::string& c : contentFiles) { if (Misc::StringUtils::ciEndsWith(c, ".esm") || Misc::StringUtils::ciEndsWith(c, ".omwgame")) return c; } - return ""; + return {}; } void MWState::Character::addSlot(const std::filesystem::path& path, const std::string& game) diff --git a/apps/openmw/mwstate/character.hpp b/apps/openmw/mwstate/character.hpp index 3c68d9f490..4e51301bba 100644 --- a/apps/openmw/mwstate/character.hpp +++ b/apps/openmw/mwstate/character.hpp @@ -2,6 +2,7 @@ #define GAME_STATE_CHARACTER_H #include +#include #include @@ -14,9 +15,7 @@ namespace MWState std::filesystem::file_time_type mTimeStamp; }; - bool operator<(const Slot& left, const Slot& right); - - std::string getFirstGameFile(const std::vector& contentFiles); + std::string_view getFirstGameFile(const std::vector& contentFiles); class Character { @@ -63,7 +62,12 @@ namespace MWState ///< Return signature information for this character. /// /// \attention This function must not be called if there are no slots. + + friend bool operator<(const Character& left, const Character& right); }; + + bool operator<(const Slot& left, const Slot& right); + bool operator<(const Character& left, const Character& right); } #endif diff --git a/apps/openmw/mwstate/charactermanager.cpp b/apps/openmw/mwstate/charactermanager.cpp index 32f0fe0aef..6d2583776b 100644 --- a/apps/openmw/mwstate/charactermanager.cpp +++ b/apps/openmw/mwstate/charactermanager.cpp @@ -18,10 +18,8 @@ MWState::CharacterManager::CharacterManager(std::filesystem::path saves, const s } else { - for (std::filesystem::directory_iterator iter(mPath); iter != std::filesystem::directory_iterator(); ++iter) + for (const std::filesystem::path& characterDir : std::filesystem::directory_iterator(mPath)) { - std::filesystem::path characterDir = *iter; - if (std::filesystem::is_directory(characterDir)) { Character character(characterDir, mGame); @@ -30,6 +28,7 @@ MWState::CharacterManager::CharacterManager(std::filesystem::path saves, const s mCharacters.push_back(character); } } + mCharacters.sort(); } } diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index b019dabdfe..6ea510e1e2 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -274,8 +274,8 @@ namespace MWWorld pos.z() += mPhysics->getRenderingHalfExtents(caster).z() * 2 * Constants::TorsoHeight; } - if (MWBase::Environment::get().getWorld()->isUnderwater( - caster.getCell(), pos)) // Underwater casting not possible + // Actors can't cast target spells underwater + if (caster.getClass().isActor() && MWBase::Environment::get().getWorld()->isUnderwater(caster.getCell(), pos)) return; osg::Quat orient; @@ -564,15 +564,19 @@ namespace MWWorld for (const auto& sound : magicBoltState.mSounds) sound->setPosition(pos); - if (projectile->isActive()) + const Ptr caster = magicBoltState.getCaster(); + + const MWBase::World& world = *MWBase::Environment::get().getWorld(); + const bool active = projectile->isActive(); + if (active && !world.isUnderwater(caster.getCell(), pos)) continue; - const auto target = projectile->getTarget(); - const auto caster = magicBoltState.getCaster(); + const Ptr target = !active ? projectile->getTarget() : Ptr(); + assert(target != caster); MWMechanics::CastSpell cast(caster, target); - cast.mHitPosition = Misc::Convert::toOsg(projectile->getHitPosition()); + cast.mHitPosition = !active ? Misc::Convert::toOsg(projectile->getHitPosition()) : pos; cast.mId = magicBoltState.mSpellId; cast.mSourceName = magicBoltState.mSourceName; cast.mItem = magicBoltState.mItem; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 57d794c535..c642d14b16 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3047,28 +3047,31 @@ namespace MWWorld // TODO: as a better solutuon we should handle projectiles during physics update, not during world update. const osg::Vec3f sourcePos = worldPos + orient * osg::Vec3f(0, -1, 0) * 64.f; - // Early out if the launch position is underwater - bool underwater = isUnderwater(MWMechanics::getPlayer().getCell(), worldPos); - if (underwater) - { - MWMechanics::projectileHit(actor, Ptr(), bow, projectile, worldPos, attackStrength); - mRendering->emitWaterRipple(worldPos); - return; - } - // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit // result. std::vector targetActors; if (!actor.isEmpty() && actor.getClass().isActor() && actor != MWMechanics::getPlayer()) actor.getClass().getCreatureStats(actor).getAiSequence().getCombatTargets(targetActors); - // Check for impact, if yes, handle hit, if not, launch projectile + // Check for impact, if yes, handle hit MWPhysics::RayCastingResult result = mPhysics->castRay( sourcePos, worldPos, { actor }, targetActors, 0xff, MWPhysics::CollisionType_Projectile); + if (result.mHit) + { MWMechanics::projectileHit(actor, result.mHitObject, bow, projectile, result.mHitPos, attackStrength); - else - mProjectileManager->launchProjectile(actor, projectile, worldPos, orient, bow, speed, attackStrength); + return; + } + + // Bail out if the launch position is underwater + if (isUnderwater(MWMechanics::getPlayer().getCell(), worldPos)) + { + MWMechanics::projectileHit(actor, Ptr(), bow, projectile, worldPos, attackStrength); + mRendering->emitWaterRipple(worldPos); + return; + } + + mProjectileManager->launchProjectile(actor, projectile, worldPos, orient, bow, speed, attackStrength); } void World::launchMagicBolt( diff --git a/apps/openmw_tests/mwdialogue/testkeywordsearch.cpp b/apps/openmw_tests/mwdialogue/testkeywordsearch.cpp index a3f0d8d3c0..0eb996019e 100644 --- a/apps/openmw_tests/mwdialogue/testkeywordsearch.cpp +++ b/apps/openmw_tests/mwdialogue/testkeywordsearch.cpp @@ -46,7 +46,7 @@ TEST_F(KeywordSearchTest, keyword_test_conflict_resolution2) TEST_F(KeywordSearchTest, keyword_test_conflict_resolution3) { - // testing that the longest keyword is chosen, rather than maximizing the + // Test that the longest keyword is chosen, rather than maximizing the // amount of highlighted characters by highlighting the first and last keyword MWDialogue::KeywordSearch search; search.seed("foo bar", 0); @@ -64,12 +64,12 @@ TEST_F(KeywordSearchTest, keyword_test_conflict_resolution3) TEST_F(KeywordSearchTest, keyword_test_utf8_word_begin) { - // We make sure that the search works well even if the character is not ASCII + // Make sure that the search works well on UTF-8 strings containing some non-ASCII (French) MWDialogue::KeywordSearch search; search.seed("états", 0); search.seed("ïrradiés", 0); search.seed("ça nous déçois", 0); - search.seed("ois", 0); + search.seed("nous", 0); std::string text = "les nations unis ont réunis le monde entier, états units inclus pour parler du problème des gens ïrradiés " @@ -86,38 +86,22 @@ TEST_F(KeywordSearchTest, keyword_test_utf8_word_begin) TEST_F(KeywordSearchTest, keyword_test_non_alpha_non_whitespace_word_begin) { - // We make sure that the search works well even if the separator is not a whitespace + // Make sure that the search works well even if the separator is not whitespace MWDialogue::KeywordSearch search; search.seed("Report to caius cosades", 0); - std::string text = "I was told to \"Report to caius cosades\""; + std::string text = "I was told to \"Report to Caius Cosades\""; std::vector::Match> matches; search.highlightKeywords(text.begin(), text.end(), matches); EXPECT_EQ(matches.size(), 1); - EXPECT_EQ(std::string(matches[0].mBeg, matches[0].mEnd), "Report to caius cosades"); -} - -TEST_F(KeywordSearchTest, keyword_test_russian_non_ascii_before) -{ - // We make sure that the search works well even if the separator is not a whitespace with russian chars - MWDialogue::KeywordSearch search; - search.seed("Доложить Каю Косадесу", 0); - - std::string text - = "Что? Да. Я Кай Косадес. То есть как это, вам велели «Доложить Каю Косадесу»? О чем вы говорите?"; - - std::vector::Match> matches; - search.highlightKeywords(text.begin(), text.end(), matches); - - EXPECT_EQ(matches.size(), 1); - EXPECT_EQ(std::string(matches[0].mBeg, matches[0].mEnd), "Доложить Каю Косадесу"); + EXPECT_EQ(std::string(matches[0].mBeg, matches[0].mEnd), "Report to Caius Cosades"); } TEST_F(KeywordSearchTest, keyword_test_russian_ascii_before) { - // We make sure that the search works well even if the separator is not a whitespace with russian chars + // Make sure that the search works well even if the separator is not whitespace with Russian chars MWDialogue::KeywordSearch search; search.seed("Доложить Каю Косадесу", 0); @@ -130,3 +114,53 @@ TEST_F(KeywordSearchTest, keyword_test_russian_ascii_before) EXPECT_EQ(matches.size(), 1); EXPECT_EQ(std::string(matches[0].mBeg, matches[0].mEnd), "Доложить Каю Косадесу"); } + +TEST_F(KeywordSearchTest, keyword_test_substrings_without_word_separators) +{ + // Make sure that the search does not highlight substrings within words + // i.e. "Force" does not contain "orc" + // and "bring" does not contain "ring" + MWDialogue::KeywordSearch search; + search.seed("orc", 0); + search.seed("ring", 0); + + std::string text = "Bring the Force, Lucan!"; + + std::vector::Match> matches; + search.highlightKeywords(text.begin(), text.end(), matches); + + EXPECT_EQ(matches.size(), 0); +} + +TEST_F(KeywordSearchTest, keyword_test_initial_substrings_match) +{ + // Make sure that the search highlights prefix substrings + // "Orcs" should match "orc" + // "ring" is not matched because "-" is not a word separator + MWDialogue::KeywordSearch search; + search.seed("orc", 0); + search.seed("ring", 0); + + std::string text = "Bring the Orcs some gold-rings."; + + std::vector::Match> matches; + search.highlightKeywords(text.begin(), text.end(), matches); + + EXPECT_EQ(matches.size(), 1); + EXPECT_EQ(std::string(matches[0].mBeg, matches[0].mEnd), "Orc"); +} + +TEST_F(KeywordSearchTest, keyword_test_french_substrings) +{ + // Substrings within words should not match + MWDialogue::KeywordSearch search; + search.seed("ages", 0); + search.seed("orc", 0); + + std::string text = "traçages et forces"; + + std::vector::Match> matches; + search.highlightKeywords(text.begin(), text.end(), matches); + + EXPECT_EQ(matches.size(), 0); +} diff --git a/components/widgets/list.cpp b/components/widgets/list.cpp index 28fa48e081..739d50bff2 100644 --- a/components/widgets/list.cpp +++ b/components/widgets/list.cpp @@ -29,14 +29,14 @@ namespace Gui MyGUI::Align::Top | MyGUI::Align::Left | MyGUI::Align::Stretch, getName() + "_ScrollView"); } - void MWList::addItem(std::string_view name) + void MWList::addItem(std::string_view name, int verticalPadding) { - mItems.emplace_back(name); + mItems.emplace_back(name, verticalPadding); } void MWList::addSeparator() { - mItems.emplace_back(std::string{}); + addItem({}); } void MWList::adjustSize() @@ -48,7 +48,6 @@ namespace Gui { constexpr int _scrollBarWidth = 20; // fetch this from skin? const int scrollBarWidth = scrollbarShown ? _scrollBarWidth : 0; - constexpr int spacing = 3; int viewPosition = -mScrollView->getViewOffset().top; while (mScrollView->getChildCount()) @@ -60,25 +59,27 @@ namespace Gui int i = 0; for (const auto& item : mItems) { - if (!item.empty()) + mItemHeight += item.mVPadding; + if (!item.mName.empty()) { if (mListItemSkin.empty()) return; MyGUI::Button* button = mScrollView->createWidget(mListItemSkin, MyGUI::IntCoord(0, mItemHeight, mScrollView->getSize().width - scrollBarWidth - 2, 24), - MyGUI::Align::Left | MyGUI::Align::Top, getName() + "_item_" + item); - button->setCaption(item); + MyGUI::Align::Left | MyGUI::Align::Top, getName() + "_item_" + item.mName); + button->setCaption(item.mName); button->getSubWidgetText()->setWordWrap(true); button->getSubWidgetText()->setTextAlign(MyGUI::Align::Left); button->eventMouseWheel += MyGUI::newDelegate(this, &MWList::onMouseWheelMoved); button->eventMouseButtonClick += MyGUI::newDelegate(this, &MWList::onItemSelected); button->setNeedKeyFocus(true); - int height = button->getTextSize().height; + // Morrowind list item text widgets are typically 18 pixels tall + int height = button->getTextSize().height + 2; button->setSize(MyGUI::IntSize(button->getSize().width, height)); button->setUserData(i); - mItemHeight += height + spacing; + mItemHeight += height; } else { @@ -87,8 +88,9 @@ namespace Gui MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch); separator->setNeedMouseFocus(false); - mItemHeight += 18 + spacing; + mItemHeight += 18; } + mItemHeight += item.mVPadding; ++i; } @@ -123,18 +125,19 @@ namespace Gui const std::string& MWList::getItemNameAt(size_t at) { assert(at < mItems.size() && "List item out of bounds"); - return mItems[at]; + return mItems[at].mName; } void MWList::sort() { // A special case for separators is not needed for now - std::sort(mItems.begin(), mItems.end(), Misc::StringUtils::ciLess); + std::sort(mItems.begin(), mItems.end(), + [](const auto& left, const auto& right) { return Misc::StringUtils::ciLess(left.mName, right.mName); }); } void MWList::removeItem(const std::string& name) { - auto it = std::find(mItems.begin(), mItems.end(), name); + auto it = std::find_if(mItems.begin(), mItems.end(), [&name](const auto& item) { return item.mName == name; }); assert(it != mItems.end()); mItems.erase(it); } diff --git a/components/widgets/list.hpp b/components/widgets/list.hpp index 022214dd1c..94829d93df 100644 --- a/components/widgets/list.hpp +++ b/components/widgets/list.hpp @@ -36,7 +36,7 @@ namespace Gui void adjustSize(); void sort(); - void addItem(std::string_view name); + void addItem(std::string_view name, int verticalPadding = 0); void addSeparator(); ///< add a seperator between the current and the next item. void removeItem(const std::string& name); size_t getItemCount(); @@ -65,7 +65,18 @@ namespace Gui MyGUI::Widget* mClient; std::string mListItemSkin; - std::vector mItems; + struct ListItemData + { + std::string mName; + int mVPadding; + + ListItemData(std::string_view name, int verticalPadding) + : mName(name) + , mVPadding(verticalPadding) + { + } + }; + std::vector mItems; int mItemHeight; // height of all items }; diff --git a/docs/requirements.txt b/docs/requirements.txt index f46cc5c95d..d22bcceae6 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,4 +2,5 @@ parse_cmake sphinx==7.1.2 docutils==0.18.1 jinja2==3.1.6 -sphinx_rtd_theme==3.0.1 +sphinx-design==0.5.0 +git+https://github.com/glassmancody/sphinxawesome-theme.git@openmw \ No newline at end of file diff --git a/docs/source/_ext/omw-directives.py b/docs/source/_ext/omw-directives.py new file mode 100644 index 0000000000..245c84ad7a --- /dev/null +++ b/docs/source/_ext/omw-directives.py @@ -0,0 +1,76 @@ +from docutils import nodes +from docutils.parsers.rst import Directive, directives + +class OMWSettingDirective(Directive): + has_content = True + option_spec = { + 'title': directives.unchanged_required, + 'type': directives.unchanged_required, + 'range': directives.unchanged, + 'default': directives.unchanged, + 'location': directives.unchanged, + } + + badge_map = { + 'float32': 'bdg-secondary', 'float64': 'bdg-muted', + 'int': 'bdg-primary', 'uint': 'bdg-light', + 'string': 'bdg-success', 'boolean': 'bdg-warning', + 'color': 'bdg-info', + } + + def run(self): + opts = self.options + title = opts['title'] + type_tags = opts['type'].split('|') + badges = ' '.join(f':{self.badge_map.get(t)}:`{t}`' for t in type_tags) + + values = [ + badges, + opts.get('range', ''), + opts.get('default', ''), + opts.get('location', ':bdg-danger:`user settings.cfg`') + ] + + table = nodes.table(classes=['omw-setting']) + tgroup = nodes.tgroup(cols=4) + table += tgroup + for _ in range(4): + tgroup += nodes.colspec(colwidth=20) + + thead = nodes.thead() + tgroup += thead + thead += nodes.row('', *[ + nodes.entry('', nodes.paragraph(text=label)) + for label in ['Type', 'Range', 'Default', 'Location'] + ]) + + tbody = nodes.tbody() + tgroup += tbody + row = nodes.row() + + for i, val in enumerate(values): + entry = nodes.entry() + inline, _ = self.state.inline_text(val, self.lineno) + if i == 2 and 'color' in type_tags: + rgba = [float(c) for c in opts['default'].split()] + style = f'background-color:rgba({rgba[0]*255}, {rgba[1]*255}, {rgba[2]*255}, {rgba[3]})' + chip = nodes.raw('', f'', format='html') + wrapper = nodes.container(classes=['type-color-wrapper'], children=[chip] + inline) + entry += wrapper + else: + entry += inline + row += entry + tbody += row + + desc = nodes.paragraph() + self.state.nested_parse(self.content, self.content_offset, desc) + + section = nodes.section(ids=[nodes.make_id(title)]) + section += nodes.title(text=title) + section += table + section += desc + return [section] + +def setup(app): + app.add_css_file("omw-directives.css") + app.add_directive("omw-setting", OMWSettingDirective) diff --git a/docs/source/_static/luadoc.css b/docs/source/_static/luadoc.css index aa83013def..b2efec85b4 100644 --- a/docs/source/_static/luadoc.css +++ b/docs/source/_static/luadoc.css @@ -1,113 +1,87 @@ -#luadoc tt { font-family: monospace; } - -#luadoc p, -#luadoc td, -#luadoc th { font-size: .95em; line-height: 1.2em;} - -#luadoc p, -#luadoc ul -{ margin: 10px 0 0 10px;} - -#luadoc strong { font-weight: bold;} - -#luadoc em { font-style: italic;} - -#luadoc h1 { - font-size: 1.5em; - margin: 25px 0 20px 0; -} -#luadoc h2, -#luadoc h3, -#luadoc h4 { margin: 15px 0 10px 0; } -#luadoc h2 { font-size: 1.25em; } -#luadoc h3 { font-size: 1.15em; } -#luadoc h4 { font-size: 1.06em; } - -#luadoc hr { - color:#cccccc; - background: #00007f; - height: 1px; -} - -#luadoc blockquote { margin-left: 3em; } - -#luadoc ul { list-style-type: disc; } - -#luadoc p.name { - font-family: "Andale Mono", monospace; - padding-top: 1em; -} - -#luadoc p:first-child { - margin-top: 0px; -} - -#luadoc table.function_list { - border-width: 1px; - border-style: solid; - border-color: #cccccc; - border-collapse: collapse; -} -#luadoc table.function_list td { - border-width: 1px; - padding: 3px; - border-style: solid; - border-color: #cccccc; -} - -#luadoc table.function_list td.name { background-color: #f0f0f0; } -#luadoc table.function_list td.summary { width: 100%; } - -#luadoc dl.table dt, -#luadoc dl.function dt {border-top: 1px solid #ccc; padding-top: 1em;} -#luadoc dl.table dd, -#luadoc dl.function dd {padding-bottom: 1em; margin: 10px 0 0 20px;} -#luadoc dl.table h3, -#luadoc dl.function h3 {font-size: .95em;} - - - -#luadoc pre.example { - background-color: #eeffcc; - border: 1px solid #e1e4e5; - padding: 10px; - margin: 10px 0 10px 0; - overflow-x: auto; -} - -#luadoc code { - background-color: inherit; - color: inherit; - border: none; - font-family: monospace; +#luadoc code, #luadoc tt, #luadoc p em { + font-family: 'JetBrains Mono', monospace; } #luadoc pre.example code { - color: #404040; - background-color: #eeffcc; - border: none; white-space: pre; - padding: 0px; } -#luadoc dt { - background: inherit; +#luadoc code:not([class*="language-"]) { + background-color: unset; +} + +#luadoc pre:not([class*="language-"]) { + background-color: inherit; color: inherit; - width: 100%; - padding: 0px; + border: none; } -#luadoc a:not(:link) { - font-weight: bold; - color: #000; +#luadoc pre code { + white-space: normal; +} + +#content #luadoc code a:not(.toc-backref) { + font-weight: 600; +} + +#luadoc > p:nth-child(1) { + margin: 12px 0 12px 0; +} + +#luadoc a:not([href]) { text-decoration: none; cursor: inherit; + color: inherit; } -#luadoc a:link { font-weight: bold; color: #004080; text-decoration: none; } -#luadoc a:visited { font-weight: bold; color: #006699; text-decoration: none; } -#luadoc a:link:hover { text-decoration: underline; } -#luadoc dl, -#luadoc dd {margin: 0px; line-height: 1.2em;} +#luadoc a:not([href]):hover { + color: inherit; +} + +#luadoc code a em { + color: inherit; + font-weight: inherit; +} + +#luadoc em { + font-size: 13px; + font-style: normal; +} + +.context-wrapper { + display: flex; + margin-top: 14px; + gap: 4px; +} + +#luadoc ul { list-style-type: disc; } #luadoc li {list-style: bullet;} +#luadoc .function a:not(.toc-backref) { + font-weight: 500; +} + +#content #luadoc dl.function dt:not(.sig):first-child { + border-bottom: 2px solid hsl(var(--muted) / 50%); + background-color: hsl(var(--muted)); + padding: 6px; +} + +#content #luadoc dl.function dt:not(.sig):first-child a { + text-decoration: none; +} + +@media (prefers-color-scheme: dark) { + #content #luadoc dl.function dt:not(.sig):first-child { + border-bottom: 2px solid hsl(var(--accent) / 50%); + background-color: hsl(var(--accent)); + } +} + +#luadoc .table-wrapper.container { + padding: 0; +} + +table.docutils { + width: 100%; +} \ No newline at end of file diff --git a/docs/source/_static/omw-directives.css b/docs/source/_static/omw-directives.css new file mode 100644 index 0000000000..38f6218025 --- /dev/null +++ b/docs/source/_static/omw-directives.css @@ -0,0 +1,21 @@ +.omw-setting td:nth-child(1) { width: 20%; } +.omw-setting td:nth-child(2) { width: 20%; } +.omw-setting td:nth-child(3) { width: 20%; } +.omw-setting td:nth-child(4) { width: 20%; } + +.omw-setting .type-color-wrapper { + display: flex; + flex-direction: row; + align-items: center; + justify-content: left; + padding: 0; + gap: 12px; +} + +.color-chip { + display: inline-block; + width: 1.5em; + height: 1.5em; + border-radius: 0.2em; + border: 1px solid #333; +} diff --git a/docs/source/_static/prism-dark.css b/docs/source/_static/prism-dark.css new file mode 100644 index 0000000000..293a0d1267 --- /dev/null +++ b/docs/source/_static/prism-dark.css @@ -0,0 +1,156 @@ +@media (prefers-color-scheme: dark) { + + /** + * Github Dark theme for Prism.js + * Based on Github: https://github.com + * @author Katorly + */ + /* General */ + pre[class*="language-"], + code[class*="language-"] { + color: #c9d1d9; + font-size: 13px; + text-shadow: none; + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + direction: ltr; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + line-height: 1.5; + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; + } + + pre[class*="language-"]::selection, + code[class*="language-"]::selection, + pre[class*="language-"]::mozselection, + code[class*="language-"]::mozselection { + text-shadow: none; + background: #234879; + } + + @media print { + + pre[class*="language-"], + code[class*="language-"] { + text-shadow: none; + } + } + + pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; + background: #161b22; + } + + :not(pre)>code[class*="language-"] { + padding: .1em .3em; + border-radius: .3em; + color: #c9d1d9; + background: #343942; + } + + /* Line highlighting */ + pre[data-line] { + position: relative; + } + + pre[class*="language-"]>code[class*="language-"] { + position: relative; + z-index: 1; + } + + .line-highlight { + position: absolute; + left: 0; + right: 0; + padding: inherit 0; + margin-top: 1em; + background: #2f2a1e; + box-shadow: inset 5px 0 0 #674c16; + z-index: 0; + pointer-events: none; + line-height: inherit; + white-space: pre; + } + + /* Tokens */ + .namespace { + opacity: .7; + } + + .token.comment, + .token.prolog, + .token.doctype, + .token.cdata { + color: #8b949e; + } + + .token.punctuation { + color: #c9d1d9; + } + + .token.property, + .token.tag, + .token.boolean, + .token.number, + .token.constant, + .token.symbol, + .token.deleted { + color: #79c0ff; + } + + .token.selector, + .token.attr-name, + .token.string, + .token.char, + .token.builtin, + .token.inserted { + color: #a5d6ff; + } + + .token.operator, + .token.entity, + .token.url, + .language-css .token.string, + .style .token.string { + color: #a5d6ff; + background: #161b22; + } + + .token.atrule, + .token.attr-value, + .token.keyword { + color: #a5d6ff; + } + + .token.function { + color: #d2a8ff; + } + + .token.regex, + .token.important, + .token.variable { + color: #a8daff; + } + + .token.important, + .token.bold { + font-weight: bold; + } + + .token.italic { + font-style: italic; + } + + .token.entity { + cursor: help; + } +} \ No newline at end of file diff --git a/docs/source/_static/prism.css b/docs/source/_static/prism.css new file mode 100644 index 0000000000..088a96cb6c --- /dev/null +++ b/docs/source/_static/prism.css @@ -0,0 +1,152 @@ +/** + * Github Light theme for Prism.js + * Based on Github: https://github.com + * @author Katorly + */ +/* General */ +pre[class*="language-"], +code[class*="language-"] { + color: #24292f; + font-size: 13px; + text-shadow: none; + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + direction: ltr; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + line-height: 1.5; + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +pre[class*="language-"]::selection, +code[class*="language-"]::selection, +pre[class*="language-"]::mozselection, +code[class*="language-"]::mozselection { + text-shadow: none; + background: #9fc6e9; +} + +@media print { + + pre[class*="language-"], + code[class*="language-"] { + text-shadow: none; + } +} + +pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; + background: #f6f8fa; +} + +:not(pre)>code[class*="language-"] { + padding: .1em .3em; + border-radius: .3em; + color: #24292f; + background: #eff1f3; +} + +/* Line highlighting */ +pre[data-line] { + position: relative; +} + +pre[class*="language-"]>code[class*="language-"] { + position: relative; + z-index: 1; +} + +.line-highlight { + position: absolute; + left: 0; + right: 0; + padding: inherit 0; + margin-top: 1em; + background: #fff8c5; + box-shadow: inset 5px 0 0 #eed888; + z-index: 0; + pointer-events: none; + line-height: inherit; + white-space: pre; +} + +/* Tokens */ +.namespace { + opacity: .7; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: #6e7781; +} + +.token.punctuation { + color: #24292f; +} + +.token.property, +.token.tag, +.token.boolean, +.token.number, +.token.constant, +.token.symbol, +.token.deleted { + color: #0550ae; +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: #0a3069; +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string { + color: #0550ae; +} + +.token.atrule, +.token.attr-value, +.token.keyword { + color: #cf222e; +} + +.token.function { + color: #8250df; +} + +.token.regex, +.token.important, +.token.variable { + color: #0a3069; +} + +.token.important, +.token.bold { + font-weight: bold; +} + +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} \ No newline at end of file diff --git a/docs/source/_static/prism.js b/docs/source/_static/prism.js new file mode 100644 index 0000000000..fa92d07899 --- /dev/null +++ b/docs/source/_static/prism.js @@ -0,0 +1,7 @@ +/* PrismJS 1.30.0 +https://prismjs.com/download#themes=prism&languages=json+lua+yaml&plugins=toolbar */ +var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(e){var n=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,t=0,r={},a={manual:e.Prism&&e.Prism.manual,disableWorkerMessageHandler:e.Prism&&e.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof i?new i(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&").replace(/=g.reach);A+=w.value.length,w=w.next){var P=w.value;if(n.length>e.length)return;if(!(P instanceof i)){var E,S=1;if(y){if(!(E=l(b,A,e,m))||E.index>=e.length)break;var L=E.index,O=E.index+E[0].length,C=A;for(C+=w.value.length;L>=C;)C+=(w=w.next).value.length;if(A=C-=w.value.length,w.value instanceof i)continue;for(var j=w;j!==n.tail&&(Cg.reach&&(g.reach=W);var I=w.prev;if(_&&(I=u(n,I,_),A+=_.length),c(n,I,S),w=u(n,I,new i(f,p?a.tokenize(N,p):N,k,N)),M&&u(n,w,M),S>1){var T={cause:f+","+d,reach:W};o(e,n,t,w.prev,A,T),g&&T.reach>g.reach&&(g.reach=T.reach)}}}}}}function s(){var e={value:null,prev:null,next:null},n={value:null,prev:e,next:null};e.next=n,this.head=e,this.tail=n,this.length=0}function u(e,n,t){var r=n.next,a={value:t,prev:n,next:r};return n.next=a,r.prev=a,e.length++,a}function c(e,n,t){for(var r=n.next,a=0;a"+i.content+""},!e.document)return e.addEventListener?(a.disableWorkerMessageHandler||e.addEventListener("message",(function(n){var t=JSON.parse(n.data),r=t.language,i=t.code,l=t.immediateClose;e.postMessage(a.highlight(i,a.languages[r],r)),l&&e.close()}),!1),a):a;var g=a.util.currentScript();function f(){a.manual||a.highlightAll()}if(g&&(a.filename=g.src,g.hasAttribute("data-manual")&&(a.manual=!0)),!a.manual){var h=document.readyState;"loading"===h||"interactive"===h&&g&&g.defer?document.addEventListener("DOMContentLoaded",f):window.requestAnimationFrame?window.requestAnimationFrame(f):window.setTimeout(f,16)}return a}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); +Prism.languages.json={property:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?=\s*:)/,lookbehind:!0,greedy:!0},string:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?!\s*:)/,lookbehind:!0,greedy:!0},comment:{pattern:/\/\/.*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},number:/-?\b\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,punctuation:/[{}[\],]/,operator:/:/,boolean:/\b(?:false|true)\b/,null:{pattern:/\bnull\b/,alias:"keyword"}},Prism.languages.webmanifest=Prism.languages.json; +Prism.languages.lua={comment:/^#!.+|--(?:\[(=*)\[[\s\S]*?\]\1\]|.*)/m,string:{pattern:/(["'])(?:(?!\1)[^\\\r\n]|\\z(?:\r\n|\s)|\\(?:\r\n|[^z]))*\1|\[(=*)\[[\s\S]*?\]\2\]/,greedy:!0},number:/\b0x[a-f\d]+(?:\.[a-f\d]*)?(?:p[+-]?\d+)?\b|\b\d+(?:\.\B|(?:\.\d*)?(?:e[+-]?\d+)?\b)|\B\.\d+(?:e[+-]?\d+)?\b/i,keyword:/\b(?:and|break|do|else|elseif|end|false|for|function|goto|if|in|local|nil|not|or|repeat|return|then|true|until|while)\b/,function:/(?!\d)\w+(?=\s*(?:[({]))/,operator:[/[-+*%^&|#]|\/\/?|<[<=]?|>[>=]?|[=~]=?/,{pattern:/(^|[^.])\.\.(?!\.)/,lookbehind:!0}],punctuation:/[\[\](){},;]|\.+|:+/}; +!function(e){var n=/[*&][^\s[\]{},]+/,r=/!(?:<[\w\-%#;/?:@&=+$,.!~*'()[\]]+>|(?:[a-zA-Z\d-]*!)?[\w\-%#;/?:@&=+$.~*'()]+)?/,t="(?:"+r.source+"(?:[ \t]+"+n.source+")?|"+n.source+"(?:[ \t]+"+r.source+")?)",a="(?:[^\\s\\x00-\\x08\\x0e-\\x1f!\"#%&'*,\\-:>?@[\\]`{|}\\x7f-\\x84\\x86-\\x9f\\ud800-\\udfff\\ufffe\\uffff]|[?:-])(?:[ \t]*(?:(?![#:])|:))*".replace(//g,(function(){return"[^\\s\\x00-\\x08\\x0e-\\x1f,[\\]{}\\x7f-\\x84\\x86-\\x9f\\ud800-\\udfff\\ufffe\\uffff]"})),d="\"(?:[^\"\\\\\r\n]|\\\\.)*\"|'(?:[^'\\\\\r\n]|\\\\.)*'";function o(e,n){n=(n||"").replace(/m/g,"")+"m";var r="([:\\-,[{]\\s*(?:\\s<>[ \t]+)?)(?:<>)(?=[ \t]*(?:$|,|\\]|\\}|(?:[\r\n]\\s*)?#))".replace(/<>/g,(function(){return t})).replace(/<>/g,(function(){return e}));return RegExp(r,n)}e.languages.yaml={scalar:{pattern:RegExp("([\\-:]\\s*(?:\\s<>[ \t]+)?[|>])[ \t]*(?:((?:\r?\n|\r)[ \t]+)\\S[^\r\n]*(?:\\2[^\r\n]+)*)".replace(/<>/g,(function(){return t}))),lookbehind:!0,alias:"string"},comment:/#.*/,key:{pattern:RegExp("((?:^|[:\\-,[{\r\n?])[ \t]*(?:<>[ \t]+)?)<>(?=\\s*:\\s)".replace(/<>/g,(function(){return t})).replace(/<>/g,(function(){return"(?:"+a+"|"+d+")"}))),lookbehind:!0,greedy:!0,alias:"atrule"},directive:{pattern:/(^[ \t]*)%.+/m,lookbehind:!0,alias:"important"},datetime:{pattern:o("\\d{4}-\\d\\d?-\\d\\d?(?:[tT]|[ \t]+)\\d\\d?:\\d{2}:\\d{2}(?:\\.\\d*)?(?:[ \t]*(?:Z|[-+]\\d\\d?(?::\\d{2})?))?|\\d{4}-\\d{2}-\\d{2}|\\d\\d?:\\d{2}(?::\\d{2}(?:\\.\\d*)?)?"),lookbehind:!0,alias:"number"},boolean:{pattern:o("false|true","i"),lookbehind:!0,alias:"important"},null:{pattern:o("null|~","i"),lookbehind:!0,alias:"important"},string:{pattern:o(d),lookbehind:!0,greedy:!0},number:{pattern:o("[+-]?(?:0x[\\da-f]+|0o[0-7]+|(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:e[+-]?\\d+)?|\\.inf|\\.nan)","i"),lookbehind:!0},tag:r,important:n,punctuation:/---|[:[\]{}\-,|>?]|\.\.\./},e.languages.yml=e.languages.yaml}(Prism); +!function(){if("undefined"!=typeof Prism&&"undefined"!=typeof document){var e=[],t={},n=function(){};Prism.plugins.toolbar={};var a=Prism.plugins.toolbar.registerButton=function(n,a){var r;r="function"==typeof a?a:function(e){var t;return"function"==typeof a.onClick?((t=document.createElement("button")).type="button",t.addEventListener("click",(function(){a.onClick.call(this,e)}))):"string"==typeof a.url?(t=document.createElement("a")).href=a.url:t=document.createElement("span"),a.className&&t.classList.add(a.className),t.textContent=a.text,t},n in t?console.warn('There is a button with the key "'+n+'" registered already.'):e.push(t[n]=r)},r=Prism.plugins.toolbar.hook=function(a){var r=a.element.parentNode;if(r&&/pre/i.test(r.nodeName)&&!r.parentNode.classList.contains("code-toolbar")){var o=document.createElement("div");o.classList.add("code-toolbar"),r.parentNode.insertBefore(o,r),o.appendChild(r);var i=document.createElement("div");i.classList.add("toolbar");var l=e,d=function(e){for(;e;){var t=e.getAttribute("data-toolbar-order");if(null!=t)return(t=t.trim()).length?t.split(/\s*,\s*/g):[];e=e.parentElement}}(a.element);d&&(l=d.map((function(e){return t[e]||n}))),l.forEach((function(e){var t=e(a);if(t){var n=document.createElement("div");n.classList.add("toolbar-item"),n.appendChild(t),i.appendChild(n)}})),o.appendChild(i)}};a("label",(function(e){var t=e.element.parentNode;if(t&&/pre/i.test(t.nodeName)&&t.hasAttribute("data-label")){var n,a,r=t.getAttribute("data-label");try{a=document.querySelector("template#"+r)}catch(e){}return a?n=a.content:(t.hasAttribute("data-url")?(n=document.createElement("a")).href=t.getAttribute("data-url"):n=document.createElement("span"),n.textContent=r),n}})),Prism.hooks.add("complete",r)}}(); diff --git a/docs/source/_static/theme-override.css b/docs/source/_static/theme-override.css new file mode 100644 index 0000000000..e41ab2c2d6 --- /dev/null +++ b/docs/source/_static/theme-override.css @@ -0,0 +1,140 @@ +:root { + --link: #4a90e2; + --link-hover: #1c6cd9; + + --readthedocs-search-color: hsl(var(--foreground)); + --readthedocs-search-link-color: var(--link); + --readthedocs-search-content-background-color: hsl(var(--background)); + --readthedocs-search-content-border-color: hsl(var(--border)); + --readthedocs-search-footer-background-color: hsl(var(--background)); + --readthedocs-search-footer-color: hsl(var(--foreground)); + --readthedocs-search-footer-code-background-color: hsl(var(--background)); + --readthedocs-search-footer-code-border-color: hsl(var(--background)); + --readthedocs-search-input-background-color: hsl(var(--accent)); + --readthedocs-search-result-color: hsl(var(--foreground)); + --readthedocs-search-result-icon-color: hsl(var(--foreground)); + --readthedocs-search-result-heading-color: hsl(var(--foreground)); + --readthedocs-search-result-subheading-color: hsl(var(--foreground)); + --readthedocs-search-result-active-background-color: hsl(var(--accent)); + --readthedocs-search-result-border-color: hsl(var(--border)); + + --readthedocs-flyout-background-color: hsl(var(--background)); + --readthedocs-flyout-color: hsl(var(--foreground)); + --readthedocs-flyout-current-version-color: hsl(var(--foreground)); + --readthedocs-flyout-item-link-color: var(--link); + --readthedocs-flyout-link-color: var(--link); + --readthedocs-flyout-section-heading-color: hsl(var(--foreground)); +} + +/* Less aggressive dark background */ +@media (prefers-color-scheme: dark) { + :root { + --background: 220 14% 9% !important; + --border: 216 14% 17%; + --accent: 216 14% 17%; + --input: 216 14% 17%; + --muted: 223 27% 14%; + --card: 220 14% 9%; + + --link: #ffffff; + --link-hover: #ffffff; + } + + .contents ul li a.reference:hover, .sd-dropdown .toctree-wrapper ul li a.reference, details.sd-dropdown .sd-summary-title { + --muted-foreground: 215.4 16.3% 86.9%; + } +} + +/* Enable hover-to-highlight in TOC */ +.contents ul li a.reference:hover, .toctree-wrapper ul li a.reference:hover { + text-decoration: underline !important; +} + +/* Hide text underline in tables since the underlines clash with table lines */ +#content a.sd-badge:not(.toc-backref), #content table a:not(.toc-backref) { + text-decoration: none; +} + +/* Override link colors, default is foreground */ +#content a:not(.toc-backref) { + color: var(--link); + font-weight: 600; +} + +#content a:not(.toc-backref):hover, #content a:not(.toc-backref):focus { + color: var(--link-hover); +} + +#content .highlight { + background-color: #f6f8fa !important; + border-radius: 0.25rem; + font-variant-ligatures: none; +} + +@media (prefers-color-scheme: dark) { + #content .highlight { + background-color: #161b22 !important; + } +} + +/* Disables the saturation filter on project icon in dark mode */ +.dark\:invert { + --tw-invert: none !important; +} + +/* Disable no-wrap set on some code blocks, causing x-overflow issues in right bar */ +code { + white-space: normal; +} + +/* Hide the keybind shortcut for search, we haven't linked this to sphinx search addon yet */ +#searchbox kbd { + display: none !important; +} + +#search-input { + padding-right: 12px !important; +} + +/* Table headers are bolded in dark mode, do it in light mode too */ +table th { + font-weight: 600 !important; +} + +/* Highlight non-header rows on hover */ +tbody tr:hover { + background-color: hsl(var(--muted)); +} + +/* Increase maximum width for docs, default is quite narrow at 1400px max */ +@media (min-width: 1400px) { + .container { + max-width: 2000px !important; + } +} + +/* Less intrusive scrollbar in the TOC */ +#left-sidebar::-webkit-scrollbar, #right-sidebar .sticky::-webkit-scrollbar { + width: 6px; + border-radius: 0; +} + +#left-sidebar::-webkit-scrollbar-thumb, #right-sidebar .sticky::-webkit-scrollbar-thumb { + background-color: hsl(var(--border));; + border-radius: 0; +} + +#left-sidebar::-webkit-scrollbar-thumb:hover, #right-sidebar .sticky::-webkit-scrollbar-thumb:hover { + background-color: hsl(var(--muted));; +} + +@media (min-width: 1024px) { + .container:has(#left-sidebar) { + grid-template-columns: 310px minmax(0, 1fr); + } +} + +/* Always enable scrollbar in left TOC to prevent layout shifts when expanding */ +#left-sidebar { + overflow-y: scroll; +} diff --git a/docs/source/conf.py b/docs/source/conf.py index fbce7975da..aaa42e3678 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -16,11 +16,16 @@ import os import sys import subprocess +from dataclasses import asdict +from sphinxawesome_theme import ThemeOptions +from sphinxawesome_theme.postprocess import Icons + # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. project_root = os.path.abspath('../../') sys.path.insert(0, project_root) +sys.path.append(os.path.abspath("_ext")) # -- General configuration ------------------------------------------------ @@ -37,6 +42,8 @@ extensions = [ 'sphinx.ext.coverage', 'sphinx.ext.viewcode', 'sphinx.ext.autosectionlabel', + 'sphinx_design', + 'omw-directives' ] #autosectionlabel_prefix_document = True @@ -91,7 +98,14 @@ except Exception as ex: rst_prolog = f""" .. |luaApiRevision| replace:: {luaApiRevision} +.. |luaApiRevisionBadge| replace:: :bdg-link-info-line:`API v{luaApiRevision} ` + .. |ppApiRevision| replace:: {ppApiRevision} +.. |bdg-ctx-menu| replace:: :bdg-warning:`menu` +.. |bdg-ctx-global| replace:: :bdg-danger:`global` +.. |bdg-ctx-player| replace:: :bdg-secondary:`player` +.. |bdg-ctx-local| replace:: :bdg-info:`local` +.. |bdg-ctx-all| replace:: :bdg-danger:`global` :bdg-warning:`menu` :bdg-info:`local` """ # The language for content autogenerated by Sphinx. Refer to documentation @@ -125,6 +139,7 @@ exclude_patterns = [] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' +pygments_style_dark = 'github-dark' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] @@ -138,22 +153,39 @@ primary_domain = 'c' # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'sphinx_rtd_theme' +html_theme = 'sphinxawesome_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -html_theme_options = { - 'navigation_with_keys': True, - 'flyout_display': 'attached', -} +html_theme_options = asdict(ThemeOptions( + show_breadcrumbs=False, + main_nav_links= { + "Modding": "reference/modding/index", + "Lua API": "reference/lua-scripting/overview", + "Post-Processing": "reference/postprocessing/index", + }, + show_scrolltop=False, # Useful, but hard to position with the Flyout addon since the best place for that is bottom right +)) + +html_permalinks_icon = Icons.permalinks_icon + +html_js_files = [ + 'prism.js' +] + +html_css_files = [ + "theme-override.css", + "luadoc.css", + "figures.css", + "prism.css", + "prism-dark.css", +] # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] def setup(app): - app.add_css_file('figures.css') - app.add_css_file('luadoc.css') try: subprocess.call(['bash', project_root + '/docs/source/generate_luadoc.sh']) except Exception as e: @@ -161,19 +193,19 @@ def setup(app): # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +html_title = 'OpenMW' # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = 'OpenMW Documentation' +# html_short_title = 'OpenMW Documentation' # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = 'https://gitlab.com/OpenMW/openmw-docs/raw/master/docs/source/_static/images/openmw.png' +html_logo = 'https://gitlab.com/OpenMW/openmw-docs/raw/master/docs/source/_static/images/openmw.png' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = 'https://gitlab.com/OpenMW/openmw-docs/raw/master/docs/source/_static/images/favicon.png' +html_favicon = 'https://gitlab.com/OpenMW/openmw-docs/raw/master/docs/source/_static/images/favicon.png' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, diff --git a/docs/source/index.rst b/docs/source/index.rst index de3773ddbd..6906403e9d 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,9 +1,34 @@ Welcome to OpenMW's Documentation! ================================== -.. toctree:: - :caption: Table of Contents - :maxdepth: 3 +This documentation covers all aspects of OpenMW development, scripting, and content creation. +Use the categorized sections below to quickly access technical references, modding tools, and installation guides. - manuals/index - reference/index +.. dropdown:: Table of Contents + :icon: book + + .. toctree:: + :caption: Reference + :titlesonly: + :maxdepth: 4 + + reference/modding/index + reference/postprocessing/index + + .. toctree:: + :caption: Lua + :titlesonly: + :maxdepth: 4 + + reference/lua-scripting/overview + reference/lua-scripting/api + reference/lua-scripting/teal + + .. toctree:: + :caption: Help + :titlesonly: + :maxdepth: 4 + + manuals/installation/index + manuals/openmw-cs/index + manuals/documentationHowTo diff --git a/docs/source/install_luadocumentor_in_docker.sh b/docs/source/install_luadocumentor_in_docker.sh index 5d37a37351..388ba8b739 100755 --- a/docs/source/install_luadocumentor_in_docker.sh +++ b/docs/source/install_luadocumentor_in_docker.sh @@ -33,7 +33,7 @@ PATH=$PATH:~/luarocks/bin echo "Install openmwluadocumentor" git clone https://gitlab.com/ptmikheev/openmw-luadocumentor.git cd openmw-luadocumentor -git checkout 78577b255d19a1f4f4f539662e00357936b73c33 +git checkout 122e4f55c5f2dd62076135211e03edfb8dec3a55 luarocks make luarocks/openmwluadocumentor-0.2.0-1.rockspec cd ~ rm -r openmw-luadocumentor diff --git a/docs/source/reference/documentationHowTo.rst b/docs/source/manuals/documentationHowTo.rst similarity index 99% rename from docs/source/reference/documentationHowTo.rst rename to docs/source/manuals/documentationHowTo.rst index d2b67d02ca..fb1062cd19 100644 --- a/docs/source/reference/documentationHowTo.rst +++ b/docs/source/manuals/documentationHowTo.rst @@ -1,6 +1,6 @@ -####################################### -So you want to help with documentation? -####################################### +############### +Help with docs? +############### Or a beginner's guide to writing docs without having to deal with more techie stuff than you have to. ##################################################################################################### diff --git a/docs/source/manuals/index.rst b/docs/source/manuals/index.rst deleted file mode 100644 index e6f0cbef2e..0000000000 --- a/docs/source/manuals/index.rst +++ /dev/null @@ -1,8 +0,0 @@ -User Manuals -============ - -.. toctree:: - :maxdepth: 2 - - openmw-cs/index - installation/index diff --git a/docs/source/manuals/installation/index.rst b/docs/source/manuals/installation/index.rst index 6e6f5034ef..c1113603dc 100644 --- a/docs/source/manuals/installation/index.rst +++ b/docs/source/manuals/installation/index.rst @@ -4,9 +4,12 @@ Installation Guide In order to use OpenMW, you must install both the engine and the game files for a compatible game. -.. toctree:: - :maxdepth: 2 +.. dropdown:: Table of Contents + :icon: book - install-openmw - install-game-files - common-problems \ No newline at end of file + .. toctree:: + :maxdepth: 2 + + install-openmw + install-game-files + common-problems diff --git a/docs/source/manuals/installation/install-game-files.rst b/docs/source/manuals/installation/install-game-files.rst index 42d4eb8d78..cb44f7d1de 100644 --- a/docs/source/manuals/installation/install-game-files.rst +++ b/docs/source/manuals/installation/install-game-files.rst @@ -163,7 +163,10 @@ Debian/Ubuntu - using "Steam Proton" & "OpenMW launcher". #. Launch "OpenMW launcher" and follow the setup wizard, when asked, point it at the location you installed Morrowind to, we will be looking for the directory that contains the Morrowing.esm file, for example '/steam library/steamapps/common/Morrowind/Data Files/'. #. Everything should now be in place, click that big "PLAY" button and fire up OpenMW. -Note, Bloodmoon.esm needs to be below Tribunal.esm in your datafiles list, if you don't have the right order a red "!" will apear next to the filename in the datafiles section of the OpenMW launcher, just drag bloodmoon below tribunal to fix it. +.. note:: + `Bloodmoon.esm` needs to be below `Tribunal.esm` in your data files list. + If you don't have the right order a red `!` will apear next to the filename in the data files section of the OpenMW launcher. + To fix, drag `Bloodmoon.esm` below `Tribunal.esm`. Wine ~~~~ diff --git a/docs/source/manuals/installation/install-openmw.rst b/docs/source/manuals/installation/install-openmw.rst index 2ef72abfd5..c99b04da91 100644 --- a/docs/source/manuals/installation/install-openmw.rst +++ b/docs/source/manuals/installation/install-openmw.rst @@ -2,53 +2,59 @@ Install OpenMW ============== -The (easier) Binary Way -======================= +Direct Download +=============== If you're not sure what any of the different methods mean, you should probably stick to this one. Simply download the latest version for your operating system from `github.com/OpenMW/openmw/releases `_ and run the install package once downloaded. It's now installed! - .. note:: - There is no need to uninstall previous versions - as OpenMW automatically installs into a separate directory for each new version. - Your saves and configuration are compatible and accessible between versions. +.. note:: + There is no need to uninstall previous versions + as OpenMW automatically installs into a separate directory for each new version. + Your saves and configuration are compatible and accessible between versions. -The (bleeding edge) Source Way -============================== +From Source +=========== Visit the `Development Environment Setup `_ section of the Wiki for detailed instructions on how to build the engine. -The Ubuntu Way -============== +Ubuntu +====== A `Launchpad PPA `_ is available. -Add it and install OpenMW:: +Add it and install OpenMW. + +.. code-block:: console $ sudo add-apt-repository ppa:openmw/openmw $ sudo apt update $ sudo apt install openmw -The Arch Linux Way -================== +Arch Linux +========== The binary package is available in the official [community] Repositories. -To install, simply run the following as root (or in sudo):: +To install, simply run the following as root (or in sudo). - # pacman -S openmw +.. code-block:: console -The Void Linux Way -================== + $ pacman -S openmw + +Void Linux +========== The binary package is available in the official Repository -To install simply run the following as root (or in sudo):: +To install simply run the following as root (or in sudo). - # xbps-install openmw +.. code-block:: console -The Debian Way -============== + $ xbps-install openmw + +Debian +====== OpenMW is available from the unstable (sid) repository of Debian contrib and can be easily installed if you are using testing or unstable. @@ -56,10 +62,11 @@ However, it depends on several packages which are not in stable, so it is not possible to install OpenMW in Wheezy without creating a FrankenDebian. This is not recommended or supported. -The Flatpak Way -=============== +Flatpak +======= OpenMW is available as a flatpak. With flatpak installed, run the command below. It should show up on your desktop. -:: - # flatpak install openmw +.. code-block:: console + + $ flatpak install openmw diff --git a/docs/source/manuals/openmw-cs/index.rst b/docs/source/manuals/openmw-cs/index.rst index f1f51409d4..637f5858b2 100644 --- a/docs/source/manuals/openmw-cs/index.rst +++ b/docs/source/manuals/openmw-cs/index.rst @@ -1,33 +1,35 @@ -OpenMW CS User Manual +OpenMW-CS User Manual ##################### -The following document is the complete user manual for *OpenMW CS*, the +The following document is the complete user manual for *OpenMW-CS*, the construction set for the OpenMW game engine. It is intended to serve both as an introduction and a reference for the application. Even if you are familiar with modding *The Elder Scrolls III: Morrowind* you should at least read the first few chapters to familiarise yourself with the new interface. .. warning:: - OpenMW CS is still software in development. The manual does not cover any of + OpenMW-CS is still software in development. The manual does not cover any of its shortcomings, it is written as if everything was working as intended. Please report any software problems as bugs in the software, not errors in the manual. -.. toctree:: - :caption: Table of Contents - :maxdepth: 2 +.. dropdown:: Table of Contents + :icon: book - foreword - tour - files-and-directories - starting-dialog - tables - tables-file - tables-world - tables-mechanics - tables-characters - tables-assets - record-types - record-filters - cell-view - records-drag-and-drop + .. toctree:: + :maxdepth: 2 + + foreword + tour + files-and-directories + starting-dialog + tables + tables-file + tables-world + tables-mechanics + tables-characters + tables-assets + record-types + record-filters + cell-view + records-drag-and-drop diff --git a/docs/source/reference/index.rst b/docs/source/reference/index.rst deleted file mode 100644 index 9aa409f784..0000000000 --- a/docs/source/reference/index.rst +++ /dev/null @@ -1,11 +0,0 @@ -################## -Reference Material -################## - -.. toctree:: - :maxdepth: 2 - - modding/index - lua-scripting/index - postprocessing/index - documentationHowTo diff --git a/docs/source/reference/lua-scripting/ai/combat.rst b/docs/source/reference/lua-scripting/ai/combat.rst new file mode 100644 index 0000000000..06dd9c1452 --- /dev/null +++ b/docs/source/reference/lua-scripting/ai/combat.rst @@ -0,0 +1,36 @@ +Combat +====== + +.. include:: ../version.rst + +Attack another actor. + +**Arguments** + +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - name + - type + - description + * - type + - string [required] + - the name of the package (see packages listed below) + * - cancelOther + - boolean [default=true] + - whether to cancel all other AI packages + * - target + - `GameObject <../openmw_core.html##(GameObject)>`_ [required] + - the actor to attack + +**Examples** + +.. code-block:: Lua + + -- from local script add package to self + local AI = require('openmw.interfaces').AI + AI.startPackage({type='Combat', target=anotherActor}) + + -- via event to any actor + actor:sendEvent('StartAIPackage', {type='Combat', target=anotherActor}) diff --git a/docs/source/reference/lua-scripting/ai/escort.rst b/docs/source/reference/lua-scripting/ai/escort.rst new file mode 100644 index 0000000000..fd9f2d05e5 --- /dev/null +++ b/docs/source/reference/lua-scripting/ai/escort.rst @@ -0,0 +1,49 @@ +Escort +====== + +.. include:: ../version.rst + +Escort another actor to the given location. + +**Arguments** + +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - name + - type + - description + * - type + - string [required] + - the name of the package (see packages listed below) + * - cancelOther + - boolean [default=true] + - whether to cancel all other AI packages + * - target + - `GameObject <../openmw_core.html##(GameObject)>`_ [required] + - the actor to follow + * - destPosition + - `3d vector <../openmw_util.html##(Vector3)>`_ [required] + - the destination point + * - destCell + - Cell [optional] + - the destination cell + * - duration + - number [optional] + - duration in game time (will be rounded up to the next hour) + * - isRepeat + - boolean [optional] + - Will the package repeat (true or false) + +**Example** + +.. code-block:: Lua + + actor:sendEvent('StartAIPackage', { + type = 'Escort', + target = object.self, + destPosition = util.vector3(x, y, z), + duration = 3 * time.hour, + isRepeat = true + }) diff --git a/docs/source/reference/lua-scripting/ai/follow.rst b/docs/source/reference/lua-scripting/ai/follow.rst new file mode 100644 index 0000000000..ac6b70bcf7 --- /dev/null +++ b/docs/source/reference/lua-scripting/ai/follow.rst @@ -0,0 +1,37 @@ +Follow +====== + +.. include:: ../version.rst + +Follow another actor. + +**Arguments** + +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - name + - type + - description + * - type + - string [required] + - the name of the package (see packages listed below) + * - cancelOther + - boolean [default=true] + - whether to cancel all other AI packages + * - target + - `GameObject <../openmw_core.html##(GameObject)>`_ [required] + - the actor to follow + * - destCell + - Cell [optional] + - the destination cell + * - duration + - number [optional] + - duration in game time (will be rounded up to the next hour) + * - destPosition + - `3d vector <../openmw_util.html##(Vector3)>`_ [optional] + - the destination point + * - isRepeat + - boolean [optional] + - Will the package repeat (true or false) diff --git a/docs/source/reference/lua-scripting/ai/pursue.rst b/docs/source/reference/lua-scripting/ai/pursue.rst new file mode 100644 index 0000000000..08385c57ae --- /dev/null +++ b/docs/source/reference/lua-scripting/ai/pursue.rst @@ -0,0 +1,25 @@ +Pursue +====== + +.. include:: ../version.rst + +Pursue another actor. + +**Arguments** + +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - name + - type + - description + * - type + - string [required] + - the name of the package (see packages listed below) + * - cancelOther + - boolean [default=true] + - whether to cancel all other AI packages + * - target + - `GameObject <../openmw_core.html##(GameObject)>`_ [required] + - the actor to pursue diff --git a/docs/source/reference/lua-scripting/ai/travel.rst b/docs/source/reference/lua-scripting/ai/travel.rst new file mode 100644 index 0000000000..06a47c868d --- /dev/null +++ b/docs/source/reference/lua-scripting/ai/travel.rst @@ -0,0 +1,28 @@ +Travel +====== + +.. include:: ../version.rst + +Go to given location. + +**Arguments** + +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - name + - type + - description + * - type + - string [required] + - the name of the package (see packages listed below) + * - cancelOther + - boolean [default=true] + - whether to cancel all other AI packages + * - destPosition + - `3d vector <../openmw_util.html##(Vector3)>`_ [required] + - the point to travel to + * - isRepeat + - boolean [optional] + - Will the package repeat (true or false) diff --git a/docs/source/reference/lua-scripting/ai/wander.rst b/docs/source/reference/lua-scripting/ai/wander.rst new file mode 100644 index 0000000000..fc702f12e2 --- /dev/null +++ b/docs/source/reference/lua-scripting/ai/wander.rst @@ -0,0 +1,53 @@ +Wander +====== + +.. include:: ../version.rst + +Wander nearby current position. + +**Arguments** + +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - name + - type + - description + * - type + - string [required] + - the name of the package (see packages listed below) + * - distance + - float [default=0] + - the actor to follow + * - duration + - number [optional] + - duration in game time (will be rounded up to the next hour) + * - idle + - table [optional] + - Idle chance values, up to 8 + * - isRepeat + - boolean [optional] + - Will the package repeat (true or false) + +**Example** + +.. code-block:: Lua + + local idleTable = { + idle2 = 60, + idle3 = 50, + idle4 = 40, + idle5 = 30, + idle6 = 20, + idle7 = 10, + idle8 = 0, + idle9 = 25 + } + actor:sendEvent('StartAIPackage', { + type = 'Wander', + distance = 5000, + duration = 5 * time.hour, + idle = idleTable, + isRepeat = true + }) diff --git a/docs/source/reference/lua-scripting/aipackages.rst b/docs/source/reference/lua-scripting/aipackages.rst deleted file mode 100644 index 7a23d156f5..0000000000 --- a/docs/source/reference/lua-scripting/aipackages.rst +++ /dev/null @@ -1,225 +0,0 @@ -Built-in AI packages -==================== - -.. include:: version.rst - -Starting an AI package ----------------------- - -There are two ways to start AI package: - -.. code-block:: Lua - - -- from local script add package to self - local AI = require('openmw.interfaces').AI - AI.startPackage(options) - - -- via event to any actor - actor:sendEvent('StartAIPackage', options) - -``options`` is Lua table with arguments of the AI package. - -**Common arguments that can be used with any AI package** - -.. list-table:: - :header-rows: 1 - :widths: 20 20 60 - - * - name - - type - - description - * - type - - string [required] - - the name of the package (see packages listed below) - * - cancelOther - - boolean [default=true] - - whether to cancel all other AI packages - -Combat ------- - -Attack another actor. - -**Arguments** - -.. list-table:: - :header-rows: 1 - :widths: 20 20 60 - - * - name - - type - - description - * - target - - `GameObject `_ [required] - - the actor to attack - -**Examples** - -.. code-block:: Lua - - -- from local script add package to self - local AI = require('openmw.interfaces').AI - AI.startPackage({type='Combat', target=anotherActor}) - - -- via event to any actor - actor:sendEvent('StartAIPackage', {type='Combat', target=anotherActor}) - -Pursue ------- - -Pursue another actor. - -**Arguments** - -.. list-table:: - :header-rows: 1 - :widths: 20 20 60 - - * - name - - type - - description - * - target - - `GameObject `_ [required] - - the actor to pursue - -Follow ------- - -Follow another actor. - -**Arguments** - -.. list-table:: - :header-rows: 1 - :widths: 20 20 60 - - * - name - - type - - description - * - target - - `GameObject `_ [required] - - the actor to follow - * - destCell - - Cell [optional] - - the destination cell - * - duration - - number [optional] - - duration in game time (will be rounded up to the next hour) - * - destPosition - - `3d vector `_ [optional] - - the destination point - * - isRepeat - - boolean [optional] - - Will the package repeat (true or false) - -Escort ------- - -Escort another actor to the given location. - -**Arguments** - -.. list-table:: - :header-rows: 1 - :widths: 20 20 60 - - * - name - - type - - description - * - target - - `GameObject `_ [required] - - the actor to follow - * - destPosition - - `3d vector `_ [required] - - the destination point - * - destCell - - Cell [optional] - - the destination cell - * - duration - - number [optional] - - duration in game time (will be rounded up to the next hour) - * - isRepeat - - boolean [optional] - - Will the package repeat (true or false) - -**Example** - -.. code-block:: Lua - - actor:sendEvent('StartAIPackage', { - type = 'Escort', - target = object.self, - destPosition = util.vector3(x, y, z), - duration = 3 * time.hour, - isRepeat = true - }) - -Wander ------- - -Wander nearby current position. - -**Arguments** - -.. list-table:: - :header-rows: 1 - :widths: 20 20 60 - - * - name - - type - - description - * - distance - - float [default=0] - - the actor to follow - * - duration - - number [optional] - - duration in game time (will be rounded up to the next hour) - * - idle - - table [optional] - - Idle chance values, up to 8 - * - isRepeat - - boolean [optional] - - Will the package repeat (true or false) - -**Example** - -.. code-block:: Lua - - local idleTable = { - idle2 = 60, - idle3 = 50, - idle4 = 40, - idle5 = 30, - idle6 = 20, - idle7 = 10, - idle8 = 0, - idle9 = 25 - } - actor:sendEvent('StartAIPackage', { - type = 'Wander', - distance = 5000, - duration = 5 * time.hour, - idle = idleTable, - isRepeat = true - }) - -Travel ------- - -Go to given location. - -**Arguments** - -.. list-table:: - :header-rows: 1 - :widths: 20 20 60 - - * - name - - type - - description - * - destPosition - - `3d vector `_ [required] - - the point to travel to - * - isRepeat - - boolean [optional] - - Will the package repeat (true or false) diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index 82a860b355..b8920b7ff1 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -1,60 +1,21 @@ -################# -Lua API reference -################# +############# +API Reference +############# .. include:: version.rst .. toctree:: :hidden: - engine_handlers - user_interface - aipackages + index_packages + index_auxpackages + index_aipackages + index_interfaces + UI setting_renderers + Engine Handlers events - openmw_ambient - openmw_animation - openmw_async - openmw_camera - openmw_core - openmw_debug - openmw_input - openmw_markup - openmw_menu - openmw_nearby - openmw_postprocessing - openmw_self - openmw_storage - openmw_types - openmw_ui - openmw_util - openmw_vfs - openmw_world - openmw_aux_calendar - openmw_aux_time - openmw_aux_ui - openmw_aux_util - interface_activation - interface_ai - interface_animation - interface_camera - interface_controls - interface_gamepadcontrols - interface_item_usage - interface_mwui - interface_settings - interface_skill_progression - interface_ui - interface_crimes - iterables - - -- :ref:`Engine handlers reference` -- :ref:`User interface reference ` -- `Game object reference `_ -- `Cell reference `_ -- :ref:`Built-in AI packages` -- :ref:`Built-in events` + Iterables **API packages** @@ -66,7 +27,7 @@ Player scripts are local scripts that are attached to a player. .. include:: tables/packages.rst -**openmw_aux** +**Auxiliary packages** ``openmw_aux.*`` are built-in libraries that are itself implemented in Lua. They can not do anything that is not possible with the basic API, they only make it more convenient. Sources can be found in ``resources/vfs/openmw_aux``. In theory mods can override them, but it is not recommended. diff --git a/docs/source/reference/lua-scripting/engine_handlers.rst b/docs/source/reference/lua-scripting/engine_handlers.rst index ac6979a236..29b14aee55 100644 --- a/docs/source/reference/lua-scripting/engine_handlers.rst +++ b/docs/source/reference/lua-scripting/engine_handlers.rst @@ -7,6 +7,8 @@ Engine handler is a function defined by a script, that can be called by the engi **Can be defined by any script** +|bdg-ctx-all| + .. list-table:: :widths: 20 80 @@ -16,6 +18,8 @@ Engine handler is a function defined by a script, that can be called by the engi **Can be defined by any non-menu script** +|bdg-ctx-global| |bdg-ctx-local| + .. list-table:: :widths: 20 80 @@ -39,6 +43,8 @@ Engine handler is a function defined by a script, that can be called by the engi **Only for global scripts** +|bdg-ctx-global| + .. list-table:: :widths: 20 80 @@ -61,6 +67,8 @@ Engine handler is a function defined by a script, that can be called by the engi **Only for local scripts** +|bdg-ctx-local| + .. list-table:: :widths: 20 80 @@ -86,6 +94,8 @@ Engine handler is a function defined by a script, that can be called by the engi **Only menu scripts and local scripts attached to a player** +|bdg-ctx-menu| |bdg-ctx-player| + .. list-table:: :widths: 20 80 @@ -140,6 +150,8 @@ Engine handler is a function defined by a script, that can be called by the engi **Only for local scripts attached to a player** +|bdg-ctx-player| + .. list-table:: :widths: 20 80 @@ -152,6 +164,8 @@ Engine handler is a function defined by a script, that can be called by the engi **Only for menu scripts** +|bdg-ctx-menu| + .. list-table:: :widths: 20 80 diff --git a/docs/source/reference/lua-scripting/events.rst b/docs/source/reference/lua-scripting/events.rst index 282e3d1173..007e0e43d1 100644 --- a/docs/source/reference/lua-scripting/events.rst +++ b/docs/source/reference/lua-scripting/events.rst @@ -1,5 +1,5 @@ -Built-in events -=============== +Events +====== .. include:: version.rst diff --git a/docs/source/reference/lua-scripting/index.rst b/docs/source/reference/lua-scripting/index.rst deleted file mode 100644 index f3764c4401..0000000000 --- a/docs/source/reference/lua-scripting/index.rst +++ /dev/null @@ -1,18 +0,0 @@ -#################### -OpenMW Lua scripting -#################### - -.. note:: - OpenMW Lua is not compatible with MWSE. - -.. include:: version.rst - -.. toctree:: - :caption: Table of Contents - :includehidden: - :maxdepth: 2 - - overview - api - teal - diff --git a/docs/source/reference/lua-scripting/index_aipackages.rst b/docs/source/reference/lua-scripting/index_aipackages.rst new file mode 100644 index 0000000000..2b8b41bb08 --- /dev/null +++ b/docs/source/reference/lua-scripting/index_aipackages.rst @@ -0,0 +1,24 @@ +AI packages +============ + +.. include:: version.rst + +.. toctree:: + :hidden: + :glob: + + ai/* + +Starting an AI package +---------------------- + +There are two ways to start AI package: + +.. code-block:: Lua + + -- from local script add package to self + local AI = require('openmw.interfaces').AI + AI.startPackage(options) + + -- via event to any actor + actor:sendEvent('StartAIPackage', options) diff --git a/docs/source/reference/lua-scripting/index_auxpackages.rst b/docs/source/reference/lua-scripting/index_auxpackages.rst new file mode 100644 index 0000000000..ea17a3ddbb --- /dev/null +++ b/docs/source/reference/lua-scripting/index_auxpackages.rst @@ -0,0 +1,21 @@ +################## +Auxiliary Packages +################## + +.. include:: version.rst + +.. toctree:: + :hidden: + + calendar + time + ui + util + + +**Auxiliary packages** + +``openmw_aux.*`` are built-in libraries that are itself implemented in Lua. They can not do anything that is not possible with the basic API, they only make it more convenient. +Sources can be found in ``resources/vfs/openmw_aux``. In theory mods can override them, but it is not recommended. + +.. include:: tables/aux_packages.rst diff --git a/docs/source/reference/lua-scripting/index_interfaces.rst b/docs/source/reference/lua-scripting/index_interfaces.rst new file mode 100644 index 0000000000..37a2df63c4 --- /dev/null +++ b/docs/source/reference/lua-scripting/index_interfaces.rst @@ -0,0 +1,25 @@ +########## +Interfaces +########## + +.. include:: version.rst + +.. toctree:: + :hidden: + + Activation + AI + AnimationController + Camera + Controls + Crimes + GamepadControls + ItemUsage + MWUI + Settings + SkillProgression + UI + +**Interfaces of built-in scripts** + +.. include:: tables/interfaces.rst diff --git a/docs/source/reference/lua-scripting/index_packages.rst b/docs/source/reference/lua-scripting/index_packages.rst new file mode 100644 index 0000000000..53a836519f --- /dev/null +++ b/docs/source/reference/lua-scripting/index_packages.rst @@ -0,0 +1,37 @@ +######## +Packages +######## + +.. include:: version.rst + +.. toctree:: + :hidden: + + ambient + animation + async + camera + core + debug + input + markup + menu + nearby + postprocessing + self + storage + types + ui + util + vfs + world + +**API packages** + +API packages provide functions that can be called by scripts. I.e. it is a script-to-engine interaction. +A package can be loaded with ``require('')``. +It can not be overloaded even if there is a lua file with the same name. +The list of available packages is different for global and for local scripts. +Player scripts are local scripts that are attached to a player. + +.. include:: tables/packages.rst diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index b889b09a9f..852d63ca0a 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -1,5 +1,5 @@ -Overview of Lua scripting -######################### +Overview +######## .. include:: version.rst @@ -384,8 +384,8 @@ Player scripts are local scripts that are attached to a player. .. include:: tables/packages.rst -openmw_aux ----------- +Auxiliary packages +------------------ ``openmw_aux.*`` are built-in libraries that are themselves implemented in Lua. They can not do anything that is not possible with the basic API, they only make it more convenient. Sources can be found in ``resources/vfs/openmw_aux``. In theory mods can override them, but it is not recommended. @@ -544,7 +544,7 @@ The protection mod attaches an additional local script to every actor. The scrip In order to be able to intercept the event, the protection script should be placed in the load order below the original script. -See :ref:`the list of events ` that are used by built-in scripts. +See :ref:`the list of events ` that are used by built-in scripts. Timers @@ -618,7 +618,7 @@ An example: } } -Also in `openmw_aux`_ is the helper function ``runRepeatedly``, it is implemented on top of unsavable timers: +Also in `Auxiliary packages`_ is the helper function ``runRepeatedly``, it is implemented on top of unsavable timers: .. code-block:: Lua @@ -641,7 +641,7 @@ Using IDE for Lua scripting Find the directory ``resources/lua_api`` in your installation of OpenMW. It describes OpenMW LuaAPI in `LDT Documentation Language `__. -It is the source from which the :ref:`API reference ` is generated. +It is the source from which the :ref:`API reference ` is generated. If you write scripts using `Lua Development Tools `__ (eclipse-based IDE), you can import these files to get code autocompletion and integrated OpenMW API reference. Here are the steps: diff --git a/docs/source/reference/lua-scripting/setting_renderers.rst b/docs/source/reference/lua-scripting/setting_renderers.rst index b85c7fbaab..f315615cb4 100644 --- a/docs/source/reference/lua-scripting/setting_renderers.rst +++ b/docs/source/reference/lua-scripting/setting_renderers.rst @@ -1,5 +1,5 @@ -Built-in Setting Renderers -========================== +Setting Renderers +================= .. include:: version.rst diff --git a/docs/source/reference/lua-scripting/tables/aux_packages.rst b/docs/source/reference/lua-scripting/tables/aux_packages.rst index d0217ce202..202f5219c2 100644 --- a/docs/source/reference/lua-scripting/tables/aux_packages.rst +++ b/docs/source/reference/lua-scripting/tables/aux_packages.rst @@ -1,12 +1,19 @@ -+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ -| Built-in library | Can be used | Description | -+=========================================================+====================+===============================================================+ -|:ref:`openmw_aux.calendar ` | everywhere | | Game time calendar | -+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ -|:ref:`openmw_aux.util ` | everywhere | | Miscellaneous utils | -+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ -|:ref:`openmw_aux.time ` | everywhere | | Timers and game time utils | -+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ -|:ref:`openmw_aux.ui ` | by player and menu | | User interface utils | -| | scripts | | -+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +.. list-table:: + :widths: 30 40 60 + :header-rows: 1 + + * - Module + - Context + - Description + * - :doc:`calendar ` + - |bdg-ctx-all| + - Game time calendar + * - :doc:`time ` + - |bdg-ctx-all| + - Timers and game time utils + * - :doc:`ui ` + - |bdg-ctx-menu| |bdg-ctx-player| + - User interface utils + * - :doc:`util ` + - |bdg-ctx-all| + - Miscellaneous utils diff --git a/docs/source/reference/lua-scripting/tables/interfaces.rst b/docs/source/reference/lua-scripting/tables/interfaces.rst index d8dfffe47d..8496d01029 100644 --- a/docs/source/reference/lua-scripting/tables/interfaces.rst +++ b/docs/source/reference/lua-scripting/tables/interfaces.rst @@ -1,48 +1,43 @@ .. list-table:: - :widths: 20 20 60 + :widths: 30 40 60 + :header-rows: 1 * - Interface - - Can be used + - Context - Description - * - :ref:`Activation ` - - by global scripts + * - :doc:`Activation ` + - |bdg-ctx-global| - Allows to extend or override built-in activation mechanics. - * - :ref:`AI ` - - by local scripts + * - :doc:`AI ` + - |bdg-ctx-local| - Control basic AI of NPCs and creatures. - * - :ref:`AnimationController ` - - by local scripts + * - :doc:`AnimationController ` + - |bdg-ctx-local| - Control animations of NPCs and creatures. - * - :ref:`Camera ` - - by player scripts - - | Allows to alter behavior of the built-in camera script - | without overriding the script completely. - * - :ref:`Controls ` - - by player scripts - - | Allows to alter behavior of the built-in script - | that handles player controls. - * - :ref:`GamepadControls ` - - by player scripts - - | Allows to alter behavior of the built-in script - | that handles player gamepad controls. - * - :ref:`ItemUsage ` - - by global scripts - - | Allows to extend or override built-in item usage - | mechanics. - * - :ref:`SkillProgression ` - - by local scripts - - | Control, extend, and override skill progression of the - | player. - * - :ref:`Settings ` - - by player, menu, and global scripts - - Save, display and track changes of setting values. - * - :ref:`MWUI ` - - by player and menu scripts - - Morrowind-style UI templates. - * - :ref:`UI ` - - by player scripts - - | High-level UI modes interface. Allows to override parts - | of the interface. - * - :ref:`Crimes ` - - by global scripts + * - :doc:`Camera ` + - |bdg-ctx-player| + - Allows to alter behavior of the built-in camera script without overriding the script completely. + * - :doc:`Controls ` + - |bdg-ctx-player| + - Allows to alter behavior of the built-in script that handles player controls. + * - :doc:`Crimes ` + - |bdg-ctx-global| - Commit crimes. + * - :doc:`GamepadControls ` + - |bdg-ctx-player| + - Allows to alter behavior of the built-in script that handles player gamepad controls. + * - :doc:`ItemUsage ` + - |bdg-ctx-global| + - Allows to extend or override built-in item usage mechanics. + * - :doc:`MWUI ` + - |bdg-ctx-menu| |bdg-ctx-player| + - Morrowind-style UI templates. + * - :doc:`Settings ` + - |bdg-ctx-global| |bdg-ctx-menu| |bdg-ctx-player| + - Save, display and track changes of setting values. + * - :doc:`SkillProgression ` + - |bdg-ctx-local| + - Control, extend, and override skill progression of the player. + * - :doc:`UI ` + - |bdg-ctx-player| + - High-level UI modes interface. Allows to override parts of the interface. diff --git a/docs/source/reference/lua-scripting/tables/packages.rst b/docs/source/reference/lua-scripting/tables/packages.rst index e66926e5e4..2485f2c0cd 100644 --- a/docs/source/reference/lua-scripting/tables/packages.rst +++ b/docs/source/reference/lua-scripting/tables/packages.rst @@ -1,49 +1,64 @@ -+------------------------------------------------------------+--------------------+---------------------------------------------------------------+ -| Package | Can be used | Description | -+============================================================+====================+===============================================================+ -|:ref:`openmw.ambient ` | by player and menu | | Controls background sounds for given player. | -| | scripts | | -+------------------------------------------------------------+--------------------+---------------------------------------------------------------+ -|:ref:`openmw.animation ` | by local and | | Animation controls | -| | player scripts | | -+------------------------------------------------------------+--------------------+---------------------------------------------------------------+ -|:ref:`openmw.async ` | everywhere | | Timers and callbacks. | -+------------------------------------------------------------+--------------------+---------------------------------------------------------------+ -|:ref:`openmw.camera ` | by player scripts | | Controls camera. | -+------------------------------------------------------------+--------------------+---------------------------------------------------------------+ -|:ref:`openmw.core ` | everywhere | | Functions that are common for both global and local scripts | -+------------------------------------------------------------+--------------------+---------------------------------------------------------------+ -|:ref:`openmw.debug ` | by player scripts | | Collection of debug utils. | -+------------------------------------------------------------+--------------------+---------------------------------------------------------------+ -|:ref:`openmw.input ` | by player and menu | | User input. | -| | scripts | | -+------------------------------------------------------------+--------------------+---------------------------------------------------------------+ -|:ref:`openmw.interfaces