diff --git a/CMakeLists.txt b/CMakeLists.txt index 7301496172..a1d25f9350 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,8 +82,8 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 50) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 78) -set(OPENMW_POSTPROCESSING_API_REVISION 2) +set(OPENMW_LUA_API_REVISION 80) +set(OPENMW_POSTPROCESSING_API_REVISION 3) set(OPENMW_VERSION_COMMITHASH "") set(OPENMW_VERSION_TAGHASH "") @@ -590,30 +590,10 @@ if(OPENMW_LTO_BUILD) endif() endif() - -if (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang) - set(OPENMW_CXX_FLAGS "-Wall -Wextra -Wundef -Wextra-semi -Wno-unused-parameter -pedantic -Wno-long-long -Wnon-virtual-dtor -Wunused ${OPENMW_CXX_FLAGS}") - - if (CMAKE_CXX_COMPILER_ID STREQUAL GNU) - # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105438 - set(OPENMW_CXX_FLAGS "-Wno-array-bounds ${OPENMW_CXX_FLAGS}") - endif() - - if (APPLE) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++") - endif() - - if (CMAKE_CXX_COMPILER_ID STREQUAL Clang AND NOT APPLE) - if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 3.6 OR CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 3.6) - set(OPENMW_CXX_FLAGS "${OPENMW_CXX_FLAGS} -Wno-potentially-evaluated-expression") - endif () - endif() - - if (CMAKE_CXX_COMPILER_ID STREQUAL GNU) - set(OPENMW_CXX_FLAGS "${OPENMW_CXX_FLAGS} -Wno-unused-but-set-parameter -Wduplicated-branches -Wduplicated-cond -Wlogical-op") - endif() -endif (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang) +if (APPLE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++") +endif() # Extern @@ -624,8 +604,42 @@ if (BUILD_OPENCS OR BUILD_OPENCS_TESTS) add_subdirectory (extern/osgQt) endif() +if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" OR CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + add_compile_options("/W4") + + set(WARNINGS_DISABLE + 4100 # Unreferenced formal parameter (-Wunused-parameter) + 4127 # Conditional expression is constant + 4996 # Function was declared deprecated + 5054 # Deprecated operations between enumerations of different types caused by Qt headers + ) + + foreach(d ${WARNINGS_DISABLE}) + add_compile_options("/wd${d}") + endforeach(d) + + if(OPENMW_MSVC_WERROR) + add_compile_options("/WX") + endif() +else () + add_compile_options("-Wall" "-Wextra" "-Wundef" "-Wextra-semi" "-Wno-unused-parameter" "-pedantic" "-Wno-long-long" "-Wnon-virtual-dtor" "-Wunused") + + if (CMAKE_CXX_COMPILER_ID STREQUAL Clang AND NOT APPLE) + if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 3.6 OR CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 3.6) + add_compile_options("-Wno-potentially-evaluated-expression") + endif () + endif() + + if (CMAKE_CXX_COMPILER_ID STREQUAL GNU) + add_compile_options("-Wno-unused-but-set-parameter" "-Wduplicated-branches" "-Wduplicated-cond" "-Wlogical-op") + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105438 + add_compile_options("-Wno-array-bounds") + endif() +endif () + if (OPENMW_CXX_FLAGS) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OPENMW_CXX_FLAGS}") + separate_arguments(OPENMW_CXX_FLAGS NATIVE_COMMAND "${OPENMW_CXX_FLAGS}") + add_compile_options(${OPENMW_CXX_FLAGS}) endif() # Components @@ -715,87 +729,9 @@ if (WIN32) set_target_properties(openmw PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:WINDOWS") endif() - # Play a bit with the warning levels - - set(WARNINGS "/W4") - - set(WARNINGS_DISABLE - 4100 # Unreferenced formal parameter (-Wunused-parameter) - 4127 # Conditional expression is constant - 4996 # Function was declared deprecated - 5054 # Deprecated operations between enumerations of different types caused by Qt headers - ) - - foreach(d ${WARNINGS_DISABLE}) - list(APPEND WARNINGS "/wd${d}") - endforeach(d) - - if(OPENMW_MSVC_WERROR) - list(APPEND WARNINGS "/WX") - endif() - - target_compile_options(components PRIVATE ${WARNINGS}) - target_compile_options(osg-ffmpeg-videoplayer PRIVATE ${WARNINGS}) - if (MSVC_VERSION GREATER_EQUAL 1915 AND MSVC_VERSION LESS 1920) target_compile_definitions(components INTERFACE _ENABLE_EXTENDED_ALIGNED_STORAGE) endif() - - if (BUILD_BSATOOL) - target_compile_options(bsatool PRIVATE ${WARNINGS}) - endif() - - if (BUILD_ESMTOOL) - target_compile_options(esmtool PRIVATE ${WARNINGS}) - endif() - - if (BUILD_ESSIMPORTER) - target_compile_options(openmw-essimporter PRIVATE ${WARNINGS}) - endif() - - if (BUILD_LAUNCHER) - target_compile_options(openmw-launcher PRIVATE ${WARNINGS}) - endif() - - if (BUILD_MWINIIMPORTER) - target_compile_options(openmw-iniimporter PRIVATE ${WARNINGS}) - endif() - - if (BUILD_OPENCS) - target_compile_options(openmw-cs PRIVATE ${WARNINGS}) - endif() - - if (BUILD_OPENMW) - target_compile_options(openmw PRIVATE ${WARNINGS}) - endif() - - if (BUILD_WIZARD) - target_compile_options(openmw-wizard PRIVATE ${WARNINGS}) - endif() - - if (BUILD_COMPONENTS_TESTS) - target_compile_options(components-tests PRIVATE ${WARNINGS}) - endif() - - if (BUILD_BENCHMARKS) - target_compile_options(openmw_detournavigator_navmeshtilescache_benchmark PRIVATE ${WARNINGS}) - endif() - - if (BUILD_NAVMESHTOOL) - target_compile_options(openmw-navmeshtool PRIVATE ${WARNINGS}) - endif() - - if (BUILD_BULLETOBJECTTOOL) - target_compile_options(openmw-bulletobjecttool PRIVATE ${WARNINGS} ${MT_BUILD}) - endif() - - if (BUILD_OPENCS_TESTS) - target_compile_options(openmw-cs-tests PRIVATE ${WARNINGS}) - endif() - - if (BUILD_OPENMW_TESTS) - target_compile_options(openmw-tests PRIVATE ${WARNINGS}) - endif() endif(MSVC) # TODO: At some point release builds should not use the console but rather write to a log file diff --git a/apps/bulletobjecttool/main.cpp b/apps/bulletobjecttool/main.cpp index 5cacedd07e..659d97b7f1 100644 --- a/apps/bulletobjecttool/main.cpp +++ b/apps/bulletobjecttool/main.cpp @@ -155,7 +155,7 @@ namespace VFS::Manager vfs; - VFS::registerArchives(&vfs, fileCollections, archives, true); + VFS::registerArchives(&vfs, fileCollections, archives, true, &encoder.getStatelessEncoder()); Settings::Manager::load(config); diff --git a/apps/components_tests/fx/technique.cpp b/apps/components_tests/fx/technique.cpp index ad57074b18..2becf4da5b 100644 --- a/apps/components_tests/fx/technique.cpp +++ b/apps/components_tests/fx/technique.cpp @@ -113,7 +113,8 @@ namespace void compile(const std::string& name) { - mTechnique = std::make_unique(*mVFS.get(), mImageManager, name, 1, 1, true, true); + mTechnique = std::make_unique( + *mVFS.get(), mImageManager, Technique::makeFileName(name), name, 1, 1, true, true); mTechnique->compile(); } }; diff --git a/apps/navmeshtool/main.cpp b/apps/navmeshtool/main.cpp index 08ed10c3b3..6cfa7fc61d 100644 --- a/apps/navmeshtool/main.cpp +++ b/apps/navmeshtool/main.cpp @@ -189,7 +189,7 @@ namespace NavMeshTool VFS::Manager vfs; - VFS::registerArchives(&vfs, fileCollections, archives, true); + VFS::registerArchives(&vfs, fileCollections, archives, true, &encoder.getStatelessEncoder()); Settings::Manager::load(config); diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index 8634134665..7f19008bd1 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -113,7 +113,7 @@ bool isBSA(const std::filesystem::path& path) std::unique_ptr makeArchive(const std::filesystem::path& path) { if (isBSA(path)) - return VFS::makeBsaArchive(path); + return VFS::makeBsaArchive(path, nullptr); if (std::filesystem::is_directory(path)) return std::make_unique(path); return nullptr; @@ -198,7 +198,7 @@ void readVFS(std::unique_ptr&& archive, const std::filesystem::pat { try { - readVFS(VFS::makeBsaArchive(file.second), file.second, quiet); + readVFS(VFS::makeBsaArchive(file.second, nullptr), file.second, quiet); } catch (const std::exception& e) { diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 00e5fec7b0..fa9251b949 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -143,7 +143,7 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data , mArchives(archives) , mVFS(std::make_unique()) { - VFS::registerArchives(mVFS.get(), Files::Collections(mDataPaths), mArchives, true); + VFS::registerArchives(mVFS.get(), Files::Collections(mDataPaths), mArchives, true, &mEncoder.getStatelessEncoder()); mResourcesManager.setVFS(mVFS.get()); @@ -1465,7 +1465,7 @@ std::vector CSMWorld::Data::getIds(bool listDeleted) const void CSMWorld::Data::assetsChanged() { mVFS.get()->reset(); - VFS::registerArchives(mVFS.get(), Files::Collections(mDataPaths), mArchives, true); + VFS::registerArchives(mVFS.get(), Files::Collections(mDataPaths), mArchives, true, &mEncoder.getStatelessEncoder()); const UniversalId assetTableIds[] = { UniversalId::Type_Meshes, UniversalId::Type_Icons, UniversalId::Type_Musics, UniversalId::Type_SoundsRes, UniversalId::Type_Textures, UniversalId::Type_Videos }; diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 0f82e953c1..29cfb41071 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -729,7 +729,7 @@ void OMW::Engine::prepareEngine() mVFS = std::make_unique(); - VFS::registerArchives(mVFS.get(), mFileCollections, mArchives, true); + VFS::registerArchives(mVFS.get(), mFileCollections, mArchives, true, &mEncoder.get()->getStatelessEncoder()); mResourceSystem = std::make_unique( mVFS.get(), Settings::cells().mCacheExpiryDelay, &mEncoder.get()->getStatelessEncoder()); diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 83a714cf05..8a9bd3dab9 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -397,6 +397,7 @@ namespace MWBase // Used in Lua bindings virtual const std::vector& getGuiModeStack() const = 0; virtual void setDisabledByLua(std::string_view windowId, bool disabled) = 0; + virtual bool isWindowVisible(std::string_view windowId) const = 0; virtual std::vector getAllWindowIds() const = 0; virtual std::vector getAllowedWindowIds(MWGui::GuiMode mode) const = 0; }; diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 9224f6f0d8..324ba5f98b 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -871,7 +871,8 @@ namespace MWClass ptr.getRefData().setCustomData(nullptr); // Reset to original position - MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().asVec3()); + MWBase::Environment::get().getWorld()->moveObject( + ptr, ptr.getCell()->getOriginCell(ptr), ptr.getCellRef().getPosition().asVec3()); MWBase::Environment::get().getWorld()->rotateObject( ptr, ptr.getCellRef().getPosition().asRotationVec3(), MWBase::RotationFlag_none); } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 0b61436d11..c03d132503 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1427,7 +1427,8 @@ namespace MWClass ptr.getRefData().setCustomData(nullptr); // Reset to original position - MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().asVec3()); + MWBase::Environment::get().getWorld()->moveObject( + ptr, ptr.getCell()->getOriginCell(ptr), ptr.getCellRef().getPosition().asVec3()); MWBase::Environment::get().getWorld()->rotateObject( ptr, ptr.getCellRef().getPosition().asRotationVec3(), MWBase::RotationFlag_none); } diff --git a/apps/openmw/mwgui/companionwindow.cpp b/apps/openmw/mwgui/companionwindow.cpp index 4a7863b856..841015f404 100644 --- a/apps/openmw/mwgui/companionwindow.cpp +++ b/apps/openmw/mwgui/companionwindow.cpp @@ -46,6 +46,7 @@ namespace MWGui , mSortModel(nullptr) , mModel(nullptr) , mSelectedItem(-1) + , mUpdateNextFrame(false) , mDragAndDrop(dragAndDrop) , mMessageBoxManager(manager) { @@ -170,12 +171,20 @@ namespace MWGui mItemView->resetScrollBars(); setTitle(actor.getClass().getName(actor)); + + mPtr.getClass().getContainerStore(mPtr).setContListener(this); } void CompanionWindow::onFrame(float dt) { checkReferenceAvailable(); - updateEncumbranceBar(); + + if (mUpdateNextFrame) + { + updateEncumbranceBar(); + mItemView->update(); + mUpdateNextFrame = false; + } } void CompanionWindow::updateEncumbranceBar() @@ -238,6 +247,16 @@ namespace MWGui mSortModel = nullptr; } + void CompanionWindow::itemAdded(const MWWorld::ConstPtr& item, int count) + { + mUpdateNextFrame = true; + } + + void CompanionWindow::itemRemoved(const MWWorld::ConstPtr& item, int count) + { + mUpdateNextFrame = true; + } + bool CompanionWindow::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) { if (arg.button == SDL_CONTROLLER_BUTTON_A) diff --git a/apps/openmw/mwgui/companionwindow.hpp b/apps/openmw/mwgui/companionwindow.hpp index c0cd87c9d5..9b12ffa49d 100644 --- a/apps/openmw/mwgui/companionwindow.hpp +++ b/apps/openmw/mwgui/companionwindow.hpp @@ -6,6 +6,8 @@ #include "referenceinterface.hpp" #include "windowbase.hpp" +#include "../mwworld/containerstore.hpp" + namespace MWGui { namespace Widgets @@ -19,7 +21,7 @@ namespace MWGui class SortFilterItemModel; class CompanionItemModel; - class CompanionWindow : public WindowBase, public ReferenceInterface + class CompanionWindow : public WindowBase, public ReferenceInterface, public MWWorld::ContainerStoreListener { public: CompanionWindow(DragAndDrop* dragAndDrop, MessageBoxManager* manager); @@ -32,6 +34,9 @@ namespace MWGui void onFrame(float dt) override; void clear() override { resetReference(); } + void itemAdded(const MWWorld::ConstPtr& item, int count) override; + void itemRemoved(const MWWorld::ConstPtr& item, int count) override; + std::string_view getWindowIdForLua() const override { return "Companion"; } bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override; @@ -45,6 +50,7 @@ namespace MWGui SortFilterItemModel* mSortModel; CompanionItemModel* mModel; int mSelectedItem; + bool mUpdateNextFrame; DragAndDrop* mDragAndDrop; diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 0be99d38a0..ac9c551d88 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -40,6 +40,7 @@ namespace MWGui , mSortModel(nullptr) , mModel(nullptr) , mSelectedItem(-1) + , mUpdateNextFrame(false) , mTreatNextOpenAsLoot(false) { getWidget(mDisposeCorpseButton, "DisposeCorpseButton"); @@ -196,6 +197,8 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCloseButton); setTitle(container.getClass().getName(container)); + + mPtr.getClass().getContainerStore(mPtr).setContListener(this); } void ContainerWindow::resetReference() @@ -399,4 +402,25 @@ namespace MWGui mItemView->setActiveControllerWindow(active); WindowBase::setActiveControllerWindow(active); } + + void ContainerWindow::onFrame(float dt) + { + checkReferenceAvailable(); + + if (mUpdateNextFrame) + { + mItemView->update(); + mUpdateNextFrame = false; + } + } + + void ContainerWindow::itemAdded(const MWWorld::ConstPtr& item, int count) + { + mUpdateNextFrame = true; + } + + void ContainerWindow::itemRemoved(const MWWorld::ConstPtr& item, int count) + { + mUpdateNextFrame = true; + } } diff --git a/apps/openmw/mwgui/container.hpp b/apps/openmw/mwgui/container.hpp index 96ee6a6380..672a39f9fa 100644 --- a/apps/openmw/mwgui/container.hpp +++ b/apps/openmw/mwgui/container.hpp @@ -6,6 +6,8 @@ #include "itemmodel.hpp" +#include "../mwworld/containerstore.hpp" + namespace MyGUI { class Gui; @@ -21,7 +23,7 @@ namespace MWGui namespace MWGui { - class ContainerWindow : public WindowBase, public ReferenceInterface + class ContainerWindow : public WindowBase, public ReferenceInterface, public MWWorld::ContainerStoreListener { public: ContainerWindow(DragAndDrop* dragAndDrop); @@ -30,7 +32,7 @@ namespace MWGui void onClose() override; void clear() override { resetReference(); } - void onFrame(float dt) override { checkReferenceAvailable(); } + void onFrame(float dt) override; void resetReference() override; @@ -38,6 +40,9 @@ namespace MWGui void treatNextOpenAsLoot() { mTreatNextOpenAsLoot = true; } + void itemAdded(const MWWorld::ConstPtr& item, int count) override; + void itemRemoved(const MWWorld::ConstPtr& item, int count) override; + std::string_view getWindowIdForLua() const override { return "Container"; } ControllerButtonStr* getControllerButtons() override; @@ -54,6 +59,7 @@ namespace MWGui SortFilterItemModel* mSortModel; ItemModel* mModel; int mSelectedItem; + bool mUpdateNextFrame; bool mTreatNextOpenAsLoot; MyGUI::Button* mDisposeCorpseButton; MyGUI::Button* mTakeButton; diff --git a/apps/openmw/mwgui/draganddrop.cpp b/apps/openmw/mwgui/draganddrop.cpp index 0fa2cc4e21..52b0939238 100644 --- a/apps/openmw/mwgui/draganddrop.cpp +++ b/apps/openmw/mwgui/draganddrop.cpp @@ -11,7 +11,6 @@ #include "controllers.hpp" #include "inventorywindow.hpp" #include "itemview.hpp" -#include "itemwidget.hpp" #include "sortfilteritemmodel.hpp" namespace MWGui @@ -72,25 +71,25 @@ namespace MWGui mSourceSortModel->addDragItem(mItem.mBase, count); } - ItemWidget* baseWidget = MyGUI::Gui::getInstance().createWidget( + mDraggedWidget = MyGUI::Gui::getInstance().createWidget( "MW_ItemIcon", 0, 0, 42, 42, MyGUI::Align::Default, "DragAndDrop"); Controllers::ControllerFollowMouse* controller = MyGUI::ControllerManager::getInstance() .createItem(Controllers::ControllerFollowMouse::getClassTypeName()) ->castType(); - MyGUI::ControllerManager::getInstance().addItem(baseWidget, controller); + MyGUI::ControllerManager::getInstance().addItem(mDraggedWidget, controller); - mDraggedWidget = baseWidget; - baseWidget->setItem(mItem.mBase); - baseWidget->setNeedMouseFocus(false); - baseWidget->setCount(count); - - sourceView->update(); + mDraggedWidget->setItem(mItem.mBase); + mDraggedWidget->setNeedMouseFocus(false); + mDraggedWidget->setCount(count); MWBase::Environment::get().getWindowManager()->setDragDrop(true); mIsOnDragAndDrop = true; + + // Update item view after completing drag-and-drop setup + mSourceView->update(); } void DragAndDrop::drop(ItemModel* targetModel, ItemView* targetView) @@ -124,6 +123,22 @@ namespace MWGui mSourceView->update(); } + void DragAndDrop::update() + { + if (mIsOnDragAndDrop) + { + int count = mItem.mBase.getCellRef().getCount(); + if (count < mDraggedCount) + { + mItem.mCount = count; + mDraggedCount = count; + mDraggedWidget->setCount(mDraggedCount); + mSourceSortModel->clearDragItems(); + mSourceSortModel->addDragItem(mItem.mBase, mDraggedCount); + } + } + } + void DragAndDrop::onFrame() { if (mIsOnDragAndDrop && mItem.mBase.getCellRef().getCount() == 0) @@ -137,8 +152,12 @@ namespace MWGui // since mSourceView doesn't get updated in drag() MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView(); - MyGUI::Gui::getInstance().destroyWidget(mDraggedWidget); - mDraggedWidget = nullptr; + if (mDraggedWidget) + { + MyGUI::Gui::getInstance().destroyWidget(mDraggedWidget); + mDraggedWidget = nullptr; + } + MWBase::Environment::get().getWindowManager()->setDragDrop(false); } diff --git a/apps/openmw/mwgui/draganddrop.hpp b/apps/openmw/mwgui/draganddrop.hpp index fab7f30d75..94da6cce5f 100644 --- a/apps/openmw/mwgui/draganddrop.hpp +++ b/apps/openmw/mwgui/draganddrop.hpp @@ -2,6 +2,7 @@ #define OPENMW_MWGUI_DRAGANDDROP_H #include "itemmodel.hpp" +#include "itemwidget.hpp" namespace MyGUI { @@ -18,7 +19,7 @@ namespace MWGui { public: bool mIsOnDragAndDrop; - MyGUI::Widget* mDraggedWidget; + ItemWidget* mDraggedWidget; ItemModel* mSourceModel; ItemView* mSourceView; SortFilterItemModel* mSourceSortModel; @@ -30,6 +31,7 @@ namespace MWGui void startDrag( int index, SortFilterItemModel* sortModel, ItemModel* sourceModel, ItemView* sourceView, int count); void drop(ItemModel* targetModel, ItemView* targetView); + void update(); void onFrame(); void finish(); diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index a19f04c73a..f18e8fdcbb 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -92,6 +92,7 @@ namespace MWGui , mTrading(false) , mUpdateTimer(0.f) , mPendingControllerAction(ControllerAction::None) + , mUpdateNextFrame(false) { mPreviewTexture = std::make_unique(mPreview->getTexture(), mPreview->getTextureStateSet()); @@ -169,6 +170,8 @@ namespace MWGui auto tradeModel = std::make_unique(std::make_unique(mPtr), MWWorld::Ptr()); mTradeModel = tradeModel.get(); + mPtr.getClass().getInventoryStore(mPtr).setContListener(this); + if (mSortModel) // reuse existing SortModel when possible to keep previous category/filter settings mSortModel->setSourceModel(std::move(tradeModel)); else @@ -773,22 +776,21 @@ namespace MWGui void InventoryWindow::onFrame(float dt) { - updateEncumbranceBar(); - - if (mPinned) + if (mUpdateNextFrame) { - mUpdateTimer += dt; - if (0.1f < mUpdateTimer) + if (mTrading) { - mUpdateTimer = 0; - - // Update pinned inventory in-game - if (!MWBase::Environment::get().getWindowManager()->isGuiMode()) - { - mItemView->update(); - notifyContentChanged(); - } + mTradeModel->updateBorrowed(); + MWBase::Environment::get().getWindowManager()->getTradeWindow()->mTradeModel->updateBorrowed(); + MWBase::Environment::get().getWindowManager()->getTradeWindow()->updateItemView(); + MWBase::Environment::get().getWindowManager()->getTradeWindow()->updateOffer(); } + + updateEncumbranceBar(); + mDragAndDrop->update(); + mItemView->update(); + notifyContentChanged(); + mUpdateNextFrame = false; } } @@ -940,6 +942,16 @@ namespace MWGui mPreview->rebuild(); } + void InventoryWindow::itemAdded(const MWWorld::ConstPtr& item, int count) + { + mUpdateNextFrame = true; + } + + void InventoryWindow::itemRemoved(const MWWorld::ConstPtr& item, int count) + { + mUpdateNextFrame = true; + } + MyGUI::IntSize InventoryWindow::getPreviewViewportSize() const { const MyGUI::IntSize previewWindowSize = mAvatarImage->getSize(); diff --git a/apps/openmw/mwgui/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp index 1a800d9762..dea94a1fe7 100644 --- a/apps/openmw/mwgui/inventorywindow.hpp +++ b/apps/openmw/mwgui/inventorywindow.hpp @@ -5,6 +5,7 @@ #include "windowpinnablebase.hpp" #include "../mwrender/characterpreview.hpp" +#include "../mwworld/containerstore.hpp" #include "../mwworld/ptr.hpp" namespace osg @@ -30,7 +31,7 @@ namespace MWGui class DragAndDrop; class ItemModel; - class InventoryWindow : public WindowPinnableBase + class InventoryWindow : public WindowPinnableBase, public MWWorld::ContainerStoreListener { public: InventoryWindow(DragAndDrop* dragAndDrop, osg::Group* parent, Resource::ResourceSystem* resourceSystem); @@ -62,6 +63,9 @@ namespace MWGui void setGuiMode(GuiMode mode); + void itemAdded(const MWWorld::ConstPtr& item, int count) override; + void itemRemoved(const MWWorld::ConstPtr& item, int count) override; + /// Cycle to previous/next weapon void cycle(bool next); @@ -111,7 +115,7 @@ namespace MWGui std::unique_ptr mPreview; bool mTrading; - float mUpdateTimer; + bool mUpdateNextFrame; void toggleMaximized(); diff --git a/apps/openmw/mwgui/postprocessorhud.cpp b/apps/openmw/mwgui/postprocessorhud.cpp index 7712594c54..cc95203a0f 100644 --- a/apps/openmw/mwgui/postprocessorhud.cpp +++ b/apps/openmw/mwgui/postprocessorhud.cpp @@ -33,6 +33,14 @@ namespace MWGui { + namespace + { + std::shared_ptr& getTechnique(const MyGUI::ListBox& list, size_t selected) + { + return *list.getItemDataAt>(selected); + } + } + void PostProcessorHud::ListWrapper::onKeyButtonPressed(MyGUI::KeyCode key, MyGUI::Char ch) { if (MyGUI::InputManager::getInstance().isShiftPressed() @@ -117,7 +125,7 @@ namespace MWGui if (index >= sender->getItemCount()) return; - updateConfigView(sender->getItemNameAt(index)); + updateConfigView(getTechnique(*sender, index)->getFileName()); } void PostProcessorHud::toggleTechnique(bool enabled) @@ -131,7 +139,7 @@ namespace MWGui auto* processor = MWBase::Environment::get().getWorld()->getPostProcessor(); mOverrideHint = list->getItemNameAt(selected); - auto technique = *list->getItemDataAt>(selected); + auto technique = getTechnique(*list, selected); if (technique->getDynamic()) return; @@ -167,7 +175,7 @@ namespace MWGui if (static_cast(index) != selected) { - auto technique = *mActiveList->getItemDataAt>(selected); + auto technique = getTechnique(*mActiveList, selected); if (technique->getDynamic() || technique->getInternal()) return; @@ -290,16 +298,16 @@ namespace MWGui return; if (mInactiveList->getIndexSelected() != MyGUI::ITEM_NONE) - updateConfigView(mInactiveList->getItemNameAt(mInactiveList->getIndexSelected())); + updateConfigView(getTechnique(*mInactiveList, mInactiveList->getIndexSelected())->getFileName()); else if (mActiveList->getIndexSelected() != MyGUI::ITEM_NONE) - updateConfigView(mActiveList->getItemNameAt(mActiveList->getIndexSelected())); + updateConfigView(getTechnique(*mActiveList, mActiveList->getIndexSelected())->getFileName()); } - void PostProcessorHud::updateConfigView(const std::string& name) + void PostProcessorHud::updateConfigView(VFS::Path::NormalizedView path) { auto* processor = MWBase::Environment::get().getWorld()->getPostProcessor(); - auto technique = processor->loadTechnique(name); + auto technique = processor->loadTechnique(path); if (technique->getStatus() == fx::Technique::Status::File_Not_exists) return; @@ -423,22 +431,22 @@ namespace MWGui auto* processor = MWBase::Environment::get().getWorld()->getPostProcessor(); - std::vector techniques; - for (const auto& [name, _] : processor->getTechniqueMap()) - techniques.push_back(name); - std::sort(techniques.begin(), techniques.end(), Misc::StringUtils::ciLess); + std::vector techniques; + for (const auto& vfsPath : processor->getTechniqueFiles()) + techniques.emplace_back(vfsPath); + std::sort(techniques.begin(), techniques.end()); - for (const std::string& name : techniques) + for (VFS::Path::NormalizedView path : techniques) { - auto technique = processor->loadTechnique(name); + auto technique = processor->loadTechnique(path); if (!technique->getHidden() && !processor->isTechniqueEnabled(technique)) { - std::string lowerName = Utf8Stream::lowerCaseUtf8(name); + std::string lowerName = Utf8Stream::lowerCaseUtf8(technique->getName()); std::string lowerCaption = mFilter->getCaption(); lowerCaption = Utf8Stream::lowerCaseUtf8(lowerCaption); if (lowerName.find(lowerCaption) != std::string::npos) - mInactiveList->addItem(name, technique); + mInactiveList->addItem(technique->getName(), technique); } } diff --git a/apps/openmw/mwgui/postprocessorhud.hpp b/apps/openmw/mwgui/postprocessorhud.hpp index 20e27bac3a..0028999966 100644 --- a/apps/openmw/mwgui/postprocessorhud.hpp +++ b/apps/openmw/mwgui/postprocessorhud.hpp @@ -6,6 +6,7 @@ #include #include +#include namespace MyGUI { @@ -48,7 +49,7 @@ namespace MWGui void notifyFilterChanged(MyGUI::EditBox* sender); - void updateConfigView(const std::string& name); + void updateConfigView(VFS::Path::NormalizedView path); void notifyResetButtonClicked(MyGUI::Widget* sender); diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 67c10b980f..b705fda64b 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -894,7 +894,8 @@ namespace MWGui widget->setUserString("ToolTipLayout", "BirthSignToolTip"); widget->setUserString( "ImageTexture_BirthSignImage", Misc::ResourceHelpers::correctTexturePath(sign->mTexture, vfs)); - std::string text = sign->mName + "\n#{fontcolourhtml=normal}" + sign->mDescription; + widget->setUserString("Caption_BirthSignName", sign->mName); + widget->setUserString("Caption_BirthSignDescription", sign->mDescription); std::vector abilities, powers, spells; @@ -915,26 +916,22 @@ namespace MWGui spells.push_back(spell); } - using Category = std::pair&, std::string_view>; - for (const auto& [category, label] : std::initializer_list{ - { abilities, "sBirthsignmenu1" }, { powers, "sPowers" }, { spells, "sBirthsignmenu2" } }) + using Category = std::tuple&, std::string_view, std::string_view>; + std::initializer_list categories{ { abilities, "#{sBirthsignmenu1}", "Abilities" }, + { powers, "#{sPowers}", "Powers" }, { spells, "#{sBirthsignmenu2}", "Spells" } }; + + for (const auto& [category, label, widgetName] : categories) { - bool addHeader = true; - for (const ESM::Spell* spell : category) + std::string text; + if (!category.empty()) { - if (addHeader) - { - text += "\n\n#{fontcolourhtml=header}#{"; - text += label; - text += '}'; - addHeader = false; - } - - text += "\n#{fontcolourhtml=normal}" + spell->mName; + text = std::string(label) + "\n#{fontcolourhtml=normal}"; + for (const ESM::Spell* spell : category) + text += spell->mName + ' '; + text.pop_back(); } + widget->setUserString("Caption_BirthSign" + std::string(widgetName), text); } - - widget->setUserString("Caption_BirthSignText", text); } void ToolTips::createRaceToolTip(MyGUI::Widget* widget, const ESM::Race* playerRace) diff --git a/apps/openmw/mwgui/tradeitemmodel.cpp b/apps/openmw/mwgui/tradeitemmodel.cpp index 50a55f5061..660e940367 100644 --- a/apps/openmw/mwgui/tradeitemmodel.cpp +++ b/apps/openmw/mwgui/tradeitemmodel.cpp @@ -113,6 +113,25 @@ namespace MWGui encumbrance = std::max(0.f, encumbrance); } + void TradeItemModel::updateBorrowed() + { + auto update = [](std::vector& list) { + for (auto it = list.begin(); it != list.end();) + { + size_t actualCount = it->mBase.getCellRef().getCount(); + if (actualCount < it->mCount) + it->mCount = actualCount; + if (it->mCount == 0) + it = list.erase(it); + else + ++it; + } + }; + + update(mBorrowedFromUs); + update(mBorrowedToUs); + } + void TradeItemModel::abort() { mBorrowedFromUs.clear(); diff --git a/apps/openmw/mwgui/tradeitemmodel.hpp b/apps/openmw/mwgui/tradeitemmodel.hpp index d395744d2a..856f33563d 100644 --- a/apps/openmw/mwgui/tradeitemmodel.hpp +++ b/apps/openmw/mwgui/tradeitemmodel.hpp @@ -31,6 +31,9 @@ namespace MWGui void returnItemBorrowedFromUs(ModelIndex itemIndex, ItemModel* source, size_t count); + /// Update borrowed items in this model + void updateBorrowed(); + /// Permanently transfers items that were borrowed to us from another model to this model void transferItems(); /// Aborts trade diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 3e6e886b9d..0c75779957 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -125,6 +125,7 @@ namespace MWGui , mItemToSell(-1) , mCurrentBalance(0) , mCurrentMerchantOffer(0) + , mUpdateNextFrame(false) { getWidget(mFilterAll, "AllButton"); getWidget(mFilterWeapon, "WeaponButton"); @@ -230,11 +231,24 @@ namespace MWGui // Cycle to the buy window if it's not active. if (Settings::gui().mControllerMenus && !mActiveControllerWindow) MWBase::Environment::get().getWindowManager()->cycleActiveControllerWindow(true); + + for (const auto& source : itemSources) + source.getClass().getContainerStore(source).setContListener(this); } void TradeWindow::onFrame(float dt) { checkReferenceAvailable(); + + if (isVisible() && mUpdateNextFrame) + { + mTradeModel->updateBorrowed(); + MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel()->updateBorrowed(); + MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView(); + mItemView->update(); + updateOffer(); + mUpdateNextFrame = false; + } } void TradeWindow::onNameFilterChanged(MyGUI::EditBox* _sender) @@ -680,6 +694,7 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Barter); } +<<<<<<< HEAD bool TradeWindow::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) { if (arg.button == SDL_CONTROLLER_BUTTON_A) @@ -764,4 +779,19 @@ namespace MWGui mItemView->setActiveControllerWindow(active); WindowBase::setActiveControllerWindow(active); } + + void TradeWindow::updateItemView() + { + mItemView->update(); + } + + void TradeWindow::itemAdded(const MWWorld::ConstPtr& item, int count) + { + mUpdateNextFrame = true; + } + + void TradeWindow::itemRemoved(const MWWorld::ConstPtr& item, int count) + { + mUpdateNextFrame = true; + } } diff --git a/apps/openmw/mwgui/tradewindow.hpp b/apps/openmw/mwgui/tradewindow.hpp index afd271ed2f..6373c49dd1 100644 --- a/apps/openmw/mwgui/tradewindow.hpp +++ b/apps/openmw/mwgui/tradewindow.hpp @@ -4,6 +4,8 @@ #include "referenceinterface.hpp" #include "windowbase.hpp" +#include "../mwworld/containerstore.hpp" + namespace Gui { class NumericEditBox; @@ -20,7 +22,7 @@ namespace MWGui class SortFilterItemModel; class TradeItemModel; - class TradeWindow : public WindowBase, public ReferenceInterface + class TradeWindow : public WindowBase, public ReferenceInterface, public MWWorld::ContainerStoreListener { public: TradeWindow(); @@ -31,17 +33,17 @@ namespace MWGui void onFrame(float dt) override; void clear() override { resetReference(); } - void borrowItem(int index, size_t count); - void returnItem(int index, size_t count); - - int getMerchantServices(); - bool exit() override; void resetReference() override; void onDeleteCustomData(const MWWorld::Ptr& ptr) override; + void updateItemView(); + + void itemAdded(const MWWorld::ConstPtr& item, int count) override; + void itemRemoved(const MWWorld::ConstPtr& item, int count) override; + typedef MyGUI::delegates::MultiDelegate<> EventHandle_TradeDone; EventHandle_TradeDone eventTradeDone; @@ -51,6 +53,8 @@ namespace MWGui void setActiveControllerWindow(bool active) override; private: + friend class InventoryWindow; + ItemView* mItemView; SortFilterItemModel* mSortModel; TradeItemModel* mTradeModel; @@ -84,6 +88,8 @@ namespace MWGui int mCurrentBalance; int mCurrentMerchantOffer; + bool mUpdateNextFrame; + void sellToNpc( const MWWorld::Ptr& item, int count, bool boughtItem); ///< only used for adjusting the gold balance void buyFromNpc( @@ -94,6 +100,11 @@ namespace MWGui void onItemSelected(int index); void sellItem(MyGUI::Widget* sender, int count); + void borrowItem(int index, size_t count); + void returnItem(int index, size_t count); + + int getMerchantServices(); + void onFilterChanged(MyGUI::Widget* _sender); void onNameFilterChanged(MyGUI::EditBox* _sender); void onOfferButtonClicked(MyGUI::Widget* _sender); diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 3d98e28bde..145e07d2c7 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -2578,6 +2578,14 @@ namespace MWGui updateVisible(); } + bool WindowManager::isWindowVisible(std::string_view windowId) const + { + auto it = mLuaIdToWindow.find(windowId); + if (it == mLuaIdToWindow.end()) + throw std::logic_error("Invalid window name: " + std::string(windowId)); + return it->second->isVisible(); + } + std::vector WindowManager::getAllWindowIds() const { std::vector res; diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index c8f1c7e06c..da20c83fcd 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -403,6 +403,7 @@ namespace MWGui // Used in Lua bindings const std::vector& getGuiModeStack() const override { return mGuiModes; } void setDisabledByLua(std::string_view windowId, bool disabled) override; + bool isWindowVisible(std::string_view windowId) const override; std::vector getAllWindowIds() const override; std::vector getAllowedWindowIds(GuiMode mode) const override; diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index bc5581eb74..826338ca7d 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -296,6 +296,8 @@ namespace MWLua luaManager->addAction( [=, window = std::move(window)]() { windowManager->setDisabledByLua(window, disabled); }); }; + api["_isWindowVisible"] + = [windowManager](std::string_view window) { return windowManager->isWindowVisible(window); }; // TODO // api["_showMouseCursor"] = [](bool) {}; diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index f07a325f7c..c7ea225d5f 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -2096,12 +2096,17 @@ namespace MWRender if (Settings::game().mGraphicHerbalism && ptr.getRefData().getCustomData() != nullptr && ObjectAnimation::canBeHarvested()) { - const MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); - if (!store.hasVisibleItems()) - { - HarvestVisitor visitor; - mObjectRoot->accept(visitor); - } + harvest(ptr); + } + } + + void ObjectAnimation::harvest(const MWWorld::Ptr& ptr) + { + const MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); + if (!store.hasVisibleItems()) + { + HarvestVisitor visitor; + mObjectRoot->accept(visitor); } } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index b6cb6f333c..6e39c44614 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -483,6 +483,7 @@ namespace MWRender virtual void setAccurateAiming(bool enabled) {} virtual bool canBeHarvested() const { return false; } + virtual void harvest(const MWWorld::Ptr& ptr) {} virtual void removeFromScene(); @@ -498,6 +499,7 @@ namespace MWRender bool animated, bool allowLight); bool canBeHarvested() const override; + void harvest(const MWWorld::Ptr& ptr) override; }; class UpdateVfxCallback : public SceneUtil::NodeCallback diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index 1f1b7258b3..d941e5daff 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -250,14 +251,12 @@ namespace MWRender void PostProcessor::populateTechniqueFiles() { - for (const auto& name : mVFS->getRecursiveDirectoryIterator(fx::Technique::sSubdir)) + for (const auto& path : mVFS->getRecursiveDirectoryIterator(fx::Technique::sSubdir)) { - std::filesystem::path path = Files::pathFromUnicodeString(name); - std::string fileExt = Misc::StringUtils::lowerCase(Files::pathToUnicodeString(path.extension())); - if (!path.parent_path().has_parent_path() && fileExt == fx::Technique::sExt) + std::string_view fileExt = Misc::getFileExtension(path); + if (path.parent().parent().empty() && fileExt == fx::Technique::sExt) { - const auto absolutePath = mVFS->getAbsoluteFileName(path); - mTechniqueFileMap[Files::pathToUnicodeString(absolutePath.stem())] = absolutePath; + mTechniqueFiles.emplace(path); } } } @@ -351,7 +350,7 @@ namespace MWRender if (technique->getStatus() == fx::Technique::Status::File_Not_exists) continue; - const auto lastWriteTime = std::filesystem::last_write_time(mTechniqueFileMap[technique->getName()]); + const auto lastWriteTime = mVFS->getLastModified(technique->getFileName()); const bool isDirty = technique->setLastModificationTime(lastWriteTime); if (!isDirty) @@ -363,7 +362,7 @@ namespace MWRender std::this_thread::sleep_for(std::chrono::milliseconds(5)); if (technique->compile()) - Log(Debug::Info) << "Reloaded technique : " << mTechniqueFileMap[technique->getName()]; + Log(Debug::Info) << "Reloaded technique : " << technique->getFileName(); mReload = technique->isValid(); } @@ -655,6 +654,14 @@ namespace MWRender const auto [w, h] = renderTarget.mSize.get(renderWidth(), renderHeight()); subPass.mStateSet->setAttributeAndModes(new osg::Viewport(0, 0, w, h)); + if (subPass.mMipMap) + { + subPass.mRenderTexture->setNumMipmapLevels(osg::Image::computeNumberOfMipmapLevels(w, h)); + } + else + { + subPass.mRenderTexture->setNumMipmapLevels(0); + } subPass.mRenderTexture->setTextureSize(w, h); subPass.mRenderTexture->dirtyTextureObject(); @@ -749,28 +756,35 @@ namespace MWRender return technique->isValid(); } - std::shared_ptr PostProcessor::loadTechnique(const std::string& name, bool loadNextFrame) + std::shared_ptr PostProcessor::loadTechnique(std::string_view name, bool loadNextFrame) + { + VFS::Path::Normalized path = fx::Technique::makeFileName(name); + return loadTechnique(VFS::Path::NormalizedView(path), loadNextFrame); + } + + std::shared_ptr PostProcessor::loadTechnique(VFS::Path::NormalizedView path, bool loadNextFrame) { for (const auto& technique : mTemplates) - if (Misc::StringUtils::ciEqual(technique->getName(), name)) + if (technique->getFileName() == path) return technique; for (const auto& technique : mQueuedTemplates) - if (Misc::StringUtils::ciEqual(technique->getName(), name)) + if (technique->getFileName() == path) return technique; - std::string realName = name; - auto fileIter = mTechniqueFileMap.find(name); - if (fileIter != mTechniqueFileMap.end()) - realName = fileIter->first; + std::string name; + if (mTechniqueFiles.contains(path)) + name = mVFS->getStem(path); + else + name = path.stem(); auto technique = std::make_shared(*mVFS, *mRendering.getResourceSystem()->getImageManager(), - std::move(realName), renderWidth(), renderHeight(), mUBO, mNormalsSupported); + path, std::move(name), renderWidth(), renderHeight(), mUBO, mNormalsSupported); technique->compile(); if (technique->getStatus() != fx::Technique::Status::File_Not_exists) - technique->setLastModificationTime(std::filesystem::last_write_time(fileIter->second)); + technique->setLastModificationTime(mVFS->getLastModified(path)); if (loadNextFrame) { @@ -825,7 +839,11 @@ namespace MWRender void PostProcessor::toggleMode() { for (auto& technique : mTemplates) + { + if (technique->getStatus() == fx::Technique::Status::File_Not_exists) + continue; technique->compile(); + } dirtyTechniques(true); } diff --git a/apps/openmw/mwrender/postprocessor.hpp b/apps/openmw/mwrender/postprocessor.hpp index 6b1f4612f1..eb9bc6347c 100644 --- a/apps/openmw/mwrender/postprocessor.hpp +++ b/apps/openmw/mwrender/postprocessor.hpp @@ -128,7 +128,7 @@ namespace MWRender const TechniqueList& getTemplates() const { return mTemplates; } - const auto& getTechniqueMap() const { return mTechniqueFileMap; } + const auto& getTechniqueFiles() const { return mTechniqueFiles; } void resize(); @@ -176,7 +176,8 @@ namespace MWRender void toggleMode(); - std::shared_ptr loadTechnique(const std::string& name, bool loadNextFrame = false); + std::shared_ptr loadTechnique(VFS::Path::NormalizedView path, bool loadNextFrame = false); + std::shared_ptr loadTechnique(std::string_view name, bool loadNextFrame = false); TechniqueList getChain(); @@ -232,8 +233,7 @@ namespace MWRender TechniqueList mQueuedTemplates; TechniqueList mInternalTechniques; - std::unordered_map - mTechniqueFileMap; + std::unordered_set> mTechniqueFiles; RenderingManager& mRendering; osgViewer::Viewer* mViewer; diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 81e44248ac..81688a3444 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -565,6 +565,8 @@ namespace MWRender else createSimpleWaterStateSet(mWaterGeom, Fallback::Map::getFloat("Water_World_Alpha")); + mResourceSystem->getSceneManager()->setUpNormalsRTForStateSet(mWaterGeom->getOrCreateStateSet(), true); + updateVisible(); } diff --git a/apps/openmw/mwworld/actionharvest.cpp b/apps/openmw/mwworld/actionharvest.cpp index 30f316c2db..1d9e009afe 100644 --- a/apps/openmw/mwworld/actionharvest.cpp +++ b/apps/openmw/mwworld/actionharvest.cpp @@ -11,6 +11,8 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" +#include "../mwrender/animation.hpp" + #include "class.hpp" #include "containerstore.hpp" @@ -89,8 +91,9 @@ namespace MWWorld MWBase::Environment::get().getWindowManager()->messageBox(tooltip); } - // Update animation object - MWBase::Environment::get().getWorld()->disable(target); - MWBase::Environment::get().getWorld()->enable(target); + auto world = MWBase::Environment::get().getWorld(); + MWRender::Animation* anim = world->getAnimation(target); + if (anim != nullptr) + anim->harvest(target); } } diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index fca0135e13..61618c001e 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -1354,6 +1354,15 @@ namespace MWWorld return {}; } + CellStore* MWWorld::CellStore::getOriginCell(const Ptr& object) const + { + MovedRefTracker::const_iterator found = mMovedHere.find(object.getBase()); + if (found != mMovedHere.end()) + return found->second; + + return object.getCell(); + } + Ptr CellStore::getPtr(ESM::RefId id) { if (mState == CellStore::State_Unloaded) diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 126935ace5..59127d6186 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -338,6 +338,8 @@ namespace MWWorld Ptr getMovedActor(int actorId) const; + CellStore* getOriginCell(const Ptr& object) const; + Ptr getPtr(ESM::RefId id); private: diff --git a/components/bsa/ba2dx10file.hpp b/components/bsa/ba2dx10file.hpp index cd1f822179..c902a4ccb0 100644 --- a/components/bsa/ba2dx10file.hpp +++ b/components/bsa/ba2dx10file.hpp @@ -50,6 +50,7 @@ namespace Bsa public: using BSAFile::getFilename; using BSAFile::getList; + using BSAFile::getPath; using BSAFile::open; BA2DX10File(); diff --git a/components/bsa/ba2gnrlfile.hpp b/components/bsa/ba2gnrlfile.hpp index 0bc94eae0e..080ba3a8df 100644 --- a/components/bsa/ba2gnrlfile.hpp +++ b/components/bsa/ba2gnrlfile.hpp @@ -38,6 +38,7 @@ namespace Bsa public: using BSAFile::getFilename; using BSAFile::getList; + using BSAFile::getPath; using BSAFile::open; BA2GNRLFile(); diff --git a/components/bsa/bsafile.hpp b/components/bsa/bsafile.hpp index ad7acdad17..7b910208d8 100644 --- a/components/bsa/bsafile.hpp +++ b/components/bsa/bsafile.hpp @@ -84,15 +84,15 @@ namespace Bsa protected: bool mHasChanged = false; + /// True when an archive has been loaded + bool mIsLoaded = false; + /// Table of files in this archive FileList mFiles; /// Filename string buffer std::vector mStringBuf; - /// True when an archive has been loaded - bool mIsLoaded; - /// Used for error messages std::filesystem::path mFilepath; @@ -109,11 +109,6 @@ namespace Bsa * ----------------------------------- */ - BSAFile() - : mIsLoaded(false) - { - } - virtual ~BSAFile() { close(); @@ -148,6 +143,11 @@ namespace Bsa return Files::pathToUnicodeString(mFilepath); } + const std::filesystem::path& getPath() const + { + return mFilepath; + } + // checks version of BSA from file header static BsaVersion detectVersion(const std::filesystem::path& filePath); }; diff --git a/components/bsa/compressedbsafile.hpp b/components/bsa/compressedbsafile.hpp index 83620f11bc..1e359ea3fe 100644 --- a/components/bsa/compressedbsafile.hpp +++ b/components/bsa/compressedbsafile.hpp @@ -117,6 +117,7 @@ namespace Bsa public: using BSAFile::getFilename; using BSAFile::getList; + using BSAFile::getPath; using BSAFile::open; CompressedBSAFile() = default; diff --git a/components/fx/technique.cpp b/components/fx/technique.cpp index a8cd455dea..58ea55b2f6 100644 --- a/components/fx/technique.cpp +++ b/components/fx/technique.cpp @@ -37,22 +37,20 @@ namespace namespace fx { - namespace + VFS::Path::Normalized Technique::makeFileName(std::string_view name) { - VFS::Path::Normalized makeFilePath(std::string_view name) - { - std::string fileName(name); - fileName += Technique::sExt; - VFS::Path::Normalized result(Technique::sSubdir); - result /= fileName; - return result; - } + std::string fileName(name); + fileName += '.'; + fileName += Technique::sExt; + VFS::Path::Normalized result(Technique::sSubdir); + result /= fileName; + return result; } - Technique::Technique(const VFS::Manager& vfs, Resource::ImageManager& imageManager, std::string name, int width, - int height, bool ubo, bool supportsNormals) + Technique::Technique(const VFS::Manager& vfs, Resource::ImageManager& imageManager, + VFS::Path::NormalizedView fileName, std::string name, int width, int height, bool ubo, bool supportsNormals) : mName(std::move(name)) - , mFilePath(makeFilePath(mName)) + , mFilePath(fileName) , mLastModificationTime(std::filesystem::file_time_type::clock::now()) , mWidth(width) , mHeight(height) diff --git a/components/fx/technique.hpp b/components/fx/technique.hpp index ad5e876faa..00b87a86e2 100644 --- a/components/fx/technique.hpp +++ b/components/fx/technique.hpp @@ -105,8 +105,8 @@ namespace fx using UniformMap = std::vector>; using RenderTargetMap = std::unordered_map; - static constexpr std::string_view sExt = ".omwfx"; - static constexpr std::string_view sSubdir = "shaders"; + static constexpr std::string_view sExt = "omwfx"; + static constexpr VFS::Path::NormalizedView sSubdir{ "shaders" }; enum class Status { @@ -123,8 +123,10 @@ namespace fx static constexpr FlagsType Flag_Disable_SunGlare = (1 << 4); static constexpr FlagsType Flag_Hidden = (1 << 5); - Technique(const VFS::Manager& vfs, Resource::ImageManager& imageManager, std::string name, int width, - int height, bool ubo, bool supportsNormals); + static VFS::Path::Normalized makeFileName(std::string_view name); + + Technique(const VFS::Manager& vfs, Resource::ImageManager& imageManager, VFS::Path::NormalizedView fileName, + std::string name, int width, int height, bool ubo, bool supportsNormals); bool compile(); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index cbda399120..7ee29ff0eb 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -607,6 +608,9 @@ namespace Resource if (!getSupportsNormalsRT()) return; stateset->setAttributeAndModes(new osg::ColorMaski(1, enabled, enabled, enabled, enabled)); + + if (enabled) + stateset->setAttributeAndModes(new osg::Disablei(GL_BLEND, 1)); } /// @brief Callback to read image files from the VFS. diff --git a/components/testing/util.hpp b/components/testing/util.hpp index 53331d6d37..e2183d5403 100644 --- a/components/testing/util.hpp +++ b/components/testing/util.hpp @@ -56,7 +56,9 @@ namespace TestingOpenMW Files::IStreamPtr open() override { return std::make_unique(mContent, std::ios_base::in); } - std::filesystem::path getPath() override { return "TestFile"; } + std::filesystem::file_time_type getLastModified() const override { return {}; } + + std::string getStem() const override { return "TestFile"; } private: const std::string mContent; diff --git a/components/vfs/bsaarchive.hpp b/components/vfs/bsaarchive.hpp index 2e6fac6558..f89e47d971 100644 --- a/components/vfs/bsaarchive.hpp +++ b/components/vfs/bsaarchive.hpp @@ -10,45 +10,72 @@ #include #include +#include + #include #include #include namespace VFS { + template + class BsaArchive; + template class BsaArchiveFile : public File { public: - BsaArchiveFile(const Bsa::BSAFile::FileStruct* info, FileType* bsa) + BsaArchiveFile(const Bsa::BSAFile::FileStruct* info, const BsaArchive* bsa) : mInfo(info) , mFile(bsa) { } - Files::IStreamPtr open() override { return mFile->getFile(mInfo); } + Files::IStreamPtr open() override { return mFile->getFile()->getFile(mInfo); } - std::filesystem::path getPath() override { return mInfo->name(); } + std::filesystem::file_time_type getLastModified() const override + { + return std::filesystem::last_write_time(mFile->getFile()->getPath()); + } + + std::string getStem() const override + { + std::string_view name = mInfo->name(); + auto index = name.find_last_of("\\/"); + if (index != std::string_view::npos) + name = name.substr(index + 1); + index = name.find_last_of('.'); + if (index != std::string_view::npos && index != 0) + name = name.substr(0, index); + std::string out; + std::string_view utf8 = mFile->getUtf8(name, out); + if (out.data() == utf8.data()) + out.resize(utf8.size()); + else + out = utf8; + return out; + } const Bsa::BSAFile::FileStruct* mInfo; - FileType* mFile; + const BsaArchive* mFile; }; template class BsaArchive : public Archive { public: - BsaArchive(const std::filesystem::path& filename) + BsaArchive(const std::filesystem::path& filename, const ToUTF8::StatelessUtf8Encoder* encoder) : Archive() + , mEncoder(encoder) { mFile = std::make_unique(); mFile->open(filename); - const Bsa::BSAFile::FileList& filelist = mFile->getList(); - for (Bsa::BSAFile::FileList::const_iterator it = filelist.begin(); it != filelist.end(); ++it) + std::string buffer; + for (const Bsa::BSAFile::FileStruct& file : mFile->getList()) { - mResources.emplace_back(&*it, mFile.get()); - mFiles.emplace_back(it->name()); + mResources.emplace_back(&file, this); + mFiles.emplace_back(getUtf8(file.name(), buffer)); } std::sort(mFiles.begin(), mFiles.end()); @@ -56,8 +83,12 @@ namespace VFS void listResources(FileMap& out) override { + std::string buffer; for (auto& resource : mResources) - out[VFS::Path::Normalized(resource.mInfo->name())] = &resource; + { + std::string_view path = getUtf8(resource.mInfo->name(), buffer); + out[VFS::Path::Normalized(path)] = &resource; + } } bool contains(Path::NormalizedView file) const override @@ -67,26 +98,37 @@ namespace VFS std::string getDescription() const override { return std::string{ "BSA: " } + mFile->getFilename(); } + BSAFileType* getFile() const { return mFile.get(); } + + std::string_view getUtf8(std::string_view input, std::string& buffer) const + { + if (mEncoder == nullptr) + return input; + return mEncoder->getUtf8(input, ToUTF8::BufferAllocationPolicy::UseGrowFactor, buffer); + } + private: std::unique_ptr mFile; std::vector> mResources; std::vector mFiles; + const ToUTF8::StatelessUtf8Encoder* mEncoder; }; - inline std::unique_ptr makeBsaArchive(const std::filesystem::path& path) + inline std::unique_ptr makeBsaArchive( + const std::filesystem::path& path, const ToUTF8::StatelessUtf8Encoder* encoder) { switch (Bsa::BSAFile::detectVersion(path)) { case Bsa::BsaVersion::Unknown: break; case Bsa::BsaVersion::Uncompressed: - return std::make_unique>(path); + return std::make_unique>(path, encoder); case Bsa::BsaVersion::Compressed: - return std::make_unique>(path); + return std::make_unique>(path, encoder); case Bsa::BsaVersion::BA2GNRL: - return std::make_unique>(path); + return std::make_unique>(path, encoder); case Bsa::BsaVersion::BA2DX10: - return std::make_unique>(path); + return std::make_unique>(path, encoder); } throw std::runtime_error("Unknown archive type '" + Files::pathToUnicodeString(path) + "'"); diff --git a/components/vfs/file.hpp b/components/vfs/file.hpp index f2dadb1162..7c65e3a1ba 100644 --- a/components/vfs/file.hpp +++ b/components/vfs/file.hpp @@ -2,6 +2,7 @@ #define OPENMW_COMPONENTS_VFS_FILE_H #include +#include #include @@ -14,7 +15,9 @@ namespace VFS virtual Files::IStreamPtr open() = 0; - virtual std::filesystem::path getPath() = 0; + virtual std::filesystem::file_time_type getLastModified() const = 0; + + virtual std::string getStem() const = 0; }; } diff --git a/components/vfs/filesystemarchive.cpp b/components/vfs/filesystemarchive.cpp index 3303c6656c..0b67df45bc 100644 --- a/components/vfs/filesystemarchive.cpp +++ b/components/vfs/filesystemarchive.cpp @@ -81,4 +81,14 @@ namespace VFS return Files::openConstrainedFileStream(mPath); } + std::filesystem::file_time_type FileSystemArchiveFile::getLastModified() const + { + return std::filesystem::last_write_time(mPath); + } + + std::string FileSystemArchiveFile::getStem() const + { + return Files::pathToUnicodeString(mPath.stem()); + } + } diff --git a/components/vfs/filesystemarchive.hpp b/components/vfs/filesystemarchive.hpp index 215c443b58..f6b1a2ec5e 100644 --- a/components/vfs/filesystemarchive.hpp +++ b/components/vfs/filesystemarchive.hpp @@ -17,7 +17,9 @@ namespace VFS Files::IStreamPtr open() override; - std::filesystem::path getPath() override { return mPath; } + std::filesystem::file_time_type getLastModified() const override; + + std::string getStem() const override; private: std::filesystem::path mPath; diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index 12ef378017..ff25150f67 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -81,15 +81,20 @@ namespace VFS return {}; } - std::filesystem::path Manager::getAbsoluteFileName(const std::filesystem::path& name) const + std::filesystem::file_time_type Manager::getLastModified(VFS::Path::NormalizedView name) const { - std::string normalized = Files::pathToUnicodeString(name); - Path::normalizeFilenameInPlace(normalized); - - const auto found = mIndex.find(normalized); + const auto found = mIndex.find(name); if (found == mIndex.end()) - throw std::runtime_error("Resource '" + normalized + "' is not found"); - return found->second->getPath(); + throw std::runtime_error("Resource '" + std::string(name.value()) + "' not found"); + return found->second->getLastModified(); + } + + std::string Manager::getStem(VFS::Path::NormalizedView name) const + { + const auto found = mIndex.find(name); + if (found == mIndex.end()) + throw std::runtime_error("Resource '" + std::string(name.value()) + "' not found"); + return found->second->getStem(); } RecursiveDirectoryRange Manager::getRecursiveDirectoryIterator(std::string_view path) const diff --git a/components/vfs/manager.hpp b/components/vfs/manager.hpp index b6a9d796cc..3d10b3f355 100644 --- a/components/vfs/manager.hpp +++ b/components/vfs/manager.hpp @@ -72,10 +72,9 @@ namespace VFS RecursiveDirectoryRange getRecursiveDirectoryIterator() const; - /// Retrieve the absolute path to the file - /// @note Throws an exception if the file can not be found. - /// @note May be called from any thread once the index has been built. - std::filesystem::path getAbsoluteFileName(const std::filesystem::path& name) const; + std::filesystem::file_time_type getLastModified(VFS::Path::NormalizedView name) const; + // Equivalent to std::filesystem::path::stem. The result isn't normalized. + std::string getStem(VFS::Path::NormalizedView name) const; private: std::vector> mArchives; diff --git a/components/vfs/pathutil.hpp b/components/vfs/pathutil.hpp index f5393617d7..a890be8a54 100644 --- a/components/vfs/pathutil.hpp +++ b/components/vfs/pathutil.hpp @@ -127,6 +127,27 @@ namespace VFS::Path return stream << value.mValue; } + NormalizedView parent() const + { + NormalizedView p; + const std::size_t pos = mValue.find_last_of(separator); + if (pos != std::string_view::npos) + p.mValue = mValue.substr(0, pos); + return p; + } + + std::string_view stem() const + { + std::string_view stem = mValue; + std::size_t pos = stem.find_last_of(separator); + if (pos != std::string_view::npos) + stem = stem.substr(pos + 1); + pos = stem.find_first_of(extensionSeparator); + if (pos != std::string_view::npos) + stem = stem.substr(0, pos); + return stem; + } + private: std::string_view mValue; }; @@ -259,6 +280,16 @@ namespace VFS::Path return stream << value.mValue; } + NormalizedView parent() const + { + return NormalizedView(*this).parent(); + } + + std::string_view stem() const + { + return NormalizedView(*this).stem(); + } + private: std::string mValue; }; diff --git a/components/vfs/registerarchives.cpp b/components/vfs/registerarchives.cpp index f017b5f73c..1d03766220 100644 --- a/components/vfs/registerarchives.cpp +++ b/components/vfs/registerarchives.cpp @@ -14,7 +14,7 @@ namespace VFS { void registerArchives(VFS::Manager* vfs, const Files::Collections& collections, - const std::vector& archives, bool useLooseFiles) + const std::vector& archives, bool useLooseFiles, const ToUTF8::StatelessUtf8Encoder* encoder) { const Files::PathContainer& dataDirs = collections.getPaths(); @@ -25,7 +25,7 @@ namespace VFS // Last BSA has the highest priority const auto archivePath = collections.getPath(*archive); Log(Debug::Info) << "Adding BSA archive " << archivePath; - vfs->addArchive(makeBsaArchive(archivePath)); + vfs->addArchive(makeBsaArchive(archivePath, encoder)); } else { diff --git a/components/vfs/registerarchives.hpp b/components/vfs/registerarchives.hpp index dac29f87a3..03247b74d3 100644 --- a/components/vfs/registerarchives.hpp +++ b/components/vfs/registerarchives.hpp @@ -3,13 +3,18 @@ #include +namespace ToUTF8 +{ + class StatelessUtf8Encoder; +} + namespace VFS { class Manager; /// @brief Register BSA and file system archives based on the given OpenMW configuration. void registerArchives(VFS::Manager* vfs, const Files::Collections& collections, - const std::vector& archives, bool useLooseFiles); + const std::vector& archives, bool useLooseFiles, const ToUTF8::StatelessUtf8Encoder* encoder); } #endif diff --git a/docs/source/_ext/omw-lexers.py b/docs/source/_ext/omw-lexers.py new file mode 100644 index 0000000000..02cd526304 --- /dev/null +++ b/docs/source/_ext/omw-lexers.py @@ -0,0 +1,19 @@ +from pygments.lexer import RegexLexer, bygroups +from pygments.token import Comment, Name, Operator, String, Text +from sphinx.highlighting import lexers + +class OMWConfigLexer(RegexLexer): + name = 'openmwcfg' + aliases = ['openmwcfg'] + filenames = ['openmw.cfg'] + + tokens = { + 'root': [ + (r'(\s*)(#.*$)', bygroups(Text.Whitespace, Comment.Single)), + (r'(\s*)([a-zA-Z0-9_.+-]+)(\s*(\+)?=\s*)(.*)', bygroups(Text.Whitespace, Name.Attribute, Operator, Operator, String)), + (r'.+\n', Text), + ], + } + +def setup(_): + lexers["openmwcfg"] = OMWConfigLexer() diff --git a/docs/source/_static/theme-override.css b/docs/source/_static/theme-override.css index e41ab2c2d6..cf9ae168bc 100644 --- a/docs/source/_static/theme-override.css +++ b/docs/source/_static/theme-override.css @@ -138,3 +138,17 @@ tbody tr:hover { #left-sidebar { overflow-y: scroll; } + +#content div[class^=highlight], #content pre.literal-block, p, h4, h5, h6 { + margin-bottom: 1.5rem; +} + +h5 { + font-size: 1.15rem; + font-weight: 600; +} + +h6 { + font-size: 1.08rem; + font-weight: 600; +} \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py index aaa42e3678..0b953a4275 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -43,7 +43,8 @@ extensions = [ 'sphinx.ext.viewcode', 'sphinx.ext.autosectionlabel', 'sphinx_design', - 'omw-directives' + 'omw-directives', + 'omw-lexers', ] #autosectionlabel_prefix_document = True @@ -138,7 +139,7 @@ exclude_patterns = [] #show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = 'default' pygments_style_dark = 'github-dark' # A list of ignored prefixes for module index sorting. diff --git a/docs/source/manuals/installation/install-game-files.rst b/docs/source/manuals/installation/install-game-files.rst index cb44f7d1de..0dad7bfa4d 100644 --- a/docs/source/manuals/installation/install-game-files.rst +++ b/docs/source/manuals/installation/install-game-files.rst @@ -81,15 +81,15 @@ For Distributions Using `apt` (e.g., Ubuntu, Debian) .. code:: console - sudo apt update - sudo apt install innoextract + $ sudo apt update + $ sudo apt install innoextract For macOS using Homebrew ++++++++++++++++++++++++ .. code:: console - brew install innoextract + $ brew install innoextract Once innoextract is installed, download the game from GOG. The downloaded file should be called ``setup_tes_morrowind_goty_2.0.0.7.exe`` or something similar. When ``innoextract`` is run on it, it will extract the files directly into the folder the ``setup.exe`` file is located. If you have a specific folder where you want it to be extracted to, for example in ``~/Documents/Games/Morrowind`` You can specify it with the ``-d`` flag. diff --git a/docs/source/reference/lua-scripting/events.rst b/docs/source/reference/lua-scripting/events.rst index 007e0e43d1..15c9b52eea 100644 --- a/docs/source/reference/lua-scripting/events.rst +++ b/docs/source/reference/lua-scripting/events.rst @@ -41,9 +41,53 @@ Example: core.sendGlobalEvent('UseItem', {object = potion, actor = player, force = true}) +**ModifyStat** + +Modify the corresponding stat. + +.. code-block:: Lua + + -- Consume 10 magicka + actor:sendEvent('ModifyStat', {name = 'magicka', amount = -10}) + +**AddVfx** + +Calls the corresponding method in openmw.animation + +.. code-block:: Lua + + local eventParams = { + model = 'vfx_default', + options = { + textureOverride = effect.particle, + }, + } + actor:sendEvent('AddVfx', eventParams) + +**PlaySound3d** + +Calls the corresponding function in openw.core on the target. Will use core.sound.playSoundFile3d instead of core.sound.playSound3d if you put `file` instead of `sound` in the event data. + +.. code-block:: Lua + actor:sendEvent('PlaySound3d', {sound = 'Open Lock'}) + + +**BreakInvisibility** + +Forces the actor to lose all active invisibility effects. + + UI events --------- +**ShowMessage** + +If sent to a player, shows a message as if a call to ui.showMessage was made. + +.. code-block:: Lua + + player:sendEvent('ShowMessage', {message = 'Lorem ipsum'}) + **UiModeChanged** Every time UI mode is changed built-in scripts send to player the event ``UiModeChanged`` with arguments ``oldMode, ``newMode`` (same as ``I.UI.getMode()``) @@ -91,3 +135,36 @@ Global events that just call the corresponding function in `openmw.world`. -- world.setSimulationTimeScale(scale) core.sendGlobalEvent('SetSimulationTimeScale', scale) + + +**SpawnVfx, PlaySound3d** + +Calls the corresponding function in openw.core. Note that PlaySound3d will call core.sound.playSoundFile3d instead of core.sound.playSound3d if you put `file` instead of `sound` in the event data. + +.. code-block:: Lua + core.sendGlobalEvent('SpawnVfx', {position = hitPos, model = 'vfx_destructarea', options = {scale = 10}}) + core.sendGlobalEvent('PlaySound3d', {sound = 'Open Lock', position = container.position}) + +**ConsumeItem** + +Reduces stack size of an item by a given amount, removing the item completely if stack size is reduced to 0 or less. + +.. code-block:: Lua + + core.sendGlobalEvent('ConsumeItem', {item = foobar, amount = 1}) + +**Lock** + +Lock a container or door + +.. code-block:: Lua + + core.sendGlobalEvent('Lock', {taret = selected, magnitude = 50}) + +**Unlock** + +Unlock a container or door + +.. code-block:: Lua + + core.sendGlobalEvent('Unlock', {taret = selected}) diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index 852d63ca0a..b838385524 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -126,10 +126,12 @@ The options are: Enable it in ``openmw.cfg`` the same way as any other mod: -:: +.. code-block:: openmwcfg + :caption: openmw.cfg data=path/to/my_lua_mod - content=my_lua_mod.omwscripts # or content=my_lua_mod.omwaddon + # or content=my_lua_mod.omwaddon + content=my_lua_mod.omwscripts Now every time the player presses "X" on a keyboard, a message is shown. diff --git a/docs/source/reference/modding/custom-models/pipeline-blender-collada-animated-creature.rst b/docs/source/reference/modding/custom-models/pipeline-blender-collada-animated-creature.rst index f07ba7651a..21da034a54 100644 --- a/docs/source/reference/modding/custom-models/pipeline-blender-collada-animated-creature.rst +++ b/docs/source/reference/modding/custom-models/pipeline-blender-collada-animated-creature.rst @@ -177,7 +177,6 @@ definitions and events. At a minimum it needs to include at least animation runforward: stop 4.433333 attack1: start 4.466667 attack1: stop 5.433333 - ... The textkeys file is placed in the same folder as the model and matches the model's name. diff --git a/docs/source/reference/modding/custom-models/pipeline-blender-collada-static-models.rst b/docs/source/reference/modding/custom-models/pipeline-blender-collada-static-models.rst index d52c952b3d..5fb109c962 100644 --- a/docs/source/reference/modding/custom-models/pipeline-blender-collada-static-models.rst +++ b/docs/source/reference/modding/custom-models/pipeline-blender-collada-static-models.rst @@ -168,7 +168,7 @@ the file path to the texture is incorrect and OpenMW can't find it. To fix this you can open the exported ``.dae`` file in a text editor and check the texture's filepath. In the example of this barrel model it's found on lines 13-17. -.. code:: +.. code-block:: xml diff --git a/docs/source/reference/modding/custom-shader-effects.rst b/docs/source/reference/modding/custom-shader-effects.rst index 0e7776c7ae..41dd4c7369 100644 --- a/docs/source/reference/modding/custom-shader-effects.rst +++ b/docs/source/reference/modding/custom-shader-effects.rst @@ -23,7 +23,8 @@ dungeons. To use this feature the :ref:`soft particles` setting must be enabled. This setting can either be activated in the OpenMW launcher or changed in `settings.cfg`: -:: +.. code-block:: ini + :caption: settings.cfg [Shaders] soft particles = true @@ -64,7 +65,8 @@ Blue and alpha channels are ignored. To use this feature the :ref:`post processing ` setting must be enabled. This setting can either be activated in the OpenMW launcher, in-game, or changed in `settings.cfg`: -:: +.. code-block:: ini + :caption: settings.cfg [Post Processing] enabled = true diff --git a/docs/source/reference/modding/extended.rst b/docs/source/reference/modding/extended.rst index 001492f254..8b5c30fdd0 100644 --- a/docs/source/reference/modding/extended.rst +++ b/docs/source/reference/modding/extended.rst @@ -91,7 +91,8 @@ The behavior of such a model: The actual state toggling time depends on the sunrise/sunset time settings in `openmw.cfg`: -:: +.. code-block:: openmwcfg + :caption: openmw.cfg fallback=Weather_Sunrise_Time,6 fallback=Weather_Sunset_Time,18 @@ -102,7 +103,8 @@ These settings lead to the "night" starting at 20:00 and ending at 6:00. The engine checks if the weather is bright enough to support the "interior day" mode using the Glare_View setting. If it is >= 0.5, the engine considers the weather bright. -:: +.. code-block:: openmwcfg + :caption: openmw.cfg fallback=Weather_Clear_Glare_View,1 fallback=Weather_Foggy_Glare_View,0.25 @@ -138,7 +140,8 @@ If you want to override walking animations, you should override ``xbase_anim_fem To enable this feature, you should have this line in your settings.cfg: -:: +.. code-block:: ini + :caption: settings.cfg [Game] use additional anim sources = true @@ -157,7 +160,8 @@ This feature conflicts with old mods which use scripted scabbards, arrows with p The minimum you need is the ``xbase_anim_sh.nif`` file from the `Weapon Sheathing`_ mod and this line in your settings.cfg: -:: +.. code-block:: ini + :caption: settings.cfg [Game] weapon sheathing = true @@ -205,7 +209,8 @@ Skeleton extensions It is possible to inject custom bones into actor skeletons: -:: +.. code-block:: ini + :caption: settings.cfg [Game] use additional anim sources = true @@ -323,14 +328,16 @@ General advices to create assets for this feature: Groundcover mods can be registered in the openmw.cfg via "groundcover" entries instead of "content" ones: -:: +.. code-block:: openmwcfg + :caption: openmw.cfg groundcover=my_grass_mod.esp Every static from such mod is treated as a groundcover object. Also groundcover detection should be enabled via settings.cfg: -:: +.. code-block:: ini + :caption: settings.cfg [Groundcover] enabled = true diff --git a/docs/source/reference/modding/font.rst b/docs/source/reference/modding/font.rst index 2c67a940d1..c0e91eec04 100644 --- a/docs/source/reference/modding/font.rst +++ b/docs/source/reference/modding/font.rst @@ -3,8 +3,11 @@ Fonts Default UI font and font used in magic scrolls are defined in ``openmw.cfg``: - fallback=Fonts_Font_0,MysticCards - fallback=Fonts_Font_2,DemonicLetters +.. code-block:: openmwcfg + :caption: openmw.cfg + + fallback=Fonts_Font_0,MysticCards + fallback=Fonts_Font_2,DemonicLetters When there are no ``Fonts_Font_*`` lines in user's ``openmw.cfg``, built-in TrueType fonts are used. Font used by console and another debug windows is not configurable (so ``Fonts_Font_1`` is unused). @@ -20,8 +23,11 @@ You can use --export-fonts command line option to write the converted font They can be used instead of TrueType fonts if needed by specifying their ``.fnt`` files names in the ``openmw.cfg``. For example: - fallback=Fonts_Font_0,magic_cards_regular - fallback=Fonts_Font_2,daedric_font +.. code-block:: openmwcfg + :caption: openmw.cfg + + fallback=Fonts_Font_0,magic_cards_regular + fallback=Fonts_Font_2,daedric_font In this example OpenMW will search for ``magic_cards_regular.fnt`` and ``daedric_font.fnt`` in the ``Fonts`` folder in data directories. If they are not found, built-in TrueType fonts will be used as a fallback. @@ -35,16 +41,22 @@ Unlike vanilla Morrowind, OpenMW directly supports TrueType (``.ttf``) fonts. Th OpenMW has build-in TrueType fonts: MysticCards, DemonicLetters and DejaVuLGCSansMono, which are used by default. TrueType fonts are configured via ``openmw.cfg`` too: - fallback=Fonts_Font_0,MysticCards - fallback=Fonts_Font_2,DemonicLetters +.. code-block:: openmwcfg + :caption: openmw.cfg + + fallback=Fonts_Font_0,MysticCards + fallback=Fonts_Font_2,DemonicLetters In this example, OpenMW will scan ``Fonts`` folder in data directories for ``.omwfont`` files. These files are XML files with schema provided by MyGUI. OpenMW uses ``.omwfont`` files which name (without extension) matches ``openmw.cfg`` entries. -It is also possible to adjust the font size via ``settings.cfg`` file:: +It is also possible to adjust the font size via ``settings.cfg`` file: - [GUI] - font size = 16 +.. code-block:: ini + :caption: settings.cfg + + [GUI] + font size = 16 The ``font size`` setting accepts clamped values in range from 12 to 18. diff --git a/docs/source/reference/modding/openmw-game-template.rst b/docs/source/reference/modding/openmw-game-template.rst index 68fa667911..51ddaf6efb 100644 --- a/docs/source/reference/modding/openmw-game-template.rst +++ b/docs/source/reference/modding/openmw-game-template.rst @@ -36,7 +36,8 @@ and ``data=`` tells OpenMW what folders to look for meshes, textures, audio, and other assets. The required lines would look like this, but with the paths of course different on your system. -.. code:: +.. code-block:: openmwcfg + :caption: openmw.cfg content=template.omwgame data="/home/someuser/example-suite/data" @@ -51,7 +52,8 @@ you need to remove or comment out the following lines from ``openmw.cfg``. Not doing so will either produce errors or load Morrowind content, which you probably do not want when you are making your own game. -.. code:: +.. code-block:: openmwcfg + :caption: openmw.cfg fallback-archive=Morrowind.bsa fallback-archive=Tribunal.bsa @@ -70,8 +72,10 @@ are instead assigned through ``settings.cfg``. These models are player and NPC animations, and meshes for the sky. In ``settings.cfg`` used by your OpenMW install, add the following lines under the ``[Models]`` section. -.. code:: +.. code-block:: ini + :caption: settings.cfg + [Models] xbaseanim = meshes/BasicPlayer.dae baseanim = meshes/BasicPlayer.dae xbaseanim1st = meshes/BasicPlayer.dae @@ -103,7 +107,7 @@ need to be copied to ``resources/mygui`` folder found in your OpenMW installatio folder. Overwrite any files aready in this folder. These files provide the UI font, its definition, and some minor UI tweaks. -.. code:: +.. code-block:: none openmw_box.skin.xml openmw_button.skin.xml diff --git a/docs/source/reference/modding/paths.rst b/docs/source/reference/modding/paths.rst index 4ea5219e74..c0f7d4cea6 100644 --- a/docs/source/reference/modding/paths.rst +++ b/docs/source/reference/modding/paths.rst @@ -134,7 +134,8 @@ This can't change until computers are able to read minds. Lines with options have an option name, then an equals sign (``=``), then an option value. Option names and values have leading and trailing whitespace trimmed, but whitespace within an option value is preserved - it's only removed if it's at the ends. This means that these are all equivalent: -:: + +.. code-block:: openmwcfg data=some/dir data=some/dir @@ -226,7 +227,10 @@ Navigate to the OpenMW installation directory, and open the ``openmw.cfg`` file By default, this contains a warning at the top telling you that this is the local ``openmw.cfg`` and not to modify it. However, for this kind of install, it's okay to do so, so you can remove this warning. -Change the start of the file from:: +Change the start of the file from: + +.. code-block:: openmwcfg + :caption: openmw.cfg # This is the local openmw.cfg file. Do not modify! # Modifications should be done on the user openmw.cfg file instead @@ -243,7 +247,10 @@ Change the start of the file from:: fallback=LightAttenuation_ConstantValue,0.0 fallback=LightAttenuation_UseLinear,1 -to:: +to: + +.. code-block:: openmwcfg + :caption: openmw.cfg data-local=userdata/data user-data=userdata @@ -274,7 +281,10 @@ Navigate to the OpenMW installation directory, and open the ``openmw.cfg`` file By default, this contains a warning at the top telling you that this is the local ``openmw.cfg`` and not to modify it. However, you'll need to make a small change to create this kind of install. -Change the start of the file from:: +Change the start of the file from: + +.. code-block:: openmwcfg + :caption: openmw.cfg # This is the local openmw.cfg file. Do not modify! # Modifications should be done on the user openmw.cfg file instead @@ -291,7 +301,10 @@ Change the start of the file from:: fallback=LightAttenuation_ConstantValue,0.0 fallback=LightAttenuation_UseLinear,1 -to:: +to: + +.. code-block:: openmwcfg + :caption: openmw.cfg # This is the local openmw.cfg file. Do not modify! # Modifications should be done on the user openmw.cfg file instead @@ -330,7 +343,10 @@ From scratch Start by installing OpenMW in the usual way. Don't bother with first-time setup (i.e. telling it the location of an existing *Morrowind* installation). -In the default configuration directory (see `Configuration files and log files`_), create a file called ``openmw.cfg`` containing just:: +In the default configuration directory (see `Configuration files and log files`_), create a file called ``openmw.cfg`` containing just + +.. code-block:: openmwcfg + :caption: openmw.cfg # select the game profile config=Morrowind @@ -340,7 +356,10 @@ This will put the basic setup required to play *Morrowind* into a new ``Morrowin Next, come up with a name for the subprofile you'll create for your mod list. If you're following a modding guide, they've probably already given it a name, e.g. *Total Overhaul*, so that's the example we'll use. -Add a line to the ``Morrowind/openmw.cfg`` with the profile name, e.g.:: +Add a line to the ``Morrowind/openmw.cfg`` with the profile name like this: + +.. code-block:: openmwcfg + :caption: Morrowind/openmw.cfg # select the mod list profile config=Total Overhaul @@ -356,7 +375,10 @@ The ones in the ``Morrowind`` directory are used for all profiles for *Morrowind The ones in the ``Morrowind/Total Overhaul`` directory are only used for the *Total Overhaul* profile, so you can set up that mod list and any settings it requires here, and they won't affect any other profiles you set up later. Making changes within the launcher will affect these files and leave all the others alone. -If you want the *Total Overhaul* profile to keep its saved games etc. in a dedicated location instead of mixing them in with ones from another profile, you can add a ``user-data=…`` line to your ``Morrowind/Total Overhaul/openmw.cfg``, e.g.:: +If you want the *Total Overhaul* profile to keep its saved games etc. in a dedicated location instead of mixing them in with ones from another profile, you can add a ``user-data=…`` line to your ``Morrowind/Total Overhaul/openmw.cfg``, like this: + +.. code-block:: openmwcfg + :caption: Morrowind/Total Overhaul/openmw.cfg # put saved games in a saves directory next to this file user-data=. @@ -377,12 +399,18 @@ You'll now have an empty directory e.g. at ``Documents\My Games\OpenMW\Original` Next, move all the files that were already in the default configuration directory to the profile directory you just made. Afterwards, the default configuration directory should only contain the profile directory you made. -Create a new ``openmw.cfg`` file in the default configuration directory containing:: +Create a new ``openmw.cfg`` file in the default configuration directory containing: + +.. code-block:: openmwcfg + :caption: openmw.cfg # select the profile config=Original -In the ``openmw.cfg`` in the profile directory, add these lines:: +In the ``openmw.cfg`` in the profile directory, add these lines: + +.. code-block:: openmwcfg + :caption: openmw.cfg data-local=data user-data=. @@ -402,9 +430,11 @@ Passing arguments on the command line lets you avoid this. The basic idea is that you need to pass ``--replace config`` to ignore the configuration directories that the engine would have loaded because they were specified in ``openmw.cfg`` files, and pass each one you want to use instead with ``--config ``. -E.g. if you've got a profile called *Morrowind* in your default configuration directory, and it's got a *Total Overhaul* subprofile, you could load it by running:: +E.g. if you've got a profile called *Morrowind* in your default configuration directory, and it's got a *Total Overhaul* subprofile, you could load it by running: - openmw --replace config --config ?userconfig?/Morrowind --config "?userconfig?/Morrowind/Total Overhaul" +.. code-block:: console + + $ openmw --replace config --config ?userconfig?/Morrowind --config "?userconfig?/Morrowind/Total Overhaul" You can put this command into a script or shortcut and use it to easily launch OpenMW with that profile. @@ -422,15 +452,17 @@ On Windows, you can create a desktop shortcut to run this command with these ste * At the end of that field, add the arguments for the profile you want, e.g. ``--replace config --config ?userconfig?/Morrowind --config "?userconfig?/Morrowind/Total Overhaul"``. * Press *Apply* or *OK* to save the changes, and test the shortcut by double-clicking it. -On most Linux distros, you can create a ``.desktop`` file like this:: +On most Linux distros, you can create a ``.desktop`` file like this: - [Desktop Entry] - Type=Application - Name=OpenMW - Total Overhaul - GenericName=Role Playing Game - Comment=OpenMW with the Total Overhaul profile - Keywords=Morrowind;Reimplementation Mods;esm;bsa; - TryExec=openmw - Exec=openmw --replace config --config ?userconfig?/Morrowind --config "?userconfig?/Morrowind/Total Overhaul" - Icon=openmw - Categories=Game;RolePlaying; +.. code-block:: desktop + + [Desktop Entry] + Type=Application + Name=OpenMW - Total Overhaul + GenericName=Role Playing Game + Comment=OpenMW with the Total Overhaul profile + Keywords=Morrowind;Reimplementation Mods;esm;bsa; + TryExec=openmw + Exec=openmw --replace config --config ?userconfig?/Morrowind --config "?userconfig?/Morrowind/Total Overhaul" + Icon=openmw + Categories=Game;RolePlaying; diff --git a/docs/source/reference/modding/texture-modding/convert-bump-mapped-mods.rst b/docs/source/reference/modding/texture-modding/convert-bump-mapped-mods.rst index e05177c268..89ea25519d 100644 --- a/docs/source/reference/modding/texture-modding/convert-bump-mapped-mods.rst +++ b/docs/source/reference/modding/texture-modding/convert-bump-mapped-mods.rst @@ -2,24 +2,9 @@ Normal maps from Morrowind to OpenMW ==================================== -- `General introduction to normal map conversion`_ - - `OpenMW normal-mapping`_ - - `Activating normal-mapping shaders in OpenMW`_ - - `Morrowind bump-mapping`_ - - `MGE XE normal-mapping`_ -- `Converting PeterBitt's Scamp Replacer`_ (Mod made for the MGE XE PBR prototype) - - `Tutorial - MGE`_ -- `Converting Lougian's Hlaalu Bump mapped`_ (Morrowind's bump-mapping, part 1: *without* custom models) - - `Tutorial - Morrowind, Part 1`_ -- `Converting Apel's Various Things - Sacks`_ (Morrowind's bump-mapping, part 2: *with* custom models) - - `Tutorial - Morrowind, Part 2`_ - General introduction to normal map conversion --------------------------------------------- -:Authors: Joakim (Lysol) Berg, Alexei (Capo) Dobrohotov -:Updated: 2020-03-03 - This page has general information and tutorials on how normal-mapping works in OpenMW and how you can make mods using the old environment-mapped bump-mapping technique (such as `Netch Bump mapped`_ and `Hlaalu Bump mapped`_, and maybe the most (in)famous one to previously give shiny rocks in OpenMW, the mod `On the Rocks`_!, featured in MGSO and Morrowind Rebirth) work better in OpenMW. @@ -58,14 +43,15 @@ Activating normal-mapping shaders in OpenMW Before normal (and specular and parallax) maps can show up in OpenMW, their auto-detection needs to be turned on in settings.cfg_-file. Add these rows where it would make sense: -:: +.. code-block:: ini + :caption: settings.cfg - [Shaders] - auto use object normal maps = true - auto use terrain normal maps = true + [Shaders] + auto use object normal maps = true + auto use terrain normal maps = true - auto use object specular maps = true - auto use terrain specular maps = true + auto use object specular maps = true + auto use terrain specular maps = true See OpenMW's wiki page about `texture modding`_ to read more about it. @@ -81,10 +67,11 @@ are processed which makes bump-mapped models look a bit better, can make use of the gloss map channel in the bump map and can apply bump-mapping to skinned models. Add this to settings.cfg_-file: -:: +.. code-block:: ini + :caption: settings.cfg - [Shaders] - apply lighting to environment maps = true + [Shaders] + apply lighting to environment maps = true But sometimes you may want them to look a bit better than in vanilla. Technically you aren't supposed to convert bump maps because they shouldn't be normal maps that are supported by OpenMW as well, @@ -117,9 +104,6 @@ Converting PeterBitt's Scamp Replacer ------------------------------------- **Mod made for the MGE XE PBR prototype** -:Authors: Joakim (Lysol) Berg -:Updated: 2016-11-11 - So, let's say you've found out that PeterBitt_ makes awesome models and textures featuring physically based rendering (PBR) and normal maps. Let's say that you tried to run his `PBR Scamp Replacer`_ in OpenMW and that you were greatly disappointed when the normal map didn't seem to work. Lastly, let's say you came here, looking for some answers. @@ -161,9 +145,6 @@ Converting Lougian's Hlaalu Bump mapped --------------------------------------- **Mod made for Morrowind's bump-mapping, without custom models** -:Authors: Joakim (Lysol) Berg, Alexei (Capo) Dobrohotov -:Updated: 2020-03-03 - Converting normal maps made for the Morrowind's bump-mapping can be really easy or a real pain, depending on a few circumstances. In this tutorial, we will look at a very easy, although in some cases a bit time-consuming, example. @@ -192,9 +173,6 @@ Converting Apel's Various Things - Sacks ---------------------------------------- **Mod made for Morrowind bump-mapping, with custom models** -:Authors: Joakim (Lysol) Berg, Alexei (Capostrophic) Dobrohotov -:Updated: 2020-03-03 - In part one of this tutorial, we converted a mod that only included modified Morrowind model (``.nif``) files so that the bump maps could be loaded as normal maps. We ignored those model files since they are not needed with OpenMW. In this tutorial however, diff --git a/docs/source/reference/modding/texture-modding/texture-basics.rst b/docs/source/reference/modding/texture-modding/texture-basics.rst index 96ec48508b..26a47f4e0a 100644 --- a/docs/source/reference/modding/texture-modding/texture-basics.rst +++ b/docs/source/reference/modding/texture-modding/texture-basics.rst @@ -55,17 +55,20 @@ Simply create the textures with appropriate naming convention the normal map would have to be called foo_n.dds). To enable this automatic use based on filename pattern, you will have to add the following to your -`settings.cfg `_ file:: +`settings.cfg `_ file: - [Shaders] - auto use object normal maps = true +.. code-block:: ini + :caption: settings.cfg - auto use object specular maps = true + [Shaders] + auto use object normal maps = true - normal map pattern = _n - normal height map pattern = _nh + auto use object specular maps = true - specular map pattern = _spec + normal map pattern = _n + normal height map pattern = _nh + + specular map pattern = _spec Additionally, a normal map with the `_nh` pattern enables the use of the normal map's alpha channel as height information. @@ -92,18 +95,21 @@ For example, if you wanted to add specular mapping to a terrain layer called roc you would copy this texture to a new file called rock_diffusespec.dds, and then edit its alpha channel to set the specular intensity. -The relevant settings are:: +The relevant settings are - [Shaders] - auto use terrain normal maps = true +.. code-block:: ini + :caption: settings.cfg - auto use terrain specular maps = true + [Shaders] + auto use terrain normal maps = true - terrain specular map pattern = _diffusespec + auto use terrain specular maps = true - # Also used for terrain normal maps - normal map pattern = _n - normal height map pattern = _nh + terrain specular map pattern = _diffusespec + + # Also used for terrain normal maps + normal map pattern = _n + normal height map pattern = _nh OSG native files ################ diff --git a/files/data/CMakeLists.txt b/files/data/CMakeLists.txt index 6038e8cdc8..1ae5f4da01 100644 --- a/files/data/CMakeLists.txt +++ b/files/data/CMakeLists.txt @@ -120,7 +120,9 @@ set(BUILTIN_DATA_FILES scripts/omw/console/local.lua scripts/omw/console/player.lua scripts/omw/console/menu.lua + scripts/omw/mechanics/actorcontroller.lua scripts/omw/mechanics/animationcontroller.lua + scripts/omw/mechanics/globalcontroller.lua scripts/omw/mechanics/playercontroller.lua scripts/omw/settings/menu.lua scripts/omw/music/actor.lua diff --git a/files/data/builtin.omwscripts b/files/data/builtin.omwscripts index 37367783ab..6126c0b761 100644 --- a/files/data/builtin.omwscripts +++ b/files/data/builtin.omwscripts @@ -23,6 +23,8 @@ PLAYER: scripts/omw/input/actionbindings.lua PLAYER: scripts/omw/input/smoothmovement.lua PLAYER: scripts/omw/input/gamepadcontrols.lua NPC,CREATURE: scripts/omw/ai.lua +GLOBAL: scripts/omw/mechanics/globalcontroller.lua +CREATURE, NPC, PLAYER: scripts/omw/mechanics/actorcontroller.lua # User interface PLAYER: scripts/omw/ui.lua diff --git a/files/data/mygui/openmw_hud.layout b/files/data/mygui/openmw_hud.layout index 0650df1f43..745afc52e9 100644 --- a/files/data/mygui/openmw_hud.layout +++ b/files/data/mygui/openmw_hud.layout @@ -110,8 +110,7 @@ - - + diff --git a/files/data/mygui/openmw_tooltips.layout b/files/data/mygui/openmw_tooltips.layout index ce927038c2..39cfbd06e1 100644 --- a/files/data/mygui/openmw_tooltips.layout +++ b/files/data/mygui/openmw_tooltips.layout @@ -272,16 +272,34 @@ - + - + - + - + + + + + + + + + + + + + + + + + + + diff --git a/files/data/scripts/omw/mechanics/actorcontroller.lua b/files/data/scripts/omw/mechanics/actorcontroller.lua new file mode 100644 index 0000000000..fe8c75b244 --- /dev/null +++ b/files/data/scripts/omw/mechanics/actorcontroller.lua @@ -0,0 +1,23 @@ +local self = require('openmw.self') +local core = require('openmw.core') +local types = require('openmw.types') +local Actor = types.Actor + +return { + eventHandlers = { + ModifyStat = function(data) + local stat = Actor.stats.dynamic[data.stat](self) + stat.current = stat.current + data.amount + end, + PlaySound3d = function(data) + if data.sound then + core.sound.playSound3d(data.sound, self, data.options) + else + core.sound.playSoundFile3d(data.file, self, data.options) + end + end, + BreakInvisibility = function(data) + Actor.activeEffects(self):remove(core.magic.EFFECT_TYPE.Invisibility) + end, + }, +} diff --git a/files/data/scripts/omw/mechanics/globalcontroller.lua b/files/data/scripts/omw/mechanics/globalcontroller.lua new file mode 100644 index 0000000000..22d92e7c24 --- /dev/null +++ b/files/data/scripts/omw/mechanics/globalcontroller.lua @@ -0,0 +1,39 @@ +local types = require('openmw.types') +local Lockable = types.Lockable +local Item = require('openmw.types').Item +local world = require('openmw.world') +local core = require('openmw.core') + +local function onConsumeItem(data) + local item = data.item + local amount = data.amount + if amount > item.count then + print('Warning: tried to consume '..tostring(amount)..' '..tostring(item)..'s, but there were only '..tostring(item.count)) + amount = item.count + end + item:remove(amount) +end + +local function onPlaySound3d(data) + if data.sound then + core.sound.playSound3d(data.sound, data.position, data.options) + elseif data.file then + core.sound.playSoundFile3d(data.file, data.position, data.options) + end +end + +return { + eventHandlers = { + SpawnVfx = function(data) + world.vfx.spawn(data.model, data.position, data.options) + end, + PlaySound3d = onPlaySound3d, + ConsumeItem = onConsumeItem, + Lock = function(data) + Lockable.lock(data.target, data.magnitude) + end, + Unlock = function(data) + Lockable.unlock(data.target) + end, + }, +} diff --git a/files/data/scripts/omw/mechanics/playercontroller.lua b/files/data/scripts/omw/mechanics/playercontroller.lua index 870f24415c..8b4d618917 100644 --- a/files/data/scripts/omw/mechanics/playercontroller.lua +++ b/files/data/scripts/omw/mechanics/playercontroller.lua @@ -111,4 +111,10 @@ return { engineHandlers = { onUpdate = onUpdate, }, + + eventHandlers = { + ShowMessage = function(data) + if data.message then ui.showMessage(data.message) end + end + }, } diff --git a/files/data/scripts/omw/ui.lua b/files/data/scripts/omw/ui.lua index e29bbe254e..31502fd6ee 100644 --- a/files/data/scripts/omw/ui.lua +++ b/files/data/scripts/omw/ui.lua @@ -155,6 +155,13 @@ local function onUiModeChangedEvent(data) end end +local function isWindowVisible(windowName) + if replacedWindows[windowName] then + return replacedWindows[windowName].visible + end + return ui._isWindowVisible(windowName) +end + return { interfaceName = 'UI', --- @@ -164,7 +171,7 @@ return { interface = { --- Interface version -- @field [parent=#UI] #number version - version = 1, + version = 2, --- All available UI modes. -- Use `view(I.UI.MODE)` in `luap` console mode to see the list. @@ -240,6 +247,13 @@ return { -- @return #boolean isHudVisible = function() return ui._isHudVisible() end, + --- + -- Returns if the given window is visible or not + -- @function [parent=#UI] isWindowVisible + -- @param #string windowName + -- @return #boolean + isWindowVisible = isWindowVisible, + -- TODO -- registerHudElement = function(name, showFn, hideFn) end, -- showHudElement = function(name, bool) end,