Merge branch 'master' of gitlab.com:openmw/openmw into lua_controller_cursor

pull/3235/head
Zackhasacat 3 months ago
commit d73c1c8590

@ -172,6 +172,20 @@ Clang_Format:
- CI/check_file_names.sh
- CI/check_clang_format.sh
Lupdate:
extends: .Ubuntu_Image
stage: checks
cache:
key: Ubuntu_lupdate.ubuntu_22.04.v1
paths:
- apt-cache/
variables:
LUPDATE: lupdate
before_script:
- CI/install_debian_deps.sh openmw-qt-translations
script:
- CI/check_qt_translations.sh
Teal:
stage: checks
extends: .Ubuntu_Image
@ -196,6 +210,7 @@ Ubuntu_GCC_Debug:
CCACHE_SIZE: 3G
CMAKE_BUILD_TYPE: Debug
CMAKE_CXX_FLAGS_DEBUG: -O0
BUILD_SHARED_LIBS: 1
# When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks.
timeout: 2h

@ -79,6 +79,7 @@ Programmers
Eduard Cot (trombonecot)
Eli2
Emanuel Guével (potatoesmaster)
Epoch
Eris Caffee (eris)
eroen
escondida
@ -187,6 +188,7 @@ Programmers
pkubik
PLkolek
PlutonicOverkill
Qlonever
Radu-Marius Popovici (rpopovici)
Rafael Moura (dhustkoder)
Randy Davin (Kindi)

@ -24,7 +24,9 @@
Bug #5129: Stuttering animation on Centurion Archer
Bug #5280: Unskinned shapes in skinned equipment are rendered in the wrong place
Bug #5371: Keyframe animation tracks are used for any file that begins with an X
Bug #5413: Enemies do a battlecry everytime the player summons a creature
Bug #5714: Touch spells cast using ExplodeSpell don't always explode
Bug #5755: Reset friendly hit counter
Bug #5849: Paralysis breaks landing
Bug #5870: Disposing of actors who were selected in the console doesn't deselect them like vanilla
Bug #5883: Immobile creatures don't cause water ripples
@ -37,6 +39,7 @@
Bug #6402: The sound of a thunderstorm does not stop playing after entering the premises
Bug #6427: Enemy health bar disappears before damaging effect ends
Bug #6550: Cloned body parts don't inherit texture effects
Bug #6574: Crash at far away from world origin coordinates
Bug #6645: Enemy block sounds align with animation instead of blocked hits
Bug #6657: Distant terrain tiles become black when using FWIW mod
Bug #6661: Saved games that have no preview screenshot cause issues or crashes
@ -61,6 +64,7 @@
Bug #7034: Misc items defined in one content file are not treated as keys if another content file uses them as such
Bug #7042: Weapon follow animations that immediately follow the hit animations cause multiple hits
Bug #7044: Changing a class' services does not affect autocalculated NPCs
Bug #7053: Running into objects doesn't trigger GetCollidingPC
Bug #7054: Quests aren't sorted by name
Bug #7064: NPCs don't report crime if the player is casting offensive spells on them while sneaking
Bug #7077: OpenMW fails to load certain particle effects in .osgt format
@ -107,6 +111,7 @@
Bug #7619: Long map notes may get cut off
Bug #7630: Charm can be cast on creatures
Bug #7631: Cannot trade with/talk to Creeper or Mudcrab Merchant when they're fleeing
Bug #7633: Groundcover should ignore non-geometry Drawables
Bug #7636: Animations bug out when switching between 1st and 3rd person, while playing a scripted animation
Bug #7637: Actors can sometimes move while playing scripted animations
Bug #7639: NPCs don't use hand-to-hand if their other melee skills were damaged during combat
@ -128,18 +133,28 @@
Bug #7724: Guards don't help vs werewolves
Bug #7733: Launcher shows incorrect data paths when there's two plugins with the same name
Bug #7742: Governing attribute training limit should use the modified attribute
Bug #7753: Editor: Actors Don't Scale According to Their Race
Bug #7758: Water walking is not taken into account to compute path cost on the water
Bug #7761: Rain and ambient loop sounds are mutually exclusive
Bug #7765: OpenMW-CS: Touch Record option is broken
Bug #7769: Sword of the Perithia: Broken NPCs
Bug #7770: Sword of the Perithia: Script execution failure
Bug #7780: Non-ASCII texture paths in NIF files don't work
Bug #7785: OpenMW-CS initialising Skill and Attribute fields to 0 instead of -1 on non-FortifyStat spells
Bug #7794: Fleeing NPCs name tooltip doesn't appear
Bug #7796: Absorbed enchantments don't restore magicka
Bug #7832: Ingredient tooltips show magnitude for Fortify Maximum Magicka effect
Bug #7840: First run of the launcher doesn't save viewing distance as the default value
Feature #2566: Handle NAM9 records for manual cell references
Feature #3537: Shader-based water ripples
Feature #5173: Support for NiFogProperty
Feature #5492: Let rain and snow collide with statics
Feature #5926: Refraction based on water depth
Feature #5944: Option to use camera as sound listener
Feature #6149: Dehardcode Lua API_REVISION
Feature #6152: Playing music via lua scripts
Feature #6188: Specular lighting from point light sources
Feature #6411: Support translations in openmw-launcher
Feature #6447: Add LOD support to Object Paging
Feature #6491: Add support for Qt6
Feature #6556: Lua API for sounds
@ -166,13 +181,18 @@
Feature #7546: Start the game on Fredas
Feature #7554: Controller binding for tab for menu navigation
Feature #7568: Uninterruptable scripted music
Feature #7606: Launcher: allow Shift-select in Archives tab
Feature #7608: Make the missing dependencies warning when loading a savegame more helpful
Feature #7618: Show the player character's health in the save details
Feature #7625: Add some missing console error outputs
Feature #7634: Support NiParticleBomb
Feature #7648: Lua Save game API
Feature #7652: Sort inactive post processing shaders list properly
Feature #7698: Implement sAbsorb, sDamage, sDrain, sFortify and sRestore
Feature #7709: Improve resolution selection in Launcher
Feature #7792: Support Timescale Clouds
Feature #7795: Support MaxNumberRipples INI setting
Feature #7805: Lua Menu context
Task #5896: Do not use deprecated MyGUI properties
Task #6624: Drop support for saves made prior to 0.45
Task #7113: Move from std::atoi to std::from_char

