This commit is contained in:
Andrew Lanzone 2025-07-06 23:00:28 -07:00
commit b170500183
76 changed files with 895 additions and 380 deletions

View File

@ -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

View File

@ -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);

View File

@ -113,7 +113,8 @@ namespace
void compile(const std::string& name)
{
mTechnique = std::make_unique<Technique>(*mVFS.get(), mImageManager, name, 1, 1, true, true);
mTechnique = std::make_unique<Technique>(
*mVFS.get(), mImageManager, Technique::makeFileName(name), name, 1, 1, true, true);
mTechnique->compile();
}
};

View File

@ -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);

View File

@ -113,7 +113,7 @@ bool isBSA(const std::filesystem::path& path)
std::unique_ptr<VFS::Archive> 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<VFS::FileSystemArchive>(path);
return nullptr;
@ -198,7 +198,7 @@ void readVFS(std::unique_ptr<VFS::Archive>&& 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)
{

View File

@ -143,7 +143,7 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data
, mArchives(archives)
, mVFS(std::make_unique<VFS::Manager>())
{
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<ESM::RefId> 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 };

View File

@ -729,7 +729,7 @@ void OMW::Engine::prepareEngine()
mVFS = std::make_unique<VFS::Manager>();
VFS::registerArchives(mVFS.get(), mFileCollections, mArchives, true);
VFS::registerArchives(mVFS.get(), mFileCollections, mArchives, true, &mEncoder.get()->getStatelessEncoder());
mResourceSystem = std::make_unique<Resource::ResourceSystem>(
mVFS.get(), Settings::cells().mCacheExpiryDelay, &mEncoder.get()->getStatelessEncoder());

View File

@ -397,6 +397,7 @@ namespace MWBase
// Used in Lua bindings
virtual const std::vector<MWGui::GuiMode>& 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<std::string_view> getAllWindowIds() const = 0;
virtual std::vector<std::string_view> getAllowedWindowIds(MWGui::GuiMode mode) const = 0;
};

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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)

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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<ItemWidget>(
mDraggedWidget = MyGUI::Gui::getInstance().createWidget<ItemWidget>(
"MW_ItemIcon", 0, 0, 42, 42, MyGUI::Align::Default, "DragAndDrop");
Controllers::ControllerFollowMouse* controller
= MyGUI::ControllerManager::getInstance()
.createItem(Controllers::ControllerFollowMouse::getClassTypeName())
->castType<Controllers::ControllerFollowMouse>();
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);
}

View File

@ -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();

View File

@ -92,6 +92,7 @@ namespace MWGui
, mTrading(false)
, mUpdateTimer(0.f)
, mPendingControllerAction(ControllerAction::None)
, mUpdateNextFrame(false)
{
mPreviewTexture
= std::make_unique<osgMyGUI::OSGTexture>(mPreview->getTexture(), mPreview->getTextureStateSet());
@ -169,6 +170,8 @@ namespace MWGui
auto tradeModel = std::make_unique<TradeItemModel>(std::make_unique<InventoryItemModel>(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();

View File

@ -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<MWRender::InventoryPreview> mPreview;
bool mTrading;
float mUpdateTimer;
bool mUpdateNextFrame;
void toggleMaximized();

View File

@ -33,6 +33,14 @@
namespace MWGui
{
namespace
{
std::shared_ptr<fx::Technique>& getTechnique(const MyGUI::ListBox& list, size_t selected)
{
return *list.getItemDataAt<std::shared_ptr<fx::Technique>>(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<std::shared_ptr<fx::Technique>>(selected);
auto technique = getTechnique(*list, selected);
if (technique->getDynamic())
return;
@ -167,7 +175,7 @@ namespace MWGui
if (static_cast<size_t>(index) != selected)
{
auto technique = *mActiveList->getItemDataAt<std::shared_ptr<fx::Technique>>(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<std::string> techniques;
for (const auto& [name, _] : processor->getTechniqueMap())
techniques.push_back(name);
std::sort(techniques.begin(), techniques.end(), Misc::StringUtils::ciLess);
std::vector<VFS::Path::NormalizedView> 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);
}
}

View File

@ -6,6 +6,7 @@
#include <MyGUI_ListBox.h>
#include <components/settings/shadermanager.hpp>
#include <components/vfs/pathutil.hpp>
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);

View File

@ -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<const ESM::Spell*> abilities, powers, spells;
@ -915,26 +916,22 @@ namespace MWGui
spells.push_back(spell);
}
using Category = std::pair<const std::vector<const ESM::Spell*>&, std::string_view>;
for (const auto& [category, label] : std::initializer_list<Category>{
{ abilities, "sBirthsignmenu1" }, { powers, "sPowers" }, { spells, "sBirthsignmenu2" } })
using Category = std::tuple<const std::vector<const ESM::Spell*>&, std::string_view, std::string_view>;
std::initializer_list<Category> 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)

View File

@ -113,6 +113,25 @@ namespace MWGui
encumbrance = std::max(0.f, encumbrance);
}
void TradeItemModel::updateBorrowed()
{
auto update = [](std::vector<ItemStack>& 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();

View File

@ -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

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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<std::string_view> WindowManager::getAllWindowIds() const
{
std::vector<std::string_view> res;

View File

@ -403,6 +403,7 @@ namespace MWGui
// Used in Lua bindings
const std::vector<GuiMode>& getGuiModeStack() const override { return mGuiModes; }
void setDisabledByLua(std::string_view windowId, bool disabled) override;
bool isWindowVisible(std::string_view windowId) const override;
std::vector<std::string_view> getAllWindowIds() const override;
std::vector<std::string_view> getAllowedWindowIds(GuiMode mode) const override;

View File

@ -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) {};

View File

@ -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);
}
}

View File

@ -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<UpdateVfxCallback>

View File

@ -12,6 +12,7 @@
#include <osg/Texture3D>
#include <components/files/conversion.hpp>
#include <components/misc/pathhelpers.hpp>
#include <components/misc/strings/algorithm.hpp>
#include <components/misc/strings/lower.hpp>
#include <components/resource/scenemanager.hpp>
@ -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<fx::Technique> PostProcessor::loadTechnique(const std::string& name, bool loadNextFrame)
std::shared_ptr<fx::Technique> 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<fx::Technique> 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<fx::Technique>(*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);
}

