1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-06-03 14:41:32 +00:00

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

This commit is contained in:
Zackhasacat 2024-02-20 12:07:23 -06:00
commit d73c1c8590
349 changed files with 12858 additions and 2571 deletions

View file

@ -172,6 +172,20 @@ Clang_Format:
- CI/check_file_names.sh - CI/check_file_names.sh
- CI/check_clang_format.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: Teal:
stage: checks stage: checks
extends: .Ubuntu_Image extends: .Ubuntu_Image
@ -196,6 +210,7 @@ Ubuntu_GCC_Debug:
CCACHE_SIZE: 3G CCACHE_SIZE: 3G
CMAKE_BUILD_TYPE: Debug CMAKE_BUILD_TYPE: Debug
CMAKE_CXX_FLAGS_DEBUG: -O0 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. # 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 timeout: 2h

View file

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

View file

@ -24,7 +24,9 @@
Bug #5129: Stuttering animation on Centurion Archer Bug #5129: Stuttering animation on Centurion Archer
Bug #5280: Unskinned shapes in skinned equipment are rendered in the wrong place 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 #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 #5714: Touch spells cast using ExplodeSpell don't always explode
Bug #5755: Reset friendly hit counter
Bug #5849: Paralysis breaks landing Bug #5849: Paralysis breaks landing
Bug #5870: Disposing of actors who were selected in the console doesn't deselect them like vanilla 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 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 #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 #6427: Enemy health bar disappears before damaging effect ends
Bug #6550: Cloned body parts don't inherit texture effects 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 #6645: Enemy block sounds align with animation instead of blocked hits
Bug #6657: Distant terrain tiles become black when using FWIW mod Bug #6657: Distant terrain tiles become black when using FWIW mod
Bug #6661: Saved games that have no preview screenshot cause issues or crashes 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 #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 #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 #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 #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 #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 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 #7619: Long map notes may get cut off
Bug #7630: Charm can be cast on creatures Bug #7630: Charm can be cast on creatures
Bug #7631: Cannot trade with/talk to Creeper or Mudcrab Merchant when they're fleeing 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 #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 #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 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 #7724: Guards don't help vs werewolves
Bug #7733: Launcher shows incorrect data paths when there's two plugins with the same name 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 #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 #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 #7761: Rain and ambient loop sounds are mutually exclusive
Bug #7765: OpenMW-CS: Touch Record option is broken 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 #7770: Sword of the Perithia: Script execution failure
Bug #7780: Non-ASCII texture paths in NIF files don't work 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 #2566: Handle NAM9 records for manual cell references
Feature #3537: Shader-based water ripples Feature #3537: Shader-based water ripples
Feature #5173: Support for NiFogProperty Feature #5173: Support for NiFogProperty
Feature #5492: Let rain and snow collide with statics 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 #6149: Dehardcode Lua API_REVISION
Feature #6152: Playing music via lua scripts Feature #6152: Playing music via lua scripts
Feature #6188: Specular lighting from point light sources Feature #6188: Specular lighting from point light sources
Feature #6411: Support translations in openmw-launcher
Feature #6447: Add LOD support to Object Paging Feature #6447: Add LOD support to Object Paging
Feature #6491: Add support for Qt6 Feature #6491: Add support for Qt6
Feature #6556: Lua API for sounds Feature #6556: Lua API for sounds
@ -166,13 +181,18 @@
Feature #7546: Start the game on Fredas Feature #7546: Start the game on Fredas
Feature #7554: Controller binding for tab for menu navigation Feature #7554: Controller binding for tab for menu navigation
Feature #7568: Uninterruptable scripted music 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 #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 #7618: Show the player character's health in the save details
Feature #7625: Add some missing console error outputs Feature #7625: Add some missing console error outputs
Feature #7634: Support NiParticleBomb Feature #7634: Support NiParticleBomb
Feature #7648: Lua Save game API
Feature #7652: Sort inactive post processing shaders list properly Feature #7652: Sort inactive post processing shaders list properly
Feature #7698: Implement sAbsorb, sDamage, sDrain, sFortify and sRestore Feature #7698: Implement sAbsorb, sDamage, sDrain, sFortify and sRestore
Feature #7709: Improve resolution selection in Launcher 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 #5896: Do not use deprecated MyGUI properties
Task #6624: Drop support for saves made prior to 0.45 Task #6624: Drop support for saves made prior to 0.45
Task #7113: Move from std::atoi to std::from_char Task #7113: Move from std::atoi to std::from_char