@ -22,7 +22,7 @@ declare -a CMAKE_CONF_OPTS=(
-DCMAKE_C_COMPILER_LAUNCHER=ccache
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache
-DCMAKE_INSTALL_PREFIX=install
-DBUILD_SHARED_LIBS=OFF
-DBUILD_SHARED_LIBS="${BUILD_SHARED_LIBS:-OFF}"
-DUSE_SYSTEM_TINYXML=ON
-DOPENMW_USE_SYSTEM_RECASTNAVIGATION=ON
-DOPENMW_CXX_FLAGS="-Werror -Werror=implicit-fallthrough" # flags specific to OpenMW project

@ -0,0 +1,11 @@
#!/bin/bash -ex
set -o pipefail
LUPDATE="${LUPDATE:-lupdate}"
${LUPDATE:?} apps/wizard -ts files/lang/wizard_*.ts
${LUPDATE:?} apps/launcher -ts files/lang/launcher_*.ts
${LUPDATE:?} components/contentselector components/process -ts files/lang/components_*.ts
! (git diff --name-only | grep -q "^") || (echo -e "\033[0;31mBuild a 'translations' CMake target to update Qt localization for these files:\033[0;0m"; git diff --name-only | xargs -i echo -e "\033[0;31m{}\033[0;0m"; exit -1)

@ -33,9 +33,10 @@ declare -rA GROUPED_DEPS=(
libboost-system-dev libboost-iostreams-dev
libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev
libsdl2-dev libqt5opengl5-dev libopenal-dev libunshield-dev libtinyxml-dev
libbullet-dev liblz4-dev libpng-dev libjpeg-dev libluajit-5.1-dev
librecast-dev libsqlite3-dev ca-certificates libicu-dev libyaml-cpp-dev
libsdl2-dev libqt5opengl5-dev qttools5-dev qttools5-dev-tools libopenal-dev
libunshield-dev libtinyxml-dev libbullet-dev liblz4-dev libpng-dev libjpeg-dev
libluajit-5.1-dev librecast-dev libsqlite3-dev ca-certificates libicu-dev
libyaml-cpp-dev
"
# These dependencies can alternatively be built and linked statically.
@ -99,6 +100,12 @@ declare -rA GROUPED_DEPS=(
clang-format-14
git-core
"
[openmw-qt-translations]="
qttools5-dev
qttools5-dev-tools
git-core
"
)
if [[ $# -eq 0 ]]; then

@ -19,6 +19,14 @@ if(OPENMW_GL4ES_MANUAL_INIT)
add_definitions(-DOPENMW_GL4ES_MANUAL_INIT)
endif()
if (APPLE OR WIN32)
set(DEPLOY_QT_TRANSLATIONS_DEFAULT ON)
else ()
set(DEPLOY_QT_TRANSLATIONS_DEFAULT OFF)
endif ()
option(DEPLOY_QT_TRANSLATIONS "Deploy standard Qt translations to resources folder. Needed when OpenMW applications are deployed with Qt libraries" ${DEPLOY_QT_TRANSLATIONS_DEFAULT})
# Apps and tools
option(BUILD_OPENMW "Build OpenMW" ON)
option(BUILD_LAUNCHER "Build Launcher" ON)
@ -72,7 +80,7 @@ message(STATUS "Configuring OpenMW...")
set(OPENMW_VERSION_MAJOR 0)
set(OPENMW_VERSION_MINOR 49)
set(OPENMW_VERSION_RELEASE 0)
set(OPENMW_LUA_API_REVISION 51)
set(OPENMW_LUA_API_REVISION 54)
set(OPENMW_POSTPROCESSING_API_REVISION 1)
set(OPENMW_VERSION_COMMITHASH "")
@ -224,9 +232,9 @@ find_package(LZ4 REQUIRED)
if (USE_QT)
find_package(QT REQUIRED COMPONENTS Core NAMES Qt6 Qt5)
if (QT_VERSION_MAJOR VERSION_EQUAL 5)
find_package(Qt5 5.15 COMPONENTS Core Widgets Network OpenGL REQUIRED)
find_package(Qt5 5.15 COMPONENTS Core Widgets Network OpenGL LinguistTools REQUIRED)
else()
find_package(Qt6 COMPONENTS Core Widgets Network OpenGL OpenGLWidgets REQUIRED)
find_package(Qt6 COMPONENTS Core Widgets Network OpenGL OpenGLWidgets LinguistTools REQUIRED)
endif()
message(STATUS "Using Qt${QT_VERSION}")
endif()
@ -1074,3 +1082,78 @@ if (DOXYGEN_FOUND)
WORKING_DIRECTORY ${OpenMW_BINARY_DIR}
COMMENT "Generating documentation for the github-pages at ${DOXYGEN_PAGES_OUTPUT_DIR}" VERBATIM)
endif ()
# Qt localization
if (USE_QT)
file(GLOB LAUNCHER_TS_FILES ${CMAKE_SOURCE_DIR}/files/lang/launcher_*.ts)
file(GLOB WIZARD_TS_FILES ${CMAKE_SOURCE_DIR}/files/lang/wizard_*.ts)
file(GLOB COMPONENTS_TS_FILES ${CMAKE_SOURCE_DIR}/files/lang/components_*.ts)
get_target_property(QT_LUPDATE_EXECUTABLE Qt::lupdate IMPORTED_LOCATION)
add_custom_target(translations
COMMAND ${QT_LUPDATE_EXECUTABLE} ${CMAKE_SOURCE_DIR}/components/contentselector ${CMAKE_SOURCE_DIR}/components/process -ts ${COMPONENTS_TS_FILES}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/components
VERBATIM
COMMAND_EXPAND_LISTS
COMMAND ${QT_LUPDATE_EXECUTABLE} ${CMAKE_SOURCE_DIR}/apps/wizard -ts ${WIZARD_TS_FILES}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/apps/wizard
VERBATIM
COMMAND_EXPAND_LISTS
COMMAND ${QT_LUPDATE_EXECUTABLE} ${CMAKE_SOURCE_DIR}/apps/launcher -ts ${LAUNCHER_TS_FILES}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/apps/launcher
VERBATIM
COMMAND_EXPAND_LISTS)
if (BUILD_LAUNCHER OR BUILD_WIZARD)
if (APPLE)
set(QT_OPENMW_TRANSLATIONS_PATH "${APP_BUNDLE_DIR}/Contents/Resources/resources/translations")
else ()
get_generator_is_multi_config(multi_config)
if (multi_config)
set(QT_OPENMW_TRANSLATIONS_PATH "${OpenMW_BINARY_DIR}/$<CONFIG>/resources/translations")
else ()
set(QT_OPENMW_TRANSLATIONS_PATH "${OpenMW_BINARY_DIR}/resources/translations")
endif ()
endif ()
file(GLOB TS_FILES ${COMPONENTS_TS_FILES})
if (BUILD_LAUNCHER)
set(TS_FILES ${TS_FILES} ${LAUNCHER_TS_FILES})
endif ()
if (BUILD_WIZARD)
set(TS_FILES ${TS_FILES} ${WIZARD_TS_FILES})
endif ()
qt_add_translation(QM_FILES ${TS_FILES} OPTIONS -silent)
if (DEPLOY_QT_TRANSLATIONS)
# Once we set a Qt 6.2.0 as a minimum required version, we may use "qtpaths --qt-query" instead.
get_target_property(QT_QMAKE_EXECUTABLE Qt::qmake IMPORTED_LOCATION)
execute_process(COMMAND "${QT_QMAKE_EXECUTABLE}" -query QT_INSTALL_TRANSLATIONS
OUTPUT_VARIABLE QT_TRANSLATIONS_DIR OUTPUT_STRIP_TRAILING_WHITESPACE)
foreach(QM_FILE ${QM_FILES})
get_filename_component(QM_BASENAME ${QM_FILE} NAME)
string(REGEX REPLACE "[^_]+_(.*)\\.qm" "\\1" LANG_NAME ${QM_BASENAME})
if (EXISTS "${QT_TRANSLATIONS_DIR}/qtbase_${LANG_NAME}.qm")
set(QM_FILES ${QM_FILES} "${QT_TRANSLATIONS_DIR}/qtbase_${LANG_NAME}.qm")
elseif (EXISTS "${QT_TRANSLATIONS_DIR}/qt_${LANG_NAME}.qm")
set(QM_FILES ${QM_FILES} "${QT_TRANSLATIONS_DIR}/qt_${LANG_NAME}.qm")
else ()
message(FATAL_ERROR "Qt translations for '${LANG_NAME}' locale are not found in the '${QT_TRANSLATIONS_DIR}' folder.")
endif ()
endforeach(QM_FILE)
list(REMOVE_DUPLICATES QM_FILES)
endif ()
add_custom_target(qm-files
COMMAND ${CMAKE_COMMAND} -E make_directory ${QT_OPENMW_TRANSLATIONS_PATH}
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${QM_FILES} ${QT_OPENMW_TRANSLATIONS_PATH}
DEPENDS ${QM_FILES}
COMMENT "Copy *.qm files to resources folder")
endif ()
endif()

@ -76,10 +76,6 @@ namespace
bpo::value<StringsVector>()->default_value(StringsVector(), "fallback-archive")->multitoken()->composing(),
"set fallback BSA archives (later archives have higher priority)");
addOption("resources",
bpo::value<Files::MaybeQuotedPath>()->default_value(Files::MaybeQuotedPath(), "resources"),
"set resources directory");
addOption("content", bpo::value<StringsVector>()->default_value(StringsVector(), "")->multitoken()->composing(),
"content file(s): esm/esp, or omwgame/omwaddon/omwscripts");

@ -69,7 +69,7 @@ Allowed options)");
addOption("name,n", bpo::value<std::string>(), "Show only the record with this name. Only affects dump mode.");
addOption("plain,p",
"Print contents of dialogs, books and scripts. "
"(skipped by default)"
"(skipped by default) "
"Only affects dump mode.");
addOption("quiet,q", "Suppress all record information. Useful for speed tests.");
addOption("loadcells,C", "Browse through contents of all cells.");
@ -390,7 +390,7 @@ namespace
if (!quiet && interested)
{
std::cout << "\nRecord: " << n.toStringView() << " '" << record->getId() << "'\n"
std::cout << "\nRecord: " << n.toStringView() << " " << record->getId() << "\n"
<< "Record flags: " << recordFlags(record->getFlags()) << '\n';
record->print();
}

@ -464,7 +464,8 @@ namespace EsmTool
{
std::cout << " Name: " << mData.mName << std::endl;
std::cout << " Model: " << mData.mModel << std::endl;
std::cout << " Script: " << mData.mScript << std::endl;
if (!mData.mScript.empty())
std::cout << " Script: " << mData.mScript << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
@ -516,7 +517,8 @@ namespace EsmTool
std::cout << " Name: " << mData.mName << std::endl;
std::cout << " Model: " << mData.mModel << std::endl;
std::cout << " Icon: " << mData.mIcon << std::endl;
std::cout << " Script: " << mData.mScript << std::endl;
if (!mData.mScript.empty())
std::cout << " Script: " << mData.mScript << std::endl;
std::cout << " Type: " << apparatusTypeLabel(mData.mData.mType) << " (" << mData.mData.mType << ")"
<< std::endl;
std::cout << " Weight: " << mData.mData.mWeight << std::endl;
@ -679,7 +681,8 @@ namespace EsmTool
{
std::cout << " Name: " << mData.mName << std::endl;
std::cout << " Model: " << mData.mModel << std::endl;
std::cout << " Script: " << mData.mScript << std::endl;
if (!mData.mScript.empty())
std::cout << " Script: " << mData.mScript << std::endl;
std::cout << " Flags: " << creatureFlags((int)mData.mFlags) << std::endl;
std::cout << " Blood Type: " << mData.mBloodType + 1 << std::endl;
std::cout << " Original: " << mData.mOriginal << std::endl;
@ -719,9 +722,6 @@ namespace EsmTool
std::cout << " AI Fight:" << (int)mData.mAiData.mFight << std::endl;
std::cout << " AI Flee:" << (int)mData.mAiData.mFlee << std::endl;
std::cout << " AI Alarm:" << (int)mData.mAiData.mAlarm << std::endl;
std::cout << " AI U1:" << (int)mData.mAiData.mU1 << std::endl;
std::cout << " AI U2:" << (int)mData.mAiData.mU2 << std::endl;
std::cout << " AI U3:" << (int)mData.mAiData.mU3 << std::endl;
std::cout << " AI Services:" << Misc::StringUtils::format("0x%08X", mData.mAiData.mServices) << std::endl;
for (const ESM::AIPackage& package : mData.mAiPackage.mList)
@ -747,7 +747,8 @@ namespace EsmTool
{
std::cout << " Name: " << mData.mName << std::endl;
std::cout << " Model: " << mData.mModel << std::endl;
std::cout << " Script: " << mData.mScript << std::endl;
if (!mData.mScript.empty())
std::cout << " Script: " << mData.mScript << std::endl;
std::cout << " OpenSound: " << mData.mOpenSound << std::endl;
std::cout << " CloseSound: " << mData.mCloseSound << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
@ -1111,9 +1112,6 @@ namespace EsmTool
std::cout << " AI Fight:" << (int)mData.mAiData.mFight << std::endl;
std::cout << " AI Flee:" << (int)mData.mAiData.mFlee << std::endl;
std::cout << " AI Alarm:" << (int)mData.mAiData.mAlarm << std::endl;
std::cout << " AI U1:" << (int)mData.mAiData.mU1 << std::endl;
std::cout << " AI U2:" << (int)mData.mAiData.mU2 << std::endl;
std::cout << " AI U3:" << (int)mData.mAiData.mU3 << std::endl;
std::cout << " AI Services:" << Misc::StringUtils::format("0x%08X", mData.mAiData.mServices) << std::endl;
for (const ESM::AIPackage& package : mData.mAiPackage.mList)
@ -1338,28 +1336,26 @@ namespace EsmTool
template <>
void Record<CellState>::print()
{
std::cout << " Id:" << std::endl;
std::cout << " CellId: " << mData.mCellState.mId << std::endl;
std::cout << " Index:" << std::endl;
std::cout << " WaterLevel: " << mData.mCellState.mWaterLevel << std::endl;
std::cout << " HasFogOfWar: " << mData.mCellState.mHasFogOfWar << std::endl;
std::cout << " LastRespawn:" << std::endl;
std::cout << " Cell Id: \"" << mData.mCellState.mId.toString() << "\"" << std::endl;
std::cout << " Water Level: " << mData.mCellState.mWaterLevel << std::endl;
std::cout << " Has Fog Of War: " << mData.mCellState.mHasFogOfWar << std::endl;
std::cout << " Last Respawn:" << std::endl;
std::cout << " Day:" << mData.mCellState.mLastRespawn.mDay << std::endl;
std::cout << " Hour:" << mData.mCellState.mLastRespawn.mHour << std::endl;
if (mData.mCellState.mHasFogOfWar)
{
std::cout << " NorthMarkerAngle: " << mData.mFogState.mNorthMarkerAngle << std::endl;
std::cout << " North Marker Angle: " << mData.mFogState.mNorthMarkerAngle << std::endl;
std::cout << " Bounds:" << std::endl;
std::cout << " MinX: " << mData.mFogState.mBounds.mMinX << std::endl;
std::cout << " MinY: " << mData.mFogState.mBounds.mMinY << std::endl;
std::cout << " MaxX: " << mData.mFogState.mBounds.mMaxX << std::endl;
std::cout << " MaxY: " << mData.mFogState.mBounds.mMaxY << std::endl;
std::cout << " Min X: " << mData.mFogState.mBounds.mMinX << std::endl;
std::cout << " Min Y: " << mData.mFogState.mBounds.mMinY << std::endl;
std::cout << " Max X: " << mData.mFogState.mBounds.mMaxX << std::endl;
std::cout << " Max Y: " << mData.mFogState.mBounds.mMaxY << std::endl;
for (const ESM::FogTexture& fogTexture : mData.mFogState.mFogTextures)
{
std::cout << " FogTexture:" << std::endl;
std::cout << " Fog Texture:" << std::endl;
std::cout << " X: " << fogTexture.mX << std::endl;
std::cout << " Y: " << fogTexture.mY << std::endl;
std::cout << " ImageData: (" << fogTexture.mImageData.size() << ")" << std::endl;
std::cout << " Image Data: (" << fogTexture.mImageData.size() << ")" << std::endl;
}
}
}
@ -1367,7 +1363,7 @@ namespace EsmTool
template <>
std::string Record<ESM::Cell>::getId() const
{
return mData.mName;
return std::string(); // No ID for Cell record
}
template <>
@ -1397,9 +1393,7 @@ namespace EsmTool
template <>
std::string Record<CellState>::getId() const
{
std::ostringstream stream;
stream << mData.mCellState.mId;
return stream.str();
return std::string(); // No ID for CellState record
}
} // end namespace

@ -71,6 +71,8 @@ openmw_add_executable(openmw-launcher
${UI_HDRS}
)
add_dependencies(openmw-launcher qm-files)
if (WIN32)
INSTALL(TARGETS openmw-launcher RUNTIME DESTINATION ".")
endif (WIN32)

@ -3,7 +3,9 @@
#include <QDebug>
#include <QFileDialog>
#include <QList>
#include <QMessageBox>
#include <QPair>
#include <QPushButton>
#include <algorithm>
@ -162,8 +164,8 @@ Launcher::DataFilesPage::DataFilesPage(const Files::ConfigurationManager& cfg, C
connect(ui.directoryUpButton, &QPushButton::released, this, [this]() { this->moveDirectory(-1); });
connect(ui.directoryDownButton, &QPushButton::released, this, [this]() { this->moveDirectory(1); });
connect(ui.directoryRemoveButton, &QPushButton::released, this, [this]() { this->removeDirectory(); });
connect(ui.archiveUpButton, &QPushButton::released, this, [this]() { this->moveArchive(-1); });
connect(ui.archiveDownButton, &QPushButton::released, this, [this]() { this->moveArchive(1); });
connect(ui.archiveUpButton, &QPushButton::released, this, [this]() { this->moveArchives(-1); });
connect(ui.archiveDownButton, &QPushButton::released, this, [this]() { this->moveArchives(1); });
connect(
ui.directoryListWidget->model(), &QAbstractItemModel::rowsMoved, this, [this]() { this->sortDirectories(); });
@ -218,6 +220,18 @@ void Launcher::DataFilesPage::buildView()
&DataFilesPage::readNavMeshToolStderr);
connect(mNavMeshToolInvoker->getProcess(), qOverload<int, QProcess::ExitStatus>(&QProcess::finished), this,
&DataFilesPage::navMeshToolFinished);
buildArchiveContextMenu();
}
void Launcher::DataFilesPage::buildArchiveContextMenu()
{
connect(ui.archiveListWidget, &QListWidget::customContextMenuRequested, this,
&DataFilesPage::slotShowArchiveContextMenu);
mArchiveContextMenu = new QMenu(ui.archiveListWidget);
mArchiveContextMenu->addAction(tr("&Check Selected"), this, SLOT(slotCheckMultiSelectedItems()));
mArchiveContextMenu->addAction(tr("&Uncheck Selected"), this, SLOT(slotUncheckMultiSelectedItems()));
}
bool Launcher::DataFilesPage::loadSettings()
@ -294,7 +308,7 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
// Display new content with custom formatting
if (mNewDataDirs.contains(canonicalDirPath))
{
tooltip += "Will be added to the current profile\n";
tooltip += tr("Will be added to the current profile");
QFont font = item->font();
font.setBold(true);
font.setItalic(true);
@ -312,7 +326,10 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
if (mSelector->containsDataFiles(currentDir))
{
item->setIcon(QIcon(":/images/openmw-plugin.png"));
tooltip += "Contains content file(s)";
if (!tooltip.isEmpty())
tooltip += "\n";
tooltip += tr("Contains content file(s)");
}
else
{
@ -707,17 +724,71 @@ void Launcher::DataFilesPage::removeDirectory()
refreshDataFilesView();
}
void Launcher::DataFilesPage::moveArchive(int step)
void Launcher::DataFilesPage::slotShowArchiveContextMenu(const QPoint& pos)
{
QPoint globalPos = ui.archiveListWidget->viewport()->mapToGlobal(pos);
mArchiveContextMenu->exec(globalPos);
}
void Launcher::DataFilesPage::setCheckStateForMultiSelectedItems(bool checked)
{
Qt::CheckState checkState = checked ? Qt::Checked : Qt::Unchecked;
for (QListWidgetItem* selectedItem : ui.archiveListWidget->selectedItems())
{
selectedItem->setCheckState(checkState);
}
}
void Launcher::DataFilesPage::slotUncheckMultiSelectedItems()
{
int selectedRow = ui.archiveListWidget->currentRow();
setCheckStateForMultiSelectedItems(false);
}
void Launcher::DataFilesPage::slotCheckMultiSelectedItems()
{
setCheckStateForMultiSelectedItems(true);
}
void Launcher::DataFilesPage::moveArchives(int step)
{
QList<QListWidgetItem*> selectedItems = ui.archiveListWidget->selectedItems();
QList<QPair<int, QListWidgetItem*>> sortedItems;
for (QListWidgetItem* selectedItem : selectedItems)
{
int selectedRow = ui.archiveListWidget->row(selectedItem);
sortedItems.append(qMakePair(selectedRow, selectedItem));
}
if (step > 0)
{
std::sort(sortedItems.begin(), sortedItems.end(), [](auto a, auto b) { return a.first > b.first; });
}
else
{
std::sort(sortedItems.begin(), sortedItems.end(), [](auto a, auto b) { return a.first < b.first; });
}
for (auto i : sortedItems)
{
if (!moveArchive(i.second, step))
break;
}
}
bool Launcher::DataFilesPage::moveArchive(QListWidgetItem* listItem, int step)
{
int selectedRow = ui.archiveListWidget->row(listItem);
int newRow = selectedRow + step;
if (selectedRow == -1 || newRow < 0 || newRow > ui.archiveListWidget->count() - 1)
return;
return false;
const auto* item = ui.archiveListWidget->takeItem(selectedRow);
const QListWidgetItem* item = ui.archiveListWidget->takeItem(selectedRow);
addArchive(item->text(), item->checkState(), newRow);
ui.archiveListWidget->setCurrentRow(newRow);
return true;
}
void Launcher::DataFilesPage::addArchive(const QString& name, Qt::CheckState selected, int row)

@ -6,6 +6,7 @@
#include <components/process/processinvoker.hpp>
#include <QDir>
#include <QMenu>
#include <QStringList>
#include <QWidget>
@ -39,6 +40,7 @@ namespace Launcher
ContentSelectorView::ContentSelector* mSelector;
Ui::DataFilesPage ui;
QMenu* mArchiveContextMenu;
public:
explicit DataFilesPage(const Files::ConfigurationManager& cfg, Config::GameSettings& gameSettings,
@ -72,9 +74,13 @@ namespace Launcher
void addSubdirectories(bool append);
void sortDirectories();
void removeDirectory();
void moveArchive(int step);
void moveArchives(int step);
void moveDirectory(int step);
void slotShowArchiveContextMenu(const QPoint& pos);
void slotCheckMultiSelectedItems();
void slotUncheckMultiSelectedItems();
void on_newProfileAction_triggered();
void on_cloneProfileAction_triggered();
void on_deleteProfileAction_triggered();
@ -120,7 +126,10 @@ namespace Launcher
void addArchive(const QString& name, Qt::CheckState selected, int row = -1);
void addArchivesFromDir(const QString& dir);
bool moveArchive(QListWidgetItem* listItem, int step);
void buildView();
void buildArchiveContextMenu();
void setCheckStateForMultiSelectedItems(bool checked);
void setProfile(int index, bool savePrevious);
void setProfile(const QString& previous, const QString& current, bool savePrevious);
void removeProfile(const QString& profile);

@ -1,13 +1,14 @@
#include <iostream>
#include <QDir>
#include <QTranslator>
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/variables_map.hpp>
#include <components/debug/debugging.hpp>
#include <components/files/configurationmanager.hpp>
#include <components/files/qtconversion.hpp>
#include <components/l10n/qttranslations.hpp>
#include <components/platform/platform.hpp>
#ifdef MAC_OS_X_VERSION_MIN_REQUIRED
@ -34,12 +35,13 @@ int runLauncher(int argc, char* argv[])
{
QApplication app(argc, argv);
// Internationalization
QString locale = QLocale::system().name().section('_', 0, 0);
QString resourcesPath(".");
if (!variables["resources"].empty())
{
resourcesPath = Files::pathToQString(variables["resources"].as<Files::MaybeQuotedPath>().u8string());
}
QTranslator appTranslator;
appTranslator.load(":/translations/" + locale + ".qm");
app.installTranslator(&appTranslator);
l10n::installQtTranslations(app, "launcher", resourcesPath);
Launcher::MainDialog mainWin(configurationManager);

@ -294,6 +294,7 @@ bool Launcher::SettingsPage::loadSettings()
hrtfProfileSelectorComboBox->setCurrentIndex(hrtfProfileIndex);
}
}
loadSettingBool(Settings::sound().mCameraListener, *cameraListenerCheckBox);
}
// Interface Changes
@ -490,6 +491,9 @@ void Launcher::SettingsPage::saveSettings()
Settings::sound().mHrtf.set(hrtfProfileSelectorComboBox->currentText().toStdString());
else
Settings::sound().mHrtf.set({});
const bool cCameraListener = cameraListenerCheckBox->checkState() != Qt::Unchecked;
Settings::sound().mCameraListener.set(cCameraListener);
}
// Interface Changes

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>573</width>
<height>384</height>
<height>557</height>
</rect>
</property>
<property name="contextMenuPolicy">
@ -29,6 +29,12 @@
</item>
<item row="1" column="0">
<widget class="QLabel" name="dataNoteLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;note: content files that are not part of current Content List are &lt;span style=&quot; font-style:italic;font-weight: bold&quot;&gt;highlighted&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
@ -41,14 +47,111 @@
<string>Data Directories</string>
</attribute>
<layout class="QGridLayout" name="dirTabLayout">
<item row="0" column="0" rowspan="26">
<item row="0" column="0">
<widget class="QListWidget" name="directoryListWidget">
<property name="dragDropMode">
<enum>QAbstractItemView::InternalMove</enum>
</property>
</widget>
</item>
<item row="32" column="0" colspan="2">
<item row="0" column="1">
<layout class="QVBoxLayout" name="directoryButtons">
<item>
<widget class="QPushButton" name="directoryAddSubdirsButton">
<property name="baseSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="toolTip">
<string>Scan directories for likely data directories and append them at the end of the list.</string>
</property>
<property name="text">
<string>Append</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="directoryInsertButton">
<property name="baseSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="toolTip">
<string>Scan directories for likely data directories and insert them above the selected position</string>
</property>
<property name="text">
<string>Insert Above</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="directoryUpButton">
<property name="baseSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="toolTip">
<string>Move selected directory one position up</string>
</property>
<property name="text">
<string>Move Up</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="directoryDownButton">
<property name="baseSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="toolTip">
<string>Move selected directory one position down</string>
</property>
<property name="text">
<string>Move Down</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="directoryRemoveButton">
<property name="baseSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="toolTip">
<string>Remove selected directory</string>
</property>
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
<item>
<spacer name="directoryButtonsSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="1" column="0" colspan="2">
<widget class="QLabel" name="directoryNoteLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
@ -61,116 +164,6 @@
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QPushButton" name="directoryAddSubdirsButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="toolTip">
<string>Scan directories for likely data directories and append them at the end of the list.</string>
</property>
<property name="text">
<string>Append</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QPushButton" name="directoryInsertButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="toolTip">
<string>Scan directories for likely data directories and insert them above the selected position</string>
</property>
<property name="text">
<string>Insert Above</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QPushButton" name="directoryUpButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="toolTip">
<string>Move selected directory one position up</string>
</property>
<property name="text">
<string>Move Up</string>
</property>
</widget>
</item>
<item row="12" column="1">
<widget class="QPushButton" name="directoryDownButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="toolTip">
<string>Move selected directory one position down</string>
</property>
<property name="text">
<string>Move Down</string>
</property>
</widget>
</item>
<item row="16" column="1">
<widget class="QPushButton" name="directoryRemoveButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="toolTip">
<string>Remove selected directory</string>
</property>
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="archiveTab">
@ -178,64 +171,90 @@
<string>Archive Files</string>
</attribute>
<layout class="QGridLayout" name="archiveTabLayout">
<item row="0" column="0" rowspan="26">
<item row="0" column="0">
<widget class="QListWidget" name="archiveListWidget">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::InternalMove</enum>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QPushButton" name="archiveUpButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>33</verstretch>
</sizepolicy>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>33</height>
</size>
<property name="defaultDropAction">
<enum>Qt::CopyAction</enum>
</property>
<property name="toolTip">
<string>Move selected archive one position up</string>
</property>
<property name="text">
<string>Move Up</string>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
</widget>
</item>
<item row="27" column="0" colspan="2">
<item row="0" column="1">
<layout class="QVBoxLayout" name="archiveButtons">
<item>
<widget class="QPushButton" name="archiveUpButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>33</verstretch>
</sizepolicy>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="toolTip">
<string>Move selected archive one position up</string>
</property>
<property name="text">
<string>Move Up</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="archiveDownButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>33</verstretch>
</sizepolicy>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="toolTip">
<string>Move selected archive one position down</string>
</property>
<property name="text">
<string>Move Down</string>
</property>
</widget>
</item>
<item>
<spacer name="archiveButtonsSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="1" column="0" colspan="2">
<widget class="QLabel" name="archiveNoteLabel">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;note: archives that are not part of current Content List are &lt;span style=&quot; font-style:italic;font-weight: bold&quot;&gt;highlighted&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="archiveDownButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>33</verstretch>
</sizepolicy>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="toolTip">
<string>Move selected archive one position down</string>
</property>
<property name="text">
<string>Move Down</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="navigationMeshCacheTab">

@ -6,13 +6,13 @@
<rect>
<x>0</x>
<y>0</y>
<width>720</width>
<width>750</width>
<height>635</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>720</width>
<width>750</width>
<height>635</height>
</size>
</property>
@ -148,7 +148,7 @@ QToolButton {
<string>Display</string>
</property>
<property name="toolTip">
<string>Allows to change graphics settings</string>
<string>Allows to change display settings</string>
</property>
</action>
<action name="settingsAction">

@ -36,7 +36,7 @@
<item row="6" column="0">
<widget class="QCheckBox" name="allowNPCToFollowOverWaterSurfaceCheckBox">
<property name="toolTip">
<string>Give actors an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled.</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Give actors an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Always allow actors to follow over water</string>
@ -206,7 +206,7 @@
<item row="5" column="1">
<widget class="QCheckBox" name="classicReflectedAbsorbSpellsCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Effects of reflected Absorb spells are not mirrored -- like in Morrowind.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Effects of reflected Absorb spells are not mirrored - like in Morrowind.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Classic reflected Absorb spells behavior</string>
@ -524,7 +524,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. Can negatively impact performance.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Use anti-alias alpha testing</string>
<string>Use anti-aliased alpha testing</string>
</property>
</widget>
</item>
@ -663,6 +663,9 @@
</item>
<item row="2" column="1">
<widget class="QDoubleSpinBox" name="viewingDistanceComboBox">
<property name="decimals">
<number>3</number>
</property>
<property name="suffix">
<string> cells</string>
</property>
@ -670,7 +673,7 @@
<double>0.000000000000000</double>
</property>
<property name="singleStep">
<double>0.500000000000000</double>
<double>0.125000000000000</double>
</property>
</widget>
</item>
@ -1320,6 +1323,16 @@
</item>
</layout>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="cameraListenerCheckBox">
<property name="toolTip">
<string>In third-person view, use the camera as the sound listener instead of the player character.</string>
</property>
<property name="text">
<string>Use the camera as the sound listener</string>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation">

@ -584,7 +584,7 @@ void MwIniImporter::importGameFiles(
reader.close();
}
auto sortedFiles = dependencySort(unsortedFiles);
auto sortedFiles = dependencySort(std::move(unsortedFiles));
// hard-coded dependency Morrowind - Tribunal - Bloodmoon
if (findString(sortedFiles, "Morrowind.esm") != sortedFiles.end())

@ -88,10 +88,6 @@ namespace NavMeshTool
->composing(),
"set fallback BSA archives (later archives have higher priority)");
addOption("resources",
bpo::value<Files::MaybeQuotedPath>()->default_value(Files::MaybeQuotedPath(), "resources"),
"set resources directory");
addOption("content",
bpo::value<StringsVector>()->default_value(StringsVector(), "")->multitoken()->composing(),
"content file(s): esm/esp, or omwgame/omwaddon/omwscripts");

@ -116,9 +116,9 @@ void readVFS(std::unique_ptr<VFS::Archive>&& archive, const std::filesystem::pat
for (const auto& name : vfs.getRecursiveDirectoryIterator(""))
{
if (isNIF(name))
if (isNIF(name.value()))
{
readNIF(archivePath, name, &vfs, quiet);
readNIF(archivePath, name.value(), &vfs, quiet);
}
}

@ -172,7 +172,7 @@ else()
set (OPENCS_OPENMW_CFG "")
endif(APPLE)
add_library(openmw-cs-lib
add_library(openmw-cs-lib STATIC
${OPENCS_SRC}
${OPENCS_UI_HDR}
${OPENCS_MOC_SRC}

@ -119,8 +119,6 @@ boost::program_options::variables_map CS::Editor::readConfiguration()
boost::program_options::value<Files::MaybeQuotedPathContainer::value_type>()->default_value(
Files::MaybeQuotedPathContainer::value_type(), ""));
addOption("encoding", boost::program_options::value<std::string>()->default_value("win1252"));
addOption("resources",
boost::program_options::value<Files::MaybeQuotedPath>()->default_value(Files::MaybeQuotedPath(), "resources"));
addOption("fallback-archive",
boost::program_options::value<std::vector<std::string>>()
->default_value(std::vector<std::string>(), "fallback-archive")

@ -691,15 +691,6 @@ void CSMTools::ReferenceableCheckStage::npcCheck(
return;
}
}
else if (npc.mNpdt.mHealth != 0)
{
for (size_t i = 0; i < npc.mNpdt.mAttributes.size(); ++i)
{
if (npc.mNpdt.mAttributes[i] == 0)
messages.add(id, ESM::Attribute::indexToRefId(i).getRefIdString() + " is equal to zero", {},
CSMDoc::Message::Severity_Warning);
}
}
if (level <= 0)
messages.add(id, "Level is non-positive", "", CSMDoc::Message::Severity_Warning);

@ -18,16 +18,18 @@
#include <apps/opencs/model/world/universalid.hpp>
#include <components/esm3/cellref.hpp>
#include <components/esm3/loadbody.hpp>
#include <components/esm3/loadfact.hpp>
CSMTools::ReferenceCheckStage::ReferenceCheckStage(const CSMWorld::RefCollection& references,
const CSMWorld::RefIdCollection& referencables, const CSMWorld::IdCollection<CSMWorld::Cell>& cells,
const CSMWorld::IdCollection<ESM::Faction>& factions)
const CSMWorld::IdCollection<ESM::Faction>& factions, const CSMWorld::IdCollection<ESM::BodyPart>& bodyparts)
: mReferences(references)
, mObjects(referencables)
, mDataSet(referencables.getDataSet())
, mCells(cells)
, mFactions(factions)
, mBodyParts(bodyparts)
{
mIgnoreBaseRecords = false;
}
@ -49,9 +51,11 @@ void CSMTools::ReferenceCheckStage::perform(int stage, CSMDoc::Messages& message
else
{
// Check for non existing referenced object
if (mObjects.searchId(cellRef.mRefID) == -1)
if (mObjects.searchId(cellRef.mRefID) == -1 && mBodyParts.searchId(cellRef.mRefID) == -1)
{
messages.add(id, "Instance of a non-existent object '" + cellRef.mRefID.getRefIdString() + "'", "",
CSMDoc::Message::Severity_Error);
}
else
{
// Check if reference charge is valid for it's proper referenced type

@ -8,6 +8,7 @@
namespace ESM
{
struct BodyPart;
struct Faction;
}
@ -29,7 +30,8 @@ namespace CSMTools
{
public:
ReferenceCheckStage(const CSMWorld::RefCollection& references, const CSMWorld::RefIdCollection& referencables,
const CSMWorld::IdCollection<CSMWorld::Cell>& cells, const CSMWorld::IdCollection<ESM::Faction>& factions);
const CSMWorld::IdCollection<CSMWorld::Cell>& cells, const CSMWorld::IdCollection<ESM::Faction>& factions,
const CSMWorld::IdCollection<ESM::BodyPart>& bodyparts);
void perform(int stage, CSMDoc::Messages& messages) override;
int setup() override;
@ -40,6 +42,7 @@ namespace CSMTools
const CSMWorld::RefIdData& mDataSet;
const CSMWorld::IdCollection<CSMWorld::Cell>& mCells;
const CSMWorld::IdCollection<ESM::Faction>& mFactions;
const CSMWorld::IdCollection<ESM::BodyPart>& mBodyParts;
bool mIgnoreBaseRecords;
};
}

@ -105,8 +105,8 @@ CSMDoc::OperationHolder* CSMTools::Tools::getVerifier()
mData.getFactions(), mData.getScripts(), mData.getResources(CSMWorld::UniversalId::Type_Meshes),
mData.getResources(CSMWorld::UniversalId::Type_Icons), mData.getBodyParts()));
mVerifierOperation->appendStage(new ReferenceCheckStage(
mData.getReferences(), mData.getReferenceables(), mData.getCells(), mData.getFactions()));
mVerifierOperation->appendStage(new ReferenceCheckStage(mData.getReferences(), mData.getReferenceables(),
mData.getCells(), mData.getFactions(), mData.getBodyParts()));
mVerifierOperation->appendStage(new ScriptCheckStage(mDocument));

@ -67,6 +67,11 @@ namespace CSMWorld
return mMaleParts[ESM::getMeshPart(index)];
}
const osg::Vec2f& ActorAdapter::RaceData::getGenderWeightHeight(bool isFemale)
{
return isFemale ? mWeightsHeights.mFemaleWeightHeight : mWeightsHeights.mMaleWeightHeight;
}
bool ActorAdapter::RaceData::hasDependency(const ESM::RefId& id) const
{
return mDependencies.find(id) != mDependencies.end();
@ -90,10 +95,11 @@ namespace CSMWorld
mDependencies.emplace(id);
}
void ActorAdapter::RaceData::reset_data(const ESM::RefId& id, bool isBeast)
void ActorAdapter::RaceData::reset_data(const ESM::RefId& id, const WeightsHeights& raceStats, bool isBeast)
{
mId = id;
mIsBeast = isBeast;
mWeightsHeights = raceStats;
for (auto& str : mFemaleParts)
str = ESM::RefId();
for (auto& str : mMaleParts)
@ -163,6 +169,11 @@ namespace CSMWorld
return it->second.first;
}
const osg::Vec2f& ActorAdapter::ActorData::getRaceWeightHeight() const
{
return mRaceData->getGenderWeightHeight(isFemale());
}
bool ActorAdapter::ActorData::hasDependency(const ESM::RefId& id) const
{
return mDependencies.find(id) != mDependencies.end();
@ -504,7 +515,11 @@ namespace CSMWorld
}
auto& race = raceRecord.get();
data->reset_data(id, race.mData.mFlags & ESM::Race::Beast);
WeightsHeights scaleStats = { osg::Vec2f(race.mData.mMaleWeight, race.mData.mMaleHeight),
osg::Vec2f(race.mData.mFemaleWeight, race.mData.mFemaleHeight) };
data->reset_data(id, scaleStats, race.mData.mFlags & ESM::Race::Beast);
// Setup body parts
for (int i = 0; i < mBodyParts.getSize(); ++i)