View File

@ -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<fx::Technique> loadTechnique(const std::string& name, bool loadNextFrame = false);
std::shared_ptr<fx::Technique> loadTechnique(VFS::Path::NormalizedView path, bool loadNextFrame = false);
std::shared_ptr<fx::Technique> loadTechnique(std::string_view name, bool loadNextFrame = false);
TechniqueList getChain();
@ -232,8 +233,7 @@ namespace MWRender
TechniqueList mQueuedTemplates;
TechniqueList mInternalTechniques;
std::unordered_map<std::string, std::filesystem::path, Misc::StringUtils::CiHash, Misc::StringUtils::CiEqual>
mTechniqueFileMap;
std::unordered_set<VFS::Path::Normalized, VFS::Path::Hash, std::equal_to<>> mTechniqueFiles;
RenderingManager& mRendering;
osgViewer::Viewer* mViewer;

View File

@ -565,6 +565,8 @@ namespace MWRender
else
createSimpleWaterStateSet(mWaterGeom, Fallback::Map::getFloat("Water_World_Alpha"));
mResourceSystem->getSceneManager()->setUpNormalsRTForStateSet(mWaterGeom->getOrCreateStateSet(), true);
updateVisible();
}

View File

@ -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);
}
}

View File

@ -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)

View File

@ -338,6 +338,8 @@ namespace MWWorld
Ptr getMovedActor(int actorId) const;
CellStore* getOriginCell(const Ptr& object) const;
Ptr getPtr(ESM::RefId id);
private:

View File

@ -50,6 +50,7 @@ namespace Bsa
public:
using BSAFile::getFilename;
using BSAFile::getList;
using BSAFile::getPath;
using BSAFile::open;
BA2DX10File();

View File

@ -38,6 +38,7 @@ namespace Bsa
public:
using BSAFile::getFilename;
using BSAFile::getList;
using BSAFile::getPath;
using BSAFile::open;
BA2GNRLFile();

View File

@ -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<char> 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);
};

View File

@ -117,6 +117,7 @@ namespace Bsa
public:
using BSAFile::getFilename;
using BSAFile::getList;
using BSAFile::getPath;
using BSAFile::open;
CompressedBSAFile() = default;

View File

@ -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)

View File

@ -105,8 +105,8 @@ namespace fx
using UniformMap = std::vector<std::shared_ptr<Types::UniformBase>>;
using RenderTargetMap = std::unordered_map<std::string_view, Types::RenderTarget>;
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();

View File

@ -4,6 +4,7 @@
#include <filesystem>
#include <osg/AlphaFunc>
#include <osg/Capability>
#include <osg/ColorMaski>
#include <osg/Group>
#include <osg/Node>
@ -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.

View File

@ -56,7 +56,9 @@ namespace TestingOpenMW
Files::IStreamPtr open() override { return std::make_unique<std::stringstream>(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;

View File

@ -10,45 +10,72 @@
#include <components/bsa/bsafile.hpp>
#include <components/bsa/compressedbsafile.hpp>
#include <components/toutf8/toutf8.hpp>
#include <algorithm>
#include <memory>
#include <stdexcept>
namespace VFS
{
template <typename BSAFileType>
class BsaArchive;
template <typename FileType>
class BsaArchiveFile : public File
{
public:
BsaArchiveFile(const Bsa::BSAFile::FileStruct* info, FileType* bsa)
BsaArchiveFile(const Bsa::BSAFile::FileStruct* info, const BsaArchive<FileType>* 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<FileType>* mFile;
};
template <typename BSAFileType>
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<BSAFileType>();
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<BSAFileType> mFile;
std::vector<BsaArchiveFile<BSAFileType>> mResources;
std::vector<VFS::Path::Normalized> mFiles;
const ToUTF8::StatelessUtf8Encoder* mEncoder;
};
inline std::unique_ptr<VFS::Archive> makeBsaArchive(const std::filesystem::path& path)
inline std::unique_ptr<VFS::Archive> 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<BsaArchive<Bsa::BSAFile>>(path);
return std::make_unique<BsaArchive<Bsa::BSAFile>>(path, encoder);
case Bsa::BsaVersion::Compressed:
return std::make_unique<BsaArchive<Bsa::CompressedBSAFile>>(path);
return std::make_unique<BsaArchive<Bsa::CompressedBSAFile>>(path, encoder);
case Bsa::BsaVersion::BA2GNRL:
return std::make_unique<BsaArchive<Bsa::BA2GNRLFile>>(path);
return std::make_unique<BsaArchive<Bsa::BA2GNRLFile>>(path, encoder);
case Bsa::BsaVersion::BA2DX10:
return std::make_unique<BsaArchive<Bsa::BA2DX10File>>(path);
return std::make_unique<BsaArchive<Bsa::BA2DX10File>>(path, encoder);
}
throw std::runtime_error("Unknown archive type '" + Files::pathToUnicodeString(path) + "'");

View File

@ -2,6 +2,7 @@
#define OPENMW_COMPONENTS_VFS_FILE_H
#include <filesystem>
#include <string>
#include <components/files/istreamptr.hpp>
@ -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;
};
}

