mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-09-24 04:11:50 -04:00
Merge branch 'master' of https://gitlab.com/OpenMW/openmw
This commit is contained in:
commit
b170500183
146
CMakeLists.txt
146
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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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 };
|
||||
|
@ -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());
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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) {};
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -565,6 +565,8 @@ namespace MWRender
|
||||
else
|
||||
createSimpleWaterStateSet(mWaterGeom, Fallback::Map::getFloat("Water_World_Alpha"));
|
||||
|
||||
mResourceSystem->getSceneManager()->setUpNormalsRTForStateSet(mWaterGeom->getOrCreateStateSet(), true);
|
||||
|
||||
updateVisible();
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -338,6 +338,8 @@ namespace MWWorld
|
||||
|
||||
Ptr getMovedActor(int actorId) const;
|
||||
|
||||
CellStore* getOriginCell(const Ptr& object) const;
|
||||
|
||||
Ptr getPtr(ESM::RefId id);
|
||||
|
||||
private:
|
||||
|
@ -50,6 +50,7 @@ namespace Bsa
|
||||
public:
|
||||
using BSAFile::getFilename;
|
||||
using BSAFile::getList;
|
||||
using BSAFile::getPath;
|
||||
using BSAFile::open;
|
||||
|
||||
BA2DX10File();
|
||||
|
@ -38,6 +38,7 @@ namespace Bsa
|
||||
public:
|
||||
using BSAFile::getFilename;
|
||||
using BSAFile::getList;
|
||||
using BSAFile::getPath;
|
||||
using BSAFile::open;
|
||||
|
||||
BA2GNRLFile();
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -117,6 +117,7 @@ namespace Bsa
|
||||
public:
|
||||
using BSAFile::getFilename;
|
||||
using BSAFile::getList;
|
||||
using BSAFile::getPath;
|
||||
using BSAFile::open;
|
||||
|
||||
CompressedBSAFile() = default;
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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) + "'");
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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
|
||||
|
19
docs/source/_ext/omw-lexers.py
Normal file
19
docs/source/_ext/omw-lexers.py
Normal 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()
|
@ -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;
|
||||
}
|
@ -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.
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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})
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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">
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
################
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
|
23
files/data/scripts/omw/mechanics/actorcontroller.lua
Normal file
23
files/data/scripts/omw/mechanics/actorcontroller.lua
Normal 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,
|
||||
},
|
||||
}
|
39
files/data/scripts/omw/mechanics/globalcontroller.lua
Normal file
39
files/data/scripts/omw/mechanics/globalcontroller.lua
Normal 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,
|
||||
},
|
||||
}
|
@ -111,4 +111,10 @@ return {
|
||||
engineHandlers = {
|
||||
onUpdate = onUpdate,
|
||||
},
|
||||
|
||||
eventHandlers = {
|
||||
ShowMessage = function(data)
|
||||
if data.message then ui.showMessage(data.message) end
|
||||
end
|
||||
},
|
||||
}
|
||||
|
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user