@ -41,6 +41,12 @@ namespace CSMWorld
/// Tracks unique strings
using RefIdSet = std::unordered_set<ESM::RefId>;
struct WeightsHeights
{
osg::Vec2f mMaleWeightHeight;
osg::Vec2f mFemaleWeightHeight;
};
/// Contains base race data shared between actors
class RaceData
{
@ -57,6 +63,8 @@ namespace CSMWorld
const ESM::RefId& getFemalePart(ESM::PartReferenceType index) const;
/// Retrieves the associated body part
const ESM::RefId& getMalePart(ESM::PartReferenceType index) const;
const osg::Vec2f& getGenderWeightHeight(bool isFemale);
/// Checks if the race has a data dependency
bool hasDependency(const ESM::RefId& id) const;
@ -67,7 +75,8 @@ namespace CSMWorld
/// Marks an additional dependency
void addOtherDependency(const ESM::RefId& id);
/// Clears parts and dependencies
void reset_data(const ESM::RefId& raceId, bool isBeast = false);
void reset_data(const ESM::RefId& raceId,
const WeightsHeights& raceStats = { osg::Vec2f(1.f, 1.f), osg::Vec2f(1.f, 1.f) }, bool isBeast = false);
private:
bool handles(ESM::PartReferenceType type) const;
@ -75,6 +84,7 @@ namespace CSMWorld
bool mIsBeast;
RacePartList mFemaleParts;
RacePartList mMaleParts;
WeightsHeights mWeightsHeights;
RefIdSet mDependencies;
};
using RaceDataPtr = std::shared_ptr<RaceData>;
@ -96,6 +106,8 @@ namespace CSMWorld
std::string getSkeleton() const;
/// Retrieves the associated actor part
ESM::RefId getPart(ESM::PartReferenceType index) const;
const osg::Vec2f& getRaceWeightHeight() const;
/// Checks if the actor has a data dependency
bool hasDependency(const ESM::RefId& id) const;