View file

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

11
CI/check_qt_translations.sh Executable file
View file

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

View file

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

View file

@ -19,6 +19,14 @@ if(OPENMW_GL4ES_MANUAL_INIT)
add_definitions(-DOPENMW_GL4ES_MANUAL_INIT) add_definitions(-DOPENMW_GL4ES_MANUAL_INIT)
endif() 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 # Apps and tools
option(BUILD_OPENMW "Build OpenMW" ON) option(BUILD_OPENMW "Build OpenMW" ON)
option(BUILD_LAUNCHER "Build Launcher" ON) option(BUILD_LAUNCHER "Build Launcher" ON)
@ -72,7 +80,7 @@ message(STATUS "Configuring OpenMW...")
set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MAJOR 0)
set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_MINOR 49)
set(OPENMW_VERSION_RELEASE 0) 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_POSTPROCESSING_API_REVISION 1)
set(OPENMW_VERSION_COMMITHASH "") set(OPENMW_VERSION_COMMITHASH "")
@ -224,9 +232,9 @@ find_package(LZ4 REQUIRED)
if (USE_QT) if (USE_QT)
find_package(QT REQUIRED COMPONENTS Core NAMES Qt6 Qt5) find_package(QT REQUIRED COMPONENTS Core NAMES Qt6 Qt5)
if (QT_VERSION_MAJOR VERSION_EQUAL 5) 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() else()
find_package(Qt6 COMPONENTS Core Widgets Network OpenGL OpenGLWidgets REQUIRED) find_package(Qt6 COMPONENTS Core Widgets Network OpenGL OpenGLWidgets LinguistTools REQUIRED)
endif() endif()
message(STATUS "Using Qt${QT_VERSION}") message(STATUS "Using Qt${QT_VERSION}")
endif() endif()
@ -1074,3 +1082,78 @@ if (DOXYGEN_FOUND)
WORKING_DIRECTORY ${OpenMW_BINARY_DIR} WORKING_DIRECTORY ${OpenMW_BINARY_DIR}
COMMENT "Generating documentation for the github-pages at ${DOXYGEN_PAGES_OUTPUT_DIR}" VERBATIM) COMMENT "Generating documentation for the github-pages at ${DOXYGEN_PAGES_OUTPUT_DIR}" VERBATIM)
endif () 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()

View file

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

View file

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

View file

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

View file

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

View file