View File

@ -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());
}
}

View File

@ -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;

View File

@ -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

View File

@ -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<std::unique_ptr<Archive>> mArchives;

View File

@ -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;
};

View File

@ -14,7 +14,7 @@ namespace VFS
{
void registerArchives(VFS::Manager* vfs, const Files::Collections& collections,
const std::vector<std::string>& archives, bool useLooseFiles)
const std::vector<std::string>& 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
{

View File

@ -3,13 +3,18 @@
#include <components/files/collections.hpp>
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<std::string>& archives, bool useLooseFiles);
const std::vector<std::string>& archives, bool useLooseFiles, const ToUTF8::StatelessUtf8Encoder* encoder);
}
#endif

View File

@ -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()

View File

@ -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;
}

View File

@ -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.

View File

@ -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.

View File

@ -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})

View File

@ -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.

View File

@ -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.

View File

@ -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
<library_images>
<image id="id-image-4" name="the_barrel">

View File

@ -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 <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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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 <directory path here>``.
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;

View File

@ -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,

View File

@ -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 </source/reference/modding/paths>`_ file::
`settings.cfg </source/reference/modding/paths>`_ 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
################

View File

@ -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

View File

@ -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

View File

@ -110,8 +110,7 @@
</Widget>
<!-- Crosshair -->
<Widget type="ImageBox" skin="HUD_Crosshair" position="0 0 32 32" align="Center Center" name="Crosshair">
</Widget>
<Widget type="ImageBox" skin="HUD_Crosshair" position="0 0 27 27" align="Center Center" name="Crosshair"/>
</Widget>

View File

@ -272,16 +272,34 @@
</Widget>
<!-- Birthsign tooltip -->
<Widget type="VBox" skin="HUD_Box_NoTransp" position="0 0 300 300" align="Stretch" name="BirthSignToolTip">
<Widget type="VBox" skin="HUD_Box_NoTransp" position="0 0 0 0" align="Stretch" name="BirthSignToolTip">
<Property key="AutoResize" value="true"/>
<Property key="Padding" value="8"/>
<Property key="Padding" value="10"/>
<!-- Birthsign image -->
<Widget type="Widget" skin="MW_Box" position="18 13 263 137" align="Top HCenter">
<Widget type="ImageBox" skin="ImageBox" position="2 2 259 133" name="BirthSignImage" align="Left Top"/>
<Widget type="ImageBox" skin="ImageBox" position="2 2 259 133" name="BirthSignImage" align="Top"/>
</Widget>
<Widget type="AutoSizedTextBox" skin="NormalText" position="8 154 284 138" align="Top" name="BirthSignText">
<Widget type="AutoSizedTextBox" skin="NormalText" align="Top" name="BirthSignName">
<Property key="TextAlign" value="Top HCenter"/>
</Widget>
<Widget type="AutoSizedEditBox" skin="SandText" position="0 0 400 100" align="Top" name="BirthSignDescription">
<Property key="MultiLine" value="true"/>
<Property key="WordWrap" value="true"/>
<Property key="TextAlign" value="Left Top"/>
</Widget>
<Widget type="AutoSizedTextBox" skin="NormalText" align="Top" name="BirthSignAbilities">
<Property key="TextAlign" value="Top HCenter"/>
</Widget>
<Widget type="AutoSizedTextBox" skin="NormalText" align="Top" name="BirthSignPowers">
<Property key="TextAlign" value="Top HCenter"/>
</Widget>
<Widget type="AutoSizedTextBox" skin="NormalText" align="Top" name="BirthSignSpells">
<Property key="TextAlign" value="Top HCenter"/>
</Widget>
</Widget>

View File

@ -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,
},
}

View File

@ -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,
},
}

View File

@ -111,4 +111,10 @@ return {
engineHandlers = {
onUpdate = onUpdate,
},
eventHandlers = {
ShowMessage = function(data)
if data.message then ui.showMessage(data.message) end
end
},
}

View File

@ -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,