@ -585,19 +585,22 @@ namespace CSMWorld
void set(Record<ESXRecordT>& record, const QVariant& data) override
{
ESXRecordT record2 = record.get();
float bodyAttr = std::clamp(data.toFloat(), 0.5f, 2.0f);
if (mWeight)
{
if (mMale)
record2.mData.mMaleWeight = data.toFloat();
record2.mData.mMaleWeight = bodyAttr;
else
record2.mData.mFemaleWeight = data.toFloat();
record2.mData.mFemaleWeight = bodyAttr;
}
else
{
if (mMale)
record2.mData.mMaleHeight = data.toFloat();
record2.mData.mMaleHeight = bodyAttr;
else
record2.mData.mFemaleHeight = data.toFloat();
record2.mData.mFemaleHeight = bodyAttr;
}
record.setModified(record2);
}

@ -385,6 +385,26 @@ namespace CSMWorld
case 0:
{
effect.mEffectID = static_cast<short>(value.toInt());
switch (effect.mEffectID)
{
case ESM::MagicEffect::DrainSkill:
case ESM::MagicEffect::DamageSkill:
case ESM::MagicEffect::RestoreSkill:
case ESM::MagicEffect::FortifySkill:
case ESM::MagicEffect::AbsorbSkill:
effect.mAttribute = -1;
break;
case ESM::MagicEffect::DrainAttribute:
case ESM::MagicEffect::DamageAttribute:
case ESM::MagicEffect::RestoreAttribute:
case ESM::MagicEffect::FortifyAttribute:
case ESM::MagicEffect::AbsorbAttribute:
effect.mSkill = -1;
break;
default:
effect.mSkill = -1;
effect.mAttribute = -1;
}
break;
}
case 1:

@ -30,18 +30,18 @@ void CSMWorld::Resources::recreate(const VFS::Manager* vfs, const char* const* e
for (const auto& filepath : vfs->getRecursiveDirectoryIterator(""))
{
if (filepath.size() < baseSize + 1 || filepath.substr(0, baseSize) != mBaseDirectory
|| (filepath[baseSize] != '/' && filepath[baseSize] != '\\'))
const std::string_view view = filepath.view();
if (view.size() < baseSize + 1 || !view.starts_with(mBaseDirectory) || view[baseSize] != '/')
continue;
if (extensions)
{
std::string::size_type extensionIndex = filepath.find_last_of('.');
const auto extensionIndex = view.find_last_of('.');
if (extensionIndex == std::string::npos)
if (extensionIndex == std::string_view::npos)
continue;
std::string extension = filepath.substr(extensionIndex + 1);
std::string_view extension = view.substr(extensionIndex + 1);
int i = 0;
@ -53,10 +53,9 @@ void CSMWorld::Resources::recreate(const VFS::Manager* vfs, const char* const* e
continue;
}
std::string file = filepath.substr(baseSize + 1);
std::string file(view.substr(baseSize + 1));
mFiles.push_back(file);
std::replace(file.begin(), file.end(), '\\', '/');
mIndex.insert(std::make_pair(Misc::StringUtils::lowerCase(file), static_cast<int>(mFiles.size()) - 1));
mIndex.emplace(std::move(file), static_cast<int>(mFiles.size()) - 1);
}
}

@ -7,6 +7,7 @@
#include <osg/Group>
#include <osg/MatrixTransform>
#include <osg/Node>
#include <osg/Vec3d>
#include <apps/opencs/model/world/actoradapter.hpp>
#include <apps/opencs/model/world/idcollection.hpp>
@ -29,7 +30,7 @@ namespace CSVRender
Actor::Actor(const ESM::RefId& id, CSMWorld::Data& data)
: mId(id)
, mData(data)
, mBaseNode(new osg::Group())
, mBaseNode(new osg::PositionAttitudeTransform())
, mSkeleton(nullptr)
{
mActorData = mData.getActorAdapter()->getActorData(mId);
@ -60,6 +61,10 @@ namespace CSVRender
// Attach parts to skeleton
loadBodyParts();
const osg::Vec2f& attributes = mActorData->getRaceWeightHeight();
mBaseNode->setScale(osg::Vec3d(attributes.x(), attributes.x(), attributes.y()));
}
else
{

@ -5,6 +5,7 @@
#include <string_view>
#include <osg/Group>
#include <osg/PositionAttitudeTransform>
#include <osg/ref_ptr>
#include <QObject>
@ -59,7 +60,7 @@ namespace CSVRender
CSMWorld::Data& mData;
CSMWorld::ActorAdapter::ActorDataPtr mActorData;
osg::ref_ptr<osg::Group> mBaseNode;
osg::ref_ptr<osg::PositionAttitudeTransform> mBaseNode;
SceneUtil::Skeleton* mSkeleton;
SceneUtil::NodeMapVisitor::NodeMap mNodeMap;
};

@ -61,10 +61,14 @@ add_openmw_dir (mwscript
add_openmw_dir (mwlua
luamanagerimp object objectlists userdataserializer luaevents engineevents objectvariant
context globalscripts localscripts playerscripts luabindings objectbindings cellbindings mwscriptbindings
camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings postprocessingbindings stats debugbindings itemdata
types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair types/armor types/light types/static types/clothing types/levelledlist types/terminal
worker magicbindings factionbindings classbindings animationbindings
context menuscripts globalscripts localscripts playerscripts luabindings objectbindings cellbindings
mwscriptbindings camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings
postprocessingbindings stats debugbindings corebindings worldbindings worker magicbindings factionbindings
classbindings itemdata inputprocessor animationbindings birthsignbindings
types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc
types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus
types/potion types/ingredient types/misc types/repair types/armor types/light types/static
types/clothing types/levelledlist types/terminal
)
add_openmw_dir (mwsound
@ -84,7 +88,7 @@ add_openmw_dir (mwworld
add_openmw_dir (mwphysics
physicssystem trace collisiontype actor convert object heightfield closestnotmerayresultcallback
contacttestresultcallback deepestnotmecontacttestresultcallback stepper movementsolver projectile
contacttestresultcallback stepper movementsolver projectile
actorconvexcallback raycasting mtphysics contacttestwrapper projectileconvexcallback
)

@ -14,6 +14,7 @@
#include <components/debug/gldebug.hpp>
#include <components/misc/rng.hpp>
#include <components/misc/strings/format.hpp>
#include <components/vfs/manager.hpp>
#include <components/vfs/registerarchives.hpp>
@ -109,10 +110,23 @@ namespace
profiler.removeUserStatsLine(" -Async");
}
struct ScheduleNonDialogMessageBox
struct ScreenCaptureMessageBox
{
void operator()(std::string message) const
void operator()(std::string filePath) const
{
if (filePath.empty())
{
MWBase::Environment::get().getWindowManager()->scheduleMessageBox(
"#{OMWEngine:ScreenshotFailed}", MWGui::ShowInDialogueMode_Never);
return;
}
std::string messageFormat
= MWBase::Environment::get().getL10nManager()->getMessage("OMWEngine", "ScreenshotMade");
std::string message = Misc::StringUtils::format(messageFormat, filePath);
MWBase::Environment::get().getWindowManager()->scheduleMessageBox(
std::move(message), MWGui::ShowInDialogueMode_Never);
}
@ -717,9 +731,8 @@ void OMW::Engine::prepareEngine()
mScreenCaptureOperation = new SceneUtil::AsyncScreenCaptureOperation(mWorkQueue,
new SceneUtil::WriteScreenshotToFileOperation(mCfgMgr.getScreenshotPath(),
Settings::general().mScreenshotFormat,
Settings::general().mNotifyOnSavedScreenshot
? std::function<void(std::string)>(ScheduleNonDialogMessageBox{})
: std::function<void(std::string)>(IgnoreString{})));
Settings::general().mNotifyOnSavedScreenshot ? std::function<void(std::string)>(ScreenCaptureMessageBox{})
: std::function<void(std::string)>(IgnoreString{})));
mScreenCaptureHandler = new osgViewer::ScreenCaptureHandler(mScreenCaptureOperation);
@ -890,8 +903,8 @@ void OMW::Engine::prepareEngine()
<< 100 * static_cast<double>(result.second) / result.first << "%)";
}
mLuaManager->init();
mLuaManager->loadPermanentStorage(mCfgMgr.getUserConfigPath());
mLuaManager->init();
// starts a separate lua thread if "lua num threads" > 0
mLuaWorker = std::make_unique<MWLua::Worker>(*mLuaManager, *mViewer);

@ -55,6 +55,8 @@ namespace MWBase
virtual void newGameStarted() = 0;
virtual void gameLoaded() = 0;
virtual void gameEnded() = 0;
virtual void noGame() = 0;
virtual void objectAddedToScene(const MWWorld::Ptr& ptr) = 0;
virtual void objectRemovedFromScene(const MWWorld::Ptr& ptr) = 0;
virtual void objectTeleported(const MWWorld::Ptr& ptr) = 0;
@ -64,8 +66,10 @@ namespace MWBase
virtual void animationTextKey(const MWWorld::Ptr& actor, const std::string& key) = 0;
virtual void playAnimation(const MWWorld::Ptr& object, const std::string& groupname,
const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult,
std::string_view start, std::string_view stop, float startpoint, size_t loops, bool loopfallback)
std::string_view start, std::string_view stop, float startpoint, uint32_t loops, bool loopfallback)
= 0;
virtual void skillLevelUp(const MWWorld::Ptr& actor, ESM::RefId skillId, std::string_view source) = 0;
virtual void skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale) = 0;
virtual void exteriorCreated(MWWorld::CellStore& cell) = 0;
virtual void actorDied(const MWWorld::Ptr& actor) = 0;
virtual void questUpdated(const ESM::RefId& questId, int stage) = 0;
@ -79,6 +83,12 @@ namespace MWBase
struct InputEvent
{
struct WheelChange
{
int x;
int y;
};
enum
{
KeyPressed,
@ -89,8 +99,11 @@ namespace MWBase
TouchPressed,
TouchReleased,
TouchMoved,
MouseButtonPressed,
MouseButtonReleased,
MouseWheel,
} mType;
std::variant<SDL_Keysym, int, SDLUtil::TouchEvent> mValue;
std::variant<SDL_Keysym, int, SDLUtil::TouchEvent, WheelChange> mValue;
};
virtual void inputEvent(const InputEvent& event) = 0;