@ -3,7 +3,9 @@
#include <QDebug> #include <QDebug>
#include <QFileDialog> #include <QFileDialog>
#include <QList>
#include <QMessageBox> #include <QMessageBox>
#include <QPair>
#include <QPushButton> #include <QPushButton>
#include <algorithm> #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.directoryUpButton, &QPushButton::released, this, [this]() { this->moveDirectory(-1); });
connect(ui.directoryDownButton, &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.directoryRemoveButton, &QPushButton::released, this, [this]() { this->removeDirectory(); });
connect(ui.archiveUpButton, &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->moveArchive(1); }); connect(ui.archiveDownButton, &QPushButton::released, this, [this]() { this->moveArchives(1); });
connect( connect(
ui.directoryListWidget->model(), &QAbstractItemModel::rowsMoved, this, [this]() { this->sortDirectories(); }); ui.directoryListWidget->model(), &QAbstractItemModel::rowsMoved, this, [this]() { this->sortDirectories(); });
@ -218,6 +220,18 @@ void Launcher::DataFilesPage::buildView()
&DataFilesPage::readNavMeshToolStderr); &DataFilesPage::readNavMeshToolStderr);
connect(mNavMeshToolInvoker->getProcess(), qOverload<int, QProcess::ExitStatus>(&QProcess::finished), this, connect(mNavMeshToolInvoker->getProcess(), qOverload<int, QProcess::ExitStatus>(&QProcess::finished), this,
&DataFilesPage::navMeshToolFinished); &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() bool Launcher::DataFilesPage::loadSettings()
@ -294,7 +308,7 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
// Display new content with custom formatting // Display new content with custom formatting
if (mNewDataDirs.contains(canonicalDirPath)) 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(); QFont font = item->font();
font.setBold(true); font.setBold(true);
font.setItalic(true); font.setItalic(true);
@ -312,7 +326,10 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
if (mSelector->containsDataFiles(currentDir)) if (mSelector->containsDataFiles(currentDir))
{ {
item->setIcon(QIcon(":/images/openmw-plugin.png")); item->setIcon(QIcon(":/images/openmw-plugin.png"));
tooltip += "Contains content file(s)"; if (!tooltip.isEmpty())
tooltip += "\n";
tooltip += tr("Contains content file(s)");
} }
else else
{ {
@ -707,17 +724,71 @@ void Launcher::DataFilesPage::removeDirectory()
refreshDataFilesView(); refreshDataFilesView();
} }
void Launcher::DataFilesPage::moveArchive(int step) void Launcher::DataFilesPage::slotShowArchiveContextMenu(const QPoint& pos)
{ {
int selectedRow = ui.archiveListWidget->currentRow(); 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()
{
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; int newRow = selectedRow + step;
if (selectedRow == -1 || newRow < 0 || newRow > ui.archiveListWidget->count() - 1) 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); addArchive(item->text(), item->checkState(), newRow);
ui.archiveListWidget->setCurrentRow(newRow); ui.archiveListWidget->setCurrentRow(newRow);
return true;
} }
void Launcher::DataFilesPage::addArchive(const QString& name, Qt::CheckState selected, int row) void Launcher::DataFilesPage::addArchive(const QString& name, Qt::CheckState selected, int row)

View file

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

View file

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

View file

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

View file

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>573</width> <width>573</width>
<height>384</height> <height>557</height>
</rect> </rect>
</property> </property>
<property name="contextMenuPolicy"> <property name="contextMenuPolicy">
@ -29,6 +29,12 @@
</item> </item>
<item row="1" column="0"> <item row="1" column="0">
<widget class="QLabel" name="dataNoteLabel"> <widget class="QLabel" name="dataNoteLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text"> <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> <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> </property>
@ -41,14 +47,111 @@
<string>Data Directories</string> <string>Data Directories</string>
</attribute> </attribute>
<layout class="QGridLayout" name="dirTabLayout"> <layout class="QGridLayout" name="dirTabLayout">
<item row="0" column="0" rowspan="26"> <item row="0" column="0">
<widget class="QListWidget" name="directoryListWidget"> <widget class="QListWidget" name="directoryListWidget">
<property name="dragDropMode"> <property name="dragDropMode">
<enum>QAbstractItemView::InternalMove</enum> <enum>QAbstractItemView::InternalMove</enum>
</property> </property>
</widget> </widget>
</item> </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"> <widget class="QLabel" name="directoryNoteLabel">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Minimum"> <sizepolicy hsizetype="Expanding" vsizetype="Minimum">
@ -61,116 +164,6 @@
</property> </property>
</widget> </widget>
</item> </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> </layout>
</widget> </widget>
<widget class="QWidget" name="archiveTab"> <widget class="QWidget" name="archiveTab">
@ -178,64 +171,90 @@
<string>Archive Files</string> <string>Archive Files</string>
</attribute> </attribute>
<layout class="QGridLayout" name="archiveTabLayout"> <layout class="QGridLayout" name="archiveTabLayout">
<item row="0" column="0" rowspan="26"> <item row="0" column="0">
<widget class="QListWidget" name="archiveListWidget"> <widget class="QListWidget" name="archiveListWidget">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="dragDropMode"> <property name="dragDropMode">
<enum>QAbstractItemView::InternalMove</enum> <enum>QAbstractItemView::InternalMove</enum>
</property> </property>
<property name="defaultDropAction">
<enum>Qt::CopyAction</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="0" column="1">
<widget class="QPushButton" name="archiveUpButton"> <layout class="QVBoxLayout" name="archiveButtons">
<property name="sizePolicy"> <item>
<sizepolicy hsizetype="Minimum" vsizetype="Fixed"> <widget class="QPushButton" name="archiveUpButton">
<horstretch>0</horstretch> <property name="sizePolicy">
<verstretch>33</verstretch> <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
</sizepolicy> <horstretch>0</horstretch>
</property> <verstretch>33</verstretch>
<property name="baseSize"> </sizepolicy>
<size> </property>
<width>0</width> <property name="baseSize">
<height>33</height> <size>
</size> <width>0</width>
</property> <height>33</height>
<property name="toolTip"> </size>
<string>Move selected archive one position up</string> </property>
</property> <property name="toolTip">
<property name="text"> <string>Move selected archive one position up</string>
<string>Move Up</string> </property>
</property> <property name="text">
</widget> <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>
<item row="27" column="0" colspan="2"> <item row="1" column="0" colspan="2">
<widget class="QLabel" name="archiveNoteLabel"> <widget class="QLabel" name="archiveNoteLabel">
<property name="text"> <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> <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> </property>
</widget> </widget>
</item> </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> </layout>
</widget> </widget>
<widget class="QWidget" name="navigationMeshCacheTab"> <widget class="QWidget" name="navigationMeshCacheTab">

View file

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

View file

@ -36,7 +36,7 @@
<item row="6" column="0"> <item row="6" column="0">
<widget class="QCheckBox" name="allowNPCToFollowOverWaterSurfaceCheckBox"> <widget class="QCheckBox" name="allowNPCToFollowOverWaterSurfaceCheckBox">
<property name="toolTip"> <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>
<property name="text"> <property name="text">
<string>Always allow actors to follow over water</string> <string>Always allow actors to follow over water</string>
@ -206,7 +206,7 @@
<item row="5" column="1"> <item row="5" column="1">
<widget class="QCheckBox" name="classicReflectedAbsorbSpellsCheckBox"> <widget class="QCheckBox" name="classicReflectedAbsorbSpellsCheckBox">
<property name="toolTip"> <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>
<property name="text"> <property name="text">
<string>Classic reflected Absorb spells behavior</string> <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> <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>
<property name="text"> <property name="text">
<string>Use anti-alias alpha testing</string> <string>Use anti-aliased alpha testing</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -663,6 +663,9 @@
</item> </item>
<item row="2" column="1"> <item row="2" column="1">
<widget class="QDoubleSpinBox" name="viewingDistanceComboBox"> <widget class="QDoubleSpinBox" name="viewingDistanceComboBox">
<property name="decimals">
<number>3</number>
</property>
<property name="suffix"> <property name="suffix">
<string> cells</string> <string> cells</string>
</property> </property>
@ -670,7 +673,7 @@
<double>0.000000000000000</double> <double>0.000000000000000</double>
</property> </property>
<property name="singleStep"> <property name="singleStep">
<double>0.500000000000000</double> <double>0.125000000000000</double>
</property> </property>
</widget> </widget>
</item> </item>
@ -1320,6 +1323,16 @@
</item> </item>
</layout> </layout>
</item> </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> <item>
<spacer> <spacer>
<property name="orientation"> <property name="orientation">

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -691,15 +691,6 @@ void CSMTools::ReferenceableCheckStage::npcCheck(
return; 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) if (level <= 0)
messages.add(id, "Level is non-positive", "", CSMDoc::Message::Severity_Warning); messages.add(id, "Level is non-positive", "", CSMDoc::Message::Severity_Warning);

View file

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

View file

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

View file

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

View file

@ -67,6 +67,11 @@ namespace CSMWorld
return mMaleParts[ESM::getMeshPart(index)]; 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 bool ActorAdapter::RaceData::hasDependency(const ESM::RefId& id) const
{ {
return mDependencies.find(id) != mDependencies.end(); return mDependencies.find(id) != mDependencies.end();
@ -90,10 +95,11 @@ namespace CSMWorld
mDependencies.emplace(id); 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; mId = id;
mIsBeast = isBeast; mIsBeast = isBeast;
mWeightsHeights = raceStats;
for (auto& str : mFemaleParts) for (auto& str : mFemaleParts)
str = ESM::RefId(); str = ESM::RefId();
for (auto& str : mMaleParts) for (auto& str : mMaleParts)
@ -163,6 +169,11 @@ namespace CSMWorld
return it->second.first; return it->second.first;
} }
const osg::Vec2f& ActorAdapter::ActorData::getRaceWeightHeight() const
{
return mRaceData->getGenderWeightHeight(isFemale());
}
bool ActorAdapter::ActorData::hasDependency(const ESM::RefId& id) const bool ActorAdapter::ActorData::hasDependency(const ESM::RefId& id) const
{ {
return mDependencies.find(id) != mDependencies.end(); return mDependencies.find(id) != mDependencies.end();
@ -504,7 +515,11 @@ namespace CSMWorld
} }
auto& race = raceRecord.get(); 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 // Setup body parts
for (int i = 0; i < mBodyParts.getSize(); ++i) for (int i = 0; i < mBodyParts.getSize(); ++i)

View file

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

View file

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

View file

@ -385,6 +385,26 @@ namespace CSMWorld
case 0: case 0:
{ {
effect.mEffectID = static_cast<short>(value.toInt()); 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; break;
} }
case 1: case 1:

View file

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

View file

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

View file

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

View file

@ -61,10 +61,14 @@ add_openmw_dir (mwscript
add_openmw_dir (mwlua add_openmw_dir (mwlua
luamanagerimp object objectlists userdataserializer luaevents engineevents objectvariant luamanagerimp object objectlists userdataserializer luaevents engineevents objectvariant
context globalscripts localscripts playerscripts luabindings objectbindings cellbindings mwscriptbindings context menuscripts globalscripts localscripts playerscripts luabindings objectbindings cellbindings
camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings postprocessingbindings stats debugbindings itemdata mwscriptbindings camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings
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 postprocessingbindings stats debugbindings corebindings worldbindings worker magicbindings factionbindings
worker magicbindings factionbindings classbindings animationbindings 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 add_openmw_dir (mwsound
@ -84,7 +88,7 @@ add_openmw_dir (mwworld
add_openmw_dir (mwphysics add_openmw_dir (mwphysics
physicssystem trace collisiontype actor convert object heightfield closestnotmerayresultcallback physicssystem trace collisiontype actor convert object heightfield closestnotmerayresultcallback
contacttestresultcallback deepestnotmecontacttestresultcallback stepper movementsolver projectile contacttestresultcallback stepper movementsolver projectile
actorconvexcallback raycasting mtphysics contacttestwrapper projectileconvexcallback actorconvexcallback raycasting mtphysics contacttestwrapper projectileconvexcallback
) )

View file

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

View file

@ -55,6 +55,8 @@ namespace MWBase
virtual void newGameStarted() = 0; virtual void newGameStarted() = 0;
virtual void gameLoaded() = 0; virtual void gameLoaded() = 0;
virtual void gameEnded() = 0;
virtual void noGame() = 0;
virtual void objectAddedToScene(const MWWorld::Ptr& ptr) = 0; virtual void objectAddedToScene(const MWWorld::Ptr& ptr) = 0;
virtual void objectRemovedFromScene(const MWWorld::Ptr& ptr) = 0; virtual void objectRemovedFromScene(const MWWorld::Ptr& ptr) = 0;
virtual void objectTeleported(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 animationTextKey(const MWWorld::Ptr& actor, const std::string& key) = 0;
virtual void playAnimation(const MWWorld::Ptr& object, const std::string& groupname, virtual void playAnimation(const MWWorld::Ptr& object, const std::string& groupname,
const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult, 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; = 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 exteriorCreated(MWWorld::CellStore& cell) = 0;
virtual void actorDied(const MWWorld::Ptr& actor) = 0; virtual void actorDied(const MWWorld::Ptr& actor) = 0;
virtual void questUpdated(const ESM::RefId& questId, int stage) = 0; virtual void questUpdated(const ESM::RefId& questId, int stage) = 0;
@ -79,6 +83,12 @@ namespace MWBase
struct InputEvent struct InputEvent
{ {
struct WheelChange
{
int x;
int y;
};
enum enum
{ {
KeyPressed, KeyPressed,
@ -89,8 +99,11 @@ namespace MWBase
TouchPressed, TouchPressed,
TouchReleased, TouchReleased,
TouchMoved, TouchMoved,
MouseButtonPressed,
MouseButtonReleased,
MouseWheel,
} mType; } 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; virtual void inputEvent(const InputEvent& event) = 0;

View file

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

View file

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

View file

@ -166,7 +166,8 @@ namespace MWBase
virtual void setConsoleSelectedObject(const MWWorld::Ptr& object) = 0; virtual void setConsoleSelectedObject(const MWWorld::Ptr& object) = 0;
virtual MWWorld::Ptr getConsoleSelectedObject() const = 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_Default = "#FFFFFF";
static constexpr std::string_view sConsoleColor_Error = "#FF2222"; static constexpr std::string_view sConsoleColor_Error = "#FF2222";

View file

@ -304,7 +304,7 @@ namespace MWBase
virtual const MWPhysics::RayCastingInterface* getRayCasting() const = 0; virtual const MWPhysics::RayCastingInterface* getRayCasting() const = 0;
virtual bool castRenderingRay(MWPhysics::RayCastingResult& res, const osg::Vec3f& from, const osg::Vec3f& to, 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; = 0;
virtual void setActorCollisionMode(const MWWorld::Ptr& ptr, bool internal, bool external) = 0; virtual void setActorCollisionMode(const MWWorld::Ptr& ptr, bool internal, bool external) = 0;

View file

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

View file

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

View file

@ -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); return getClassModel<ESM::Apparatus>(ptr);
} }

View file

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

View file

@ -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); return getClassModel<ESM::Armor>(ptr);
} }

View file

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

View file

@ -42,7 +42,7 @@ namespace MWClass
return false; 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); return getClassModel<ESM::BodyPart>(ptr);
} }

View file

@ -25,7 +25,7 @@ namespace MWClass
bool hasToolTip(const MWWorld::ConstPtr& ptr) const override; bool hasToolTip(const MWWorld::ConstPtr& ptr) const override;
///< @return true if this object has a tooltip when focused (default implementation: true) ///< @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;
}; };
} }

View file

@ -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); return getClassModel<ESM::Book>(ptr);
} }

View file

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

View file

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

View file

@ -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); return getClassModel<ESM::Clothing>(ptr);
} }

View file

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

View file

@ -126,7 +126,7 @@ namespace MWClass
physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World); 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); return getClassModel<ESM::Container>(ptr);
} }

View file

@ -85,7 +85,7 @@ namespace MWClass
void respawn(const MWWorld::Ptr& ptr) const override; 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; bool useAnim() const override;

View file

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

View file

@ -105,9 +105,9 @@ namespace MWClass
float getMaxSpeed(const MWWorld::Ptr& ptr) const override; 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: ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation:
///< list getModel(). ///< list getModel().

View file

@ -94,7 +94,7 @@ namespace MWClass
return true; 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); return getClassModel<ESM::Door>(ptr);
} }

View file

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

View file

@ -96,14 +96,14 @@ namespace MWClass
std::string_view getName(const MWWorld::ConstPtr& ptr) const override { return {}; } 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. // 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. // 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. // 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")) || Misc::StringUtils::ciEndsWith(model, "lod.nif"))
return {}; return {};

View file

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

View file

@ -54,7 +54,7 @@ namespace MWClass
return ESM4Impl::getToolTipInfo(getName(ptr), count); 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; std::string_view getName(const MWWorld::ConstPtr& ptr) const override;
static const ESM4::Npc* getTraitsRecord(const MWWorld::Ptr& ptr); static const ESM4::Npc* getTraitsRecord(const MWWorld::Ptr& ptr);