@ -110,7 +110,9 @@ namespace MWBase
virtual bool awarenessCheck(const MWWorld::Ptr& ptr, const MWWorld::Ptr& observer) = 0;
/// Makes \a ptr fight \a target. Also shouts a combat taunt.
virtual void startCombat(const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) = 0;
virtual void startCombat(
const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, const std::set<MWWorld::Ptr>* targetAllies)
= 0;
/// Removes an actor and its allies from combat with the actor's targets.
virtual void stopCombat(const MWWorld::Ptr& ptr) = 0;
@ -171,7 +173,7 @@ namespace MWBase
///< Forces an object to refresh its animation state.
virtual bool playAnimationGroup(
const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number = 1, bool scripted = false)
const MWWorld::Ptr& ptr, std::string_view groupName, int mode, uint32_t number = 1, bool scripted = false)
= 0;
///< Run animation for a MW-reference. Calls to this function for references that are currently not
/// in the scene should be ignored.
@ -180,8 +182,8 @@ namespace MWBase
/// \param number How many times the animation should be run
/// \param scripted Whether the animation should be treated as a scripted animation.
/// \return Success or error
virtual bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, float speed,
std::string_view startKey, std::string_view stopKey, bool forceLoop)
virtual bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, uint32_t loops,
float speed, std::string_view startKey, std::string_view stopKey, bool forceLoop)
= 0;
///< Lua variant of playAnimationGroup. The mode parameter is omitted
/// and forced to 0. modes 1 and 2 can be emulated by doing clearAnimationQueue() and

@ -44,6 +44,9 @@ namespace MWBase
virtual void askLoadRecent() = 0;
virtual void requestNewGame() = 0;
virtual void requestLoad(const std::filesystem::path& filepath) = 0;
virtual State getState() const = 0;
virtual void newGame(bool bypass = false) = 0;

@ -166,7 +166,8 @@ namespace MWBase
virtual void setConsoleSelectedObject(const MWWorld::Ptr& object) = 0;
virtual MWWorld::Ptr getConsoleSelectedObject() const = 0;
virtual void setConsoleMode(const std::string& mode) = 0;
virtual void setConsoleMode(std::string_view mode) = 0;
virtual const std::string& getConsoleMode() = 0;
static constexpr std::string_view sConsoleColor_Default = "#FFFFFF";
static constexpr std::string_view sConsoleColor_Error = "#FF2222";

@ -304,7 +304,7 @@ namespace MWBase
virtual const MWPhysics::RayCastingInterface* getRayCasting() const = 0;
virtual bool castRenderingRay(MWPhysics::RayCastingResult& res, const osg::Vec3f& from, const osg::Vec3f& to,
bool ignorePlayer, bool ignoreActors)
bool ignorePlayer, bool ignoreActors, std::span<const MWWorld::Ptr> ignoreList = {})
= 0;
virtual void setActorCollisionMode(const MWWorld::Ptr& ptr, bool internal, bool external) = 0;

@ -62,7 +62,7 @@ namespace MWClass
physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World);
}
std::string Activator::getModel(const MWWorld::ConstPtr& ptr) const
std::string_view Activator::getModel(const MWWorld::ConstPtr& ptr) const
{
return getClassModel<ESM::Activator>(ptr);
}
@ -141,15 +141,14 @@ namespace MWClass
ESM::RefId Activator::getSoundIdFromSndGen(const MWWorld::Ptr& ptr, std::string_view name) const
{
const std::string model
= getModel(ptr); // Assume it's not empty, since we wouldn't have gotten the soundgen otherwise
// Assume it's not empty, since we wouldn't have gotten the soundgen otherwise
const std::string_view model = getModel(ptr);
const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore();
const ESM::RefId* creatureId = nullptr;
for (const ESM::Creature& iter : store.get<ESM::Creature>())
{
if (!iter.mModel.empty()
&& Misc::StringUtils::ciEqual(model, Misc::ResourceHelpers::correctMeshPath(iter.mModel)))
if (!iter.mModel.empty() && Misc::StringUtils::ciEqual(model, iter.mModel))
{
creatureId = !iter.mOriginal.empty() ? &iter.mOriginal : &iter.mId;
break;

@ -41,7 +41,7 @@ namespace MWClass
std::unique_ptr<MWWorld::Action> activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override;
///< Generate action for activation
std::string getModel(const MWWorld::ConstPtr& ptr) const override;
std::string_view getModel(const MWWorld::ConstPtr& ptr) const override;
bool useAnim() const override;
///< Whether or not to use animated variant of model (default false)

@ -35,7 +35,7 @@ namespace MWClass
}
}
std::string Apparatus::getModel(const MWWorld::ConstPtr& ptr) const
std::string_view Apparatus::getModel(const MWWorld::ConstPtr& ptr) const
{
return getClassModel<ESM::Apparatus>(ptr);
}

@ -49,7 +49,7 @@ namespace MWClass
std::unique_ptr<MWWorld::Action> use(const MWWorld::Ptr& ptr, bool force = false) const override;
///< Generate action for using via inventory menu
std::string getModel(const MWWorld::ConstPtr& ptr) const override;
std::string_view getModel(const MWWorld::ConstPtr& ptr) const override;
bool canSell(const MWWorld::ConstPtr& item, int npcServices) const override;
};

@ -44,7 +44,7 @@ namespace MWClass
}
}
std::string Armor::getModel(const MWWorld::ConstPtr& ptr) const
std::string_view Armor::getModel(const MWWorld::ConstPtr& ptr) const
{
return getClassModel<ESM::Armor>(ptr);
}

@ -74,7 +74,7 @@ namespace MWClass
std::unique_ptr<MWWorld::Action> use(const MWWorld::Ptr& ptr, bool force = false) const override;
///< Generate action for using via inventory menu
std::string getModel(const MWWorld::ConstPtr& ptr) const override;
std::string_view getModel(const MWWorld::ConstPtr& ptr) const override;
int getEnchantmentPoints(const MWWorld::ConstPtr& ptr) const override;

@ -42,7 +42,7 @@ namespace MWClass
return false;
}
std::string BodyPart::getModel(const MWWorld::ConstPtr& ptr) const
std::string_view BodyPart::getModel(const MWWorld::ConstPtr& ptr) const
{
return getClassModel<ESM::BodyPart>(ptr);
}

@ -25,7 +25,7 @@ namespace MWClass
bool hasToolTip(const MWWorld::ConstPtr& ptr) const override;
///< @return true if this object has a tooltip when focused (default implementation: true)
std::string getModel(const MWWorld::ConstPtr& ptr) const override;
std::string_view getModel(const MWWorld::ConstPtr& ptr) const override;
};
}

@ -41,7 +41,7 @@ namespace MWClass
}
}
std::string Book::getModel(const MWWorld::ConstPtr& ptr) const
std::string_view Book::getModel(const MWWorld::ConstPtr& ptr) const
{
return getClassModel<ESM::Book>(ptr);
}

@ -54,7 +54,7 @@ namespace MWClass
std::unique_ptr<MWWorld::Action> use(const MWWorld::Ptr& ptr, bool force = false) const override;
///< Generate action for using via inventory menu
std::string getModel(const MWWorld::ConstPtr& ptr) const override;
std::string_view getModel(const MWWorld::ConstPtr& ptr) const override;
int getEnchantmentPoints(const MWWorld::ConstPtr& ptr) const override;

@ -4,22 +4,16 @@
#include "../mwworld/livecellref.hpp"
#include "../mwworld/ptr.hpp"
#include <components/misc/resourcehelpers.hpp>
#include <components/resource/resourcesystem.hpp>
#include <string>
#include <string_view>
namespace MWClass
{
template <class Class>
std::string getClassModel(const MWWorld::ConstPtr& ptr)
std::string_view getClassModel(const MWWorld::ConstPtr& ptr)
{
const MWWorld::LiveCellRef<Class>* ref = ptr.get<Class>();
if (!ref->mBase->mModel.empty())
return Misc::ResourceHelpers::correctMeshPath(ref->mBase->mModel);
return {};
return ref->mBase->mModel;
}
}

@ -39,7 +39,7 @@ namespace MWClass
}
}
std::string Clothing::getModel(const MWWorld::ConstPtr& ptr) const
std::string_view Clothing::getModel(const MWWorld::ConstPtr& ptr) const
{
return getClassModel<ESM::Clothing>(ptr);
}

@ -66,7 +66,7 @@ namespace MWClass
std::unique_ptr<MWWorld::Action> use(const MWWorld::Ptr& ptr, bool force = false) const override;
///< Generate action for using via inventory menu
std::string getModel(const MWWorld::ConstPtr& ptr) const override;
std::string_view getModel(const MWWorld::ConstPtr& ptr) const override;
int getEnchantmentPoints(const MWWorld::ConstPtr& ptr) const override;

@ -126,7 +126,7 @@ namespace MWClass
physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World);
}
std::string Container::getModel(const MWWorld::ConstPtr& ptr) const
std::string_view Container::getModel(const MWWorld::ConstPtr& ptr) const
{
return getClassModel<ESM::Container>(ptr);
}

@ -85,7 +85,7 @@ namespace MWClass
void respawn(const MWWorld::Ptr& ptr) const override;
std::string getModel(const MWWorld::ConstPtr& ptr) const override;
std::string_view getModel(const MWWorld::ConstPtr& ptr) const override;
bool useAnim() const override;

@ -178,14 +178,14 @@ namespace MWClass
objects.insertCreature(ptr, model, hasInventoryStore(ptr));
}
std::string Creature::getModel(const MWWorld::ConstPtr& ptr) const
std::string_view Creature::getModel(const MWWorld::ConstPtr& ptr) const
{
return getClassModel<ESM::Creature>(ptr);
}
void Creature::getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector<std::string>& models) const
void Creature::getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector<std::string_view>& models) const
{
std::string model = getModel(ptr);
std::string_view model = getModel(ptr);
if (!model.empty())
models.push_back(model);
@ -474,20 +474,17 @@ namespace MWClass
}
const MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
const MWMechanics::AiSequence& aiSequence = stats.getAiSequence();
const bool isInCombat = aiSequence.isInCombat();
if (stats.isDead())
{
// by default user can loot friendly actors during death animation
if (Settings::game().mCanLootDuringDeathAnimation && !isInCombat)
// by default user can loot non-fighting actors during death animation
if (Settings::game().mCanLootDuringDeathAnimation)
return std::make_unique<MWWorld::ActionOpen>(ptr);
// otherwise wait until death animation
if (stats.isDeathAnimationFinished())
return std::make_unique<MWWorld::ActionOpen>(ptr);
}
else if ((!isInCombat || aiSequence.isFleeing()) && !stats.getKnockedDown())
else if (!stats.getKnockedDown())
return std::make_unique<MWWorld::ActionTalk>(ptr);
// Tribunal and some mod companions oddly enough must use open action as fallback
@ -651,13 +648,13 @@ namespace MWClass
if (sounds.empty())
{
const std::string model = getModel(ptr);
const std::string_view model = getModel(ptr);
if (!model.empty())
{
for (const ESM::Creature& creature : store.get<ESM::Creature>())
{
if (creature.mId != ourId && creature.mOriginal != ourId && !creature.mModel.empty()
&& Misc::StringUtils::ciEqual(model, Misc::ResourceHelpers::correctMeshPath(creature.mModel)))
&& Misc::StringUtils::ciEqual(model, creature.mModel))
{
const ESM::RefId& fallbackId = !creature.mOriginal.empty() ? creature.mOriginal : creature.mId;
sound = store.get<ESM::SoundGenerator>().begin();

@ -105,9 +105,9 @@ namespace MWClass
float getMaxSpeed(const MWWorld::Ptr& ptr) const override;
std::string getModel(const MWWorld::ConstPtr& ptr) const override;
std::string_view getModel(const MWWorld::ConstPtr& ptr) const override;
void getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector<std::string>& models) const override;
void getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector<std::string_view>& models) const override;
///< Get a list of models to preload that this object may use (directly or indirectly). default implementation:
///< list getModel().

@ -94,7 +94,7 @@ namespace MWClass
return true;
}
std::string Door::getModel(const MWWorld::ConstPtr& ptr) const
std::string_view Door::getModel(const MWWorld::ConstPtr& ptr) const
{
return getClassModel<ESM::Door>(ptr);
}