View file

@ -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); return getClassModel<ESM::Ingredient>(ptr);
} }

View file

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

View file

@ -70,7 +70,7 @@ namespace MWClass
return true; 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); return getClassModel<ESM::Light>(ptr);
} }

View file

@ -69,7 +69,7 @@ namespace MWClass
float getRemainingUsageTime(const MWWorld::ConstPtr& ptr) const override; float getRemainingUsageTime(const MWWorld::ConstPtr& ptr) const override;
///< Returns the remaining duration of the object. ///< 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; float getWeight(const MWWorld::ConstPtr& ptr) const override;

View file

@ -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); return getClassModel<ESM::Lockpick>(ptr);
} }

View file

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

View file

@ -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); return getClassModel<ESM::Miscellaneous>(ptr);
} }

View file

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

View file

@ -19,9 +19,11 @@
#include <components/esm3/loadsoun.hpp> #include <components/esm3/loadsoun.hpp>
#include <components/esm3/npcstate.hpp> #include <components/esm3/npcstate.hpp>
#include <components/settings/values.hpp> #include <components/settings/values.hpp>
#include <components/vfs/pathutil.hpp>
#include "../mwbase/dialoguemanager.hpp" #include "../mwbase/dialoguemanager.hpp"
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/luamanager.hpp"
#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/soundmanager.hpp" #include "../mwbase/soundmanager.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
@ -424,45 +426,51 @@ namespace MWClass
return (ref->mBase->mRecordFlags & ESM::FLAG_Persistent) != 0; 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>(); 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); const ESM::Race* race = MWBase::Environment::get().getESMStore()->get<ESM::Race>().find(ref->mBase->mRace);
if (race->mData.mFlags & ESM::Race::Beast) if (race->mData.mFlags & ESM::Race::Beast)
model = Settings::models().mBaseanimkna; return Settings::models().mBaseanimkna;
return model; 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 MWWorld::LiveCellRef<ESM::NPC>* npc = ptr.get<ESM::NPC>();
const auto& esmStore = MWBase::Environment::get().getESMStore(); const auto& esmStore = MWBase::Environment::get().getESMStore();
const ESM::Race* race = esmStore->get<ESM::Race>().search(npc->mBase->mRace); models.push_back(getModel(ptr));
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);
if (!npc->mBase->mModel.empty()) 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()) if (!npc->mBase->mHead.empty())
{ {
const ESM::BodyPart* head = esmStore->get<ESM::BodyPart>().search(npc->mBase->mHead); const ESM::BodyPart* head = esmStore->get<ESM::BodyPart>().search(npc->mBase->mHead);
if (head) if (head)
models.push_back(Misc::ResourceHelpers::correctMeshPath(head->mModel)); models.push_back(head->mModel);
} }
if (!npc->mBase->mHair.empty()) if (!npc->mBase->mHair.empty())
{ {
const ESM::BodyPart* hair = esmStore->get<ESM::BodyPart>().search(npc->mBase->mHair); const ESM::BodyPart* hair = esmStore->get<ESM::BodyPart>().search(npc->mBase->mHair);
if (hair) if (hair)
models.push_back(Misc::ResourceHelpers::correctMeshPath(hair->mModel)); models.push_back(hair->mModel);
} }
bool female = (npc->mBase->mFlags & ESM::NPC::Female); bool female = (npc->mBase->mFlags & ESM::NPC::Female);
@ -486,7 +494,7 @@ namespace MWClass
const ESM::BodyPart* part = esmStore->get<ESM::BodyPart>().search(partname); const ESM::BodyPart* part = esmStore->get<ESM::BodyPart>().search(partname);
if (part && !part->mModel.empty()) if (part && !part->mModel.empty())
models.push_back(Misc::ResourceHelpers::correctMeshPath(part->mModel)); models.push_back(part->mModel);
} }
}; };
if (equipped->getType() == ESM::Clothing::sRecordId) if (equipped->getType() == ESM::Clothing::sRecordId)
@ -501,7 +509,7 @@ namespace MWClass
} }
else else
{ {
std::string model = equipped->getClass().getModel(*equipped); std::string_view model = equipped->getClass().getModel(*equipped);
if (!model.empty()) if (!model.empty())
models.push_back(model); models.push_back(model);
} }
@ -510,14 +518,14 @@ namespace MWClass
} }
// preload body parts // 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 const std::vector<const ESM::BodyPart*>& parts
= MWRender::NpcAnimation::getBodyParts(race->mId, female, false, false); = MWRender::NpcAnimation::getBodyParts(race->mId, female, false, false);
for (const ESM::BodyPart* part : parts) for (const ESM::BodyPart* part : parts)
{ {
if (part && !part->mModel.empty()) 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; ESM::RefId weapskill = ESM::Skill::HandToHand;
if (!weapon.isEmpty()) if (!weapon.isEmpty())
weapskill = weapon.getClass().getEquipmentSkill(weapon); 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(); const MWMechanics::AiSequence& seq = victim.getClass().getCreatureStats(victim).getAiSequence();
@ -845,7 +853,7 @@ namespace MWClass
ESM::RefId skill = armor.getClass().getEquipmentSkill(armor); ESM::RefId skill = armor.getClass().getEquipmentSkill(armor);
if (ptr == MWMechanics::getPlayer()) if (ptr == MWMechanics::getPlayer())
skillUsageSucceeded(ptr, skill, 0); skillUsageSucceeded(ptr, skill, ESM::Skill::Armor_HitByOpponent);
if (skill == ESM::Skill::LightArmor) if (skill == ESM::Skill::LightArmor)
sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Light Armor Hit"), 1.0f, 1.0f); 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); sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Heavy Armor Hit"), 1.0f, 1.0f);
} }
else if (ptr == MWMechanics::getPlayer()) 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::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()) if (stats.isDead())
{ {
// by default user can loot friendly actors during death animation // by default user can loot non-fighting actors during death animation
if (Settings::game().mCanLootDuringDeathAnimation && !stats.getAiSequence().isInCombat()) if (Settings::game().mCanLootDuringDeathAnimation)
return std::make_unique<MWWorld::ActionOpen>(ptr); return std::make_unique<MWWorld::ActionOpen>(ptr);
// otherwise wait until death animation // otherwise wait until death animation
if (stats.isDeathAnimationFinished()) if (stats.isDeathAnimationFinished())
return std::make_unique<MWWorld::ActionOpen>(ptr); return std::make_unique<MWWorld::ActionOpen>(ptr);
} }
else if (!stats.getAiSequence().isInCombat()) else
{ {
if (stats.getKnockedDown() || MWBase::Environment::get().getMechanicsManager()->isSneaking(actor)) const bool allowStealingFromKO
return std::make_unique<MWWorld::ActionOpen>(ptr); // stealing = Settings::game().mAlwaysAllowStealingFromKnockedOutActors || !inCombatWithActor;
if (stats.getKnockedDown() && allowStealingFromKO)
return std::make_unique<MWWorld::ActionOpen>(ptr);
// Can't talk to werewolves const bool allowStealingWhileSneaking = !inCombatWithActor;
if (!getNpcStats(ptr).isWerewolf()) 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); 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 (inCombatWithActor)
if (!getScript(ptr).empty() && ptr.getRefData().getLocals().getIntVar(getScript(ptr), "companion")) return std::make_unique<MWWorld::FailedAction>("#{sActorInCombat}");
return std::make_unique<MWWorld::ActionOpen>(ptr);
return std::make_unique<MWWorld::FailedAction>(); return std::make_unique<MWWorld::FailedAction>();
} }
@ -1079,7 +1090,8 @@ namespace MWClass
if (customData.mNpcStats.isDead() && customData.mNpcStats.isDeathAnimationFinished()) if (customData.mNpcStats.isDead() && customData.mNpcStats.isDeathAnimationFinished())
return true; return true;
if (!customData.mNpcStats.getAiSequence().isInCombat()) const MWMechanics::AiSequence& aiSeq = customData.mNpcStats.getAiSequence();
if (!aiSeq.isInCombat() || aiSeq.isFleeing())
return true; return true;
if (Settings::game().mAlwaysAllowStealingFromKnockedOutActors && customData.mNpcStats.getKnockedDown()) 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 void Npc::skillUsageSucceeded(const MWWorld::Ptr& ptr, ESM::RefId skill, int usageType, float extraFactor) const
{ {
MWMechanics::NpcStats& stats = getNpcStats(ptr); MWBase::Environment::get().getLuaManager()->skillUse(ptr, skill, usageType, extraFactor);
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);
} }
float Npc::getArmorRating(const MWWorld::Ptr& ptr) const float Npc::getArmorRating(const MWWorld::Ptr& ptr) const