@ -51,7 +51,7 @@ namespace MWClass
ESM::RefId getScript(const MWWorld::ConstPtr& ptr) const override;
///< Return name of the script attached to ptr
std::string getModel(const MWWorld::ConstPtr& ptr) const override;
std::string_view getModel(const MWWorld::ConstPtr& ptr) const override;
MWWorld::DoorState getDoorState(const MWWorld::ConstPtr& ptr) const override;
/// This does not actually cause the door to move. Use World::activateDoor instead.

@ -96,14 +96,14 @@ namespace MWClass
std::string_view getName(const MWWorld::ConstPtr& ptr) const override { return {}; }
std::string getModel(const MWWorld::ConstPtr& ptr) const override
std::string_view getModel(const MWWorld::ConstPtr& ptr) const override
{
std::string model = getClassModel<Record>(ptr);
std::string_view model = getClassModel<Record>(ptr);
// Hide meshes meshes/marker/* and *LOD.nif in ESM4 cells. It is a temporarty hack.
// Needed because otherwise LOD meshes are rendered on top of normal meshes.
// TODO: Figure out a better way find markers and LOD meshes; show LOD only outside of active grid.
if (model.empty() || Misc::StringUtils::ciStartsWith(model, "meshes\\marker")
if (model.empty() || Misc::StringUtils::ciStartsWith(model, "marker")
|| Misc::StringUtils::ciEndsWith(model, "lod.nif"))
return {};

@ -175,17 +175,14 @@ namespace MWClass
return getCustomData(ptr).mIsFemale;
}
std::string ESM4Npc::getModel(const MWWorld::ConstPtr& ptr) const
std::string_view ESM4Npc::getModel(const MWWorld::ConstPtr& ptr) const
{
const ESM4NpcCustomData& data = getCustomData(ptr);
if (data.mTraits == nullptr)
return {};
std::string_view model;
if (data.mTraits->mIsTES4)
model = data.mTraits->mModel;
else
model = data.mIsFemale ? data.mRace->mModelFemale : data.mRace->mModelMale;
return Misc::ResourceHelpers::correctMeshPath(model);
return data.mTraits->mModel;
return data.mIsFemale ? data.mRace->mModelFemale : data.mRace->mModelMale;
}
std::string_view ESM4Npc::getName(const MWWorld::ConstPtr& ptr) const

@ -54,7 +54,7 @@ namespace MWClass
return ESM4Impl::getToolTipInfo(getName(ptr), count);
}
std::string getModel(const MWWorld::ConstPtr& ptr) const override;
std::string_view getModel(const MWWorld::ConstPtr& ptr) const override;
std::string_view getName(const MWWorld::ConstPtr& ptr) const override;
static const ESM4::Npc* getTraitsRecord(const MWWorld::Ptr& ptr);

@ -39,7 +39,7 @@ namespace MWClass
}
}
std::string Ingredient::getModel(const MWWorld::ConstPtr& ptr) const
std::string_view Ingredient::getModel(const MWWorld::ConstPtr& ptr) const
{
return getClassModel<ESM::Ingredient>(ptr);
}

@ -47,7 +47,7 @@ namespace MWClass
const std::string& getInventoryIcon(const MWWorld::ConstPtr& ptr) const override;
///< Return name of inventory icon.
std::string getModel(const MWWorld::ConstPtr& ptr) const override;
std::string_view getModel(const MWWorld::ConstPtr& ptr) const override;
float getWeight(const MWWorld::ConstPtr& ptr) const override;

@ -70,7 +70,7 @@ namespace MWClass
return true;
}
std::string Light::getModel(const MWWorld::ConstPtr& ptr) const
std::string_view Light::getModel(const MWWorld::ConstPtr& ptr) const
{
return getClassModel<ESM::Light>(ptr);
}

@ -69,7 +69,7 @@ namespace MWClass
float getRemainingUsageTime(const MWWorld::ConstPtr& ptr) const override;
///< Returns the remaining duration of the object.
std::string getModel(const MWWorld::ConstPtr& ptr) const override;
std::string_view getModel(const MWWorld::ConstPtr& ptr) const override;
float getWeight(const MWWorld::ConstPtr& ptr) const override;

@ -38,7 +38,7 @@ namespace MWClass
}
}
std::string Lockpick::getModel(const MWWorld::ConstPtr& ptr) const
std::string_view Lockpick::getModel(const MWWorld::ConstPtr& ptr) const
{
return getClassModel<ESM::Lockpick>(ptr);
}

@ -59,7 +59,7 @@ namespace MWClass
std::unique_ptr<MWWorld::Action> use(const MWWorld::Ptr& ptr, bool force = false) const override;
///< Generate action for using via inventory menu
std::string getModel(const MWWorld::ConstPtr& ptr) const override;
std::string_view getModel(const MWWorld::ConstPtr& ptr) const override;
bool canSell(const MWWorld::ConstPtr& item, int npcServices) const override;

@ -49,7 +49,7 @@ namespace MWClass
}
}
std::string Miscellaneous::getModel(const MWWorld::ConstPtr& ptr) const
std::string_view Miscellaneous::getModel(const MWWorld::ConstPtr& ptr) const
{
return getClassModel<ESM::Miscellaneous>(ptr);
}

@ -45,7 +45,7 @@ namespace MWClass
const std::string& getInventoryIcon(const MWWorld::ConstPtr& ptr) const override;
///< Return name of inventory icon.
std::string getModel(const MWWorld::ConstPtr& ptr) const override;
std::string_view getModel(const MWWorld::ConstPtr& ptr) const override;
std::unique_ptr<MWWorld::Action> use(const MWWorld::Ptr& ptr, bool force = false) const override;
///< Generate action for using via inventory menu

@ -19,9 +19,11 @@
#include <components/esm3/loadsoun.hpp>
#include <components/esm3/npcstate.hpp>
#include <components/settings/values.hpp>
#include <components/vfs/pathutil.hpp>
#include "../mwbase/dialoguemanager.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/luamanager.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/windowmanager.hpp"
@ -424,45 +426,51 @@ namespace MWClass
return (ref->mBase->mRecordFlags & ESM::FLAG_Persistent) != 0;
}
std::string Npc::getModel(const MWWorld::ConstPtr& ptr) const
std::string_view Npc::getModel(const MWWorld::ConstPtr& ptr) const
{
const MWWorld::LiveCellRef<ESM::NPC>* ref = ptr.get<ESM::NPC>();
std::string_view model = Settings::models().mBaseanim.get();
const ESM::Race* race = MWBase::Environment::get().getESMStore()->get<ESM::Race>().find(ref->mBase->mRace);
if (race->mData.mFlags & ESM::Race::Beast)
model = Settings::models().mBaseanimkna.get();
// Base animations should be in the meshes dir
constexpr std::string_view prefix = "meshes/";
assert(VFS::Path::pathEqual(prefix, model.substr(0, prefix.size())));
return model.substr(prefix.size());
}
std::string Npc::getCorrectedModel(const MWWorld::ConstPtr& ptr) const
{
const MWWorld::LiveCellRef<ESM::NPC>* ref = ptr.get<ESM::NPC>();
std::string model = Settings::models().mBaseanim;
const std::string& model = Settings::models().mBaseanim;
const ESM::Race* race = MWBase::Environment::get().getESMStore()->get<ESM::Race>().find(ref->mBase->mRace);
if (race->mData.mFlags & ESM::Race::Beast)
model = Settings::models().mBaseanimkna;
return Settings::models().mBaseanimkna;
return model;
}
void Npc::getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector<std::string>& models) const
void Npc::getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector<std::string_view>& models) const
{
const MWWorld::LiveCellRef<ESM::NPC>* npc = ptr.get<ESM::NPC>();
const auto& esmStore = MWBase::Environment::get().getESMStore();
const ESM::Race* race = esmStore->get<ESM::Race>().search(npc->mBase->mRace);
if (race && race->mData.mFlags & ESM::Race::Beast)
models.push_back(Settings::models().mBaseanimkna);
// keep these always loaded just in case
models.push_back(Settings::models().mXargonianswimkna);
models.push_back(Settings::models().mXbaseanimfemale);
models.push_back(Settings::models().mXbaseanim);
models.push_back(getModel(ptr));
if (!npc->mBase->mModel.empty())
models.push_back(Misc::ResourceHelpers::correctMeshPath(npc->mBase->mModel));
models.push_back(npc->mBase->mModel);
if (!npc->mBase->mHead.empty())
{
const ESM::BodyPart* head = esmStore->get<ESM::BodyPart>().search(npc->mBase->mHead);
if (head)
models.push_back(Misc::ResourceHelpers::correctMeshPath(head->mModel));
models.push_back(head->mModel);
}
if (!npc->mBase->mHair.empty())
{
const ESM::BodyPart* hair = esmStore->get<ESM::BodyPart>().search(npc->mBase->mHair);
if (hair)
models.push_back(Misc::ResourceHelpers::correctMeshPath(hair->mModel));
models.push_back(hair->mModel);
}
bool female = (npc->mBase->mFlags & ESM::NPC::Female);
@ -486,7 +494,7 @@ namespace MWClass
const ESM::BodyPart* part = esmStore->get<ESM::BodyPart>().search(partname);
if (part && !part->mModel.empty())
models.push_back(Misc::ResourceHelpers::correctMeshPath(part->mModel));
models.push_back(part->mModel);
}
};
if (equipped->getType() == ESM::Clothing::sRecordId)
@ -501,7 +509,7 @@ namespace MWClass
}
else
{
std::string model = equipped->getClass().getModel(*equipped);
std::string_view model = equipped->getClass().getModel(*equipped);
if (!model.empty())
models.push_back(model);
}
@ -510,14 +518,14 @@ namespace MWClass
}
// preload body parts
if (race)
if (const ESM::Race* race = esmStore->get<ESM::Race>().search(npc->mBase->mRace))
{
const std::vector<const ESM::BodyPart*>& parts
= MWRender::NpcAnimation::getBodyParts(race->mId, female, false, false);
for (const ESM::BodyPart* part : parts)
{
if (part && !part->mModel.empty())
models.push_back(Misc::ResourceHelpers::correctMeshPath(part->mModel));
models.push_back(part->mModel);
}
}
}
@ -655,7 +663,7 @@ namespace MWClass
ESM::RefId weapskill = ESM::Skill::HandToHand;
if (!weapon.isEmpty())
weapskill = weapon.getClass().getEquipmentSkill(weapon);
skillUsageSucceeded(ptr, weapskill, 0);
skillUsageSucceeded(ptr, weapskill, ESM::Skill::Weapon_SuccessfulHit);
const MWMechanics::AiSequence& seq = victim.getClass().getCreatureStats(victim).getAiSequence();
@ -845,7 +853,7 @@ namespace MWClass
ESM::RefId skill = armor.getClass().getEquipmentSkill(armor);
if (ptr == MWMechanics::getPlayer())
skillUsageSucceeded(ptr, skill, 0);
skillUsageSucceeded(ptr, skill, ESM::Skill::Armor_HitByOpponent);
if (skill == ESM::Skill::LightArmor)
sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Light Armor Hit"), 1.0f, 1.0f);
@ -855,7 +863,7 @@ namespace MWClass
sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Heavy Armor Hit"), 1.0f, 1.0f);
}
else if (ptr == MWMechanics::getPlayer())
skillUsageSucceeded(ptr, ESM::Skill::Unarmored, 0);
skillUsageSucceeded(ptr, ESM::Skill::Unarmored, ESM::Skill::Armor_HitByOpponent);
}
}
@ -917,35 +925,38 @@ namespace MWClass
}
const MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
const MWMechanics::AiSequence& aiSequence = stats.getAiSequence();
const bool isPursuing = aiSequence.isInPursuit() && actor == MWMechanics::getPlayer();
const bool inCombatWithActor = aiSequence.isInCombat(actor) || isPursuing;
if (stats.isDead())
{
// by default user can loot friendly actors during death animation
if (Settings::game().mCanLootDuringDeathAnimation && !stats.getAiSequence().isInCombat())
// by default user can loot non-fighting actors during death animation
if (Settings::game().mCanLootDuringDeathAnimation)
return std::make_unique<MWWorld::ActionOpen>(ptr);
// otherwise wait until death animation
if (stats.isDeathAnimationFinished())
return std::make_unique<MWWorld::ActionOpen>(ptr);
}
else if (!stats.getAiSequence().isInCombat())
else
{
if (stats.getKnockedDown() || MWBase::Environment::get().getMechanicsManager()->isSneaking(actor))
return std::make_unique<MWWorld::ActionOpen>(ptr); // stealing
const bool allowStealingFromKO
= Settings::game().mAlwaysAllowStealingFromKnockedOutActors || !inCombatWithActor;
if (stats.getKnockedDown() && allowStealingFromKO)
return std::make_unique<MWWorld::ActionOpen>(ptr);
// Can't talk to werewolves
if (!getNpcStats(ptr).isWerewolf())
const bool allowStealingWhileSneaking = !inCombatWithActor;
if (MWBase::Environment::get().getMechanicsManager()->isSneaking(actor) && allowStealingWhileSneaking)
return std::make_unique<MWWorld::ActionOpen>(ptr);
const bool allowTalking = !inCombatWithActor && !getNpcStats(ptr).isWerewolf();
if (allowTalking)
return std::make_unique<MWWorld::ActionTalk>(ptr);
}
else // In combat
{
if (Settings::game().mAlwaysAllowStealingFromKnockedOutActors && stats.getKnockedDown())
return std::make_unique<MWWorld::ActionOpen>(ptr); // stealing
}
// Tribunal and some mod companions oddly enough must use open action as fallback
if (!getScript(ptr).empty() && ptr.getRefData().getLocals().getIntVar(getScript(ptr), "companion"))
return std::make_unique<MWWorld::ActionOpen>(ptr);
if (inCombatWithActor)
return std::make_unique<MWWorld::FailedAction>("#{sActorInCombat}");
return std::make_unique<MWWorld::FailedAction>();
}
@ -1079,7 +1090,8 @@ namespace MWClass
if (customData.mNpcStats.isDead() && customData.mNpcStats.isDeathAnimationFinished())
return true;
if (!customData.mNpcStats.getAiSequence().isInCombat())
const MWMechanics::AiSequence& aiSeq = customData.mNpcStats.getAiSequence();
if (!aiSeq.isInCombat() || aiSeq.isFleeing())
return true;
if (Settings::game().mAlwaysAllowStealingFromKnockedOutActors && customData.mNpcStats.getKnockedDown())
@ -1131,16 +1143,7 @@ namespace MWClass
void Npc::skillUsageSucceeded(const MWWorld::Ptr& ptr, ESM::RefId skill, int usageType, float extraFactor) const
{
MWMechanics::NpcStats& stats = getNpcStats(ptr);
if (stats.isWerewolf())
return;
MWWorld::LiveCellRef<ESM::NPC>* ref = ptr.get<ESM::NPC>();
const ESM::Class* class_ = MWBase::Environment::get().getESMStore()->get<ESM::Class>().find(ref->mBase->mClass);
stats.useSkill(skill, *class_, usageType, extraFactor);
MWBase::Environment::get().getLuaManager()->skillUse(ptr, skill, usageType, extraFactor);
}
float Npc::getArmorRating(const MWWorld::Ptr& ptr) const

@ -85,7 +85,7 @@ namespace MWClass
const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful,
const MWMechanics::DamageSourceType sourceType) const override;
void getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector<std::string>& models) const override;
void getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector<std::string_view>& models) const override;
///< Get a list of models to preload that this object may use (directly or indirectly). default implementation:
///< list getModel().
@ -131,7 +131,9 @@ namespace MWClass
ESM::RefId getSoundIdFromSndGen(const MWWorld::Ptr& ptr, std::string_view name) const override;
std::string getModel(const MWWorld::ConstPtr& ptr) const override;
std::string_view getModel(const MWWorld::ConstPtr& ptr) const override;
std::string getCorrectedModel(const MWWorld::ConstPtr& ptr) const override;
float getSkill(const MWWorld::Ptr& ptr, ESM::RefId id) const override;

@ -38,7 +38,7 @@ namespace MWClass
}
}
std::string Potion::getModel(const MWWorld::ConstPtr& ptr) const
std::string_view Potion::getModel(const MWWorld::ConstPtr& ptr) const
{
return getClassModel<ESM::Potion>(ptr);
}

@ -47,7 +47,7 @@ namespace MWClass
const std::string& getInventoryIcon(const MWWorld::ConstPtr& ptr) const override;
///< Return name of inventory icon.
std::string getModel(const MWWorld::ConstPtr& ptr) const override;
std::string_view getModel(const MWWorld::ConstPtr& ptr) const override;
float getWeight(const MWWorld::ConstPtr& ptr) const override;

@ -38,7 +38,7 @@ namespace MWClass
}
}
std::string Probe::getModel(const MWWorld::ConstPtr& ptr) const
std::string_view Probe::getModel(const MWWorld::ConstPtr& ptr) const
{
return getClassModel<ESM::Probe>(ptr);
}

@ -54,7 +54,7 @@ namespace MWClass
std::unique_ptr<MWWorld::Action> use(const MWWorld::Ptr& ptr, bool force = false) const override;
///< Generate action for using via inventory menu
std::string getModel(const MWWorld::ConstPtr& ptr) const override;
std::string_view getModel(const MWWorld::ConstPtr& ptr) const override;
bool canSell(const MWWorld::ConstPtr& item, int npcServices) const override;

@ -36,7 +36,7 @@ namespace MWClass
}
}
std::string Repair::getModel(const MWWorld::ConstPtr& ptr) const
std::string_view Repair::getModel(const MWWorld::ConstPtr& ptr) const
{
return getClassModel<ESM::Repair>(ptr);
}

@ -44,7 +44,7 @@ namespace MWClass
const std::string& getInventoryIcon(const MWWorld::ConstPtr& ptr) const override;
///< Return name of inventory icon.
std::string getModel(const MWWorld::ConstPtr& ptr) const override;
std::string_view getModel(const MWWorld::ConstPtr& ptr) const override;
std::unique_ptr<MWWorld::Action> use(const MWWorld::Ptr& ptr, bool force = false) const override;
///< Generate action for using via inventory menu (default implementation: return a

@ -43,7 +43,7 @@ namespace MWClass
physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World);
}
std::string Static::getModel(const MWWorld::ConstPtr& ptr) const
std::string_view Static::getModel(const MWWorld::ConstPtr& ptr) const
{
return getClassModel<ESM::Static>(ptr);
}

@ -32,7 +32,7 @@ namespace MWClass
bool hasToolTip(const MWWorld::ConstPtr& ptr) const override;
///< @return true if this object has a tooltip when focused (default implementation: true)
std::string getModel(const MWWorld::ConstPtr& ptr) const override;
std::string_view getModel(const MWWorld::ConstPtr& ptr) const override;
};
}

@ -43,7 +43,7 @@ namespace MWClass
}
}
std::string Weapon::getModel(const MWWorld::ConstPtr& ptr) const
std::string_view Weapon::getModel(const MWWorld::ConstPtr& ptr) const
{
return getClassModel<ESM::Weapon>(ptr);
}

@ -72,7 +72,7 @@ namespace MWClass
std::unique_ptr<MWWorld::Action> use(const MWWorld::Ptr& ptr, bool force = false) const override;
///< Generate action for using via inventory menu
std::string getModel(const MWWorld::ConstPtr& ptr) const override;
std::string_view getModel(const MWWorld::ConstPtr& ptr) const override;
bool canSell(const MWWorld::ConstPtr& item, int npcServices) const override;

@ -543,7 +543,8 @@ namespace MWDialogue
mPermanentDispositionChange += perm;
MWWorld::Ptr player = MWMechanics::getPlayer();
player.getClass().skillUsageSucceeded(player, ESM::Skill::Speechcraft, success ? 0 : 1);
player.getClass().skillUsageSucceeded(
player, ESM::Skill::Speechcraft, success ? ESM::Skill::Speechcraft_Success : ESM::Skill::Speechcraft_Fail);
if (success)
{

@ -375,12 +375,18 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons
return mChoice;
case SelectWrapper::Function_AiSetting:
{
int argument = select.getArgument();
if (argument < 0 || argument > 3)
{
throw std::runtime_error("AiSetting index is out of range");
}
return mActor.getClass()
.getCreatureStats(mActor)
.getAiSetting((MWMechanics::AiSetting)select.getArgument())
.getAiSetting(static_cast<MWMechanics::AiSetting>(argument))
.getModified(false);
}
case SelectWrapper::Function_PcAttribute:
{
ESM::RefId attribute = ESM::Attribute::indexToRefId(select.getArgument());

@ -126,7 +126,7 @@ namespace
MWGui::BookPage::ClickCallback callback = [this](intptr_t linkId) { notifyTopicClicked(linkId); };
getPage(LeftBookPage)->adviseLinkClicked(callback);
getPage(RightBookPage)->adviseLinkClicked(callback);
getPage(RightBookPage)->adviseLinkClicked(std::move(callback));
getPage(LeftBookPage)->eventMouseWheel
+= MyGUI::newDelegate(this, &JournalWindowImpl::notifyMouseWheel);
@ -140,7 +140,7 @@ namespace
getPage(LeftTopicIndex)->adviseLinkClicked(callback);
getPage(CenterTopicIndex)->adviseLinkClicked(callback);
getPage(RightTopicIndex)->adviseLinkClicked(callback);
getPage(RightTopicIndex)->adviseLinkClicked(std::move(callback));
}
adjustButton(PrevPageBTN);
@ -376,7 +376,7 @@ namespace
setVisible(PageTwoNum, relPages > 1);
getPage(LeftBookPage)->showPage((relPages > 0) ? book : Book(), page + 0);
getPage(RightBookPage)->showPage((relPages > 0) ? book : Book(), page + 1);
getPage(RightBookPage)->showPage((relPages > 0) ? std::move(book) : Book(), page + 1);
setText(PageOneNum, page + 1);
setText(PageTwoNum, page + 2);

@ -164,8 +164,10 @@ namespace MWGui
const MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats(player);
setClassImage(mClassImage,
ESM::RefId::stringRefId(getLevelupClassImage(pcStats.getSkillIncreasesForSpecialization(0),
pcStats.getSkillIncreasesForSpecialization(1), pcStats.getSkillIncreasesForSpecialization(2))));
ESM::RefId::stringRefId(
getLevelupClassImage(pcStats.getSkillIncreasesForSpecialization(ESM::Class::Specialization::Combat),
pcStats.getSkillIncreasesForSpecialization(ESM::Class::Specialization::Magic),
pcStats.getSkillIncreasesForSpecialization(ESM::Class::Specialization::Stealth))));
int level = creatureStats.getLevel() + 1;
mLevelText->setCaptionWithReplacing("#{sLevelUpMenu1} " + MyGUI::utility::toString(level));

@ -134,7 +134,7 @@ namespace MWGui
return false;
}
else
player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, 1);
player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, ESM::Skill::Sneak_PickPocket);
return true;
}

@ -5,6 +5,7 @@
#include <MyGUI_TextIterator.h>
#include "../mwbase/environment.hpp"
#include "../mwbase/luamanager.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/world.hpp"
@ -174,10 +175,7 @@ namespace MWGui
}
// increase skill
MWWorld::LiveCellRef<ESM::NPC>* playerRef = player.get<ESM::NPC>();
const ESM::Class* class_ = store.get<ESM::Class>().find(playerRef->mBase->mClass);
pcStats.increaseSkill(skill->mId, *class_, true);
MWBase::Environment::get().getLuaManager()->skillLevelUp(player, skill->mId, "trainer");
// remove gold
player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price);