View file

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

View file

@ -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); return getClassModel<ESM::Potion>(ptr);
} }

View file

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

View file

@ -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); return getClassModel<ESM::Probe>(ptr);
} }

View file

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

View file

@ -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); return getClassModel<ESM::Repair>(ptr);
} }

View file

@ -44,7 +44,7 @@ namespace MWClass
const std::string& getInventoryIcon(const MWWorld::ConstPtr& ptr) const override; const std::string& getInventoryIcon(const MWWorld::ConstPtr& ptr) const override;
///< Return name of inventory icon. ///< 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; 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 ///< Generate action for using via inventory menu (default implementation: return a

View file

@ -43,7 +43,7 @@ namespace MWClass
physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World); 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); return getClassModel<ESM::Static>(ptr);
} }

View file

@ -32,7 +32,7 @@ namespace MWClass
bool hasToolTip(const MWWorld::ConstPtr& ptr) const override; bool hasToolTip(const MWWorld::ConstPtr& ptr) const override;
///< @return true if this object has a tooltip when focused (default implementation: true) ///< @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;
}; };
} }

View file

@ -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); return getClassModel<ESM::Weapon>(ptr);
} }

View file

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

View file

@ -543,7 +543,8 @@ namespace MWDialogue
mPermanentDispositionChange += perm; mPermanentDispositionChange += perm;
MWWorld::Ptr player = MWMechanics::getPlayer(); 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) if (success)
{ {

View file

@ -375,12 +375,18 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons
return mChoice; return mChoice;
case SelectWrapper::Function_AiSetting: 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() return mActor.getClass()
.getCreatureStats(mActor) .getCreatureStats(mActor)
.getAiSetting((MWMechanics::AiSetting)select.getArgument()) .getAiSetting(static_cast<MWMechanics::AiSetting>(argument))
.getModified(false); .getModified(false);
}
case SelectWrapper::Function_PcAttribute: case SelectWrapper::Function_PcAttribute:
{ {
ESM::RefId attribute = ESM::Attribute::indexToRefId(select.getArgument()); ESM::RefId attribute = ESM::Attribute::indexToRefId(select.getArgument());

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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