@ -371,7 +371,7 @@ namespace MWGui::Widgets
std::string spellLine = MWMechanics::getMagicEffectString(*magicEffect, attribute, skill);
if (mEffectParams.mMagnMin || mEffectParams.mMagnMax)
if ((mEffectParams.mMagnMin || mEffectParams.mMagnMax) && !mEffectParams.mNoMagnitude)
{
ESM::MagicEffect::MagnitudeDisplayType displayType = magicEffect->getMagnitudeDisplayType();
if (displayType == ESM::MagicEffect::MDT_TimesInt)
@ -386,7 +386,7 @@ namespace MWGui::Widgets
spellLine += formatter.str();
}
else if (displayType != ESM::MagicEffect::MDT_None && !mEffectParams.mNoMagnitude)
else if (displayType != ESM::MagicEffect::MDT_None)
{
spellLine += " " + MyGUI::utility::toString(mEffectParams.mMagnMin);
if (mEffectParams.mMagnMin != mEffectParams.mMagnMax)

@ -51,6 +51,7 @@
#include <components/l10n/manager.hpp>
#include <components/lua_ui/util.hpp>
#include <components/lua_ui/widget.hpp>
#include <components/settings/values.hpp>
@ -546,7 +547,8 @@ namespace MWGui
{
try
{
LuaUi::clearUserInterface();
LuaUi::clearGameInterface();
LuaUi::clearMenuInterface();
mStatsWatcher.reset();
@ -1675,7 +1677,10 @@ namespace MWGui
void WindowManager::onKeyFocusChanged(MyGUI::Widget* widget)
{
if (widget && widget->castType<MyGUI::EditBox>(false))
bool isEditBox = widget && widget->castType<MyGUI::EditBox>(false);
LuaUi::WidgetExtension* luaWidget = dynamic_cast<LuaUi::WidgetExtension*>(widget);
bool capturesInput = luaWidget ? luaWidget->isTextInput() : isEditBox;
if (widget && capturesInput)
SDL_StartTextInput();
else
SDL_StopTextInput();
@ -2173,11 +2178,16 @@ namespace MWGui
mConsole->print(msg, color);
}
void WindowManager::setConsoleMode(const std::string& mode)
void WindowManager::setConsoleMode(std::string_view mode)
{
mConsole->setConsoleMode(mode);
}
const std::string& WindowManager::getConsoleMode()
{
return mConsole->getConsoleMode();
}
void WindowManager::createCursors()
{
MyGUI::ResourceManager::EnumeratorPtr enumerator = MyGUI::ResourceManager::getInstance().getEnumerator();

@ -192,7 +192,8 @@ namespace MWGui
void setConsoleSelectedObject(const MWWorld::Ptr& object) override;
MWWorld::Ptr getConsoleSelectedObject() const override;
void printToConsole(const std::string& msg, std::string_view color) override;
void setConsoleMode(const std::string& mode) override;
void setConsoleMode(std::string_view mode) override;
const std::string& getConsoleMode() override;
/// Set time left for the player to start drowning (update the drowning bar)
/// @param time time left to start drowning

@ -10,6 +10,7 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/inputmanager.hpp"
#include "../mwbase/luamanager.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/world.hpp"
@ -119,15 +120,22 @@ namespace MWInput
mBindingsManager->setPlayerControlsEnabled(!guiMode);
mBindingsManager->mouseReleased(arg, id);
}
MWBase::Environment::get().getLuaManager()->inputEvent(
{ MWBase::LuaManager::InputEvent::MouseButtonReleased, arg.button });
}
void MouseManager::mouseWheelMoved(const SDL_MouseWheelEvent& arg)
{
MWBase::InputManager* input = MWBase::Environment::get().getInputManager();
if (mBindingsManager->isDetectingBindingState() || !input->controlsDisabled())
{
mBindingsManager->mouseWheelMoved(arg);
}
input->setJoystickLastUsed(false);
MWBase::Environment::get().getLuaManager()->inputEvent({ MWBase::LuaManager::InputEvent::MouseWheel,
MWBase::LuaManager::InputEvent::WheelChange{ arg.x, arg.y } });
}
void MouseManager::mousePressed(const SDL_MouseButtonEvent& arg, Uint8 id)
@ -161,7 +169,11 @@ namespace MWInput
const MWGui::SettingsWindow* settingsWindow
= MWBase::Environment::get().getWindowManager()->getSettingsWindow();
if ((!settingsWindow || !settingsWindow->isVisible()) && !input->controlsDisabled())
{
mBindingsManager->mousePressed(arg, id);
}
MWBase::Environment::get().getLuaManager()->inputEvent(
{ MWBase::LuaManager::InputEvent::MouseButtonPressed, arg.button });
}
void MouseManager::updateCursorMode()

@ -21,24 +21,6 @@
#include "animationbindings.hpp"
#include <array>
namespace MWLua
{
struct AnimationGroup;
struct TextKeyCallback;
}
namespace sol
{
template <>
struct is_automagical<MWLua::AnimationGroup> : std::false_type
{
};
template <>
struct is_automagical<std::shared_ptr<MWLua::TextKeyCallback>> : std::false_type
{
};
}
namespace MWLua
{
using BlendMask = MWRender::Animation::BlendMask;
@ -119,7 +101,7 @@ namespace MWLua
if (asTable)
{
AnimationPriorities priorities = AnimationPriorities(Priority::Priority_Default);
for (auto entry : asTable.value())
for (const auto& entry : asTable.value())
{
if (!entry.first.is<BoneGroup>() || !entry.second.is<Priority>())
throw std::runtime_error("Priority table must consist of BoneGroup-Priority pairs only");
@ -249,7 +231,7 @@ namespace MWLua
// Extended variant of MWScript's PlayGroup and LoopGroup
api["playQueued"] = sol::overload(
[mechanics](const sol::object& object, const std::string& groupname, const sol::table& options) {
int numberOfLoops = options.get_or("loops", std::numeric_limits<int>::max());
uint32_t numberOfLoops = options.get_or("loops", std::numeric_limits<uint32_t>::max());
float speed = options.get_or("speed", 1.f);
std::string startKey = options.get_or<std::string>("startkey", "start");
std::string stopKey = options.get_or<std::string>("stopkey", "stop");
@ -265,7 +247,7 @@ namespace MWLua
});
api["playBlended"] = [](const sol::object& object, std::string_view groupname, const sol::table& options) {
int loops = options.get_or("loops", 0);
uint32_t loops = options.get_or("loops", 0u);
MWRender::Animation::AnimPriority priority = getPriorityArgument(options);
BlendMask blendMask = options.get_or("blendmask", BlendMask::BlendMask_All);
bool autoDisable = options.get_or("autodisable", true);
@ -344,18 +326,19 @@ namespace MWLua
[world, context](const sol::object& staticOrID, const osg::Vec3f& worldPos) {
auto model = getStaticModelOrThrow(staticOrID);
context.mLuaManager->addAction(
[world, model, worldPos]() { world->spawnEffect(model, "", worldPos); }, "openmw.vfx.spawn");
[world, model = std::move(model), worldPos]() { world->spawnEffect(model, "", worldPos); },
"openmw.vfx.spawn");
},
[world, context](const sol::object& staticOrID, const osg::Vec3f& worldPos, const sol::table& options) {
auto model = getStaticModelOrThrow(staticOrID);
bool magicVfx = options.get_or("mwMagicVfx", true);
std::string textureOverride = options.get_or<std::string>("particleTextureOverride", "");
std::string texture = options.get_or<std::string>("particleTextureOverride", "");
float scale = options.get_or("scale", 1.f);
context.mLuaManager->addAction(
[world, model, textureOverride, worldPos, scale, magicVfx]() {
world->spawnEffect(model, textureOverride, worldPos, scale, magicVfx);
[world, model = std::move(model), texture = std::move(texture), worldPos, scale, magicVfx]() {
world->spawnEffect(model, texture, worldPos, scale, magicVfx);
},
"openmw.vfx.spawn");
});

@ -0,0 +1,55 @@
#include <components/esm3/loadbsgn.hpp>
#include <components/lua/luastate.hpp>
#include <components/misc/resourcehelpers.hpp>
#include <components/resource/resourcesystem.hpp>
#include "../mwbase/environment.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/esmstore.hpp"
#include "birthsignbindings.hpp"
#include "luamanagerimp.hpp"
#include "types/types.hpp"
namespace sol
{
template <>
struct is_automagical<ESM::BirthSign> : std::false_type
{
};
template <>
struct is_automagical<MWWorld::Store<ESM::BirthSign>> : std::false_type
{
};
}
namespace MWLua
{
sol::table initBirthSignRecordBindings(const Context& context)
{
sol::state_view& lua = context.mLua->sol();
sol::table birthSigns(context.mLua->sol(), sol::create);
addRecordFunctionBinding<ESM::BirthSign>(birthSigns, context);
auto signT = lua.new_usertype<ESM::BirthSign>("ESM3_BirthSign");
signT[sol::meta_function::to_string] = [](const ESM::BirthSign& rec) -> std::string {
return "ESM3_BirthSign[" + rec.mId.toDebugString() + "]";
};
signT["id"] = sol::readonly_property([](const ESM::BirthSign& rec) { return rec.mId.serializeText(); });
signT["name"] = sol::readonly_property([](const ESM::BirthSign& rec) -> std::string_view { return rec.mName; });
signT["description"]
= sol::readonly_property([](const ESM::BirthSign& rec) -> std::string_view { return rec.mDescription; });
auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS();
signT["texture"] = sol::readonly_property([vfs](const ESM::BirthSign& rec) -> std::string {
return Misc::ResourceHelpers::correctTexturePath(rec.mTexture, vfs);
});
signT["spells"] = sol::readonly_property([lua](const ESM::BirthSign& rec) -> sol::table {
sol::table res(lua, sol::create);
for (size_t i = 0; i < rec.mPowers.mList.size(); ++i)
res[i + 1] = rec.mPowers.mList[i].serializeText();
return res;
});
return LuaUtil::makeReadOnly(birthSigns);
}
}

@ -0,0 +1,13 @@
#ifndef MWLUA_BIRTHSIGNBINDINGS_H
#define MWLUA_BIRTHSIGNBINDINGS_H
#include <sol/forward.hpp>
#include "context.hpp"
namespace MWLua
{
sol::table initBirthSignRecordBindings(const Context& context);
}
#endif // MWLUA_BIRTHSIGNBINDINGS_H

@ -25,7 +25,7 @@ namespace sol
namespace MWLua
{
sol::table initCoreClassBindings(const Context& context)
sol::table initClassRecordBindings(const Context& context)
{
sol::state_view& lua = context.mLua->sol();
sol::table classes(context.mLua->sol(), sol::create);

@ -7,7 +7,7 @@
namespace MWLua
{
sol::table initCoreClassBindings(const Context& context);
sol::table initClassRecordBindings(const Context& context);
}
#endif // MWLUA_CLASSBINDINGS_H

@ -15,6 +15,7 @@ namespace MWLua
struct Context
{
bool mIsMenu;
bool mIsGlobal;
LuaManager* mLuaManager;
LuaUtil::LuaState* mLua;

@ -0,0 +1,149 @@
#include "corebindings.hpp"
#include <chrono>
#include <stdexcept>
#include <components/debug/debuglog.hpp>
#include <components/esm3/loadfact.hpp>
#include <components/lua/l10n.hpp>
#include <components/lua/luastate.hpp>
#include <components/lua/serialization.hpp>
#include <components/misc/strings/algorithm.hpp>
#include <components/misc/strings/lower.hpp>
#include <components/version/version.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/statemanager.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/datetimemanager.hpp"
#include "../mwworld/esmstore.hpp"
#include "animationbindings.hpp"
#include "factionbindings.hpp"
#include "luaevents.hpp"
#include "magicbindings.hpp"
#include "soundbindings.hpp"
#include "stats.hpp"
namespace MWLua
{
static sol::table initContentFilesBindings(sol::state_view& lua)
{
const std::vector<std::string>& contentList = MWBase::Environment::get().getWorld()->getContentFiles();
sol::table list(lua, sol::create);
for (size_t i = 0; i < contentList.size(); ++i)
list[i + 1] = Misc::StringUtils::lowerCase(contentList[i]);
sol::table res(lua, sol::create);
res["list"] = LuaUtil::makeReadOnly(list);
res["indexOf"] = [&contentList](std::string_view contentFile) -> sol::optional<int> {
for (size_t i = 0; i < contentList.size(); ++i)
if (Misc::StringUtils::ciEqual(contentList[i], contentFile))
return i + 1;
return sol::nullopt;
};
res["has"] = [&contentList](std::string_view contentFile) -> bool {
for (size_t i = 0; i < contentList.size(); ++i)
if (Misc::StringUtils::ciEqual(contentList[i], contentFile))
return true;
return false;
};
return LuaUtil::makeReadOnly(res);
}
void addCoreTimeBindings(sol::table& api, const Context& context)
{
MWWorld::DateTimeManager* timeManager = MWBase::Environment::get().getWorld()->getTimeManager();
api["getSimulationTime"] = [timeManager]() { return timeManager->getSimulationTime(); };
api["getSimulationTimeScale"] = [timeManager]() { return timeManager->getSimulationTimeScale(); };
api["getGameTime"] = [timeManager]() { return timeManager->getGameTime(); };
api["getGameTimeScale"] = [timeManager]() { return timeManager->getGameTimeScale(); };
api["isWorldPaused"] = [timeManager]() { return timeManager->isPaused(); };
api["getRealTime"] = []() {
return std::chrono::duration<double>(std::chrono::steady_clock::now().time_since_epoch()).count();
};
// TODO: remove in global context?
api["getRealFrameDuration"] = []() { return MWBase::Environment::get().getFrameDuration(); };
}
sol::table initCorePackage(const Context& context)
{
auto* lua = context.mLua;
if (lua->sol()["openmw_core"] != sol::nil)
return lua->sol()["openmw_core"];
sol::table api(lua->sol(), sol::create);
api["API_REVISION"] = Version::getLuaApiRevision(); // specified in CMakeLists.txt
api["quit"] = [lua]() {
Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback();
MWBase::Environment::get().getStateManager()->requestQuit();
};
api["sendGlobalEvent"] = [context](std::string eventName, const sol::object& eventData) {
context.mLuaEvents->addGlobalEvent(
{ std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer) });
};
api["contentFiles"] = initContentFilesBindings(lua->sol());
api["sound"] = initCoreSoundBindings(context);
api["vfx"] = initCoreVfxBindings(context);
api["getFormId"] = [](std::string_view contentFile, unsigned int index) -> std::string {
const std::vector<std::string>& contentList = MWBase::Environment::get().getWorld()->getContentFiles();
for (size_t i = 0; i < contentList.size(); ++i)
if (Misc::StringUtils::ciEqual(contentList[i], contentFile))
return ESM::RefId(ESM::FormId{ index, int(i) }).serializeText();
throw std::runtime_error("Content file not found: " + std::string(contentFile));
};
addCoreTimeBindings(api, context);
api["magic"] = initCoreMagicBindings(context);
api["stats"] = initCoreStatsBindings(context);
initCoreFactionBindings(context);
api["factions"] = &MWBase::Environment::get().getESMStore()->get<ESM::Faction>();
api["l10n"] = LuaUtil::initL10nLoader(lua->sol(), MWBase::Environment::get().getL10nManager());
const MWWorld::Store<ESM::GameSetting>* gmstStore
= &MWBase::Environment::get().getESMStore()->get<ESM::GameSetting>();
api["getGMST"] = [lua = context.mLua, gmstStore](const std::string& setting) -> sol::object {
const ESM::GameSetting* gmst = gmstStore->search(setting);
if (gmst == nullptr)
return sol::nil;
const ESM::Variant& value = gmst->mValue;
switch (value.getType())
{
case ESM::VT_Float:
return sol::make_object<float>(lua->sol(), value.getFloat());
case ESM::VT_Short:
case ESM::VT_Long:
case ESM::VT_Int:
return sol::make_object<int>(lua->sol(), value.getInteger());
case ESM::VT_String:
return sol::make_object<std::string>(lua->sol(), value.getString());
case ESM::VT_Unknown:
case ESM::VT_None:
break;
}
return sol::nil;
};
lua->sol()["openmw_core"] = LuaUtil::makeReadOnly(api);
return lua->sol()["openmw_core"];
}
sol::table initCorePackageForMenuScripts(const Context& context)
{
sol::table api(context.mLua->sol(), sol::create);
for (auto& [k, v] : LuaUtil::getMutableFromReadOnly(initCorePackage(context)))
api[k] = v;
api["sendGlobalEvent"] = [context](std::string eventName, const sol::object& eventData) {
if (MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame)
{
throw std::logic_error("Can't send global events when no game is loaded");
}
context.mLuaEvents->addGlobalEvent(
{ std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer) });
};
api["sound"] = sol::nil;
api["vfx"] = sol::nil;
return LuaUtil::makeReadOnly(api);
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save