diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7dc69484c6..406d6411d9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -210,6 +210,7 @@ Ubuntu_GCC_Debug: CCACHE_SIZE: 3G CMAKE_BUILD_TYPE: Debug CMAKE_CXX_FLAGS_DEBUG: -O0 + BUILD_SHARED_LIBS: 1 # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. timeout: 2h @@ -520,19 +521,19 @@ Ubuntu_GCC_integration_tests_asan: - build/OpenMW-*.dmg - "build/**/*.log" -macOS13_Xcode14_arm64: +macOS14_Xcode15_arm64: extends: .MacOS - image: macos-12-xcode-14 + image: macos-14-xcode-15 tags: - saas-macos-medium-m1 cache: - key: macOS12_Xcode14_arm64.v4 + key: macOS14_Xcode15_arm64.v1 variables: CCACHE_SIZE: 3G .Windows_Ninja_Base: tags: - - windows + - saas-windows-medium-amd64 rules: - if: $CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "merge_request_event" before_script: @@ -569,60 +570,54 @@ macOS13_Xcode14_arm64: - $env:CCACHE_BASEDIR = Get-Location - $env:CCACHE_DIR = "$(Get-Location)\ccache" - New-Item -Type Directory -Force -Path $env:CCACHE_DIR - - New-Item -Type File -Force -Path MSVC2019_64_Ninja\.cmake\api\v1\query\codemodel-v2 - - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -N -b -t -C $multiview -E + - New-Item -Type File -Force -Path MSVC2022_64_Ninja\.cmake\api\v1\query\codemodel-v2 + - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2022 -k -V -N -b -t -C $multiview -E - Get-Volume - - cd MSVC2019_64_Ninja + - cd MSVC2022_64_Ninja - .\ActivateMSVC.ps1 - cmake --build . --config $config - - ccache --show-stats + - ccache --show-stats -v - cd $config - echo "CI_COMMIT_REF_NAME ${CI_COMMIT_REF_NAME}`nCI_JOB_ID ${CI_JOB_ID}`nCI_COMMIT_SHA ${CI_COMMIT_SHA}" | Out-File -Encoding UTF8 CI-ID.txt - $artifactDirectory = "$(Make-SafeFileName("${CI_PROJECT_NAMESPACE}"))/$(Make-SafeFileName("${CI_COMMIT_REF_NAME}"))/$(Make-SafeFileName("${CI_COMMIT_SHORT_SHA}-${CI_JOB_ID}"))/" - Get-ChildItem -Recurse *.ilk | Remove-Item - | if (Get-ChildItem -Recurse *.pdb) { - 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt + 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt if (Test-Path env:AWS_ACCESS_KEY_ID) { - aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" s3://openmw-artifacts/${artifactDirectory} + aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" s3://openmw-artifacts/${artifactDirectory} } Push-Location .. ..\CI\Store-Symbols.ps1 if (Test-Path env:AWS_ACCESS_KEY_ID) { aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp --recursive --exclude * --include *.ex_ --include *.dl_ --include *.pd_ .\SymStore s3://openmw-sym } - 7z a -tzip "..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_sym_store.zip"))" '.\SymStore\*' $config\CI-ID.txt + 7z a -tzip "..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_sym_store.zip"))" '.\SymStore\*' $config\CI-ID.txt Pop-Location Get-ChildItem -Recurse *.pdb | Remove-Item } - - 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" '*' + - 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" '*' - | if (Test-Path env:AWS_ACCESS_KEY_ID) { - aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" s3://openmw-artifacts/${artifactDirectory} + aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" s3://openmw-artifacts/${artifactDirectory} } - if ($executables) { foreach ($exe in $executables.Split(',')) { & .\$exe } } after_script: - Get-Volume - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log cache: - key: ninja-v8 + key: ninja-2022-v9 paths: - ccache - deps - - MSVC2019_64_Ninja/deps/Qt + - MSVC2022_64_Ninja/deps/Qt artifacts: when: always paths: - "*.zip" - "*.log" - - MSVC2019_64_Ninja/*.log - - MSVC2019_64_Ninja/*/*.log - - MSVC2019_64_Ninja/*/*/*.log - - MSVC2019_64_Ninja/*/*/*/*.log - - MSVC2019_64_Ninja/*/*/*/*/*.log - - MSVC2019_64_Ninja/*/*/*/*/*/*.log - - MSVC2019_64_Ninja/*/*/*/*/*/*/*.log - - MSVC2019_64_Ninja/*/*/*/*/*/*/*/*.log + - MSVC2022_64_Ninja/*.log + - MSVC2022_64_Ninja/**/*.log # 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 @@ -655,7 +650,7 @@ macOS13_Xcode14_arm64: .Windows_MSBuild_Base: tags: - - windows + - saas-windows-medium-amd64 rules: - if: $CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "merge_request_event" before_script: @@ -665,7 +660,6 @@ macOS13_Xcode14_arm64: - choco source disable -n=chocolatey - choco install git --force --params "/GitAndUnixToolsOnPath" -y - choco install 7zip -y - - choco install ccache -y - choco install vswhere -y - choco install python -y - choco install awscli -y @@ -688,62 +682,51 @@ macOS13_Xcode14_arm64: - $time = (Get-Date -Format "HH:mm:ss") - echo ${time} - echo "started by ${GITLAB_USER_NAME}" - - $env:CCACHE_BASEDIR = Get-Location - - $env:CCACHE_DIR = "$(Get-Location)\ccache" - - New-Item -Type Directory -Force -Path $env:CCACHE_DIR - - New-Item -Type File -Force -Path MSVC2019_64\.cmake\api\v1\query\codemodel-v2 - - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -b -t -C $multiview -E - - cd MSVC2019_64 + - New-Item -Type File -Force -Path MSVC2022_64\.cmake\api\v1\query\codemodel-v2 + - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2022 -k -V -b -t -C $multiview -E + - cd MSVC2022_64 - Get-Volume - cmake --build . --config $config - - ccache --show-stats - cd $config - echo "CI_COMMIT_REF_NAME ${CI_COMMIT_REF_NAME}`nCI_JOB_ID ${CI_JOB_ID}`nCI_COMMIT_SHA ${CI_COMMIT_SHA}" | Out-File -Encoding UTF8 CI-ID.txt - $artifactDirectory = "$(Make-SafeFileName("${CI_PROJECT_NAMESPACE}"))/$(Make-SafeFileName("${CI_COMMIT_REF_NAME}"))/$(Make-SafeFileName("${CI_COMMIT_SHORT_SHA}-${CI_JOB_ID}"))/" - Get-ChildItem -Recurse *.ilk | Remove-Item - | if (Get-ChildItem -Recurse *.pdb) { - 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt + 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt if (Test-Path env:AWS_ACCESS_KEY_ID) { - aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" s3://openmw-artifacts/${artifactDirectory} + aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" s3://openmw-artifacts/${artifactDirectory} } Push-Location .. ..\CI\Store-Symbols.ps1 if (Test-Path env:AWS_ACCESS_KEY_ID) { aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp --recursive --exclude * --include *.ex_ --include *.dl_ --include *.pd_ .\SymStore s3://openmw-sym } - 7z a -tzip "..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_sym_store.zip"))" '.\SymStore\*' $config\CI-ID.txt + 7z a -tzip "..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_sym_store.zip"))" '.\SymStore\*' $config\CI-ID.txt Pop-Location Get-ChildItem -Recurse *.pdb | Remove-Item } - - 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" '*' + - 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" '*' - | if (Test-Path env:AWS_ACCESS_KEY_ID) { - aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" s3://openmw-artifacts/${artifactDirectory} + aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" s3://openmw-artifacts/${artifactDirectory} } - if ($executables) { foreach ($exe in $executables.Split(',')) { & .\$exe } } after_script: - Get-Volume - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log cache: - key: msbuild-v8 + key: msbuild-2022-v9 paths: - - ccache - deps - - MSVC2019_64/deps/Qt + - MSVC2022_64/deps/Qt artifacts: when: always paths: - "*.zip" - "*.log" - - MSVC2019_64/*.log - - MSVC2019_64/*/*.log - - MSVC2019_64/*/*/*.log - - MSVC2019_64/*/*/*/*.log - - MSVC2019_64/*/*/*/*/*.log - - MSVC2019_64/*/*/*/*/*/*.log - - MSVC2019_64/*/*/*/*/*/*/*.log - - MSVC2019_64/*/*/*/*/*/*/*/*.log + - MSVC2022_64/*.log + - MSVC2022_64/**/*.log # 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 diff --git a/AUTHORS.md b/AUTHORS.md index e2903febe4..7c06d72287 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -15,6 +15,7 @@ Programmers Nicolay Korslund - Project leader 2008-2010 scrawl - Top contributor + AbduSharif Adam Hogan (aurix) Aesylwinn aegis @@ -79,6 +80,7 @@ Programmers Eduard Cot (trombonecot) Eli2 Emanuel Guével (potatoesmaster) + Epoch Eris Caffee (eris) eroen escondida @@ -187,6 +189,7 @@ Programmers pkubik PLkolek PlutonicOverkill + Qlonever Radu-Marius Popovici (rpopovici) Rafael Moura (dhustkoder) Randy Davin (Kindi) diff --git a/CHANGELOG.md b/CHANGELOG.md index a96eb36c9f..e935364264 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,8 @@ Bug #5977: Fatigueless NPCs' corpse underwater changes animation on game load Bug #6025: Subrecords cannot overlap records Bug #6027: Collisionshape becomes spiderweb-like when the mesh is too complex + Bug #6146: Lua command `actor:setEquipment` doesn't trigger mwscripts when equipping or unequipping a scripted item + Bug #6156: 1ft Charm or Sound magic effect vfx doesn't work properly Bug #6190: Unintuitive sun specularity time of day dependence Bug #6222: global map cell size can crash openmw if set to too high a value Bug #6313: Followers with high Fight can turn hostile @@ -44,9 +46,11 @@ 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 #6716: mwscript comparison operator handling is too restrictive + Bug #6723: "Turn to movement direction" makes the player rotate wildly with COLLADA Bug #6754: Beast to Non-beast transformation mod is not working on OpenMW Bug #6758: Main menu background video can be stopped by opening the options menu Bug #6807: Ultimate Galleon is not working properly + Bug #6846: Launcher only works with default config paths Bug #6893: Lua: Inconsistent behavior with actors affected by Disable and SetDelete commands Bug #6894: Added item combines with equipped stack instead of creating a new unequipped stack Bug #6932: Creatures flee from my followers and we have to chase after them @@ -56,6 +60,7 @@ Bug #6973: Fade in happens after the scene load and is shown Bug #6974: Only harmful effects are reflected Bug #6977: Sun damage implementation does not match research + Bug #6985: Issues with Magic Cards numbers readability Bug #6986: Sound magic effect does not make noise Bug #6987: Set/Mod Blindness should not darken the screen Bug #6992: Crossbow reloading doesn't look the same as in Morrowind @@ -71,12 +76,15 @@ Bug #7084: Resurrecting an actor doesn't take into account base record changes Bug #7088: Deleting last save game of last character doesn't clear character name/details Bug #7092: BSA archives from higher priority directories don't take priority + Bug #7102: Some HQ Creatures mod models can hit the 8 texture slots limit with 0.48 Bug #7103: Multiple paths pointing to the same plugin but with different cases lead to automatically removed config entries Bug #7122: Teleportation to underwater should cancel active water walking effect Bug #7131: MyGUI log spam when post processing HUD is open Bug #7134: Saves with an invalid last generated RefNum can be loaded Bug #7163: Myar Aranath: Wheat breaks the GUI + Bug #7168: Fix average scene luminance Bug #7172: Current music playlist continues playing indefinitely if next playlist is empty + Bug #7202: Post-processing normals for terrain, water randomly stop rendering Bug #7204: Missing actor scripts freeze the game Bug #7229: Error marker loading failure is not handled Bug #7243: Supporting loading external files from VFS from esm files @@ -93,24 +101,29 @@ Bug #7415: Unbreakable lock discrepancies Bug #7416: Modpccrimelevel is different from vanilla Bug #7428: AutoCalc flag is not used to calculate enchantment costs + Bug #7447: OpenMW-CS: Dragging a cell of a different type (from the initial type) into the 3D view crashes OpenMW-CS Bug #7450: Evading obstacles does not work for actors missing certain animations Bug #7459: Icons get stacked on the cursor when picking up multiple items simultaneously Bug #7472: Crash when enchanting last projectiles Bug #7475: Equipping a constant effect item doesn't update the magic menu Bug #7502: Data directories dialog (0.48.0) forces adding subdirectory instead of intended directory Bug #7505: Distant terrain does not support sample size greater than cell size + Bug #7535: Bookart paths for textures in OpenMW vs vanilla Morrowind Bug #7553: Faction reaction loading is incorrect Bug #7557: Terrain::ChunkManager::createChunk is called twice for the same position, lod on initial loading Bug #7573: Drain Fatigue can't bring fatigue below zero by default Bug #7585: Difference in interior lighting between OpenMW with legacy lighting method enabled and vanilla Morrowind + Bug #7587: Quick load related crash Bug #7603: Scripts menu size is not updated properly Bug #7604: Goblins Grunt becomes idle once injured Bug #7609: ForceGreeting should not open dialogue for werewolves Bug #7611: Beast races' idle animations slide after turning or jumping in place Bug #7617: The death prompt asks the player if they wanted to load the character's last created save Bug #7619: Long map notes may get cut off + Bug #7623: Incorrect placement of the script info in the engraved ring of healing tooltip Bug #7630: Charm can be cast on creatures Bug #7631: Cannot trade with/talk to Creeper or Mudcrab Merchant when they're fleeing + Bug #7633: Groundcover should ignore non-geometry Drawables Bug #7636: Animations bug out when switching between 1st and 3rd person, while playing a scripted animation Bug #7637: Actors can sometimes move while playing scripted animations Bug #7639: NPCs don't use hand-to-hand if their other melee skills were damaged during combat @@ -128,30 +141,51 @@ Bug #7679: Scene luminance value flashes when toggling shaders Bug #7685: Corky sometimes doesn't follow Llovyn Andus Bug #7712: Casting doesn't support spells and enchantments with no effects + Bug #7721: CS: Special Chars Not Allowed in IDs Bug #7723: Assaulting vampires and werewolves shouldn't be a crime Bug #7724: Guards don't help vs werewolves Bug #7733: Launcher shows incorrect data paths when there's two plugins with the same name Bug #7742: Governing attribute training limit should use the modified attribute + Bug #7753: Editor: Actors Don't Scale According to Their Race Bug #7758: Water walking is not taken into account to compute path cost on the water Bug #7761: Rain and ambient loop sounds are mutually exclusive + Bug #7763: Bullet shape loading problems, assorted Bug #7765: OpenMW-CS: Touch Record option is broken Bug #7769: Sword of the Perithia: Broken NPCs Bug #7770: Sword of the Perithia: Script execution failure Bug #7780: Non-ASCII texture paths in NIF files don't work Bug #7785: OpenMW-CS initialising Skill and Attribute fields to 0 instead of -1 on non-FortifyStat spells + Bug #7794: Fleeing NPCs name tooltip doesn't appear Bug #7796: Absorbed enchantments don't restore magicka + Bug #7823: Game crashes when launching it. + 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 + Bug #7841: Editor: "Dirty" water heights are saved in modified CELLs + Bug #7859: AutoCalc flag is not used to calculate potion value + Bug #7861: OpenMW-CS: Incorrect DIAL's type in INFO records + Bug #7872: Region sounds use wrong odds + Bug #7886: Equip and unequip animations can't share the animation track section + Bug #7887: Editor: Mismatched reported script data size and actual data size causes a crash during save + Bug #7898: Editor: Invalid reference scales are allowed + Bug #7899: Editor: Doors can't be unlocked + Bug #7901: Editor: Teleport-related fields shouldn't be editable if a ref does not teleport + Bug #7908: Key bindings names in the settings menu are layout-specific + Bug #7943: Using "addSoulGem" and "dropSoulGem" commands to creatures works only with "Weapon & Shield" flagged ones Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty Feature #5492: Let rain and snow collide with statics - Feature #6149: Dehardcode Lua API_REVISION + Feature #5926: Refraction based on water depth + Feature #5944: Option to use camera as sound listener Feature #6152: Playing music via lua scripts Feature #6188: Specular lighting from point light sources Feature #6411: Support translations in openmw-launcher Feature #6447: Add LOD support to Object Paging Feature #6491: Add support for Qt6 Feature #6556: Lua API for sounds + Feature #6679: Design a custom Input Action API Feature #6726: Lua API for creating new objects + Feature #6727: Lua API for records of all object types Feature #6864: Lua file access API Feature #6922: Improve launcher appearance Feature #6933: Support high-resolution cursor textures @@ -164,16 +198,19 @@ Feature #7125: Remembering console commands between sessions Feature #7129: Add support for non-adaptive VSync Feature #7130: Ability to set MyGUI logging verbosity + Feature #7142: MWScript Lua API Feature #7148: Optimize string literal lookup in mwscript + Feature #7161: OpenMW-CS: Make adding and filtering TopicInfos easier Feature #7194: Ori to show texture paths Feature #7214: Searching in the in-game console - Feature #7284: Searching in the console with regex and toggleable case-sensitivity + Feature #7248: Searching in the console with regex and toggleable case-sensitivity Feature #7468: Factions API for Lua Feature #7477: NegativeLight Magic Effect flag Feature #7499: OpenMW-CS: Generate record filters by drag & dropping cell content to the filters field Feature #7546: Start the game on Fredas Feature #7554: Controller binding for tab for menu navigation Feature #7568: Uninterruptable scripted music + Feature #7590: [Lua] Ability to deserialize YAML data from scripts Feature #7606: Launcher: allow Shift-select in Archives tab Feature #7608: Make the missing dependencies warning when loading a savegame more helpful Feature #7618: Show the player character's health in the save details @@ -183,11 +220,20 @@ Feature #7652: Sort inactive post processing shaders list properly Feature #7698: Implement sAbsorb, sDamage, sDrain, sFortify and sRestore Feature #7709: Improve resolution selection in Launcher + Feature #7777: Support external Bethesda material files (BGSM/BGEM) Feature #7792: Support Timescale Clouds Feature #7795: Support MaxNumberRipples INI setting Feature #7805: Lua Menu context + Feature #7860: Lua: Expose NPC AI settings (fight, alarm, flee) + Feature #7875: Disable MyGUI windows snapping + Feature #7914: Do not allow to move GUI windows out of screen + Feature #7923: Don't show non-existent higher ranks for factions with fewer than 9 ranks + Feature #7932: Support two-channel normal maps Task #5896: Do not use deprecated MyGUI properties + Task #6085: Replace boost::filesystem with std::filesystem + Task #6149: Dehardcode Lua API_REVISION Task #6624: Drop support for saves made prior to 0.45 + Task #7048: Get rid of std::bind Task #7113: Move from std::atoi to std::from_char Task #7117: Replace boost::scoped_array with std::vector Task #7151: Do not use std::strerror to get errno error message diff --git a/CI/before_script.linux.sh b/CI/before_script.linux.sh index 0edd38628f..ab61ed3e59 100755 --- a/CI/before_script.linux.sh +++ b/CI/before_script.linux.sh @@ -22,7 +22,7 @@ declare -a CMAKE_CONF_OPTS=( -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_INSTALL_PREFIX=install - -DBUILD_SHARED_LIBS=OFF + -DBUILD_SHARED_LIBS="${BUILD_SHARED_LIBS:-OFF}" -DUSE_SYSTEM_TINYXML=ON -DOPENMW_USE_SYSTEM_RECASTNAVIGATION=ON -DOPENMW_CXX_FLAGS="-Werror -Werror=implicit-fallthrough" # flags specific to OpenMW project diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index e11ceb499d..2423f0c54f 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -347,6 +347,26 @@ add_qt_style_dlls() { QT_STYLES[$CONFIG]="${QT_STYLES[$CONFIG]} $@" } +declare -A QT_IMAGEFORMATS +QT_IMAGEFORMATS["Release"]="" +QT_IMAGEFORMATS["Debug"]="" +QT_IMAGEFORMATS["RelWithDebInfo"]="" +add_qt_image_dlls() { + local CONFIG=$1 + shift + QT_IMAGEFORMATS[$CONFIG]="${QT_IMAGEFORMATS[$CONFIG]} $@" +} + +declare -A QT_ICONENGINES +QT_ICONENGINES["Release"]="" +QT_ICONENGINES["Debug"]="" +QT_ICONENGINES["RelWithDebInfo"]="" +add_qt_icon_dlls() { + local CONFIG=$1 + shift + QT_ICONENGINES[$CONFIG]="${QT_ICONENGINES[$CONFIG]} $@" +} + if [ -z $PLATFORM ]; then PLATFORM="$(uname -m)" fi @@ -528,8 +548,12 @@ if ! [ -z $UNITY_BUILD ]; then add_cmake_opts "-DOPENMW_UNITY_BUILD=True" fi -if ! [ -z $USE_CCACHE ]; then - add_cmake_opts "-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache" +if [ -n "$USE_CCACHE" ]; then + if [ -n "$NMAKE" ] || [ -n "$NINJA" ]; then + add_cmake_opts "-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DPRECOMPILE_HEADERS_WITH_MSVC=OFF" + else + echo "Ignoring -C (CCache) as it is incompatible with Visual Studio CMake generators" + fi fi # turn on LTO by default @@ -549,7 +573,7 @@ ICU_VER="70_1" LUAJIT_VER="v2.1.0-beta3-452-g7a0cf5fd" LZ4_VER="1.9.2" OPENAL_VER="1.23.0" -QT_VER="5.15.2" +QT_VER="6.6.2" OSG_ARCHIVE_NAME="OSGoS 3.6.5" OSG_ARCHIVE="OSGoS-3.6.5-123-g68c5c573d-msvc${OSG_MSVC_YEAR}-win${BITS}" @@ -880,7 +904,7 @@ printf "Qt ${QT_VER}... " printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then pushd "$DEPS" > /dev/null - AQT_VERSION="v3.1.7" + AQT_VERSION="v3.1.12" if ! [ -f "aqt_x64-${AQT_VERSION}.exe" ]; then download "aqt ${AQT_VERSION}"\ "https://github.com/miurahr/aqtinstall/releases/download/${AQT_VERSION}/aqt_x64.exe" \ @@ -901,6 +925,9 @@ printf "Qt ${QT_VER}... " echo Done. fi + QT_MAJOR_VER=$(echo "${QT_VER}" | awk -F '[.]' '{printf "%d", $1}') + QT_MINOR_VER=$(echo "${QT_VER}" | awk -F '[.]' '{printf "%d", $2}') + cd $QT_SDK for CONFIGURATION in ${CONFIGURATIONS[@]}; do if [ $CONFIGURATION == "Debug" ]; then @@ -908,13 +935,24 @@ printf "Qt ${QT_VER}... " else DLLSUFFIX="" fi - if [ "${QT_VER:0:1}" -eq "6" ]; then - add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt${QT_VER:0:1}"{Core,Gui,Network,OpenGL,OpenGLWidgets,Widgets}${DLLSUFFIX}.dll + + if [ "${QT_MAJOR_VER}" -eq 6 ]; then + add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt${QT_MAJOR_VER}"{Core,Gui,Network,OpenGL,OpenGLWidgets,Widgets,Svg}${DLLSUFFIX}.dll + + # Since Qt 6.7.0 plugin is called "qmodernwindowsstyle" + if [ "${QT_MINOR_VER}" -ge 7 ]; then + add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qmodernwindowsstyle${DLLSUFFIX}.dll" + else + add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll" + fi else - add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt${QT_VER:0:1}"{Core,Gui,Network,OpenGL,Widgets}${DLLSUFFIX}.dll + add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt${QT_MAJOR_VER}"{Core,Gui,Network,OpenGL,Widgets,Svg}${DLLSUFFIX}.dll + add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll" fi + add_qt_platform_dlls $CONFIGURATION "$(pwd)/plugins/platforms/qwindows${DLLSUFFIX}.dll" - add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll" + add_qt_image_dlls $CONFIGURATION "$(pwd)/plugins/imageformats/qsvg${DLLSUFFIX}.dll" + add_qt_icon_dlls $CONFIGURATION "$(pwd)/plugins/iconengines/qsvgicon${DLLSUFFIX}.dll" done echo Done. } @@ -1109,6 +1147,20 @@ fi cp "$DLL" "${DLL_PREFIX}styles" done echo + echo "- Qt Image Format DLLs..." + mkdir -p ${DLL_PREFIX}imageformats + for DLL in ${QT_IMAGEFORMATS[$CONFIGURATION]}; do + echo " $(basename $DLL)" + cp "$DLL" "${DLL_PREFIX}imageformats" + done + echo + echo "- Qt Icon Engine DLLs..." + mkdir -p ${DLL_PREFIX}iconengines + for DLL in ${QT_ICONENGINES[$CONFIGURATION]}; do + echo " $(basename $DLL)" + cp "$DLL" "${DLL_PREFIX}iconengines" + done + echo done #fi diff --git a/CI/check_qt_translations.sh b/CI/check_qt_translations.sh index f3a82ed2e6..1fc2e19002 100755 --- a/CI/check_qt_translations.sh +++ b/CI/check_qt_translations.sh @@ -4,8 +4,8 @@ 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 +${LUPDATE:?} -locations none apps/wizard -ts files/lang/wizard_*.ts +${LUPDATE:?} -locations none apps/launcher -ts files/lang/launcher_*.ts +${LUPDATE:?} -locations none 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) diff --git a/CI/file_name_exceptions.txt b/CI/file_name_exceptions.txt index c3bcee8661..dff3527348 100644 --- a/CI/file_name_exceptions.txt +++ b/CI/file_name_exceptions.txt @@ -20,6 +20,7 @@ apps/openmw_test_suite/lua/test_storage.cpp apps/openmw_test_suite/lua/test_ui_content.cpp apps/openmw_test_suite/lua/test_utilpackage.cpp apps/openmw_test_suite/lua/test_inputactions.cpp +apps/openmw_test_suite/lua/test_yaml.cpp apps/openmw_test_suite/misc/test_endianness.cpp apps/openmw_test_suite/misc/test_resourcehelpers.cpp apps/openmw_test_suite/misc/test_stringops.cpp diff --git a/CI/install_debian_deps.sh b/CI/install_debian_deps.sh index b7784cf3f0..d29f16f55f 100755 --- a/CI/install_debian_deps.sh +++ b/CI/install_debian_deps.sh @@ -36,7 +36,7 @@ declare -rA GROUPED_DEPS=( libsdl2-dev libqt5opengl5-dev qttools5-dev qttools5-dev-tools libopenal-dev libunshield-dev libtinyxml-dev libbullet-dev liblz4-dev libpng-dev libjpeg-dev libluajit-5.1-dev librecast-dev libsqlite3-dev ca-certificates libicu-dev - libyaml-cpp-dev + libyaml-cpp-dev libqt5svg5 libqt5svg5-dev " # These dependencies can alternatively be built and linked statically. diff --git a/CMakeLists.txt b/CMakeLists.txt index d62cda4f2b..b478a799eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,14 @@ if(OPENMW_GL4ES_MANUAL_INIT) add_definitions(-DOPENMW_GL4ES_MANUAL_INIT) endif() +if (APPLE OR WIN32) + set(DEPLOY_QT_TRANSLATIONS_DEFAULT ON) +else () + set(DEPLOY_QT_TRANSLATIONS_DEFAULT OFF) +endif () + +option(DEPLOY_QT_TRANSLATIONS "Deploy standard Qt translations to resources folder. Needed when OpenMW applications are deployed with Qt libraries" ${DEPLOY_QT_TRANSLATIONS_DEFAULT}) + # Apps and tools option(BUILD_OPENMW "Build OpenMW" ON) option(BUILD_LAUNCHER "Build Launcher" ON) @@ -36,6 +44,7 @@ option(BUILD_BENCHMARKS "Build benchmarks with Google Benchmark" OFF) option(BUILD_NAVMESHTOOL "Build navmesh tool" ON) option(BUILD_BULLETOBJECTTOOL "Build Bullet object tool" ON) option(BUILD_OPENCS_TESTS "Build OpenMW Construction Set tests" OFF) +option(PRECOMPILE_HEADERS_WITH_MSVC "Precompile most common used headers with MSVC (alternative to ccache)" ON) set(OpenGL_GL_PREFERENCE LEGACY) # Use LEGACY as we use GL2; GLNVD is for GL3 and up. @@ -72,7 +81,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 53) +set(OPENMW_LUA_API_REVISION 59) set(OPENMW_POSTPROCESSING_API_REVISION 1) set(OPENMW_VERSION_COMMITHASH "") @@ -81,7 +90,7 @@ set(OPENMW_VERSION_COMMITDATE "") set(OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}") -set(OPENMW_DOC_BASEURL "https://openmw.readthedocs.io/en/stable/") +set(OPENMW_DOC_BASEURL "https://openmw.readthedocs.io/en/") set(GIT_CHECKOUT FALSE) if(EXISTS ${PROJECT_SOURCE_DIR}/.git) @@ -182,6 +191,22 @@ if (MSVC) add_compile_options(/bigobj) add_compile_options(/Zc:__cplusplus) + + if (CMAKE_CXX_COMPILER_LAUNCHER OR CMAKE_C_COMPILER_LAUNCHER) + if (CMAKE_GENERATOR MATCHES "Visual Studio") + message(STATUS "A compiler launcher was specified, but will be unused by the current generator (${CMAKE_GENERATOR})") + else() + foreach (config_lower ${CMAKE_CONFIGURATION_TYPES}) + string(TOUPPER "${config_lower}" config) + if (CMAKE_C_COMPILER_LAUNCHER STREQUAL "ccache") + string(REPLACE "/Zi" "/Z7" CMAKE_C_FLAGS_${config} "${CMAKE_C_FLAGS_${config}}") + endif() + if (CMAKE_CXX_COMPILER_LAUNCHER STREQUAL "ccache") + string(REPLACE "/Zi" "/Z7" CMAKE_CXX_FLAGS_${config} "${CMAKE_CXX_FLAGS_${config}}") + endif() + endforeach() + endif() + endif() endif() # Set up common paths @@ -209,7 +234,7 @@ else() endif(APPLE) if (WIN32) - option(USE_DEBUG_CONSOLE "whether a debug console should be enabled for debug builds, if false debug output is redirected to Visual Studio output" ON) + option(USE_DEBUG_CONSOLE "Whether a console should be displayed if OpenMW isn't launched from the command line. Does not affect the Release configuration." ON) endif() if(MSVC) @@ -224,9 +249,9 @@ find_package(LZ4 REQUIRED) if (USE_QT) find_package(QT REQUIRED COMPONENTS Core NAMES Qt6 Qt5) if (QT_VERSION_MAJOR VERSION_EQUAL 5) - find_package(Qt5 5.15 COMPONENTS Core Widgets Network OpenGL LinguistTools REQUIRED) + find_package(Qt5 5.15 COMPONENTS Core Widgets Network OpenGL LinguistTools Svg REQUIRED) else() - find_package(Qt6 COMPONENTS Core Widgets Network OpenGL OpenGLWidgets LinguistTools REQUIRED) + find_package(Qt6 COMPONENTS Core Widgets Network OpenGL OpenGLWidgets LinguistTools Svg REQUIRED) endif() message(STATUS "Using Qt${QT_VERSION}") endif() @@ -685,9 +710,8 @@ if (WIN32) if (USE_DEBUG_CONSOLE AND BUILD_OPENMW) set_target_properties(openmw PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE") set_target_properties(openmw PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:CONSOLE") - set_target_properties(openmw PROPERTIES COMPILE_DEFINITIONS $<$:_CONSOLE>) elseif (BUILD_OPENMW) - # Turn off debug console, debug output will be written to visual studio output instead + # Turn off implicit console, you won't see stdout unless launching OpenMW from a command line shell or look at openmw.log set_target_properties(openmw PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:WINDOWS") set_target_properties(openmw PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:WINDOWS") endif() @@ -710,67 +734,66 @@ if (WIN32) ) foreach(d ${WARNINGS_DISABLE}) - set(WARNINGS "${WARNINGS} /wd${d}") + list(APPEND WARNINGS "/wd${d}") endforeach(d) if(OPENMW_MSVC_WERROR) - set(WARNINGS "${WARNINGS} /WX") + list(APPEND WARNINGS "/WX") endif() - set_target_properties(components PROPERTIES COMPILE_FLAGS "${WARNINGS}") - set_target_properties(osg-ffmpeg-videoplayer PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(components PRIVATE ${WARNINGS}) + target_compile_options(osg-ffmpeg-videoplayer PRIVATE ${WARNINGS}) if (MSVC_VERSION GREATER_EQUAL 1915 AND MSVC_VERSION LESS 1920) target_compile_definitions(components INTERFACE _ENABLE_EXTENDED_ALIGNED_STORAGE) endif() if (BUILD_BSATOOL) - set_target_properties(bsatool PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(bsatool PRIVATE ${WARNINGS}) endif() if (BUILD_ESMTOOL) - set_target_properties(esmtool PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(esmtool PRIVATE ${WARNINGS}) endif() if (BUILD_ESSIMPORTER) - set_target_properties(openmw-essimporter PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(openmw-essimporter PRIVATE ${WARNINGS}) endif() if (BUILD_LAUNCHER) - set_target_properties(openmw-launcher PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(openmw-launcher PRIVATE ${WARNINGS}) endif() if (BUILD_MWINIIMPORTER) - set_target_properties(openmw-iniimporter PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(openmw-iniimporter PRIVATE ${WARNINGS}) endif() if (BUILD_OPENCS) - set_target_properties(openmw-cs PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(openmw-cs PRIVATE ${WARNINGS}) endif() if (BUILD_OPENMW) - set_target_properties(openmw PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(openmw PRIVATE ${WARNINGS}) endif() if (BUILD_WIZARD) - set_target_properties(openmw-wizard PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(openmw-wizard PRIVATE ${WARNINGS}) endif() if (BUILD_UNITTESTS) - set_target_properties(openmw_test_suite PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(openmw_test_suite PRIVATE ${WARNINGS}) endif() if (BUILD_BENCHMARKS) - set_target_properties(openmw_detournavigator_navmeshtilescache_benchmark PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(openmw_detournavigator_navmeshtilescache_benchmark PRIVATE ${WARNINGS}) endif() if (BUILD_NAVMESHTOOL) - set_target_properties(openmw-navmeshtool PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(openmw-navmeshtool PRIVATE ${WARNINGS}) endif() if (BUILD_BULLETOBJECTTOOL) - set(WARNINGS "${WARNINGS} ${MT_BUILD}") - set_target_properties(openmw-bulletobjecttool PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(openmw-bulletobjecttool PRIVATE ${WARNINGS} ${MT_BUILD}) endif() endif(MSVC) @@ -802,6 +825,18 @@ if (OPENMW_OSX_DEPLOYMENT AND APPLE) get_filename_component(QT_QMACSTYLE_PLUGIN_NAME "${QT_QMACSTYLE_PLUGIN_PATH}" NAME) configure_file("${QT_QMACSTYLE_PLUGIN_PATH}" "${APP_BUNDLE_DIR}/Contents/PlugIns/${QT_QMACSTYLE_PLUGIN_GROUP}/${QT_QMACSTYLE_PLUGIN_NAME}" COPYONLY) + get_property(QT_QSVG_PLUGIN_PATH TARGET Qt${QT_VERSION_MAJOR}::QSvgPlugin PROPERTY LOCATION_RELEASE) + get_filename_component(QT_QSVG_PLUGIN_DIR "${QT_QSVG_PLUGIN_PATH}" DIRECTORY) + get_filename_component(QT_QSVG_PLUGIN_GROUP "${QT_QSVG_PLUGIN_DIR}" NAME) + get_filename_component(QT_QSVG_PLUGIN_NAME "${QT_QSVG_PLUGIN_PATH}" NAME) + configure_file("${QT_QSVG_PLUGIN_PATH}" "${APP_BUNDLE_DIR}/Contents/PlugIns/${QT_QSVG_PLUGIN_GROUP}/${QT_QSVG_PLUGIN_NAME}" COPYONLY) + + get_property(QT_QSVG_ICON_PLUGIN_PATH TARGET Qt${QT_VERSION_MAJOR}::QSvgIconPlugin PROPERTY LOCATION_RELEASE) + get_filename_component(QT_QSVG_ICON_PLUGIN_DIR "${QT_QSVG_ICON_PLUGIN_PATH}" DIRECTORY) + get_filename_component(QT_QSVG_ICON_PLUGIN_GROUP "${QT_QSVG_ICON_PLUGIN_DIR}" NAME) + get_filename_component(QT_QSVG_ICON_PLUGIN_NAME "${QT_QSVG_ICON_PLUGIN_PATH}" NAME) + configure_file("${QT_QSVG_ICON_PLUGIN_PATH}" "${APP_BUNDLE_DIR}/Contents/PlugIns/${QT_QSVG_ICON_PLUGIN_GROUP}/${QT_QSVG_ICON_PLUGIN_NAME}" COPYONLY) + configure_file("${OpenMW_SOURCE_DIR}/files/mac/qt.conf" "${APP_BUNDLE_DIR}/Contents/Resources/qt.conf" COPYONLY) if (BUILD_OPENCS) @@ -809,6 +844,8 @@ if (OPENMW_OSX_DEPLOYMENT AND APPLE) set(OPENCS_BUNDLE_NAME "${OPENCS_BUNDLE_NAME_TMP}.app") configure_file("${QT_COCOA_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/PlugIns/${QT_COCOA_PLUGIN_GROUP}/${QT_COCOA_PLUGIN_NAME}" COPYONLY) configure_file("${QT_QMACSTYLE_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/PlugIns/${QT_QMACSTYLE_PLUGIN_GROUP}/${QT_QMACSTYLE_PLUGIN_NAME}" COPYONLY) + configure_file("${QT_QSVG_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/PlugIns/${QT_QSVG_PLUGIN_GROUP}/${QT_QSVG_PLUGIN_NAME}" COPYONLY) + configure_file("${QT_QSVG_ICON_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/PlugIns/${QT_QSVG_ICON_PLUGIN_GROUP}/${QT_QSVG_ICON_PLUGIN_NAME}" COPYONLY) configure_file("${OpenMW_SOURCE_DIR}/files/mac/qt.conf" "${OPENCS_BUNDLE_NAME}/Contents/Resources/qt.conf" COPYONLY) endif () @@ -1082,30 +1119,30 @@ if (USE_QT) 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} + COMMAND ${QT_LUPDATE_EXECUTABLE} -locations none ${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} + COMMAND ${QT_LUPDATE_EXECUTABLE} -locations none ${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} + COMMAND ${QT_LUPDATE_EXECUTABLE} -locations none ${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_TRANSLATIONS_PATH "${APP_BUNDLE_DIR}/Contents/Resources/resources/translations") + 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_TRANSLATIONS_PATH "${OpenMW_BINARY_DIR}/$/resources/translations") + set(QT_OPENMW_TRANSLATIONS_PATH "${OpenMW_BINARY_DIR}/$/resources/translations") else () - set(QT_TRANSLATIONS_PATH "${OpenMW_BINARY_DIR}/resources/translations") + set(QT_OPENMW_TRANSLATIONS_PATH "${OpenMW_BINARY_DIR}/resources/translations") endif () endif () @@ -1121,9 +1158,30 @@ if (USE_QT) 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_TRANSLATIONS_PATH} - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${QM_FILES} ${QT_TRANSLATIONS_PATH} + 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 () diff --git a/README.md b/README.md index 95ca19685d..67ba2ce003 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Font Licenses: Current Status -------------- -The main quests in Morrowind, Tribunal and Bloodmoon are all completable. Some issues with side quests are to be expected (but rare). Check the [bug tracker](https://gitlab.com/OpenMW/openmw/issues?label_name%5B%5D=1.0) for a list of issues we need to resolve before the "1.0" release. Even before the "1.0" release however, OpenMW boasts some new [features](https://wiki.openmw.org/index.php?title=Features), such as improved graphics and user interfaces. +The main quests in Morrowind, Tribunal and Bloodmoon are all completable. Some issues with side quests are to be expected (but rare). Check the [bug tracker](https://gitlab.com/OpenMW/openmw/-/issues/?milestone_title=openmw-1.0) for a list of issues we need to resolve before the "1.0" release. Even before the "1.0" release however, OpenMW boasts some new [features](https://wiki.openmw.org/index.php?title=Features), such as improved graphics and user interfaces. Pre-existing modifications created for the original Morrowind engine can be hit-and-miss. The OpenMW script compiler performs more thorough error-checking than Morrowind does, meaning that a mod created for Morrowind may not necessarily run in OpenMW. Some mods also rely on quirky behaviour or engine bugs in order to work. We are considering such compatibility issues on a case-by-case basis - in some cases adding a workaround to OpenMW may be feasible, in other cases fixing the mod will be the only option. If you know of any mods that work or don't work, feel free to add them to the [Mod status](https://wiki.openmw.org/index.php?title=Mod_status) wiki page. diff --git a/apps/benchmarks/detournavigator/CMakeLists.txt b/apps/benchmarks/detournavigator/CMakeLists.txt index 2b3a6abe51..ffe7818a5a 100644 --- a/apps/benchmarks/detournavigator/CMakeLists.txt +++ b/apps/benchmarks/detournavigator/CMakeLists.txt @@ -5,7 +5,7 @@ if (UNIX AND NOT APPLE) target_link_libraries(openmw_detournavigator_navmeshtilescache_benchmark ${CMAKE_THREAD_LIBS_INIT}) endif() -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw_detournavigator_navmeshtilescache_benchmark PRIVATE ) endif() diff --git a/apps/benchmarks/detournavigator/navmeshtilescache.cpp b/apps/benchmarks/detournavigator/navmeshtilescache.cpp index 746739c856..26873d9a03 100644 --- a/apps/benchmarks/detournavigator/navmeshtilescache.cpp +++ b/apps/benchmarks/detournavigator/navmeshtilescache.cpp @@ -182,7 +182,7 @@ namespace for (auto _ : state) { const auto& key = keys[n++ % keys.size()]; - const auto result = cache.get(key.mAgentBounds, key.mTilePosition, key.mRecastMesh); + auto result = cache.get(key.mAgentBounds, key.mTilePosition, key.mRecastMesh); benchmark::DoNotOptimize(result); } } @@ -241,7 +241,7 @@ namespace while (state.KeepRunning()) { const auto& key = keys[n++ % keys.size()]; - const auto result = cache.set( + auto result = cache.set( key.mAgentBounds, key.mTilePosition, key.mRecastMesh, std::make_unique()); benchmark::DoNotOptimize(result); } diff --git a/apps/benchmarks/esm/CMakeLists.txt b/apps/benchmarks/esm/CMakeLists.txt index 74870ceda1..9b5afd649d 100644 --- a/apps/benchmarks/esm/CMakeLists.txt +++ b/apps/benchmarks/esm/CMakeLists.txt @@ -5,7 +5,7 @@ if (UNIX AND NOT APPLE) target_link_libraries(openmw_esm_refid_benchmark ${CMAKE_THREAD_LIBS_INIT}) endif() -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw_esm_refid_benchmark PRIVATE ) endif() diff --git a/apps/benchmarks/settings/CMakeLists.txt b/apps/benchmarks/settings/CMakeLists.txt index ccdd51eeac..51e2d2b0fd 100644 --- a/apps/benchmarks/settings/CMakeLists.txt +++ b/apps/benchmarks/settings/CMakeLists.txt @@ -8,7 +8,7 @@ if (UNIX AND NOT APPLE) target_link_libraries(openmw_settings_access_benchmark ${CMAKE_THREAD_LIBS_INIT}) endif() -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw_settings_access_benchmark PRIVATE ) endif() diff --git a/apps/benchmarks/settings/access.cpp b/apps/benchmarks/settings/access.cpp index aecac2dac8..7660d0d55e 100644 --- a/apps/benchmarks/settings/access.cpp +++ b/apps/benchmarks/settings/access.cpp @@ -38,7 +38,7 @@ namespace { for (auto _ : state) { - static const float v = Settings::Manager::getFloat("sky blending start", "Fog"); + static float v = Settings::Manager::getFloat("sky blending start", "Fog"); benchmark::DoNotOptimize(v); } } @@ -47,8 +47,8 @@ namespace { for (auto _ : state) { - static const float v1 = Settings::Manager::getFloat("near clip", "Camera"); - static const bool v2 = Settings::Manager::getBool("transparent postpass", "Post Processing"); + static float v1 = Settings::Manager::getFloat("near clip", "Camera"); + static bool v2 = Settings::Manager::getBool("transparent postpass", "Post Processing"); benchmark::DoNotOptimize(v1); benchmark::DoNotOptimize(v2); } @@ -58,9 +58,9 @@ namespace { for (auto _ : state) { - static const float v1 = Settings::Manager::getFloat("near clip", "Camera"); - static const bool v2 = Settings::Manager::getBool("transparent postpass", "Post Processing"); - static const int v3 = Settings::Manager::getInt("reflection detail", "Water"); + static float v1 = Settings::Manager::getFloat("near clip", "Camera"); + static bool v2 = Settings::Manager::getBool("transparent postpass", "Post Processing"); + static int v3 = Settings::Manager::getInt("reflection detail", "Water"); benchmark::DoNotOptimize(v1); benchmark::DoNotOptimize(v2); benchmark::DoNotOptimize(v3); @@ -71,7 +71,8 @@ namespace { for (auto _ : state) { - benchmark::DoNotOptimize(Settings::fog().mSkyBlendingStart.get()); + float v = Settings::fog().mSkyBlendingStart.get(); + benchmark::DoNotOptimize(v); } } @@ -79,8 +80,10 @@ namespace { for (auto _ : state) { - benchmark::DoNotOptimize(Settings::postProcessing().mTransparentPostpass.get()); - benchmark::DoNotOptimize(Settings::camera().mNearClip.get()); + bool v1 = Settings::postProcessing().mTransparentPostpass.get(); + float v2 = Settings::camera().mNearClip.get(); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); } } @@ -88,9 +91,12 @@ namespace { for (auto _ : state) { - benchmark::DoNotOptimize(Settings::postProcessing().mTransparentPostpass.get()); - benchmark::DoNotOptimize(Settings::camera().mNearClip.get()); - benchmark::DoNotOptimize(Settings::water().mReflectionDetail.get()); + bool v1 = Settings::postProcessing().mTransparentPostpass.get(); + float v2 = Settings::camera().mNearClip.get(); + int v3 = Settings::water().mReflectionDetail.get(); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); } } diff --git a/apps/bsatool/CMakeLists.txt b/apps/bsatool/CMakeLists.txt index a567499ac6..e893feb91a 100644 --- a/apps/bsatool/CMakeLists.txt +++ b/apps/bsatool/CMakeLists.txt @@ -18,7 +18,7 @@ if (BUILD_WITH_CODE_COVERAGE) target_link_libraries(bsatool gcov) endif() -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(bsatool PRIVATE diff --git a/apps/bsatool/bsatool.cpp b/apps/bsatool/bsatool.cpp index 28711df929..171e5606c4 100644 --- a/apps/bsatool/bsatool.cpp +++ b/apps/bsatool/bsatool.cpp @@ -329,17 +329,19 @@ int main(int argc, char** argv) switch (bsaVersion) { - case Bsa::BSAVER_COMPRESSED: + case Bsa::BsaVersion::Unknown: + break; + case Bsa::BsaVersion::Uncompressed: + return call(info); + case Bsa::BsaVersion::Compressed: return call(info); - case Bsa::BSAVER_BA2_GNRL: + case Bsa::BsaVersion::BA2GNRL: return call(info); - case Bsa::BSAVER_BA2_DX10: + case Bsa::BsaVersion::BA2DX10: return call(info); - case Bsa::BSAVER_UNCOMPRESSED: - return call(info); - default: - throw std::runtime_error("Unrecognised BSA archive"); } + + throw std::runtime_error("Unrecognised BSA archive"); } catch (std::exception& e) { diff --git a/apps/bulletobjecttool/CMakeLists.txt b/apps/bulletobjecttool/CMakeLists.txt index 6e6e1cdbb3..d9bba10195 100644 --- a/apps/bulletobjecttool/CMakeLists.txt +++ b/apps/bulletobjecttool/CMakeLists.txt @@ -19,7 +19,7 @@ if (WIN32) install(TARGETS openmw-bulletobjecttool RUNTIME DESTINATION ".") endif() -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw-bulletobjecttool PRIVATE diff --git a/apps/bulletobjecttool/main.cpp b/apps/bulletobjecttool/main.cpp index 884c196e53..4dbdb56350 100644 --- a/apps/bulletobjecttool/main.cpp +++ b/apps/bulletobjecttool/main.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -146,7 +147,9 @@ namespace dataDirs.insert(dataDirs.begin(), resDir / "vfs"); const Files::Collections fileCollections(dataDirs); const auto& archives = variables["fallback-archive"].as(); - const auto& contentFiles = variables["content"].as(); + StringsVector contentFiles{ "builtin.omwscripts" }; + const auto& configContentFiles = variables["content"].as(); + contentFiles.insert(contentFiles.end(), configContentFiles.begin(), configContentFiles.end()); Fallback::Map::init(variables["fallback"].as().mMap); @@ -171,7 +174,8 @@ namespace constexpr double expiryDelay = 0; Resource::ImageManager imageManager(&vfs, expiryDelay); Resource::NifFileManager nifFileManager(&vfs, &encoder.getStatelessEncoder()); - Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, expiryDelay); + Resource::BgsmFileManager bgsmFileManager(&vfs, expiryDelay); + Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, &bgsmFileManager, expiryDelay); Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay); Resource::forEachBulletObject( diff --git a/apps/esmtool/CMakeLists.txt b/apps/esmtool/CMakeLists.txt index 6f7fa1a993..d26e2a2128 100644 --- a/apps/esmtool/CMakeLists.txt +++ b/apps/esmtool/CMakeLists.txt @@ -25,7 +25,7 @@ if (BUILD_WITH_CODE_COVERAGE) target_link_libraries(esmtool gcov) endif() -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(esmtool PRIVATE diff --git a/apps/esmtool/labels.cpp b/apps/esmtool/labels.cpp index d0a443de53..83b4a486e1 100644 --- a/apps/esmtool/labels.cpp +++ b/apps/esmtool/labels.cpp @@ -1,5 +1,7 @@ #include "labels.hpp" +#include +#include #include #include #include @@ -571,13 +573,14 @@ std::string_view enchantTypeLabel(int idx) std::string_view ruleFunction(int idx) { - if (idx >= 0 && idx <= 72) + if (idx >= ESM::DialogueCondition::Function_FacReactionLowest + && idx <= ESM::DialogueCondition::Function_PcWerewolfKills) { static constexpr std::string_view ruleFunctions[] = { - "Reaction Low", - "Reaction High", + "Lowest Faction Reaction", + "Highest Faction Reaction", "Rank Requirement", - "NPC? Reputation", + "NPC Reputation", "Health Percent", "Player Reputation", "NPC Level", @@ -647,6 +650,7 @@ std::string_view ruleFunction(int idx) "Flee", "Should Attack", "Werewolf", + "Werewolf Kills", }; return ruleFunctions[idx]; } @@ -987,3 +991,16 @@ std::string recordFlags(uint32_t flags) properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } + +std::string potionFlags(int flags) +{ + std::string properties; + if (flags == 0) + properties += "[None] "; + if (flags & ESM::Potion::Autocalc) + properties += "Autocalc "; + if (flags & (0xFFFFFFFF ^ ESM::Enchantment::Autocalc)) + properties += "Invalid "; + properties += Misc::StringUtils::format("(0x%08X)", flags); + return properties; +} diff --git a/apps/esmtool/labels.hpp b/apps/esmtool/labels.hpp index df6d419ca3..c3a78141b4 100644 --- a/apps/esmtool/labels.hpp +++ b/apps/esmtool/labels.hpp @@ -60,6 +60,7 @@ std::string itemListFlags(int flags); std::string lightFlags(int flags); std::string magicEffectFlags(int flags); std::string npcFlags(int flags); +std::string potionFlags(int flags); std::string raceFlags(int flags); std::string spellFlags(int flags); std::string weaponFlags(int flags); diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index e3b81daf41..cd38dadf3f 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -57,112 +57,82 @@ namespace std::cout << " Cell Name: " << p.mCellName << std::endl; } - std::string ruleString(const ESM::DialInfo::SelectStruct& ss) + std::string ruleString(const ESM::DialogueCondition& ss) { - std::string rule = ss.mSelectRule; + std::string_view type_str = "INVALID"; + std::string_view func_str; - if (rule.length() < 5) - return "INVALID"; - - char type = rule[1]; - char indicator = rule[2]; - - std::string type_str = "INVALID"; - std::string func_str = Misc::StringUtils::format("INVALID=%s", rule.substr(1, 3)); - int func = Misc::StringUtils::toNumeric(rule.substr(2, 2), 0); - - switch (type) + switch (ss.mFunction) { - case '1': - type_str = "Function"; - func_str = std::string(ruleFunction(func)); - break; - case '2': - if (indicator == 's') - type_str = "Global short"; - else if (indicator == 'l') - type_str = "Global long"; - else if (indicator == 'f') - type_str = "Global float"; + case ESM::DialogueCondition::Function_Global: + type_str = "Global"; + func_str = ss.mVariable; break; - case '3': - if (indicator == 's') - type_str = "Local short"; - else if (indicator == 'l') - type_str = "Local long"; - else if (indicator == 'f') - type_str = "Local float"; + case ESM::DialogueCondition::Function_Local: + type_str = "Local"; + func_str = ss.mVariable; break; - case '4': - if (indicator == 'J') - type_str = "Journal"; + case ESM::DialogueCondition::Function_Journal: + type_str = "Journal"; + func_str = ss.mVariable; break; - case '5': - if (indicator == 'I') - type_str = "Item type"; + case ESM::DialogueCondition::Function_Item: + type_str = "Item count"; + func_str = ss.mVariable; break; - case '6': - if (indicator == 'D') - type_str = "NPC Dead"; + case ESM::DialogueCondition::Function_Dead: + type_str = "Dead"; + func_str = ss.mVariable; break; - case '7': - if (indicator == 'X') - type_str = "Not ID"; + case ESM::DialogueCondition::Function_NotId: + type_str = "Not ID"; + func_str = ss.mVariable; break; - case '8': - if (indicator == 'F') - type_str = "Not Faction"; + case ESM::DialogueCondition::Function_NotFaction: + type_str = "Not Faction"; + func_str = ss.mVariable; break; - case '9': - if (indicator == 'C') - type_str = "Not Class"; + case ESM::DialogueCondition::Function_NotClass: + type_str = "Not Class"; + func_str = ss.mVariable; break; - case 'A': - if (indicator == 'R') - type_str = "Not Race"; + case ESM::DialogueCondition::Function_NotRace: + type_str = "Not Race"; + func_str = ss.mVariable; break; - case 'B': - if (indicator == 'L') - type_str = "Not Cell"; + case ESM::DialogueCondition::Function_NotCell: + type_str = "Not Cell"; + func_str = ss.mVariable; break; - case 'C': - if (indicator == 's') - type_str = "Not Local"; + case ESM::DialogueCondition::Function_NotLocal: + type_str = "Not Local"; + func_str = ss.mVariable; break; default: + type_str = "Function"; + func_str = ruleFunction(ss.mFunction); break; } - // Append the variable name to the function string if any. - if (type != '1') - func_str = rule.substr(5); - - // In the previous switch, we assumed that the second char was X - // for all types not qual to one. If this wasn't true, go back to - // the error message. - if (type != '1' && rule[3] != 'X') - func_str = Misc::StringUtils::format("INVALID=%s", rule.substr(1, 3)); - - char oper = rule[4]; - std::string oper_str = "??"; - switch (oper) + std::string_view oper_str = "??"; + switch (ss.mComparison) { - case '0': + case ESM::DialogueCondition::Comp_Eq: oper_str = "=="; break; - case '1': + case ESM::DialogueCondition::Comp_Ne: oper_str = "!="; break; - case '2': + case ESM::DialogueCondition::Comp_Gt: oper_str = "> "; break; - case '3': + case ESM::DialogueCondition::Comp_Ge: oper_str = ">="; break; - case '4': + case ESM::DialogueCondition::Comp_Ls: oper_str = "< "; break; - case '5': + case ESM::DialogueCondition::Comp_Le: oper_str = "<="; break; default: @@ -170,7 +140,7 @@ namespace } std::ostringstream stream; - stream << ss.mValue; + std::visit([&](auto value) { stream << value; }, ss.mValue); std::string result = Misc::StringUtils::format("%-12s %-32s %2s %s", type_str, func_str, oper_str, stream.str()); @@ -180,22 +150,23 @@ namespace void printEffectList(const ESM::EffectList& effects) { int i = 0; - for (const ESM::ENAMstruct& effect : effects.mList) + for (const ESM::IndexedENAMstruct& effect : effects.mList) { - std::cout << " Effect[" << i << "]: " << magicEffectLabel(effect.mEffectID) << " (" << effect.mEffectID - << ")" << std::endl; - if (effect.mSkill != -1) - std::cout << " Skill: " << skillLabel(effect.mSkill) << " (" << (int)effect.mSkill << ")" + std::cout << " Effect[" << i << "]: " << magicEffectLabel(effect.mData.mEffectID) << " (" + << effect.mData.mEffectID << ")" << std::endl; + if (effect.mData.mSkill != -1) + std::cout << " Skill: " << skillLabel(effect.mData.mSkill) << " (" << (int)effect.mData.mSkill << ")" << std::endl; - if (effect.mAttribute != -1) - std::cout << " Attribute: " << attributeLabel(effect.mAttribute) << " (" << (int)effect.mAttribute - << ")" << std::endl; - std::cout << " Range: " << rangeTypeLabel(effect.mRange) << " (" << effect.mRange << ")" << std::endl; + if (effect.mData.mAttribute != -1) + std::cout << " Attribute: " << attributeLabel(effect.mData.mAttribute) << " (" + << (int)effect.mData.mAttribute << ")" << std::endl; + std::cout << " Range: " << rangeTypeLabel(effect.mData.mRange) << " (" << effect.mData.mRange << ")" + << std::endl; // Area is always zero if range type is "Self" - if (effect.mRange != ESM::RT_Self) - std::cout << " Area: " << effect.mArea << std::endl; - std::cout << " Duration: " << effect.mDuration << std::endl; - std::cout << " Magnitude: " << effect.mMagnMin << "-" << effect.mMagnMax << std::endl; + if (effect.mData.mRange != ESM::RT_Self) + std::cout << " Area: " << effect.mData.mArea << std::endl; + std::cout << " Duration: " << effect.mData.mDuration << std::endl; + std::cout << " Magnitude: " << effect.mData.mMagnMin << "-" << effect.mData.mMagnMax << std::endl; i++; } } @@ -479,7 +450,7 @@ namespace EsmTool std::cout << " Script: " << mData.mScript << std::endl; std::cout << " Weight: " << mData.mData.mWeight << std::endl; std::cout << " Value: " << mData.mData.mValue << std::endl; - std::cout << " AutoCalc: " << mData.mData.mAutoCalc << std::endl; + std::cout << " Flags: " << potionFlags(mData.mData.mFlags) << std::endl; printEffectList(mData.mEffects); std::cout << " Deleted: " << mIsDeleted << std::endl; } @@ -612,7 +583,6 @@ namespace EsmTool } else std::cout << " Map Color: " << Misc::StringUtils::format("0x%08X", mData.mMapColor) << std::endl; - std::cout << " Water Level Int: " << mData.mWaterInt << std::endl; std::cout << " RefId counter: " << mData.mRefNumCounter << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl; } @@ -722,9 +692,6 @@ namespace EsmTool std::cout << " AI Fight:" << (int)mData.mAiData.mFight << std::endl; std::cout << " AI Flee:" << (int)mData.mAiData.mFlee << std::endl; std::cout << " AI Alarm:" << (int)mData.mAiData.mAlarm << std::endl; - std::cout << " AI U1:" << (int)mData.mAiData.mU1 << std::endl; - std::cout << " AI U2:" << (int)mData.mAiData.mU2 << std::endl; - std::cout << " AI U3:" << (int)mData.mAiData.mU3 << std::endl; std::cout << " AI Services:" << Misc::StringUtils::format("0x%08X", mData.mAiData.mServices) << std::endl; for (const ESM::AIPackage& package : mData.mAiPackage.mList) @@ -843,10 +810,9 @@ namespace EsmTool std::cout << " Quest Status: " << questStatusLabel(mData.mQuestStatus) << " (" << mData.mQuestStatus << ")" << std::endl; - std::cout << " Unknown1: " << mData.mData.mUnknown1 << std::endl; - std::cout << " Unknown2: " << (int)mData.mData.mUnknown2 << std::endl; + std::cout << " Type: " << dialogTypeLabel(mData.mData.mType) << std::endl; - for (const ESM::DialInfo::SelectStruct& rule : mData.mSelects) + for (const auto& rule : mData.mSelects) std::cout << " Select Rule: " << ruleString(rule) << std::endl; if (!mData.mResultScript.empty()) @@ -901,9 +867,6 @@ namespace EsmTool if (const ESM::Land::LandData* data = mData.getLandData(mData.mDataTypes)) { std::cout << " Height Offset: " << data->mHeightOffset << std::endl; - // Lots of missing members. - std::cout << " Unknown1: " << data->mUnk1 << std::endl; - std::cout << " Unknown2: " << static_cast(data->mUnk2) << std::endl; } mData.unloadData(); std::cout << " Deleted: " << mIsDeleted << std::endl; @@ -1115,9 +1078,6 @@ namespace EsmTool std::cout << " AI Fight:" << (int)mData.mAiData.mFight << std::endl; std::cout << " AI Flee:" << (int)mData.mAiData.mFlee << std::endl; std::cout << " AI Alarm:" << (int)mData.mAiData.mAlarm << std::endl; - std::cout << " AI U1:" << (int)mData.mAiData.mU1 << std::endl; - std::cout << " AI U2:" << (int)mData.mAiData.mU2 << std::endl; - std::cout << " AI U3:" << (int)mData.mAiData.mU3 << std::endl; std::cout << " AI Services:" << Misc::StringUtils::format("0x%08X", mData.mAiData.mServices) << std::endl; for (const ESM::AIPackage& package : mData.mAiPackage.mList) @@ -1144,7 +1104,6 @@ namespace EsmTool std::cout << " Coordinates: (" << point.mX << "," << point.mY << "," << point.mZ << ")" << std::endl; std::cout << " Auto-Generated: " << (int)point.mAutogenerated << std::endl; std::cout << " Connections: " << (int)point.mConnectionNum << std::endl; - std::cout << " Unknown: " << point.mUnknown << std::endl; i++; } diff --git a/apps/essimporter/CMakeLists.txt b/apps/essimporter/CMakeLists.txt index c6c98791e3..5dfb747aee 100644 --- a/apps/essimporter/CMakeLists.txt +++ b/apps/essimporter/CMakeLists.txt @@ -47,7 +47,7 @@ if (WIN32) INSTALL(TARGETS openmw-essimporter RUNTIME DESTINATION ".") endif(WIN32) -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw-essimporter PRIVATE diff --git a/apps/essimporter/converter.cpp b/apps/essimporter/converter.cpp index 4c4bd1e438..ebb0c9d281 100644 --- a/apps/essimporter/converter.cpp +++ b/apps/essimporter/converter.cpp @@ -232,7 +232,7 @@ namespace ESSImport esm.skip(4); } - esm.getExact(nam8, 32); + esm.getT(nam8); newcell.mFogOfWar.reserve(16 * 16); for (int x = 0; x < 16; ++x) diff --git a/apps/essimporter/importcellref.cpp b/apps/essimporter/importcellref.cpp index 56e888d3f6..9e8e9a6948 100644 --- a/apps/essimporter/importcellref.cpp +++ b/apps/essimporter/importcellref.cpp @@ -1,10 +1,30 @@ #include "importcellref.hpp" #include +#include + #include namespace ESSImport { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mUnknown, v.mFlags, v.mBreathMeter, v.mUnknown2, v.mDynamic, v.mUnknown3, v.mAttributes, v.mMagicEffects, + v.mUnknown4, v.mGoldPool, v.mCountDown, v.mUnknown5); + } + + template T> + void decompose(T&& v, const auto& f) + { + f(v.mUnknown1, v.mFlags, v.mUnknown2, v.mCorpseClearCountdown, v.mUnknown3); + } + + template T> + void decompose(T&& v, const auto& f) + { + f(v.mGroupIndex, v.mUnknown, v.mTime); + } void CellRef::load(ESM::ESMReader& esm) { @@ -45,14 +65,9 @@ namespace ESSImport bool isDeleted = false; ESM::CellRef::loadData(esm, isDeleted); - mActorData.mHasACDT - = esm.getHNOT("ACDT", mActorData.mACDT.mUnknown, mActorData.mACDT.mFlags, mActorData.mACDT.mBreathMeter, - mActorData.mACDT.mUnknown2, mActorData.mACDT.mDynamic, mActorData.mACDT.mUnknown3, - mActorData.mACDT.mAttributes, mActorData.mACDT.mMagicEffects, mActorData.mACDT.mUnknown4, - mActorData.mACDT.mGoldPool, mActorData.mACDT.mCountDown, mActorData.mACDT.mUnknown5); + mActorData.mHasACDT = esm.getOptionalComposite("ACDT", mActorData.mACDT); - mActorData.mHasACSC = esm.getHNOT("ACSC", mActorData.mACSC.mUnknown1, mActorData.mACSC.mFlags, - mActorData.mACSC.mUnknown2, mActorData.mACSC.mCorpseClearCountdown, mActorData.mACSC.mUnknown3); + mActorData.mHasACSC = esm.getOptionalComposite("ACSC", mActorData.mACSC); if (esm.isNextSub("ACSL")) esm.skipHSubSize(112); @@ -127,8 +142,7 @@ namespace ESSImport if (esm.isNextSub("ND3D")) esm.skipHSub(); - mActorData.mHasANIS - = esm.getHNOT("ANIS", mActorData.mANIS.mGroupIndex, mActorData.mANIS.mUnknown, mActorData.mANIS.mTime); + mActorData.mHasANIS = esm.getOptionalComposite("ANIS", mActorData.mANIS); if (esm.isNextSub("LVCR")) { @@ -146,7 +160,7 @@ namespace ESSImport // I've seen DATA *twice* on a creature record, and with the exact same content too! weird // alarmvoi0000.ess for (int i = 0; i < 2; ++i) - esm.getHNOT("DATA", mPos.pos, mPos.rot); + esm.getOptionalComposite("DATA", mPos); mDeleted = 0; if (esm.isNextSub("DELE")) diff --git a/apps/essimporter/importer.cpp b/apps/essimporter/importer.cpp index 76b685c8a3..5cc9a8259b 100644 --- a/apps/essimporter/importer.cpp +++ b/apps/essimporter/importer.cpp @@ -135,7 +135,7 @@ namespace ESSImport sub.mFileOffset = esm.getFileOffset(); sub.mName = esm.retSubName().toString(); sub.mData.resize(esm.getSubSize()); - esm.getExact(&sub.mData[0], sub.mData.size()); + esm.getExact(sub.mData.data(), sub.mData.size()); rec.mSubrecords.push_back(sub); } file.mRecords.push_back(rec); diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index 0c888afe9d..91b985948d 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -83,7 +83,7 @@ target_link_libraries(openmw-launcher components_qt ) -target_link_libraries(openmw-launcher Qt::Widgets Qt::Core) +target_link_libraries(openmw-launcher Qt::Widgets Qt::Core Qt::Svg) if (BUILD_WITH_CODE_COVERAGE) target_compile_options(openmw-launcher PRIVATE --coverage) @@ -94,7 +94,7 @@ if(USE_QT) set_property(TARGET openmw-launcher PROPERTY AUTOMOC ON) endif(USE_QT) -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw-launcher PRIVATE diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 9b14a91934..f671089bff 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -142,7 +142,7 @@ Launcher::DataFilesPage::DataFilesPage(const Files::ConfigurationManager& cfg, C ui.setupUi(this); setObjectName("DataFilesPage"); mSelector = new ContentSelectorView::ContentSelector(ui.contentSelectorWidget, /*showOMWScripts=*/true); - const QString encoding = mGameSettings.value("encoding", "win1252"); + const QString encoding = mGameSettings.value("encoding", { "win1252" }).value; mSelector->setEncoding(encoding); QVector> languages = { { "English", tr("English") }, { "French", tr("French") }, @@ -163,11 +163,11 @@ Launcher::DataFilesPage::DataFilesPage(const Files::ConfigurationManager& cfg, C connect(ui.directoryInsertButton, &QPushButton::released, this, [this]() { this->addSubdirectories(false); }); connect(ui.directoryUpButton, &QPushButton::released, this, [this]() { this->moveDirectory(-1); }); connect(ui.directoryDownButton, &QPushButton::released, this, [this]() { this->moveDirectory(1); }); - connect(ui.directoryRemoveButton, &QPushButton::released, this, [this]() { this->removeDirectory(); }); + connect(ui.directoryRemoveButton, &QPushButton::released, this, &DataFilesPage::removeDirectory); connect(ui.archiveUpButton, &QPushButton::released, this, [this]() { this->moveArchives(-1); }); connect(ui.archiveDownButton, &QPushButton::released, this, [this]() { this->moveArchives(1); }); - connect( - ui.directoryListWidget->model(), &QAbstractItemModel::rowsMoved, this, [this]() { this->sortDirectories(); }); + connect(ui.directoryListWidget->model(), &QAbstractItemModel::rowsMoved, this, &DataFilesPage::sortDirectories); + connect(ui.archiveListWidget->model(), &QAbstractItemModel::rowsMoved, this, &DataFilesPage::sortArchives); buildView(); loadSettings(); @@ -271,65 +271,79 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) ui.archiveListWidget->clear(); ui.directoryListWidget->clear(); - QStringList directories = mLauncherSettings.getDataDirectoryList(contentModelName); - if (directories.isEmpty()) - directories = mGameSettings.getDataDirs(); + QList directories = mGameSettings.getDataDirs(); + QStringList contentModelDirectories = mLauncherSettings.getDataDirectoryList(contentModelName); + if (!contentModelDirectories.isEmpty()) + { + directories.erase(std::remove_if(directories.begin(), directories.end(), + [&](const Config::SettingValue& dir) { return mGameSettings.isUserSetting(dir); }), + directories.end()); + for (const auto& dir : contentModelDirectories) + directories.push_back({ dir }); + } mDataLocal = mGameSettings.getDataLocal(); if (!mDataLocal.isEmpty()) - directories.insert(0, mDataLocal); + directories.insert(0, { mDataLocal }); - const auto& globalDataDir = mGameSettings.getGlobalDataDir(); - if (!globalDataDir.empty()) - directories.insert(0, Files::pathToQString(globalDataDir)); + const auto& resourcesVfs = mGameSettings.getResourcesVfs(); + if (!resourcesVfs.isEmpty()) + directories.insert(0, { resourcesVfs }); std::unordered_set visitedDirectories; - for (const QString& currentDir : directories) + for (const Config::SettingValue& currentDir : directories) { - // normalize user supplied directories: resolve symlink, convert to native separator, make absolute - const QString canonicalDirPath = QDir(QDir::cleanPath(currentDir)).canonicalPath(); - - if (!visitedDirectories.insert(canonicalDirPath).second) + if (!visitedDirectories.insert(currentDir.value).second) continue; // add new achives files presents in current directory - addArchivesFromDir(currentDir); + addArchivesFromDir(currentDir.value); - QString tooltip; + QStringList tooltip; // add content files presents in current directory - mSelector->addFiles(currentDir, mNewDataDirs.contains(canonicalDirPath)); + mSelector->addFiles(currentDir.value, mNewDataDirs.contains(currentDir.value)); // add current directory to list - ui.directoryListWidget->addItem(currentDir); + ui.directoryListWidget->addItem(currentDir.originalRepresentation); auto row = ui.directoryListWidget->count() - 1; auto* item = ui.directoryListWidget->item(row); + item->setData(Qt::UserRole, QVariant::fromValue(currentDir)); + + if (currentDir.value != currentDir.originalRepresentation) + tooltip << tr("Resolved as %1").arg(currentDir.value); // Display new content with custom formatting - if (mNewDataDirs.contains(canonicalDirPath)) + if (mNewDataDirs.contains(currentDir.value)) { - tooltip += tr("Will be added to the current profile"); + tooltip << tr("Will be added to the current profile"); QFont font = item->font(); font.setBold(true); font.setItalic(true); item->setFont(font); } - // deactivate data-local and global data directory: they are always included - if (currentDir == mDataLocal || Files::pathFromQString(currentDir) == globalDataDir) + // deactivate data-local and resources/vfs: they are always included + // same for ones from non-user config files + if (currentDir.value == mDataLocal || currentDir.value == resourcesVfs + || !mGameSettings.isUserSetting(currentDir)) { auto flags = item->flags(); item->setFlags(flags & ~(Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled)); + if (currentDir.value == mDataLocal) + tooltip << tr("This is the data-local directory and cannot be disabled"); + else if (currentDir.value == resourcesVfs) + tooltip << tr("This directory is part of OpenMW and cannot be disabled"); + else + tooltip << tr("This directory is enabled in an openmw.cfg other than the user one"); } // Add a "data file" icon if the directory contains a content file - if (mSelector->containsDataFiles(currentDir)) + if (mSelector->containsDataFiles(currentDir.value)) { item->setIcon(QIcon(":/images/openmw-plugin.png")); - if (!tooltip.isEmpty()) - tooltip += "\n"; - tooltip += tr("Contains content file(s)"); + tooltip << tr("Contains content file(s)"); } else { @@ -339,19 +353,26 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) auto emptyIcon = QIcon(pixmap); item->setIcon(emptyIcon); } - item->setToolTip(tooltip); + item->setToolTip(tooltip.join('\n')); } mSelector->sortFiles(); - QStringList selectedArchives = mLauncherSettings.getArchiveList(contentModelName); - if (selectedArchives.isEmpty()) - selectedArchives = mGameSettings.getArchiveList(); + QList selectedArchives = mGameSettings.getArchiveList(); + QStringList contentModelSelectedArchives = mLauncherSettings.getArchiveList(contentModelName); + if (contentModelSelectedArchives.isEmpty()) + { + selectedArchives.erase(std::remove_if(selectedArchives.begin(), selectedArchives.end(), + [&](const Config::SettingValue& dir) { return mGameSettings.isUserSetting(dir); }), + selectedArchives.end()); + for (const auto& dir : contentModelSelectedArchives) + selectedArchives.push_back({ dir }); + } // sort and tick BSA according to profile int row = 0; for (const auto& archive : selectedArchives) { - const auto match = ui.archiveListWidget->findItems(archive, Qt::MatchExactly); + const auto match = ui.archiveListWidget->findItems(archive.value, Qt::MatchExactly); if (match.isEmpty()) continue; const auto name = match[0]->text(); @@ -359,9 +380,25 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) ui.archiveListWidget->takeItem(oldrow); ui.archiveListWidget->insertItem(row, name); ui.archiveListWidget->item(row)->setCheckState(Qt::Checked); + ui.archiveListWidget->item(row)->setData(Qt::UserRole, QVariant::fromValue(archive)); + if (!mGameSettings.isUserSetting(archive)) + { + auto flags = ui.archiveListWidget->item(row)->flags(); + ui.archiveListWidget->item(row)->setFlags( + flags & ~(Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled)); + ui.archiveListWidget->item(row)->setToolTip( + tr("This archive is enabled in an openmw.cfg other than the user one")); + } row++; } + QStringList nonUserContent; + for (const auto& content : mGameSettings.getContentList()) + { + if (!mGameSettings.isUserSetting(content)) + nonUserContent.push_back(content.value); + } + mSelector->setNonUserContent(nonUserContent); mSelector->setProfileContent(mLauncherSettings.getContentListFiles(contentModelName)); } @@ -389,7 +426,19 @@ void Launcher::DataFilesPage::saveSettings(const QString& profile) { fileNames.append(item->fileName()); } - mLauncherSettings.setContentList(profileName, dirList, selectedArchivePaths(), fileNames); + QStringList dirNames; + for (const auto& dir : dirList) + { + if (mGameSettings.isUserSetting(dir)) + dirNames.push_back(dir.originalRepresentation); + } + QStringList archiveNames; + for (const auto& archive : selectedArchivePaths()) + { + if (mGameSettings.isUserSetting(archive)) + archiveNames.push_back(archive.originalRepresentation); + } + mLauncherSettings.setContentList(profileName, dirNames, archiveNames, fileNames); mGameSettings.setContentList(dirList, selectedArchivePaths(), fileNames); QString language(mSelector->languageBox()->currentData().toString()); @@ -398,38 +447,38 @@ void Launcher::DataFilesPage::saveSettings(const QString& profile) if (language == QLatin1String("Polish")) { - mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1250")); + mGameSettings.setValue(QLatin1String("encoding"), { "win1250" }); } else if (language == QLatin1String("Russian")) { - mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1251")); + mGameSettings.setValue(QLatin1String("encoding"), { "win1251" }); } else { - mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1252")); + mGameSettings.setValue(QLatin1String("encoding"), { "win1252" }); } } -QStringList Launcher::DataFilesPage::selectedDirectoriesPaths() const +QList Launcher::DataFilesPage::selectedDirectoriesPaths() const { - QStringList dirList; + QList dirList; for (int i = 0; i < ui.directoryListWidget->count(); ++i) { const QListWidgetItem* item = ui.directoryListWidget->item(i); if (item->flags() & Qt::ItemIsEnabled) - dirList.append(item->text()); + dirList.append(qvariant_cast(item->data(Qt::UserRole))); } return dirList; } -QStringList Launcher::DataFilesPage::selectedArchivePaths() const +QList Launcher::DataFilesPage::selectedArchivePaths() const { - QStringList archiveList; + QList archiveList; for (int i = 0; i < ui.archiveListWidget->count(); ++i) { const QListWidgetItem* item = ui.archiveListWidget->item(i); if (item->checkState() == Qt::Checked) - archiveList.append(item->text()); + archiveList.append(qvariant_cast(item->data(Qt::UserRole))); } return archiveList; } @@ -583,7 +632,20 @@ void Launcher::DataFilesPage::on_cloneProfileAction_triggered() if (profile.isEmpty()) return; - mLauncherSettings.setContentList(profile, selectedDirectoriesPaths(), selectedArchivePaths(), selectedFilePaths()); + const auto& dirList = selectedDirectoriesPaths(); + QStringList dirNames; + for (const auto& dir : dirList) + { + if (mGameSettings.isUserSetting(dir)) + dirNames.push_back(dir.originalRepresentation); + } + QStringList archiveNames; + for (const auto& archive : selectedArchivePaths()) + { + if (mGameSettings.isUserSetting(archive)) + archiveNames.push_back(archive.originalRepresentation); + } + mLauncherSettings.setContentList(profile, dirNames, archiveNames, selectedFilePaths()); addProfile(profile, true); } @@ -650,6 +712,9 @@ void Launcher::DataFilesPage::addSubdirectories(bool append) if (!ui.directoryListWidget->findItems(rootPath, Qt::MatchFixedString).isEmpty()) return; ui.directoryListWidget->addItem(rootPath); + auto row = ui.directoryListWidget->count() - 1; + auto* item = ui.directoryListWidget->item(row); + item->setData(Qt::UserRole, QVariant::fromValue(Config::SettingValue{ rootPath })); mNewDataDirs.push_back(rootPath); refreshDataFilesView(); return; @@ -679,8 +744,11 @@ void Launcher::DataFilesPage::addSubdirectories(bool append) const auto* dir = select.dirListWidget->item(i); if (dir->checkState() == Qt::Checked) { - ui.directoryListWidget->insertItem(selectedRow++, dir->text()); + ui.directoryListWidget->insertItem(selectedRow, dir->text()); + auto* item = ui.directoryListWidget->item(selectedRow); + item->setData(Qt::UserRole, QVariant::fromValue(Config::SettingValue{ dir->text() })); mNewDataDirs.push_back(dir->text()); + ++selectedRow; } } @@ -702,6 +770,21 @@ void Launcher::DataFilesPage::sortDirectories() } } +void Launcher::DataFilesPage::sortArchives() +{ + // Ensure disabled entries (aka ones from non-user config files) are always at the top. + for (auto i = 1; i < ui.archiveListWidget->count(); ++i) + { + if (!(ui.archiveListWidget->item(i)->flags() & Qt::ItemIsEnabled) + && (ui.archiveListWidget->item(i - 1)->flags() & Qt::ItemIsEnabled)) + { + const auto item = ui.archiveListWidget->takeItem(i); + ui.archiveListWidget->insertItem(i - 1, item); + ui.archiveListWidget->setCurrentRow(i); + } + } +} + void Launcher::DataFilesPage::moveDirectory(int step) { int selectedRow = ui.directoryListWidget->currentRow(); @@ -784,9 +867,8 @@ bool Launcher::DataFilesPage::moveArchive(QListWidgetItem* listItem, int step) if (selectedRow == -1 || newRow < 0 || newRow > ui.archiveListWidget->count() - 1) return false; - const QListWidgetItem* item = ui.archiveListWidget->takeItem(selectedRow); - - addArchive(item->text(), item->checkState(), newRow); + QListWidgetItem* item = ui.archiveListWidget->takeItem(selectedRow); + ui.archiveListWidget->insertItem(newRow, item); ui.archiveListWidget->setCurrentRow(newRow); return true; } @@ -797,6 +879,7 @@ void Launcher::DataFilesPage::addArchive(const QString& name, Qt::CheckState sel row = ui.archiveListWidget->count(); ui.archiveListWidget->insertItem(row, name); ui.archiveListWidget->item(row)->setCheckState(selected); + ui.archiveListWidget->item(row)->setData(Qt::UserRole, QVariant::fromValue(Config::SettingValue{ name })); if (mKnownArchives.filter(name).isEmpty()) // XXX why contains doesn't work here ??? { auto item = ui.archiveListWidget->item(row); @@ -819,7 +902,7 @@ void Launcher::DataFilesPage::addArchivesFromDir(const QString& path) for (const auto& fileinfo : dir.entryInfoList(archiveFilter)) { const auto absPath = fileinfo.absoluteFilePath(); - if (Bsa::BSAFile::detectVersion(Files::pathFromQString(absPath)) == Bsa::BSAVER_UNKNOWN) + if (Bsa::BSAFile::detectVersion(Files::pathFromQString(absPath)) == Bsa::BsaVersion::Unknown) continue; const auto fileName = fileinfo.fileName(); diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index e568137e8f..1b92354dab 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -25,6 +25,7 @@ namespace ContentSelectorView namespace Config { class GameSettings; + struct SettingValue; class LauncherSettings; } @@ -73,6 +74,7 @@ namespace Launcher void updateCloneProfileOkButton(const QString& text); void addSubdirectories(bool append); void sortDirectories(); + void sortArchives(); void removeDirectory(); void moveArchives(int step); void moveDirectory(int step); @@ -146,8 +148,8 @@ namespace Launcher * @return the file paths of all selected content files */ QStringList selectedFilePaths() const; - QStringList selectedArchivePaths() const; - QStringList selectedDirectoriesPaths() const; + QList selectedArchivePaths() const; + QList selectedDirectoriesPaths() const; }; } #endif diff --git a/apps/launcher/importpage.cpp b/apps/launcher/importpage.cpp index 44c5867c0d..47075db1bc 100644 --- a/apps/launcher/importpage.cpp +++ b/apps/launcher/importpage.cpp @@ -37,9 +37,9 @@ Launcher::ImportPage::ImportPage(const Files::ConfigurationManager& cfg, Config: // Detect Morrowind configuration files QStringList iniPaths; - for (const QString& path : mGameSettings.getDataDirs()) + for (const auto& path : mGameSettings.getDataDirs()) { - QDir dir(path); + QDir dir(path.value); dir.setPath(dir.canonicalPath()); // Resolve symlinks if (dir.exists(QString("Morrowind.ini"))) @@ -125,7 +125,7 @@ void Launcher::ImportPage::on_importerButton_clicked() arguments.append(QString("--fonts")); arguments.append(QString("--encoding")); - arguments.append(mGameSettings.value(QString("encoding"), QString("win1252"))); + arguments.append(mGameSettings.value(QString("encoding"), { "win1252" }).value); arguments.append(QString("--ini")); arguments.append(settingsComboBox->currentText()); arguments.append(QString("--cfg")); diff --git a/apps/launcher/main.cpp b/apps/launcher/main.cpp index 3cbd1f5092..7fdcac77ab 100644 --- a/apps/launcher/main.cpp +++ b/apps/launcher/main.cpp @@ -1,7 +1,6 @@ #include #include -#include #include #include @@ -9,6 +8,7 @@ #include #include #include +#include #include #ifdef MAC_OS_X_VERSION_MIN_REQUIRED @@ -41,16 +41,7 @@ int runLauncher(int argc, char* argv[]) resourcesPath = Files::pathToQString(variables["resources"].as().u8string()); } - // Internationalization - QString locale = QLocale::system().name().section('_', 0, 0); - - QTranslator appTranslator; - appTranslator.load(resourcesPath + "/translations/launcher_" + locale + ".qm"); - app.installTranslator(&appTranslator); - - QTranslator componentsAppTranslator; - componentsAppTranslator.load(resourcesPath + "/translations/components_" + locale + ".qm"); - app.installTranslator(&componentsAppTranslator); + l10n::installQtTranslations(app, "launcher", resourcesPath); Launcher::MainDialog mainWin(configurationManager); diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 5d558ef38f..5486251731 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -292,7 +292,7 @@ bool Launcher::MainDialog::setupLauncherSettings() if (!QFile::exists(path)) return true; - Log(Debug::Verbose) << "Loading config file: " << path.toUtf8().constData(); + Log(Debug::Info) << "Loading config file: " << path.toUtf8().constData(); QFile file(path); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) @@ -320,7 +320,7 @@ bool Launcher::MainDialog::setupGameSettings() QFile file; - auto loadFile = [&](const QString& path, bool (Config::GameSettings::*reader)(QTextStream&, bool), + auto loadFile = [&](const QString& path, bool (Config::GameSettings::*reader)(QTextStream&, const QString&, bool), bool ignoreContent = false) -> std::optional { file.setFileName(path); if (file.exists()) @@ -337,7 +337,7 @@ bool Launcher::MainDialog::setupGameSettings() QTextStream stream(&file); Misc::ensureUtf8Encoding(stream); - (mGameSettings.*reader)(stream, ignoreContent); + (mGameSettings.*reader)(stream, QFileInfo(path).dir().path(), ignoreContent); file.close(); return true; } @@ -349,29 +349,24 @@ bool Launcher::MainDialog::setupGameSettings() if (!loadFile(Files::getUserConfigPathQString(mCfgMgr), &Config::GameSettings::readUserFile)) return false; - // Now the rest - priority: user > local > global - if (auto result = loadFile(Files::getLocalConfigPathQString(mCfgMgr), &Config::GameSettings::readFile, true)) + for (const auto& path : Files::getActiveConfigPathsQString(mCfgMgr)) { - // Load global if local wasn't found - if (!*result && !loadFile(Files::getGlobalConfigPathQString(mCfgMgr), &Config::GameSettings::readFile, true)) + Log(Debug::Info) << "Loading config file: " << path.toUtf8().constData(); + if (!loadFile(path, &Config::GameSettings::readFile)) return false; } - else - return false; - if (!loadFile(Files::getUserConfigPathQString(mCfgMgr), &Config::GameSettings::readFile)) - return false; return true; } bool Launcher::MainDialog::setupGameData() { - QStringList dataDirs; + bool foundData = false; // Check if the paths actually contain data files - for (const QString& path3 : mGameSettings.getDataDirs()) + for (const auto& path3 : mGameSettings.getDataDirs()) { - QDir dir(path3); + QDir dir(path3.value); QStringList filters; filters << "*.esp" << "*.esm" @@ -379,10 +374,13 @@ bool Launcher::MainDialog::setupGameData() << "*.omwaddon"; if (!dir.entryList(filters).isEmpty()) - dataDirs.append(path3); + { + foundData = true; + break; + } } - if (dataDirs.isEmpty()) + if (!foundData) { QMessageBox msgBox; msgBox.setWindowTitle(tr("Error detecting Morrowind installation")); diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index 5e617b1459..16d515602b 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -193,8 +193,10 @@ bool Launcher::SettingsPage::loadSettings() loadSettingBool(Settings::game().mSmoothMovement, *smoothMovementCheckBox); loadSettingBool(Settings::game().mPlayerMovementIgnoresAnimation, *playerMovementIgnoresAnimationCheckBox); - distantLandCheckBox->setCheckState( - Settings::terrain().mDistantTerrain && Settings::terrain().mObjectPaging ? Qt::Checked : Qt::Unchecked); + connect(distantLandCheckBox, &QCheckBox::toggled, this, &SettingsPage::slotDistantLandToggled); + bool distantLandEnabled = Settings::terrain().mDistantTerrain && Settings::terrain().mObjectPaging; + distantLandCheckBox->setCheckState(distantLandEnabled ? Qt::Checked : Qt::Unchecked); + slotDistantLandToggled(distantLandEnabled); loadSettingBool(Settings::terrain().mObjectPagingActiveGrid, *activeGridObjectPagingCheckBox); viewingDistanceComboBox->setValue(convertToCells(Settings::camera().mViewingDistance)); @@ -244,6 +246,11 @@ bool Launcher::SettingsPage::loadSettings() int shadowResIndex = shadowResolutionComboBox->findText(QString::number(shadowRes)); if (shadowResIndex != -1) shadowResolutionComboBox->setCurrentIndex(shadowResIndex); + else + { + shadowResolutionComboBox->addItem(QString::number(shadowRes)); + shadowResolutionComboBox->setCurrentIndex(shadowResolutionComboBox->count() - 1); + } connect(shadowDistanceCheckBox, &QCheckBox::toggled, this, &SettingsPage::slotShadowDistLimitToggled); @@ -295,6 +302,7 @@ bool Launcher::SettingsPage::loadSettings() hrtfProfileSelectorComboBox->setCurrentIndex(hrtfProfileIndex); } } + loadSettingBool(Settings::sound().mCameraListener, *cameraListenerCheckBox); } // Interface Changes @@ -338,7 +346,7 @@ bool Launcher::SettingsPage::loadSettings() { loadSettingBool(Settings::input().mGrabCursor, *grabCursorCheckBox); - bool skipMenu = mGameSettings.value("skip-menu").toInt() == 1; + bool skipMenu = mGameSettings.value("skip-menu").value.toInt() == 1; if (skipMenu) { skipMenuCheckBox->setCheckState(Qt::Checked); @@ -346,8 +354,8 @@ bool Launcher::SettingsPage::loadSettings() startDefaultCharacterAtLabel->setEnabled(skipMenu); startDefaultCharacterAtField->setEnabled(skipMenu); - startDefaultCharacterAtField->setText(mGameSettings.value("start")); - runScriptAfterStartupField->setText(mGameSettings.value("script-run")); + startDefaultCharacterAtField->setText(mGameSettings.value("start").value); + runScriptAfterStartupField->setText(mGameSettings.value("script-run").value); } return true; } @@ -492,6 +500,9 @@ void Launcher::SettingsPage::saveSettings() Settings::sound().mHrtf.set(hrtfProfileSelectorComboBox->currentText().toStdString()); else Settings::sound().mHrtf.set({}); + + const bool cCameraListener = cameraListenerCheckBox->checkState() != Qt::Unchecked; + Settings::sound().mCameraListener.set(cCameraListener); } // Interface Changes @@ -532,17 +543,17 @@ void Launcher::SettingsPage::saveSettings() saveSettingBool(*grabCursorCheckBox, Settings::input().mGrabCursor); int skipMenu = skipMenuCheckBox->checkState() == Qt::Checked; - if (skipMenu != mGameSettings.value("skip-menu").toInt()) - mGameSettings.setValue("skip-menu", QString::number(skipMenu)); + if (skipMenu != mGameSettings.value("skip-menu").value.toInt()) + mGameSettings.setValue("skip-menu", { QString::number(skipMenu) }); QString startCell = startDefaultCharacterAtField->text(); - if (startCell != mGameSettings.value("start")) + if (startCell != mGameSettings.value("start").value) { - mGameSettings.setValue("start", startCell); + mGameSettings.setValue("start", { startCell }); } QString scriptRun = runScriptAfterStartupField->text(); - if (scriptRun != mGameSettings.value("script-run")) - mGameSettings.setValue("script-run", scriptRun); + if (scriptRun != mGameSettings.value("script-run").value) + mGameSettings.setValue("script-run", { scriptRun }); } } @@ -581,9 +592,16 @@ void Launcher::SettingsPage::slotShadowDistLimitToggled(bool checked) fadeStartSpinBox->setEnabled(checked); } +void Launcher::SettingsPage::slotDistantLandToggled(bool checked) +{ + activeGridObjectPagingCheckBox->setEnabled(checked); + objectPagingMinSizeComboBox->setEnabled(checked); +} + void Launcher::SettingsPage::slotLightTypeCurrentIndexChanged(int index) { lightsMaximumDistanceSpinBox->setEnabled(index != 0); + lightFadeMultiplierSpinBox->setEnabled(index != 0); lightsMaxLightsSpinBox->setEnabled(index != 0); lightsBoundingSphereMultiplierSpinBox->setEnabled(index != 0); lightsMinimumInteriorBrightnessSpinBox->setEnabled(index != 0); diff --git a/apps/launcher/settingspage.hpp b/apps/launcher/settingspage.hpp index ea675857ea..652b8ce82d 100644 --- a/apps/launcher/settingspage.hpp +++ b/apps/launcher/settingspage.hpp @@ -33,6 +33,7 @@ namespace Launcher void slotPostProcessToggled(bool checked); void slotSkyBlendingToggled(bool checked); void slotShadowDistLimitToggled(bool checked); + void slotDistantLandToggled(bool checked); void slotLightTypeCurrentIndexChanged(int index); private: diff --git a/apps/launcher/ui/datafilespage.ui b/apps/launcher/ui/datafilespage.ui index 2b54307838..3c9967ef15 100644 --- a/apps/launcher/ui/datafilespage.ui +++ b/apps/launcher/ui/datafilespage.ui @@ -36,7 +36,7 @@ - <html><head/><body><p>note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + <html><head/><body><p>Note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> @@ -160,7 +160,7 @@ - <html><head/><body><p>note: directories that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + <html><head/><body><p>Note: directories that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> @@ -251,7 +251,7 @@ - <html><head/><body><p>note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + <html><head/><body><p>Note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> @@ -305,7 +305,7 @@ - Remove unused tiles + Remove Unused Tiles true @@ -317,7 +317,7 @@ - Max size + Max Size diff --git a/apps/launcher/ui/graphicspage.ui b/apps/launcher/ui/graphicspage.ui index fa92c7b789..b3e2b15e39 100644 --- a/apps/launcher/ui/graphicspage.ui +++ b/apps/launcher/ui/graphicspage.ui @@ -26,7 +26,7 @@ - Window mode + Window Mode @@ -111,14 +111,14 @@ - Framerate limit + Framerate Limit - Window border + Window Border @@ -207,14 +207,14 @@ - Anti-aliasing + Anti-Aliasing - Vertical synchronization + Vertical Synchronization diff --git a/apps/launcher/ui/importpage.ui b/apps/launcher/ui/importpage.ui index 4626d29e8a..0b5d014afa 100644 --- a/apps/launcher/ui/importpage.ui +++ b/apps/launcher/ui/importpage.ui @@ -54,7 +54,7 @@ - File to import settings from: + File to Import Settings From: @@ -73,7 +73,7 @@ - Import add-on and plugin selection (creates a new Content List) + Import Add-on and Plugin Selection true @@ -88,7 +88,7 @@ so OpenMW provides another set of fonts to avoid these issues. These fonts use T to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. - Import bitmap fonts setup + Import Bitmap Fonts true diff --git a/apps/launcher/ui/settingspage.ui b/apps/launcher/ui/settingspage.ui index 5edca39689..5bc5c258c1 100644 --- a/apps/launcher/ui/settingspage.ui +++ b/apps/launcher/ui/settingspage.ui @@ -39,7 +39,7 @@ <html><head/><body><p>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.</p></body></html> - Always allow actors to follow over water + Always Allow Actors to Follow over Water @@ -49,7 +49,7 @@ <html><head/><body><p>Make disposition change of merchants caused by trading permanent.</p></body></html> - Permanent barter disposition changes + Permanent Barter Disposition Changes @@ -59,7 +59,7 @@ <html><head/><body><p>Don't use race weight in NPC movement speed calculations.</p></body></html> - Racial variation in speed fix + Racial Variation in Speed Fix @@ -69,7 +69,7 @@ <html><head/><body><p>Stops combat with NPCs affected by Calm spells every frame -- like in Morrowind without the MCP.</p></body></html> - Classic Calm spells behavior + Classic Calm Spells Behavior @@ -79,7 +79,7 @@ <html><head/><body><p>If enabled NPCs apply evasion maneuver to avoid collisions with others.</p></body></html> - NPCs avoid collisions + NPCs Avoid Collisions @@ -89,7 +89,7 @@ <html><head/><body><p>Make the value of filled soul gems dependent only on soul magnitude.</p></body></html> - Soulgem values rebalance + Soulgem Values Rebalance @@ -99,7 +99,7 @@ <html><head/><body><p>If this setting is true, supporting models will make use of day night switch nodes.</p></body></html> - Day night switch nodes + Day Night Switch Nodes @@ -109,7 +109,7 @@ <html><head/><body><p>Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.</p></body></html> - Followers defend immediately + Followers Defend Immediately @@ -119,7 +119,7 @@ <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>When enabled, a navigation mesh is built in the background for world geometry to be used for pathfinding. When disabled only the path grid is used to build paths. Single-core CPU systems may have a big performance impact on existing interior location and moving across the exterior world. May slightly affect performance on multi-core CPU systems. Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. Moving across external world, entering/exiting location produce nav mesh update. NPC and creatures may not be able to find path before nav mesh is built around them. Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and cast a firebolt.</p></body></html> - Use navigation mesh for pathfinding + Use Navigation Mesh for Pathfinding @@ -129,7 +129,7 @@ <html><head/><body><p>If enabled, a magical ammunition is required to bypass normal weapon resistance or weakness. If disabled, a magical ranged weapon or a magical ammunition is required.</p></body></html> - Only magical ammo bypass resistance + Only Magical Ammo Bypass Resistance @@ -139,7 +139,7 @@ <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> - Graphic herbalism + Graphic Herbalism @@ -149,7 +149,7 @@ <html><head/><body><p>Makes player swim a bit upward from the line of sight. Applies only in third person mode. Intended to make simpler swimming without diving.</p></body></html> - Swim upward correction + Swim Upward Correction @@ -159,7 +159,7 @@ <html><head/><body><p>Make enchanted weapons without Magical flag bypass normal weapons resistance, like in Morrowind.</p></body></html> - Enchanted weapons are magical + Enchanted Weapons Are Magical @@ -169,7 +169,7 @@ <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> - Merchant equipping fix + Merchant Equipping Fix @@ -179,7 +179,7 @@ <html><head/><body><p>Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.</p></body></html> - Trainers choose offered skills by base value + Trainers Choose Offered Skills by Base Value @@ -189,7 +189,7 @@ <html><head/><body><p>If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. In this case we have to increment death counter and run disposed actor's script instantly.</p><p>If this setting is false, player has to wait until end of death animation in all cases. Makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation.</p></body></html> - Can loot during death animation + Can Loot During Death Animation @@ -199,7 +199,7 @@ <html><head/><body><p>Make stealing items from NPCs that were knocked down possible during combat.</p></body></html> - Steal from knocked out actors in combat + Steal from Knocked out Actors in Combat @@ -209,7 +209,7 @@ <html><head/><body><p>Effects of reflected Absorb spells are not mirrored - like in Morrowind.</p></body></html> - Classic reflected Absorb spells behavior + Classic Reflected Absorb Spells Behavior @@ -219,7 +219,7 @@ <html><head/><body><p>Makes unarmed creature attacks able to reduce armor condition, just as attacks from NPCs and armed creatures.</p></body></html> - Unarmed creature attacks damage armor + Unarmed Creature Attacks Damage Armor @@ -230,7 +230,7 @@ - Factor strength into hand-to-hand combat + Factor Strength into Hand-to-Hand Combat @@ -246,12 +246,12 @@ - Affect werewolves + Affect Werewolves - Do not affect werewolves + Do Not Affect Werewolves @@ -262,7 +262,7 @@ <html><head/><body><p>How many threads will be spawned to compute physics update in the background. A value of 0 means that the update will be performed in the main thread.</p><p>A value greater than 1 requires the Bullet library be compiled with multithreading support.</p></body></html> - Background physics threads + Background Physics Threads @@ -272,7 +272,7 @@ - Actor collision shape type + Actor Collision Shape Type @@ -282,16 +282,16 @@ Collision is used for both physics simulation and navigation mesh generation for pathfinding. Cylinder gives the best consistency between available navigation paths and ability to move by them. Changing this value affects navigation mesh generation therefore navigation mesh disk cache generated for one value will not be useful with another. - Axis-aligned bounding box + Axis-Aligned Bounding Box - Axis-aligned bounding box + Axis-Aligned Bounding Box - Rotating box + Rotating Box @@ -343,7 +343,7 @@ <html><head/><body><p>Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.</p></body></html> - Smooth movement + Smooth Movement @@ -353,7 +353,7 @@ <html><head/><body><p>Load per-group KF-files and skeleton files from Animations folder</p></body></html> - Use additional animation sources + Use Additional Animation Sources @@ -376,7 +376,7 @@ <html><head/><body><p>Affects side and diagonal movement. Enabling this setting makes movement more realistic.</p><p>If disabled then the whole character's body is pointed to the direction of view. Diagonal movement has no special animation and causes sliding.</p><p>If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it changes straight right and straight left movement as well. Also turns the whole body up or down when swimming according to the movement direction.</p></body></html> - Turn to movement direction + Turn to Movement Direction @@ -389,7 +389,7 @@ <html><head/><body><p>Render holstered weapons (with quivers and scabbards), requires modded assets.</p></body></html> - Weapon sheathing + Weapon Sheathing @@ -402,7 +402,7 @@ <html><head/><body><p>Render holstered shield, requires modded assets.</p></body></html> - Shield sheathing + Shield Sheathing @@ -412,7 +412,7 @@ <html><head/><body><p>In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.</p></body></html> - Player movement ignores animation + Player Movement Ignores Animation @@ -422,7 +422,7 @@ <html><head/><body><p>Use casting animations for magic items, just as for spells.</p></body></html> - Use magic item animation + Use Magic Item Animation @@ -447,7 +447,7 @@ If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file (.nif or .osg file). Affects objects.</p></body></html> - Auto use object normal maps + Auto Use Object Normal Maps @@ -457,7 +457,7 @@ <html><head/><body><p>Enables soft particles for particle effects. This technique softens the intersection between individual particles and other opaque geometry by blending between them.</p></body></html> - Soft particles + Soft Particles @@ -471,7 +471,7 @@ (.osg file, not supported in .nif files). Affects objects.</p></body></html> - Auto use object specular maps + Auto Use Object Specular Maps @@ -481,7 +481,7 @@ <html><head/><body><p>See 'auto use object normal maps'. Affects terrain.</p></body></html> - Auto use terrain normal maps + Auto Use Terrain Normal Maps @@ -504,7 +504,7 @@ <html><head/><body><p>If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. The texture must contain the layer colour in the RGB channel (as usual), and a specular multiplier in the alpha channel.</p></body></html> - Auto use terrain specular maps + Auto Use Terrain Specular Maps @@ -514,7 +514,7 @@ <html><head/><body><p>Simulate coverage-preserving mipmaps to prevent alpha-tested meshes shrinking as they get further away. Will cause meshes whose textures have coverage-preserving mipmaps to grow, though, so refer to mod installation instructions for how to set this.</p></body></html> - Adjust coverage for alpha test + Adjust Coverage for Alpha Test @@ -524,7 +524,7 @@ <html><head/><body><p>Allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. Can negatively impact performance.</p></body></html> - Use anti-alias alpha testing + Use Anti-Aliased Alpha Testing @@ -537,7 +537,7 @@ </p></body></html> - Bump/reflect map local lighting + Bump/Reflect Map Local Lighting @@ -547,7 +547,7 @@ <html><head/><body><p>EXPERIMENTAL: Stop rain and snow from falling through overhangs and roofs.</p></body></html> - Weather particle occlusion + Weather Particle Occlusion @@ -570,7 +570,7 @@ <html><head/><body><p>Use exponential fog formula. By default, linear fog is used.</p></body></html> - Exponential fog + Exponential Fog @@ -613,7 +613,7 @@ This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV.</p></body></html> - Radial fog + Radial Fog @@ -626,7 +626,7 @@ <html><head/><body><p>The fraction of the maximum distance at which blending with the sky starts.</p></body></html> - Sky blending start + Sky Blending Start @@ -636,7 +636,7 @@ <html><head/><body><p>Reduce visibility of clipping plane by blending objects with the sky.</p></body></html> - Sky blending + Sky Blending @@ -652,12 +652,35 @@ - + - <html><head/><body><p>If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.</p></body></html> + <html><head/><body><p>Controls how large an object must be to be visible in the scene. The object’s size is divided by its distance to the camera and the result of the division is compared with this value. The smaller this value is, the more objects you will see in the scene.</p></body></html> + + + Object Paging Min Size + + + + + + + 3 + + 0.000000000000000 + + + 0.250000000000000 + + + 0.005000000000000 + + + + + - Distant land + Viewing Distance @@ -666,15 +689,18 @@ cells + + 3 + 0.000000000000000 - 0.500000000000000 + 0.125000000000000 - + Qt::Vertical @@ -688,45 +714,22 @@ - + - <html><head/><body><p>Controls how large an object must be to be visible in the scene. The object’s size is divided by its distance to the camera and the result of the division is compared with this value. The smaller this value is, the more objects you will see in the scene.</p></body></html> - - - Object paging min size + <html><head/><body><p>If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.</p></body></html> - - - - - Viewing distance + Distant Land - - - 3 - - - 0.000000000000000 - - - 0.250000000000000 - - - 0.005000000000000 - - - - <html><head/><body><p>Use object paging for active cells grid.</p></body></html> - Active grid object paging + Active Grid Object Paging @@ -741,16 +744,22 @@ - - + + false - - <html><head/><body><p>Controls how much eye adaptation can change from frame to frame. Smaller values makes for slower transitions.</p></body></html> + + 3 - - Auto exposure speed + + 0.010000000000000 + + + 10.000000000000000 + + + 0.001000000000000 @@ -776,26 +785,20 @@ <html><head/><body><p>Re-render transparent objects with forced alpha clipping.</p></body></html> - Transparent postpass + Transparent Postpass - - + + false - - 3 - - - 0.010000000000000 - - - 10.000000000000000 + + <html><head/><body><p>Controls how much eye adaptation can change from frame to frame. Smaller values makes for slower transitions.</p></body></html> - - 0.001000000000000 + + Auto Exposure Speed @@ -805,7 +808,7 @@ <html><head/><body><p>If this setting is true, post processing will be enabled.</p></body></html> - Enable post processing + Enable Post Processing @@ -824,17 +827,17 @@ - bounds + Bounds - primitives + Primitives - none + None @@ -845,7 +848,7 @@ <html><head/><body><p>Type of "compute scene bounds" computation method to be used. Bounds (default) for good balance between performance and shadow quality, primitives for better looking shadows or none for no computation.</p></body></html> - Shadow planes computation method + Shadow Planes Computation Method @@ -866,6 +869,9 @@ 81920 + + 128 + 8192 @@ -877,7 +883,7 @@ <html><head/><body><p>Enable shadows for NPCs and creatures besides the player character. May have a minor performance impact.</p></body></html> - Enable actor shadows + Enable Actor Shadows @@ -911,7 +917,7 @@ <html><head/><body><p>The fraction of the limit above at which shadows begin to gradually fade away.</p></body></html> - Fade start multiplier + Fade Start Multiplier @@ -921,7 +927,7 @@ <html><head/><body><p>Enable shadows exclusively for the player character. May have a very minor performance impact.</p></body></html> - Enable player shadows + Enable Player Shadows @@ -931,7 +937,7 @@ <html><head/><body><p>The resolution of each individual shadow map. Increasing it significantly improves shadow quality but may have a minor performance impact.</p></body></html> - Shadow map resolution + Shadow Map Resolution @@ -941,7 +947,7 @@ <html><head/><body><p>The distance from the camera at which shadows completely disappear.</p></body></html> - Shadow distance limit: + Shadow Distance Limit: @@ -951,7 +957,7 @@ <html><head/><body><p>Enable shadows for primarily inanimate objects. May have a significant performance impact.</p></body></html> - Enable object shadows + Enable Object Shadows @@ -969,6 +975,9 @@ 1.000000000000000 + + 0.010000000000000 + 0.900000000000000 @@ -993,7 +1002,7 @@ <html><head/><body><p>Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting.</p><p>Has no effect if actor/player shadows are not enabled.</p></body></html> - Enable indoor shadows + Enable Indoor Shadows @@ -1003,7 +1012,7 @@ <html><head/><body><p>Enable shadows for the terrain including distant terrain. May have a significant performance and shadow quality impact.</p></body></html> - Enable terrain shadows + Enable Terrain Shadows @@ -1024,7 +1033,7 @@ <html><head/><body><p>Maximum distance at which lights will appear (measured in units).</p><p>Set this to 0 to use an unlimited distance.</p></body></html> - Lights maximum distance + Maximum Light Distance @@ -1047,7 +1056,7 @@ <html><head/><body><p>Maximum number of lights per object.</p><p>A low number near default will cause light popping similar to what you would see with legacy lighting.</p></body></html> - Max light sources + Max Lights @@ -1057,7 +1066,7 @@ <html><head/><body><p>Fraction of maximum distance at which lights will start to fade.</p><p>Set this to a low value for slower transitions or a high value for quicker transitions.</p></body></html> - Lights fade multiplier + Fade Start Multiplier @@ -1070,7 +1079,7 @@ <p> "Shaders" carries all of the benefits that "Shaders (compatibility)" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware.</p></body></html> - Lighting method + Lighting Method @@ -1099,7 +1108,7 @@ <html><head/><body><p>Multipler for bounding sphere of lights.</p><p>Higher numbers allows for smooth falloff but require an increase in number of max lights.</p><p>Does not effect the illumination or strength of lights.</p></body></html> - Lights bounding sphere multiplier + Bounding Sphere Multiplier @@ -1119,7 +1128,7 @@ <html><head/><body><p>Minimum ambient interior brightness.</p><p>Increase this if you feel interiors are too dark.</p></body></html> - Lights minimum interior brightness + Minimum Interior Brightness @@ -1214,7 +1223,7 @@ Select your preferred audio device. - Audio device + Audio Device @@ -1300,7 +1309,7 @@ Select your preferred HRTF profile. - HRTF profile + HRTF Profile @@ -1330,6 +1339,16 @@ + + + + In third-person view, use the camera as the sound listener instead of the player character. + + + Use the Camera as the Sound Listener + + + @@ -1374,7 +1393,7 @@ - Tooltip and crosshair + Tooltip and Crosshair @@ -1420,7 +1439,7 @@ <html><head/><body><p>This setting scales GUI windows. A value of 1.0 results in the normal scale.</p></body></html> - GUI scaling factor + GUI Scaling Factor @@ -1430,7 +1449,7 @@ <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> - Show effect duration + Show Effect Duration @@ -1440,7 +1459,7 @@ <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> - Change dialogue topic color + Change Dialogue Topic Color @@ -1450,7 +1469,7 @@ Size of characters in game texts. - Font size + Font Size @@ -1460,7 +1479,7 @@ <html><head/><body><p>Enable zooming on local and global maps.</p></body></html> - Can zoom on maps + Can Zoom on Maps @@ -1470,7 +1489,7 @@ <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - Show projectile damage + Show Projectile Damage @@ -1480,7 +1499,7 @@ <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - Show melee info + Show Melee Info @@ -1490,14 +1509,14 @@ <html><head/><body><p>Stretch menus, load screens, etc. to the window aspect ratio.</p></body></html> - Stretch menu background + Stretch Menu Background - Show owned objects + Show Owned Objects @@ -1507,7 +1526,7 @@ <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> - Show enchant chance + Show Enchant Chance @@ -1545,7 +1564,7 @@ <html><head/><body><p>This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.</p></body></html> - Add "Time Played" to saves + Add "Time Played" to Saves @@ -1554,7 +1573,7 @@ - Maximum quicksaves + Maximum Quicksaves @@ -1581,7 +1600,7 @@ - Screenshot format + Screenshot Format @@ -1609,7 +1628,7 @@ - Notify on saved screenshot + Notify on Saved Screenshot @@ -1659,14 +1678,14 @@ <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> - Grab cursor + Grab Cursor - Skip menu and generate default character + Skip Menu and Generate Default Character @@ -1691,14 +1710,14 @@ - Start default character at + Start Default Character at - default cell + Default Cell @@ -1707,7 +1726,7 @@ - Run script after startup: + Run Script After Startup: diff --git a/apps/mwiniimporter/CMakeLists.txt b/apps/mwiniimporter/CMakeLists.txt index 704393cd0d..49be8309ab 100644 --- a/apps/mwiniimporter/CMakeLists.txt +++ b/apps/mwiniimporter/CMakeLists.txt @@ -33,7 +33,7 @@ if (BUILD_WITH_CODE_COVERAGE) target_link_libraries(openmw-iniimporter gcov) endif() -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw-iniimporter PRIVATE diff --git a/apps/navmeshtool/CMakeLists.txt b/apps/navmeshtool/CMakeLists.txt index 9abd8dc283..13c8230abd 100644 --- a/apps/navmeshtool/CMakeLists.txt +++ b/apps/navmeshtool/CMakeLists.txt @@ -21,7 +21,7 @@ if (WIN32) install(TARGETS openmw-navmeshtool RUNTIME DESTINATION ".") endif() -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw-navmeshtool PRIVATE diff --git a/apps/navmeshtool/main.cpp b/apps/navmeshtool/main.cpp index 3ec34114af..d75a1af5e2 100644 --- a/apps/navmeshtool/main.cpp +++ b/apps/navmeshtool/main.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -165,7 +166,9 @@ namespace NavMeshTool dataDirs.insert(dataDirs.begin(), resDir / "vfs"); const Files::Collections fileCollections(dataDirs); const auto& archives = variables["fallback-archive"].as(); - const auto& contentFiles = variables["content"].as(); + StringsVector contentFiles{ "builtin.omwscripts" }; + const auto& configContentFiles = variables["content"].as(); + contentFiles.insert(contentFiles.end(), configContentFiles.begin(), configContentFiles.end()); const std::size_t threadsNumber = variables["threads"].as(); if (threadsNumber < 1) @@ -218,7 +221,8 @@ namespace NavMeshTool Resource::ImageManager imageManager(&vfs, expiryDelay); Resource::NifFileManager nifFileManager(&vfs, &encoder.getStatelessEncoder()); - Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, expiryDelay); + Resource::BgsmFileManager bgsmFileManager(&vfs, expiryDelay); + Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, &bgsmFileManager, expiryDelay); Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay); DetourNavigator::RecastGlobalAllocator::init(); DetourNavigator::Settings navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager(); diff --git a/apps/niftest/CMakeLists.txt b/apps/niftest/CMakeLists.txt index f112e087e3..cf37162f6e 100644 --- a/apps/niftest/CMakeLists.txt +++ b/apps/niftest/CMakeLists.txt @@ -17,6 +17,6 @@ if (BUILD_WITH_CODE_COVERAGE) target_link_libraries(niftest gcov) endif() -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(niftest PRIVATE ) endif() diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index e18da8e3f6..0b8aa8e275 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -25,7 +26,7 @@ namespace bpo = boost::program_options; /// See if the file has the named extension -bool hasExtension(const std::filesystem::path& filename, const std::string& extensionToFind) +bool hasExtension(const std::filesystem::path& filename, std::string_view extensionToFind) { const auto extension = Files::pathToUnicodeString(filename.extension()); return Misc::StringUtils::ciEqual(extension, extensionToFind); @@ -36,63 +37,62 @@ bool isNIF(const std::filesystem::path& filename) { return hasExtension(filename, ".nif") || hasExtension(filename, ".kf"); } -/// See if the file has the "bsa" extension. -bool isBSA(const std::filesystem::path& filename) + +/// Check if the file is a material file. +bool isMaterial(const std::filesystem::path& filename) { - return hasExtension(filename, ".bsa") || hasExtension(filename, ".ba2"); + return hasExtension(filename, ".bgem") || hasExtension(filename, ".bgsm"); } -std::unique_ptr makeBsaArchive(const std::filesystem::path& path) +/// See if the file has the "bsa" extension. +bool isBSA(const std::filesystem::path& filename) { - switch (Bsa::BSAFile::detectVersion(path)) - { - case Bsa::BSAVER_COMPRESSED: - return std::make_unique::type>(path); - case Bsa::BSAVER_BA2_GNRL: - return std::make_unique::type>(path); - case Bsa::BSAVER_BA2_DX10: - return std::make_unique::type>(path); - case Bsa::BSAVER_UNCOMPRESSED: - return std::make_unique::type>(path); - case Bsa::BSAVER_UNKNOWN: - default: - std::cerr << "'" << Files::pathToUnicodeString(path) << "' is not a recognized BSA archive" << std::endl; - return nullptr; - } + return hasExtension(filename, ".bsa") || hasExtension(filename, ".ba2"); } std::unique_ptr makeArchive(const std::filesystem::path& path) { if (isBSA(path)) - return makeBsaArchive(path); + return VFS::makeBsaArchive(path); if (std::filesystem::is_directory(path)) return std::make_unique(path); return nullptr; } -void readNIF( +void readFile( const std::filesystem::path& source, const std::filesystem::path& path, const VFS::Manager* vfs, bool quiet) { const std::string pathStr = Files::pathToUnicodeString(path); + const bool isNif = isNIF(path); if (!quiet) { - if (hasExtension(path, ".kf")) - std::cout << "Reading KF file '" << pathStr << "'"; + if (isNif) + std::cout << "Reading " << (hasExtension(path, ".nif") ? "NIF" : "KF") << " file '" << pathStr << "'"; else - std::cout << "Reading NIF file '" << pathStr << "'"; + std::cout << "Reading " << (hasExtension(path, ".bgsm") ? "BGSM" : "BGEM") << " file '" << pathStr << "'"; if (!source.empty()) std::cout << " from '" << Files::pathToUnicodeString(isBSA(source) ? source.filename() : source) << "'"; std::cout << std::endl; } - std::filesystem::path fullPath = !source.empty() ? source / path : path; + const std::filesystem::path fullPath = !source.empty() ? source / path : path; try { - Nif::NIFFile file(fullPath); - Nif::Reader reader(file, nullptr); - if (vfs != nullptr) - reader.parse(vfs->get(pathStr)); + if (isNif) + { + Nif::NIFFile file(Files::pathToUnicodeString(fullPath)); + Nif::Reader reader(file, nullptr); + if (vfs != nullptr) + reader.parse(vfs->get(pathStr)); + else + reader.parse(Files::openConstrainedFileStream(fullPath)); + } else - reader.parse(Files::openConstrainedFileStream(fullPath)); + { + if (vfs != nullptr) + Bgsm::parse(vfs->get(pathStr)); + else + Bgsm::parse(Files::openConstrainedFileStream(fullPath)); + } } catch (std::exception& e) { @@ -114,27 +114,33 @@ void readVFS(std::unique_ptr&& archive, const std::filesystem::pat vfs.addArchive(std::move(archive)); vfs.buildIndex(); - for (const auto& name : vfs.getRecursiveDirectoryIterator("")) + for (const auto& name : vfs.getRecursiveDirectoryIterator()) { - if (isNIF(name.value())) + if (isNIF(name.value()) || isMaterial(name.value())) { - readNIF(archivePath, name.value(), &vfs, quiet); + readFile(archivePath, name.value(), &vfs, quiet); } } if (!archivePath.empty() && !isBSA(archivePath)) { - Files::PathContainer dataDirs = { archivePath }; - const Files::Collections fileCollections = Files::Collections(dataDirs); + const Files::Collections fileCollections({ archivePath }); const Files::MultiDirCollection& bsaCol = fileCollections.getCollection(".bsa"); const Files::MultiDirCollection& ba2Col = fileCollections.getCollection(".ba2"); - for (auto& file : bsaCol) + for (const Files::MultiDirCollection& collection : { bsaCol, ba2Col }) { - readVFS(makeBsaArchive(file.second), file.second, quiet); - } - for (auto& file : ba2Col) - { - readVFS(makeBsaArchive(file.second), file.second, quiet); + for (auto& file : collection) + { + try + { + readVFS(VFS::makeBsaArchive(file.second), file.second, quiet); + } + catch (const std::exception& e) + { + std::cerr << "Failed to read archive file '" << Files::pathToUnicodeString(file.second) + << "': " << e.what() << std::endl; + } + } } } } @@ -142,10 +148,10 @@ void readVFS(std::unique_ptr&& archive, const std::filesystem::pat bool parseOptions(int argc, char** argv, Files::PathContainer& files, Files::PathContainer& archives, bool& writeDebugLog, bool& quiet) { - bpo::options_description desc(R"(Ensure that OpenMW can use the provided NIF, KF and BSA/BA2 files + bpo::options_description desc(R"(Ensure that OpenMW can use the provided NIF, KF, BGEM/BGSM and BSA/BA2 files Usages: - niftest + niftest Scan the file or directories for NIF errors. Allowed options)"); @@ -234,9 +240,9 @@ int main(int argc, char** argv) const std::string pathStr = Files::pathToUnicodeString(path); try { - if (isNIF(path)) + if (isNIF(path) || isMaterial(path)) { - readNIF({}, path, vfs.get(), quiet); + readFile({}, path, vfs.get(), quiet); } else if (auto archive = makeArchive(path)) { @@ -244,7 +250,7 @@ int main(int argc, char** argv) } else { - std::cerr << "Error: '" << pathStr << "' is not a NIF/KF file, BSA/BA2 archive, or directory" + std::cerr << "Error: '" << pathStr << "' is not a NIF/KF/BGEM/BGSM file, BSA/BA2 archive, or directory" << std::endl; } } diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 610c5157aa..d6af64b33b 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -172,7 +172,7 @@ else() set (OPENCS_OPENMW_CFG "") endif(APPLE) -add_library(openmw-cs-lib +add_library(openmw-cs-lib STATIC ${OPENCS_SRC} ${OPENCS_UI_HDR} ${OPENCS_MOC_SRC} @@ -250,13 +250,14 @@ target_link_libraries(openmw-cs-lib ) if (QT_VERSION_MAJOR VERSION_EQUAL 6) - target_link_libraries(openmw-cs-lib Qt::Widgets Qt::Core Qt::Network Qt::OpenGL Qt::OpenGLWidgets) + target_link_libraries(openmw-cs-lib Qt::Widgets Qt::Core Qt::Network Qt::OpenGL Qt::OpenGLWidgets Qt::Svg) else() - target_link_libraries(openmw-cs-lib Qt::Widgets Qt::Core Qt::Network Qt::OpenGL) + target_link_libraries(openmw-cs-lib Qt::Widgets Qt::Core Qt::Network Qt::OpenGL Qt::Svg) endif() if (WIN32) target_link_libraries(openmw-cs-lib ${Boost_LOCALE_LIBRARY}) + target_sources(openmw-cs PRIVATE ${CMAKE_SOURCE_DIR}/files/windows/openmw-cs.exe.manifest) endif() if (WIN32 AND BUILD_OPENCS) @@ -292,7 +293,7 @@ if (BUILD_WITH_CODE_COVERAGE) target_link_libraries(openmw-cs-lib gcov) endif() -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw-cs-lib PRIVATE diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index e7f980dc0d..62fb29e600 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -81,7 +81,7 @@ int runApplication(int argc, char* argv[]) Application application(argc, argv); - application.setWindowIcon(QIcon(":./openmw-cs.png")); + application.setWindowIcon(QIcon(":openmw-cs")); CS::Editor editor(argc, argv); #ifdef __linux__ diff --git a/apps/opencs/model/doc/runner.cpp b/apps/opencs/model/doc/runner.cpp index 0099cb2f94..d647d6b498 100644 --- a/apps/opencs/model/doc/runner.cpp +++ b/apps/opencs/model/doc/runner.cpp @@ -93,7 +93,6 @@ void CSMDoc::Runner::start(bool delayed) arguments << "--data=\"" + Files::pathToQString(mProjectPath.parent_path()) + "\""; arguments << "--replace=content"; - arguments << "--content=builtin.omwscripts"; for (const auto& mContentFile : mContentFiles) { diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index 82135e0042..77effc3a5c 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -135,7 +135,7 @@ void CSMDoc::WriteDialogueCollectionStage::perform(int stage, Messages& messages if (topic.mState == CSMWorld::RecordBase::State_Deleted) { // if the topic is deleted, we do not need to bother with INFO records. - ESM::Dialogue dialogue = topic.get(); + const ESM::Dialogue& dialogue = topic.get(); writer.startRecord(dialogue.sRecordId); dialogue.save(writer, true); writer.endRecord(dialogue.sRecordId); @@ -187,6 +187,7 @@ void CSMDoc::WriteDialogueCollectionStage::perform(int stage, Messages& messages { ESM::DialInfo info = record.get(); info.mId = record.get().mOriginalId; + info.mData.mType = topic.get().mType; if (iter == infos.begin()) info.mPrev = ESM::RefId(); diff --git a/apps/opencs/model/filter/parser.cpp b/apps/opencs/model/filter/parser.cpp index aadad5f8f5..6248b03bb4 100644 --- a/apps/opencs/model/filter/parser.cpp +++ b/apps/opencs/model/filter/parser.cpp @@ -452,7 +452,10 @@ std::shared_ptr CSMFilter::Parser::parseText() return std::shared_ptr(); } - return std::make_shared(columnId, text); + auto node = std::make_shared(columnId, text); + if (!node->isValid()) + error(); + return node; } std::shared_ptr CSMFilter::Parser::parseValue() diff --git a/apps/opencs/model/filter/textnode.hpp b/apps/opencs/model/filter/textnode.hpp index 14efa0a3a0..d629cbe336 100644 --- a/apps/opencs/model/filter/textnode.hpp +++ b/apps/opencs/model/filter/textnode.hpp @@ -34,6 +34,8 @@ namespace CSMFilter ///< Return a string that represents this node. /// /// \param numericColumns Use numeric IDs instead of string to represent columns. + + bool isValid() { return mRegExp.isValid(); } }; } diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index c11996a6ea..ba3a544f36 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -90,6 +90,7 @@ void CSMPrefs::State::declare() .setTooltip( "When editing a record, open the view in a new window," " rather than docked in the main view."); + declareInt(mValues->mIdTables.mFilterDelay, "Delay before applying a filter (in miliseconds)"); declareCategory("ID Dialogues"); declareBool(mValues->mIdDialogues.mToolbar, "Show toolbar"); diff --git a/apps/opencs/model/prefs/values.hpp b/apps/opencs/model/prefs/values.hpp index 9899a239a9..d3f78e5d01 100644 --- a/apps/opencs/model/prefs/values.hpp +++ b/apps/opencs/model/prefs/values.hpp @@ -138,6 +138,7 @@ namespace CSMPrefs EnumSettingValue mJumpToAdded{ mIndex, sName, "jump-to-added", sJumpAndSelectValues, 0 }; Settings::SettingValue mExtendedConfig{ mIndex, sName, "extended-config", false }; Settings::SettingValue mSubviewNewWindow{ mIndex, sName, "subview-new-window", false }; + Settings::SettingValue mFilterDelay{ mIndex, sName, "filter-delay", 500 }; }; struct IdDialoguesCategory : Settings::WithIndex diff --git a/apps/opencs/model/tools/enchantmentcheck.cpp b/apps/opencs/model/tools/enchantmentcheck.cpp index d6cb22b738..48cee579be 100644 --- a/apps/opencs/model/tools/enchantmentcheck.cpp +++ b/apps/opencs/model/tools/enchantmentcheck.cpp @@ -60,38 +60,38 @@ void CSMTools::EnchantmentCheckStage::perform(int stage, CSMDoc::Messages& messa } else { - std::vector::const_iterator effect = enchantment.mEffects.mList.begin(); + std::vector::const_iterator effect = enchantment.mEffects.mList.begin(); for (size_t i = 1; i <= enchantment.mEffects.mList.size(); i++) { const std::string number = std::to_string(i); // At the time of writing this effects, attributes and skills are hardcoded - if (effect->mEffectID < 0 || effect->mEffectID > 142) + if (effect->mData.mEffectID < 0 || effect->mData.mEffectID > 142) { messages.add(id, "Effect #" + number + " is invalid", "", CSMDoc::Message::Severity_Error); ++effect; continue; } - if (effect->mSkill < -1 || effect->mSkill > 26) + if (effect->mData.mSkill < -1 || effect->mData.mSkill > 26) messages.add( id, "Effect #" + number + " affected skill is invalid", "", CSMDoc::Message::Severity_Error); - if (effect->mAttribute < -1 || effect->mAttribute > 7) + if (effect->mData.mAttribute < -1 || effect->mData.mAttribute > 7) messages.add( id, "Effect #" + number + " affected attribute is invalid", "", CSMDoc::Message::Severity_Error); - if (effect->mRange < 0 || effect->mRange > 2) + if (effect->mData.mRange < 0 || effect->mData.mRange > 2) messages.add(id, "Effect #" + number + " range is invalid", "", CSMDoc::Message::Severity_Error); - if (effect->mArea < 0) + if (effect->mData.mArea < 0) messages.add(id, "Effect #" + number + " area is negative", "", CSMDoc::Message::Severity_Error); - if (effect->mDuration < 0) + if (effect->mData.mDuration < 0) messages.add(id, "Effect #" + number + " duration is negative", "", CSMDoc::Message::Severity_Error); - if (effect->mMagnMin < 0) + if (effect->mData.mMagnMin < 0) messages.add( id, "Effect #" + number + " minimum magnitude is negative", "", CSMDoc::Message::Severity_Error); - if (effect->mMagnMax < 0) + if (effect->mData.mMagnMax < 0) messages.add( id, "Effect #" + number + " maximum magnitude is negative", "", CSMDoc::Message::Severity_Error); - if (effect->mMagnMin > effect->mMagnMax) + if (effect->mData.mMagnMin > effect->mData.mMagnMax) messages.add(id, "Effect #" + number + " minimum magnitude is higher than maximum magnitude", "", CSMDoc::Message::Severity_Error); ++effect; diff --git a/apps/opencs/model/tools/magiceffectcheck.cpp b/apps/opencs/model/tools/magiceffectcheck.cpp index a9ad4023fc..212b343e00 100644 --- a/apps/opencs/model/tools/magiceffectcheck.cpp +++ b/apps/opencs/model/tools/magiceffectcheck.cpp @@ -58,7 +58,12 @@ void CSMTools::MagicEffectCheckStage::perform(int stage, CSMDoc::Messages& messa return; ESM::MagicEffect effect = record.get(); - CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_MagicEffect, effect.mId); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_MagicEffect, CSMWorld::getRecordId(effect)); + + if (effect.mData.mSpeed <= 0.0f) + { + messages.add(id, "Speed is less than or equal to zero", "", CSMDoc::Message::Severity_Error); + } if (effect.mDescription.empty()) { diff --git a/apps/opencs/model/tools/topicinfocheck.cpp b/apps/opencs/model/tools/topicinfocheck.cpp index ba99a33a28..fab90d951a 100644 --- a/apps/opencs/model/tools/topicinfocheck.cpp +++ b/apps/opencs/model/tools/topicinfocheck.cpp @@ -171,10 +171,9 @@ void CSMTools::TopicInfoCheckStage::perform(int stage, CSMDoc::Messages& message // Check info conditions - for (std::vector::const_iterator it = topicInfo.mSelects.begin(); - it != topicInfo.mSelects.end(); ++it) + for (const auto& select : topicInfo.mSelects) { - verifySelectStruct((*it), id, messages); + verifySelectStruct(select, id, messages); } } @@ -308,49 +307,15 @@ bool CSMTools::TopicInfoCheckStage::verifyItem( } bool CSMTools::TopicInfoCheckStage::verifySelectStruct( - const ESM::DialInfo::SelectStruct& select, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) + const ESM::DialogueCondition& select, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) { CSMWorld::ConstInfoSelectWrapper infoCondition(select); - if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_None) + if (select.mFunction == ESM::DialogueCondition::Function_None) { messages.add(id, "Invalid condition '" + infoCondition.toString() + "'", "", CSMDoc::Message::Severity_Error); return false; } - else if (!infoCondition.variantTypeIsValid()) - { - std::ostringstream stream; - stream << "Value of condition '" << infoCondition.toString() << "' has invalid "; - - switch (select.mValue.getType()) - { - case ESM::VT_None: - stream << "None"; - break; - case ESM::VT_Short: - stream << "Short"; - break; - case ESM::VT_Int: - stream << "Int"; - break; - case ESM::VT_Long: - stream << "Long"; - break; - case ESM::VT_Float: - stream << "Float"; - break; - case ESM::VT_String: - stream << "String"; - break; - default: - stream << "unknown"; - break; - } - stream << " type"; - - messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); - return false; - } else if (infoCondition.conditionIsAlwaysTrue()) { messages.add( @@ -365,48 +330,48 @@ bool CSMTools::TopicInfoCheckStage::verifySelectStruct( } // Id checks - if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Global - && !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mGlobals, id, messages)) + if (select.mFunction == ESM::DialogueCondition::Function_Global + && !verifyId(ESM::RefId::stringRefId(select.mVariable), mGlobals, id, messages)) { return false; } - else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Journal - && !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mJournals, id, messages)) + else if (select.mFunction == ESM::DialogueCondition::Function_Journal + && !verifyId(ESM::RefId::stringRefId(select.mVariable), mJournals, id, messages)) { return false; } - else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Item - && !verifyItem(ESM::RefId::stringRefId(infoCondition.getVariableName()), id, messages)) + else if (select.mFunction == ESM::DialogueCondition::Function_Item + && !verifyItem(ESM::RefId::stringRefId(select.mVariable), id, messages)) { return false; } - else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Dead - && !verifyActor(ESM::RefId::stringRefId(infoCondition.getVariableName()), id, messages)) + else if (select.mFunction == ESM::DialogueCondition::Function_Dead + && !verifyActor(ESM::RefId::stringRefId(select.mVariable), id, messages)) { return false; } - else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotId - && !verifyActor(ESM::RefId::stringRefId(infoCondition.getVariableName()), id, messages)) + else if (select.mFunction == ESM::DialogueCondition::Function_NotId + && !verifyActor(ESM::RefId::stringRefId(select.mVariable), id, messages)) { return false; } - else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotFaction - && !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mFactions, id, messages)) + else if (select.mFunction == ESM::DialogueCondition::Function_NotFaction + && !verifyId(ESM::RefId::stringRefId(select.mVariable), mFactions, id, messages)) { return false; } - else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotClass - && !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mClasses, id, messages)) + else if (select.mFunction == ESM::DialogueCondition::Function_NotClass + && !verifyId(ESM::RefId::stringRefId(select.mVariable), mClasses, id, messages)) { return false; } - else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotRace - && !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mRaces, id, messages)) + else if (select.mFunction == ESM::DialogueCondition::Function_NotRace + && !verifyId(ESM::RefId::stringRefId(select.mVariable), mRaces, id, messages)) { return false; } - else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotCell - && !verifyCell(infoCondition.getVariableName(), id, messages)) + else if (select.mFunction == ESM::DialogueCondition::Function_NotCell + && !verifyCell(select.mVariable, id, messages)) { return false; } diff --git a/apps/opencs/model/tools/topicinfocheck.hpp b/apps/opencs/model/tools/topicinfocheck.hpp index 1bb2fc61dc..3069fbc0ff 100644 --- a/apps/opencs/model/tools/topicinfocheck.hpp +++ b/apps/opencs/model/tools/topicinfocheck.hpp @@ -84,7 +84,7 @@ namespace CSMTools const ESM::RefId& name, int rank, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); bool verifyItem(const ESM::RefId& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); bool verifySelectStruct( - const ESM::DialInfo::SelectStruct& select, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + const ESM::DialogueCondition& select, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); bool verifySound(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); template diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp index 37aaf08445..acbe6b5d38 100644 --- a/apps/opencs/model/world/actoradapter.cpp +++ b/apps/opencs/model/world/actoradapter.cpp @@ -67,6 +67,11 @@ namespace CSMWorld return mMaleParts[ESM::getMeshPart(index)]; } + const osg::Vec2f& ActorAdapter::RaceData::getGenderWeightHeight(bool isFemale) + { + return isFemale ? mWeightsHeights.mFemaleWeightHeight : mWeightsHeights.mMaleWeightHeight; + } + bool ActorAdapter::RaceData::hasDependency(const ESM::RefId& id) const { return mDependencies.find(id) != mDependencies.end(); @@ -90,10 +95,11 @@ namespace CSMWorld mDependencies.emplace(id); } - void ActorAdapter::RaceData::reset_data(const ESM::RefId& id, bool isBeast) + void ActorAdapter::RaceData::reset_data(const ESM::RefId& id, const WeightsHeights& raceStats, bool isBeast) { mId = id; mIsBeast = isBeast; + mWeightsHeights = raceStats; for (auto& str : mFemaleParts) str = ESM::RefId(); for (auto& str : mMaleParts) @@ -163,6 +169,11 @@ namespace CSMWorld return it->second.first; } + const osg::Vec2f& ActorAdapter::ActorData::getRaceWeightHeight() const + { + return mRaceData->getGenderWeightHeight(isFemale()); + } + bool ActorAdapter::ActorData::hasDependency(const ESM::RefId& id) const { return mDependencies.find(id) != mDependencies.end(); @@ -504,7 +515,11 @@ namespace CSMWorld } auto& race = raceRecord.get(); - data->reset_data(id, race.mData.mFlags & ESM::Race::Beast); + + WeightsHeights scaleStats = { osg::Vec2f(race.mData.mMaleWeight, race.mData.mMaleHeight), + osg::Vec2f(race.mData.mFemaleWeight, race.mData.mFemaleHeight) }; + + data->reset_data(id, scaleStats, race.mData.mFlags & ESM::Race::Beast); // Setup body parts for (int i = 0; i < mBodyParts.getSize(); ++i) diff --git a/apps/opencs/model/world/actoradapter.hpp b/apps/opencs/model/world/actoradapter.hpp index 9747f448ae..1650fc9006 100644 --- a/apps/opencs/model/world/actoradapter.hpp +++ b/apps/opencs/model/world/actoradapter.hpp @@ -41,6 +41,12 @@ namespace CSMWorld /// Tracks unique strings using RefIdSet = std::unordered_set; + struct WeightsHeights + { + osg::Vec2f mMaleWeightHeight; + osg::Vec2f mFemaleWeightHeight; + }; + /// Contains base race data shared between actors class RaceData { @@ -57,6 +63,8 @@ namespace CSMWorld const ESM::RefId& getFemalePart(ESM::PartReferenceType index) const; /// Retrieves the associated body part const ESM::RefId& getMalePart(ESM::PartReferenceType index) const; + + const osg::Vec2f& getGenderWeightHeight(bool isFemale); /// Checks if the race has a data dependency bool hasDependency(const ESM::RefId& id) const; @@ -67,7 +75,8 @@ namespace CSMWorld /// Marks an additional dependency void addOtherDependency(const ESM::RefId& id); /// Clears parts and dependencies - void reset_data(const ESM::RefId& raceId, bool isBeast = false); + void reset_data(const ESM::RefId& raceId, + const WeightsHeights& raceStats = { osg::Vec2f(1.f, 1.f), osg::Vec2f(1.f, 1.f) }, bool isBeast = false); private: bool handles(ESM::PartReferenceType type) const; @@ -75,6 +84,7 @@ namespace CSMWorld bool mIsBeast; RacePartList mFemaleParts; RacePartList mMaleParts; + WeightsHeights mWeightsHeights; RefIdSet mDependencies; }; using RaceDataPtr = std::shared_ptr; @@ -96,6 +106,8 @@ namespace CSMWorld std::string getSkeleton() const; /// Retrieves the associated actor part ESM::RefId getPart(ESM::PartReferenceType index) const; + + const osg::Vec2f& getRaceWeightHeight() const; /// Checks if the actor has a data dependency bool hasDependency(const ESM::RefId& id) const; diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index eba09bd8b1..5fbded5a15 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -585,19 +585,22 @@ namespace CSMWorld void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); + + float bodyAttr = std::clamp(data.toFloat(), 0.5f, 2.0f); + if (mWeight) { if (mMale) - record2.mData.mMaleWeight = data.toFloat(); + record2.mData.mMaleWeight = bodyAttr; else - record2.mData.mFemaleWeight = data.toFloat(); + record2.mData.mFemaleWeight = bodyAttr; } else { if (mMale) - record2.mData.mMaleHeight = data.toFloat(); + record2.mData.mMaleHeight = bodyAttr; else - record2.mData.mFemaleHeight = data.toFloat(); + record2.mData.mFemaleHeight = bodyAttr; } record.setModified(record2); } @@ -968,7 +971,7 @@ namespace CSMWorld void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); - record2.mScale = data.toFloat(); + record2.mScale = std::clamp(data.toFloat(), 0.5f, 2.0f); record.setModified(record2); } @@ -1133,8 +1136,8 @@ namespace CSMWorld template struct TeleportColumn : public Column { - TeleportColumn() - : Column(Columns::ColumnId_Teleport, ColumnBase::Display_Boolean) + TeleportColumn(int flags) + : Column(Columns::ColumnId_Teleport, ColumnBase::Display_Boolean, flags) { } @@ -1162,6 +1165,8 @@ namespace CSMWorld QVariant get(const Record& record) const override { + if (!record.get().mTeleport) + return QVariant(); return QString::fromUtf8(record.get().mDestCell.c_str()); } @@ -1179,6 +1184,26 @@ namespace CSMWorld bool isUserEditable() const override { return true; } }; + template + struct IsLockedColumn : public Column + { + IsLockedColumn(int flags) + : Column(Columns::ColumnId_IsLocked, ColumnBase::Display_Boolean, flags) + { + } + + QVariant get(const Record& record) const override { return record.get().mIsLocked; } + + void set(Record& record, const QVariant& data) override + { + ESXRecordT record2 = record.get(); + record2.mIsLocked = data.toBool(); + record.setModified(record2); + } + + bool isEditable() const override { return true; } + }; + template struct LockLevelColumn : public Column { @@ -1187,7 +1212,12 @@ namespace CSMWorld { } - QVariant get(const Record& record) const override { return record.get().mLockLevel; } + QVariant get(const Record& record) const override + { + if (record.get().mIsLocked) + return record.get().mLockLevel; + return QVariant(); + } void set(Record& record, const QVariant& data) override { @@ -1209,7 +1239,9 @@ namespace CSMWorld QVariant get(const Record& record) const override { - return QString::fromUtf8(record.get().mKey.getRefIdString().c_str()); + if (record.get().mIsLocked) + return QString::fromUtf8(record.get().mKey.getRefIdString().c_str()); + return QVariant(); } void set(Record& record, const QVariant& data) override @@ -1279,17 +1311,21 @@ namespace CSMWorld { ESM::Position ESXRecordT::*mPosition; int mIndex; + bool mIsDoor; PosColumn(ESM::Position ESXRecordT::*position, int index, bool door) : Column((door ? Columns::ColumnId_DoorPositionXPos : Columns::ColumnId_PositionXPos) + index, ColumnBase::Display_Float) , mPosition(position) , mIndex(index) + , mIsDoor(door) { } QVariant get(const Record& record) const override { + if (!record.get().mTeleport && mIsDoor) + return QVariant(); const ESM::Position& position = record.get().*mPosition; return position.pos[mIndex]; } @@ -1313,17 +1349,21 @@ namespace CSMWorld { ESM::Position ESXRecordT::*mPosition; int mIndex; + bool mIsDoor; RotColumn(ESM::Position ESXRecordT::*position, int index, bool door) : Column((door ? Columns::ColumnId_DoorPositionXRot : Columns::ColumnId_PositionXRot) + index, ColumnBase::Display_Double) , mPosition(position) , mIndex(index) + , mIsDoor(door) { } QVariant get(const Record& record) const override { + if (!record.get().mTeleport && mIsDoor) + return QVariant(); const ESM::Position& position = record.get().*mPosition; return osg::RadiansToDegrees(position.rot[mIndex]); } @@ -2049,6 +2089,26 @@ namespace CSMWorld bool isEditable() const override { return true; } }; + template + struct ProjectileSpeedColumn : public Column + { + ProjectileSpeedColumn() + : Column(Columns::ColumnId_ProjectileSpeed, ColumnBase::Display_Float) + { + } + + QVariant get(const Record& record) const override { return record.get().mData.mSpeed; } + + void set(Record& record, const QVariant& data) override + { + ESXRecordT record2 = record.get(); + record2.mData.mSpeed = data.toFloat(); + record.setModified(record2); + } + + bool isEditable() const override { return true; } + }; + template struct SchoolColumn : public Column { diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index 45759cd234..d4c35c5cec 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -57,8 +57,10 @@ namespace CSMWorld { ColumnId_Charges, "Charges" }, { ColumnId_Enchantment, "Enchantment" }, { ColumnId_StackCount, "Count" }, + { ColumnId_GoldValue, "Value" }, { ColumnId_Teleport, "Teleport" }, { ColumnId_TeleportCell, "Teleport Cell" }, + { ColumnId_IsLocked, "Locked" }, { ColumnId_LockLevel, "Lock Level" }, { ColumnId_Key, "Key" }, { ColumnId_Trap, "Trap" }, @@ -235,6 +237,7 @@ namespace CSMWorld { ColumnId_RegionSounds, "Sounds" }, { ColumnId_SoundName, "Sound Name" }, { ColumnId_SoundChance, "Chance" }, + { ColumnId_SoundProbability, "Probability" }, { ColumnId_FactionReactions, "Reactions" }, { ColumnId_FactionRanks, "Ranks" }, @@ -321,7 +324,6 @@ namespace CSMWorld { ColumnId_MaxAttack, "Max Attack" }, { ColumnId_CreatureMisc, "Creature Misc" }, - { ColumnId_Idle1, "Idle 1" }, { ColumnId_Idle2, "Idle 2" }, { ColumnId_Idle3, "Idle 3" }, { ColumnId_Idle4, "Idle 4" }, @@ -329,6 +331,7 @@ namespace CSMWorld { ColumnId_Idle6, "Idle 6" }, { ColumnId_Idle7, "Idle 7" }, { ColumnId_Idle8, "Idle 8" }, + { ColumnId_Idle9, "Idle 9" }, { ColumnId_RegionWeather, "Weather" }, { ColumnId_WeatherName, "Type" }, @@ -376,6 +379,7 @@ namespace CSMWorld { ColumnId_Blocked, "Blocked" }, { ColumnId_LevelledCreatureId, "Levelled Creature" }, + { ColumnId_ProjectileSpeed, "Projectile Speed" }, // end marker { -1, 0 }, diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index 74e5bdd006..469c1eee33 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -310,14 +310,14 @@ namespace CSMWorld ColumnId_MaxAttack = 284, ColumnId_CreatureMisc = 285, - ColumnId_Idle1 = 286, - ColumnId_Idle2 = 287, - ColumnId_Idle3 = 288, - ColumnId_Idle4 = 289, - ColumnId_Idle5 = 290, - ColumnId_Idle6 = 291, - ColumnId_Idle7 = 292, - ColumnId_Idle8 = 293, + ColumnId_Idle2 = 286, + ColumnId_Idle3 = 287, + ColumnId_Idle4 = 288, + ColumnId_Idle5 = 289, + ColumnId_Idle6 = 290, + ColumnId_Idle7 = 291, + ColumnId_Idle8 = 292, + ColumnId_Idle9 = 293, ColumnId_RegionWeather = 294, ColumnId_WeatherName = 295, @@ -349,6 +349,14 @@ namespace CSMWorld ColumnId_SelectionGroupObjects = 316, + ColumnId_SoundProbability = 317, + + ColumnId_IsLocked = 318, + + ColumnId_ProjectileSpeed = 319, + + ColumnId_GoldValue = 320, + // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. ColumnId_UseValue1 = 0x10000, diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index ba1f1e5ac3..470ce04131 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -161,7 +161,7 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data defines["radialFog"] = "0"; defines["lightingModel"] = "0"; defines["reverseZ"] = "0"; - defines["refraction_enabled"] = "0"; + defines["waterRefraction"] = "0"; for (const auto& define : shadowDefines) defines[define.first] = define.second; mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(defines); @@ -301,8 +301,8 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data mRegions.addColumn(new NestedParentColumn(Columns::ColumnId_RegionWeather)); index = mRegions.getColumns() - 1; mRegions.addAdapter(std::make_pair(&mRegions.getColumn(index), new RegionWeatherAdapter())); - mRegions.getNestableColumn(index)->addColumn( - new NestedChildColumn(Columns::ColumnId_WeatherName, ColumnBase::Display_String, false)); + mRegions.getNestableColumn(index)->addColumn(new NestedChildColumn( + Columns::ColumnId_WeatherName, ColumnBase::Display_String, ColumnBase::Flag_Dialogue, false)); mRegions.getNestableColumn(index)->addColumn( new NestedChildColumn(Columns::ColumnId_WeatherChance, ColumnBase::Display_UnsignedInteger8)); // Region Sounds @@ -313,6 +313,8 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data new NestedChildColumn(Columns::ColumnId_SoundName, ColumnBase::Display_Sound)); mRegions.getNestableColumn(index)->addColumn( new NestedChildColumn(Columns::ColumnId_SoundChance, ColumnBase::Display_UnsignedInteger8)); + mRegions.getNestableColumn(index)->addColumn(new NestedChildColumn( + Columns::ColumnId_SoundProbability, ColumnBase::Display_String, ColumnBase::Flag_Dialogue, false)); mBirthsigns.addColumn(new StringIdColumn); mBirthsigns.addColumn(new RecordStateColumn); @@ -500,6 +502,7 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data mMagicEffects.addColumn(new FixedRecordTypeColumn(UniversalId::Type_MagicEffect)); mMagicEffects.addColumn(new SchoolColumn); mMagicEffects.addColumn(new BaseCostColumn); + mMagicEffects.addColumn(new ProjectileSpeedColumn); mMagicEffects.addColumn(new EffectTextureColumn(Columns::ColumnId_Icon)); mMagicEffects.addColumn(new EffectTextureColumn(Columns::ColumnId_Particle)); mMagicEffects.addColumn(new EffectObjectColumn(Columns::ColumnId_CastingObject)); @@ -510,6 +513,7 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data mMagicEffects.addColumn(new EffectSoundColumn(Columns::ColumnId_HitSound)); mMagicEffects.addColumn(new EffectSoundColumn(Columns::ColumnId_AreaSound)); mMagicEffects.addColumn(new EffectSoundColumn(Columns::ColumnId_BoltSound)); + mMagicEffects.addColumn( new FlagColumn(Columns::ColumnId_AllowSpellmaking, ESM::MagicEffect::AllowSpellmaking)); mMagicEffects.addColumn( @@ -589,7 +593,8 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data mRefs.addColumn(new ChargesColumn); mRefs.addColumn(new EnchantmentChargesColumn); mRefs.addColumn(new StackSizeColumn); - mRefs.addColumn(new TeleportColumn); + mRefs.addColumn(new TeleportColumn( + ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh)); mRefs.addColumn(new TeleportCellColumn); mRefs.addColumn(new PosColumn(&CellRef::mDoorDest, 0, true)); mRefs.addColumn(new PosColumn(&CellRef::mDoorDest, 1, true)); @@ -597,6 +602,8 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data mRefs.addColumn(new RotColumn(&CellRef::mDoorDest, 0, true)); mRefs.addColumn(new RotColumn(&CellRef::mDoorDest, 1, true)); mRefs.addColumn(new RotColumn(&CellRef::mDoorDest, 2, true)); + mRefs.addColumn(new IsLockedColumn( + ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh)); mRefs.addColumn(new LockLevelColumn); mRefs.addColumn(new KeyColumn); mRefs.addColumn(new TrapColumn); diff --git a/apps/opencs/model/world/idtableproxymodel.cpp b/apps/opencs/model/world/idtableproxymodel.cpp index d4e342f5de..c03b4ea30a 100644 --- a/apps/opencs/model/world/idtableproxymodel.cpp +++ b/apps/opencs/model/world/idtableproxymodel.cpp @@ -63,9 +63,18 @@ bool CSMWorld::IdTableProxyModel::filterAcceptsRow(int sourceRow, const QModelIn CSMWorld::IdTableProxyModel::IdTableProxyModel(QObject* parent) : QSortFilterProxyModel(parent) + , mFilterTimer{ new QTimer(this) } , mSourceModel(nullptr) { setSortCaseSensitivity(Qt::CaseInsensitive); + + mFilterTimer->setSingleShot(true); + int intervalSetting = CSMPrefs::State::get()["ID Tables"]["filter-delay"].toInt(); + mFilterTimer->setInterval(intervalSetting); + + connect(&CSMPrefs::State::get(), &CSMPrefs::State::settingChanged, this, + [this](const CSMPrefs::Setting* setting) { this->settingChanged(setting); }); + connect(mFilterTimer.get(), &QTimer::timeout, this, [this]() { this->timerTimeout(); }); } QModelIndex CSMWorld::IdTableProxyModel::getModelIndex(const std::string& id, int column) const @@ -87,10 +96,8 @@ void CSMWorld::IdTableProxyModel::setSourceModel(QAbstractItemModel* model) void CSMWorld::IdTableProxyModel::setFilter(const std::shared_ptr& filter) { - beginResetModel(); - mFilter = filter; - updateColumnMap(); - endResetModel(); + mAwaitingFilter = filter; + mFilterTimer->start(); } bool CSMWorld::IdTableProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right) const @@ -131,6 +138,26 @@ void CSMWorld::IdTableProxyModel::refreshFilter() } } +void CSMWorld::IdTableProxyModel::timerTimeout() +{ + if (mAwaitingFilter) + { + beginResetModel(); + mFilter = mAwaitingFilter; + updateColumnMap(); + endResetModel(); + mAwaitingFilter.reset(); + } +} + +void CSMWorld::IdTableProxyModel::settingChanged(const CSMPrefs::Setting* setting) +{ + if (*setting == "ID Tables/filter-delay") + { + mFilterTimer->setInterval(setting->toInt()); + } +} + void CSMWorld::IdTableProxyModel::sourceRowsInserted(const QModelIndex& parent, int /*start*/, int end) { refreshFilter(); diff --git a/apps/opencs/model/world/idtableproxymodel.hpp b/apps/opencs/model/world/idtableproxymodel.hpp index 639cf47287..b7d38f5a2d 100644 --- a/apps/opencs/model/world/idtableproxymodel.hpp +++ b/apps/opencs/model/world/idtableproxymodel.hpp @@ -10,6 +10,9 @@ #include #include #include +#include + +#include "../prefs/state.hpp" #include "columns.hpp" @@ -29,6 +32,8 @@ namespace CSMWorld Q_OBJECT std::shared_ptr mFilter; + std::unique_ptr mFilterTimer; + std::shared_ptr mAwaitingFilter; std::map mColumnMap; // column ID, column index in this model (or -1) // Cache of enum values for enum columns (e.g. Modified, Record Type). @@ -68,6 +73,10 @@ namespace CSMWorld virtual void sourceDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); + void timerTimeout(); + + void settingChanged(const CSMPrefs::Setting* setting); + signals: void rowAdded(const std::string& id); diff --git a/apps/opencs/model/world/infoselectwrapper.cpp b/apps/opencs/model/world/infoselectwrapper.cpp index fb1adf16d4..2e9ed6e150 100644 --- a/apps/opencs/model/world/infoselectwrapper.cpp +++ b/apps/opencs/model/world/infoselectwrapper.cpp @@ -6,16 +6,9 @@ #include -const size_t CSMWorld::ConstInfoSelectWrapper::RuleMinSize = 5; - -const size_t CSMWorld::ConstInfoSelectWrapper::FunctionPrefixOffset = 1; -const size_t CSMWorld::ConstInfoSelectWrapper::FunctionIndexOffset = 2; -const size_t CSMWorld::ConstInfoSelectWrapper::RelationIndexOffset = 4; -const size_t CSMWorld::ConstInfoSelectWrapper::VarNameOffset = 5; - const char* CSMWorld::ConstInfoSelectWrapper::FunctionEnumStrings[] = { - "Rank Low", - "Rank High", + "Faction Reaction Low", + "Faction Reaction High", "Rank Requirement", "Reputation", "Health Percent", @@ -72,7 +65,7 @@ const char* CSMWorld::ConstInfoSelectWrapper::FunctionEnumStrings[] = { "PC Endurance", "PC Personality", "PC Luck", - "PC Corpus", + "PC Corprus", "Weather", "PC Vampire", "Level", @@ -112,55 +105,40 @@ const char* CSMWorld::ConstInfoSelectWrapper::RelationEnumStrings[] = { nullptr, }; -const char* CSMWorld::ConstInfoSelectWrapper::ComparisonEnumStrings[] = { - "Boolean", - "Integer", - "Numeric", - nullptr, -}; - -// static functions - -std::string CSMWorld::ConstInfoSelectWrapper::convertToString(FunctionName name) +namespace { - if (name < Function_None) - return FunctionEnumStrings[name]; - else + std::string_view convertToString(ESM::DialogueCondition::Function name) + { + if (name < ESM::DialogueCondition::Function_None) + return CSMWorld::ConstInfoSelectWrapper::FunctionEnumStrings[name]; return "(Invalid Data: Function)"; -} + } -std::string CSMWorld::ConstInfoSelectWrapper::convertToString(RelationType type) -{ - if (type < Relation_None) - return RelationEnumStrings[type]; - else + std::string_view convertToString(ESM::DialogueCondition::Comparison type) + { + if (type != ESM::DialogueCondition::Comp_None) + return CSMWorld::ConstInfoSelectWrapper::RelationEnumStrings[type - ESM::DialogueCondition::Comp_Eq]; return "(Invalid Data: Relation)"; -} - -std::string CSMWorld::ConstInfoSelectWrapper::convertToString(ComparisonType type) -{ - if (type < Comparison_None) - return ComparisonEnumStrings[type]; - else - return "(Invalid Data: Comparison)"; + } } // ConstInfoSelectWrapper -CSMWorld::ConstInfoSelectWrapper::ConstInfoSelectWrapper(const ESM::DialInfo::SelectStruct& select) +CSMWorld::ConstInfoSelectWrapper::ConstInfoSelectWrapper(const ESM::DialogueCondition& select) : mConstSelect(select) { - readRule(); + updateHasVariable(); + updateComparisonType(); } -CSMWorld::ConstInfoSelectWrapper::FunctionName CSMWorld::ConstInfoSelectWrapper::getFunctionName() const +ESM::DialogueCondition::Function CSMWorld::ConstInfoSelectWrapper::getFunctionName() const { - return mFunctionName; + return mConstSelect.mFunction; } -CSMWorld::ConstInfoSelectWrapper::RelationType CSMWorld::ConstInfoSelectWrapper::getRelationType() const +ESM::DialogueCondition::Comparison CSMWorld::ConstInfoSelectWrapper::getRelationType() const { - return mRelationType; + return mConstSelect.mComparison; } CSMWorld::ConstInfoSelectWrapper::ComparisonType CSMWorld::ConstInfoSelectWrapper::getComparisonType() const @@ -175,24 +153,21 @@ bool CSMWorld::ConstInfoSelectWrapper::hasVariable() const const std::string& CSMWorld::ConstInfoSelectWrapper::getVariableName() const { - return mVariableName; + return mConstSelect.mVariable; } bool CSMWorld::ConstInfoSelectWrapper::conditionIsAlwaysTrue() const { - if (!variantTypeIsValid()) - return false; - if (mComparisonType == Comparison_Boolean || mComparisonType == Comparison_Integer) { - if (mConstSelect.mValue.getType() == ESM::VT_Float) + if (std::holds_alternative(mConstSelect.mValue)) return conditionIsAlwaysTrue(getConditionFloatRange(), getValidIntRange()); else return conditionIsAlwaysTrue(getConditionIntRange(), getValidIntRange()); } else if (mComparisonType == Comparison_Numeric) { - if (mConstSelect.mValue.getType() == ESM::VT_Float) + if (std::holds_alternative(mConstSelect.mValue)) return conditionIsAlwaysTrue(getConditionFloatRange(), getValidFloatRange()); else return conditionIsAlwaysTrue(getConditionIntRange(), getValidFloatRange()); @@ -203,19 +178,16 @@ bool CSMWorld::ConstInfoSelectWrapper::conditionIsAlwaysTrue() const bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue() const { - if (!variantTypeIsValid()) - return false; - if (mComparisonType == Comparison_Boolean || mComparisonType == Comparison_Integer) { - if (mConstSelect.mValue.getType() == ESM::VT_Float) + if (std::holds_alternative(mConstSelect.mValue)) return conditionIsNeverTrue(getConditionFloatRange(), getValidIntRange()); else return conditionIsNeverTrue(getConditionIntRange(), getValidIntRange()); } else if (mComparisonType == Comparison_Numeric) { - if (mConstSelect.mValue.getType() == ESM::VT_Float) + if (std::holds_alternative(mConstSelect.mValue)) return conditionIsNeverTrue(getConditionFloatRange(), getValidFloatRange()); else return conditionIsNeverTrue(getConditionIntRange(), getValidFloatRange()); @@ -224,170 +196,36 @@ bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue() const return false; } -bool CSMWorld::ConstInfoSelectWrapper::variantTypeIsValid() const -{ - return (mConstSelect.mValue.getType() == ESM::VT_Int || mConstSelect.mValue.getType() == ESM::VT_Float); -} - -const ESM::Variant& CSMWorld::ConstInfoSelectWrapper::getVariant() const -{ - return mConstSelect.mValue; -} - std::string CSMWorld::ConstInfoSelectWrapper::toString() const { std::ostringstream stream; - stream << convertToString(mFunctionName) << " "; + stream << convertToString(getFunctionName()) << " "; if (mHasVariable) - stream << mVariableName << " "; + stream << getVariableName() << " "; - stream << convertToString(mRelationType) << " "; + stream << convertToString(getRelationType()) << " "; - switch (mConstSelect.mValue.getType()) - { - case ESM::VT_Int: - stream << mConstSelect.mValue.getInteger(); - break; - - case ESM::VT_Float: - stream << mConstSelect.mValue.getFloat(); - break; - - default: - stream << "(Invalid value type)"; - break; - } + std::visit([&](auto value) { stream << value; }, mConstSelect.mValue); return stream.str(); } -void CSMWorld::ConstInfoSelectWrapper::readRule() -{ - if (mConstSelect.mSelectRule.size() < RuleMinSize) - throw std::runtime_error("InfoSelectWrapper: rule is to small"); - - readFunctionName(); - readRelationType(); - readVariableName(); - updateHasVariable(); - updateComparisonType(); -} - -void CSMWorld::ConstInfoSelectWrapper::readFunctionName() -{ - char functionPrefix = mConstSelect.mSelectRule[FunctionPrefixOffset]; - std::string functionIndex = mConstSelect.mSelectRule.substr(FunctionIndexOffset, 2); - int convertedIndex = -1; - - // Read in function index, form ## from 00 .. 73, skip leading zero - if (functionIndex[0] == '0') - functionIndex = functionIndex[1]; - - std::stringstream stream; - stream << functionIndex; - stream >> convertedIndex; - - switch (functionPrefix) - { - case '1': - if (convertedIndex >= 0 && convertedIndex <= 73) - mFunctionName = static_cast(convertedIndex); - else - mFunctionName = Function_None; - break; - - case '2': - mFunctionName = Function_Global; - break; - case '3': - mFunctionName = Function_Local; - break; - case '4': - mFunctionName = Function_Journal; - break; - case '5': - mFunctionName = Function_Item; - break; - case '6': - mFunctionName = Function_Dead; - break; - case '7': - mFunctionName = Function_NotId; - break; - case '8': - mFunctionName = Function_NotFaction; - break; - case '9': - mFunctionName = Function_NotClass; - break; - case 'A': - mFunctionName = Function_NotRace; - break; - case 'B': - mFunctionName = Function_NotCell; - break; - case 'C': - mFunctionName = Function_NotLocal; - break; - default: - mFunctionName = Function_None; - break; - } -} - -void CSMWorld::ConstInfoSelectWrapper::readRelationType() -{ - char relationIndex = mConstSelect.mSelectRule[RelationIndexOffset]; - - switch (relationIndex) - { - case '0': - mRelationType = Relation_Equal; - break; - case '1': - mRelationType = Relation_NotEqual; - break; - case '2': - mRelationType = Relation_Greater; - break; - case '3': - mRelationType = Relation_GreaterOrEqual; - break; - case '4': - mRelationType = Relation_Less; - break; - case '5': - mRelationType = Relation_LessOrEqual; - break; - default: - mRelationType = Relation_None; - } -} - -void CSMWorld::ConstInfoSelectWrapper::readVariableName() -{ - if (mConstSelect.mSelectRule.size() >= VarNameOffset) - mVariableName = mConstSelect.mSelectRule.substr(VarNameOffset); - else - mVariableName.clear(); -} - void CSMWorld::ConstInfoSelectWrapper::updateHasVariable() { - switch (mFunctionName) + switch (getFunctionName()) { - case Function_Global: - case Function_Local: - case Function_Journal: - case Function_Item: - case Function_Dead: - case Function_NotId: - case Function_NotFaction: - case Function_NotClass: - case Function_NotRace: - case Function_NotCell: - case Function_NotLocal: + case ESM::DialogueCondition::Function_Global: + case ESM::DialogueCondition::Function_Local: + case ESM::DialogueCondition::Function_Journal: + case ESM::DialogueCondition::Function_Item: + case ESM::DialogueCondition::Function_Dead: + case ESM::DialogueCondition::Function_NotId: + case ESM::DialogueCondition::Function_NotFaction: + case ESM::DialogueCondition::Function_NotClass: + case ESM::DialogueCondition::Function_NotRace: + case ESM::DialogueCondition::Function_NotCell: + case ESM::DialogueCondition::Function_NotLocal: mHasVariable = true; break; @@ -399,103 +237,103 @@ void CSMWorld::ConstInfoSelectWrapper::updateHasVariable() void CSMWorld::ConstInfoSelectWrapper::updateComparisonType() { - switch (mFunctionName) + switch (getFunctionName()) { // Boolean - case Function_NotId: - case Function_NotFaction: - case Function_NotClass: - case Function_NotRace: - case Function_NotCell: - case Function_PcExpelled: - case Function_PcCommonDisease: - case Function_PcBlightDisease: - case Function_SameSex: - case Function_SameRace: - case Function_SameFaction: - case Function_Detected: - case Function_Alarmed: - case Function_PcCorpus: - case Function_PcVampire: - case Function_Attacked: - case Function_TalkedToPc: - case Function_ShouldAttack: - case Function_Werewolf: + case ESM::DialogueCondition::Function_NotId: + case ESM::DialogueCondition::Function_NotFaction: + case ESM::DialogueCondition::Function_NotClass: + case ESM::DialogueCondition::Function_NotRace: + case ESM::DialogueCondition::Function_NotCell: + case ESM::DialogueCondition::Function_PcExpelled: + case ESM::DialogueCondition::Function_PcCommonDisease: + case ESM::DialogueCondition::Function_PcBlightDisease: + case ESM::DialogueCondition::Function_SameSex: + case ESM::DialogueCondition::Function_SameRace: + case ESM::DialogueCondition::Function_SameFaction: + case ESM::DialogueCondition::Function_Detected: + case ESM::DialogueCondition::Function_Alarmed: + case ESM::DialogueCondition::Function_PcCorprus: + case ESM::DialogueCondition::Function_PcVampire: + case ESM::DialogueCondition::Function_Attacked: + case ESM::DialogueCondition::Function_TalkedToPc: + case ESM::DialogueCondition::Function_ShouldAttack: + case ESM::DialogueCondition::Function_Werewolf: mComparisonType = Comparison_Boolean; break; // Integer - case Function_Journal: - case Function_Item: - case Function_Dead: - case Function_RankLow: - case Function_RankHigh: - case Function_RankRequirement: - case Function_Reputation: - case Function_PcReputation: - case Function_PcLevel: - case Function_PcStrength: - case Function_PcBlock: - case Function_PcArmorer: - case Function_PcMediumArmor: - case Function_PcHeavyArmor: - case Function_PcBluntWeapon: - case Function_PcLongBlade: - case Function_PcAxe: - case Function_PcSpear: - case Function_PcAthletics: - case Function_PcEnchant: - case Function_PcDestruction: - case Function_PcAlteration: - case Function_PcIllusion: - case Function_PcConjuration: - case Function_PcMysticism: - case Function_PcRestoration: - case Function_PcAlchemy: - case Function_PcUnarmored: - case Function_PcSecurity: - case Function_PcSneak: - case Function_PcAcrobatics: - case Function_PcLightArmor: - case Function_PcShortBlade: - case Function_PcMarksman: - case Function_PcMerchantile: - case Function_PcSpeechcraft: - case Function_PcHandToHand: - case Function_PcGender: - case Function_PcClothingModifier: - case Function_PcCrimeLevel: - case Function_FactionRankDifference: - case Function_Choice: - case Function_PcIntelligence: - case Function_PcWillpower: - case Function_PcAgility: - case Function_PcSpeed: - case Function_PcEndurance: - case Function_PcPersonality: - case Function_PcLuck: - case Function_Weather: - case Function_Level: - case Function_CreatureTarget: - case Function_FriendHit: - case Function_Fight: - case Function_Hello: - case Function_Alarm: - case Function_Flee: - case Function_PcWerewolfKills: + case ESM::DialogueCondition::Function_Journal: + case ESM::DialogueCondition::Function_Item: + case ESM::DialogueCondition::Function_Dead: + case ESM::DialogueCondition::Function_FacReactionLowest: + case ESM::DialogueCondition::Function_FacReactionHighest: + case ESM::DialogueCondition::Function_RankRequirement: + case ESM::DialogueCondition::Function_Reputation: + case ESM::DialogueCondition::Function_PcReputation: + case ESM::DialogueCondition::Function_PcLevel: + case ESM::DialogueCondition::Function_PcStrength: + case ESM::DialogueCondition::Function_PcBlock: + case ESM::DialogueCondition::Function_PcArmorer: + case ESM::DialogueCondition::Function_PcMediumArmor: + case ESM::DialogueCondition::Function_PcHeavyArmor: + case ESM::DialogueCondition::Function_PcBluntWeapon: + case ESM::DialogueCondition::Function_PcLongBlade: + case ESM::DialogueCondition::Function_PcAxe: + case ESM::DialogueCondition::Function_PcSpear: + case ESM::DialogueCondition::Function_PcAthletics: + case ESM::DialogueCondition::Function_PcEnchant: + case ESM::DialogueCondition::Function_PcDestruction: + case ESM::DialogueCondition::Function_PcAlteration: + case ESM::DialogueCondition::Function_PcIllusion: + case ESM::DialogueCondition::Function_PcConjuration: + case ESM::DialogueCondition::Function_PcMysticism: + case ESM::DialogueCondition::Function_PcRestoration: + case ESM::DialogueCondition::Function_PcAlchemy: + case ESM::DialogueCondition::Function_PcUnarmored: + case ESM::DialogueCondition::Function_PcSecurity: + case ESM::DialogueCondition::Function_PcSneak: + case ESM::DialogueCondition::Function_PcAcrobatics: + case ESM::DialogueCondition::Function_PcLightArmor: + case ESM::DialogueCondition::Function_PcShortBlade: + case ESM::DialogueCondition::Function_PcMarksman: + case ESM::DialogueCondition::Function_PcMerchantile: + case ESM::DialogueCondition::Function_PcSpeechcraft: + case ESM::DialogueCondition::Function_PcHandToHand: + case ESM::DialogueCondition::Function_PcGender: + case ESM::DialogueCondition::Function_PcClothingModifier: + case ESM::DialogueCondition::Function_PcCrimeLevel: + case ESM::DialogueCondition::Function_FactionRankDifference: + case ESM::DialogueCondition::Function_Choice: + case ESM::DialogueCondition::Function_PcIntelligence: + case ESM::DialogueCondition::Function_PcWillpower: + case ESM::DialogueCondition::Function_PcAgility: + case ESM::DialogueCondition::Function_PcSpeed: + case ESM::DialogueCondition::Function_PcEndurance: + case ESM::DialogueCondition::Function_PcPersonality: + case ESM::DialogueCondition::Function_PcLuck: + case ESM::DialogueCondition::Function_Weather: + case ESM::DialogueCondition::Function_Level: + case ESM::DialogueCondition::Function_CreatureTarget: + case ESM::DialogueCondition::Function_FriendHit: + case ESM::DialogueCondition::Function_Fight: + case ESM::DialogueCondition::Function_Hello: + case ESM::DialogueCondition::Function_Alarm: + case ESM::DialogueCondition::Function_Flee: + case ESM::DialogueCondition::Function_PcWerewolfKills: mComparisonType = Comparison_Integer; break; // Numeric - case Function_Global: - case Function_Local: - case Function_NotLocal: - - case Function_Health_Percent: - case Function_PcHealthPercent: - case Function_PcMagicka: - case Function_PcFatigue: - case Function_PcHealth: + case ESM::DialogueCondition::Function_Global: + case ESM::DialogueCondition::Function_Local: + case ESM::DialogueCondition::Function_NotLocal: + + case ESM::DialogueCondition::Function_Health_Percent: + case ESM::DialogueCondition::Function_PcHealthPercent: + case ESM::DialogueCondition::Function_PcMagicka: + case ESM::DialogueCondition::Function_PcFatigue: + case ESM::DialogueCondition::Function_PcHealth: mComparisonType = Comparison_Numeric; break; @@ -511,15 +349,15 @@ std::pair CSMWorld::ConstInfoSelectWrapper::getConditionIntRange() con const int IntMin = std::numeric_limits::min(); const std::pair InvalidRange(IntMax, IntMin); - int value = mConstSelect.mValue.getInteger(); + int value = std::get(mConstSelect.mValue); - switch (mRelationType) + switch (getRelationType()) { - case Relation_Equal: - case Relation_NotEqual: + case ESM::DialogueCondition::Comp_Eq: + case ESM::DialogueCondition::Comp_Ne: return std::pair(value, value); - case Relation_Greater: + case ESM::DialogueCondition::Comp_Gt: if (value == IntMax) { return InvalidRange; @@ -530,10 +368,10 @@ std::pair CSMWorld::ConstInfoSelectWrapper::getConditionIntRange() con } break; - case Relation_GreaterOrEqual: + case ESM::DialogueCondition::Comp_Ge: return std::pair(value, IntMax); - case Relation_Less: + case ESM::DialogueCondition::Comp_Ls: if (value == IntMin) { return InvalidRange; @@ -543,7 +381,7 @@ std::pair CSMWorld::ConstInfoSelectWrapper::getConditionIntRange() con return std::pair(IntMin, value - 1); } - case Relation_LessOrEqual: + case ESM::DialogueCondition::Comp_Le: return std::pair(IntMin, value); default: @@ -557,24 +395,24 @@ std::pair CSMWorld::ConstInfoSelectWrapper::getConditionFloatRange const float FloatMin = -std::numeric_limits::infinity(); const float Epsilon = std::numeric_limits::epsilon(); - float value = mConstSelect.mValue.getFloat(); + float value = std::get(mConstSelect.mValue); - switch (mRelationType) + switch (getRelationType()) { - case Relation_Equal: - case Relation_NotEqual: + case ESM::DialogueCondition::Comp_Eq: + case ESM::DialogueCondition::Comp_Ne: return std::pair(value, value); - case Relation_Greater: + case ESM::DialogueCondition::Comp_Gt: return std::pair(value + Epsilon, FloatMax); - case Relation_GreaterOrEqual: + case ESM::DialogueCondition::Comp_Ge: return std::pair(value, FloatMax); - case Relation_Less: + case ESM::DialogueCondition::Comp_Ls: return std::pair(FloatMin, value - Epsilon); - case Relation_LessOrEqual: + case ESM::DialogueCondition::Comp_Le: return std::pair(FloatMin, value); default: @@ -587,120 +425,120 @@ std::pair CSMWorld::ConstInfoSelectWrapper::getValidIntRange() const const int IntMax = std::numeric_limits::max(); const int IntMin = std::numeric_limits::min(); - switch (mFunctionName) + switch (getFunctionName()) { // Boolean - case Function_NotId: - case Function_NotFaction: - case Function_NotClass: - case Function_NotRace: - case Function_NotCell: - case Function_PcExpelled: - case Function_PcCommonDisease: - case Function_PcBlightDisease: - case Function_SameSex: - case Function_SameRace: - case Function_SameFaction: - case Function_Detected: - case Function_Alarmed: - case Function_PcCorpus: - case Function_PcVampire: - case Function_Attacked: - case Function_TalkedToPc: - case Function_ShouldAttack: - case Function_Werewolf: + case ESM::DialogueCondition::Function_NotId: + case ESM::DialogueCondition::Function_NotFaction: + case ESM::DialogueCondition::Function_NotClass: + case ESM::DialogueCondition::Function_NotRace: + case ESM::DialogueCondition::Function_NotCell: + case ESM::DialogueCondition::Function_PcExpelled: + case ESM::DialogueCondition::Function_PcCommonDisease: + case ESM::DialogueCondition::Function_PcBlightDisease: + case ESM::DialogueCondition::Function_SameSex: + case ESM::DialogueCondition::Function_SameRace: + case ESM::DialogueCondition::Function_SameFaction: + case ESM::DialogueCondition::Function_Detected: + case ESM::DialogueCondition::Function_Alarmed: + case ESM::DialogueCondition::Function_PcCorprus: + case ESM::DialogueCondition::Function_PcVampire: + case ESM::DialogueCondition::Function_Attacked: + case ESM::DialogueCondition::Function_TalkedToPc: + case ESM::DialogueCondition::Function_ShouldAttack: + case ESM::DialogueCondition::Function_Werewolf: return std::pair(0, 1); // Integer - case Function_RankLow: - case Function_RankHigh: - case Function_Reputation: - case Function_PcReputation: - case Function_Journal: + case ESM::DialogueCondition::Function_FacReactionLowest: + case ESM::DialogueCondition::Function_FacReactionHighest: + case ESM::DialogueCondition::Function_Reputation: + case ESM::DialogueCondition::Function_PcReputation: + case ESM::DialogueCondition::Function_Journal: return std::pair(IntMin, IntMax); - case Function_Item: - case Function_Dead: - case Function_PcLevel: - case Function_PcStrength: - case Function_PcBlock: - case Function_PcArmorer: - case Function_PcMediumArmor: - case Function_PcHeavyArmor: - case Function_PcBluntWeapon: - case Function_PcLongBlade: - case Function_PcAxe: - case Function_PcSpear: - case Function_PcAthletics: - case Function_PcEnchant: - case Function_PcDestruction: - case Function_PcAlteration: - case Function_PcIllusion: - case Function_PcConjuration: - case Function_PcMysticism: - case Function_PcRestoration: - case Function_PcAlchemy: - case Function_PcUnarmored: - case Function_PcSecurity: - case Function_PcSneak: - case Function_PcAcrobatics: - case Function_PcLightArmor: - case Function_PcShortBlade: - case Function_PcMarksman: - case Function_PcMerchantile: - case Function_PcSpeechcraft: - case Function_PcHandToHand: - case Function_PcClothingModifier: - case Function_PcCrimeLevel: - case Function_Choice: - case Function_PcIntelligence: - case Function_PcWillpower: - case Function_PcAgility: - case Function_PcSpeed: - case Function_PcEndurance: - case Function_PcPersonality: - case Function_PcLuck: - case Function_Level: - case Function_PcWerewolfKills: + case ESM::DialogueCondition::Function_Item: + case ESM::DialogueCondition::Function_Dead: + case ESM::DialogueCondition::Function_PcLevel: + case ESM::DialogueCondition::Function_PcStrength: + case ESM::DialogueCondition::Function_PcBlock: + case ESM::DialogueCondition::Function_PcArmorer: + case ESM::DialogueCondition::Function_PcMediumArmor: + case ESM::DialogueCondition::Function_PcHeavyArmor: + case ESM::DialogueCondition::Function_PcBluntWeapon: + case ESM::DialogueCondition::Function_PcLongBlade: + case ESM::DialogueCondition::Function_PcAxe: + case ESM::DialogueCondition::Function_PcSpear: + case ESM::DialogueCondition::Function_PcAthletics: + case ESM::DialogueCondition::Function_PcEnchant: + case ESM::DialogueCondition::Function_PcDestruction: + case ESM::DialogueCondition::Function_PcAlteration: + case ESM::DialogueCondition::Function_PcIllusion: + case ESM::DialogueCondition::Function_PcConjuration: + case ESM::DialogueCondition::Function_PcMysticism: + case ESM::DialogueCondition::Function_PcRestoration: + case ESM::DialogueCondition::Function_PcAlchemy: + case ESM::DialogueCondition::Function_PcUnarmored: + case ESM::DialogueCondition::Function_PcSecurity: + case ESM::DialogueCondition::Function_PcSneak: + case ESM::DialogueCondition::Function_PcAcrobatics: + case ESM::DialogueCondition::Function_PcLightArmor: + case ESM::DialogueCondition::Function_PcShortBlade: + case ESM::DialogueCondition::Function_PcMarksman: + case ESM::DialogueCondition::Function_PcMerchantile: + case ESM::DialogueCondition::Function_PcSpeechcraft: + case ESM::DialogueCondition::Function_PcHandToHand: + case ESM::DialogueCondition::Function_PcClothingModifier: + case ESM::DialogueCondition::Function_PcCrimeLevel: + case ESM::DialogueCondition::Function_Choice: + case ESM::DialogueCondition::Function_PcIntelligence: + case ESM::DialogueCondition::Function_PcWillpower: + case ESM::DialogueCondition::Function_PcAgility: + case ESM::DialogueCondition::Function_PcSpeed: + case ESM::DialogueCondition::Function_PcEndurance: + case ESM::DialogueCondition::Function_PcPersonality: + case ESM::DialogueCondition::Function_PcLuck: + case ESM::DialogueCondition::Function_Level: + case ESM::DialogueCondition::Function_PcWerewolfKills: return std::pair(0, IntMax); - case Function_Fight: - case Function_Hello: - case Function_Alarm: - case Function_Flee: + case ESM::DialogueCondition::Function_Fight: + case ESM::DialogueCondition::Function_Hello: + case ESM::DialogueCondition::Function_Alarm: + case ESM::DialogueCondition::Function_Flee: return std::pair(0, 100); - case Function_Weather: + case ESM::DialogueCondition::Function_Weather: return std::pair(0, 9); - case Function_FriendHit: + case ESM::DialogueCondition::Function_FriendHit: return std::pair(0, 4); - case Function_RankRequirement: + case ESM::DialogueCondition::Function_RankRequirement: return std::pair(0, 3); - case Function_CreatureTarget: + case ESM::DialogueCondition::Function_CreatureTarget: return std::pair(0, 2); - case Function_PcGender: + case ESM::DialogueCondition::Function_PcGender: return std::pair(0, 1); - case Function_FactionRankDifference: + case ESM::DialogueCondition::Function_FactionRankDifference: return std::pair(-9, 9); // Numeric - case Function_Global: - case Function_Local: - case Function_NotLocal: + case ESM::DialogueCondition::Function_Global: + case ESM::DialogueCondition::Function_Local: + case ESM::DialogueCondition::Function_NotLocal: return std::pair(IntMin, IntMax); - case Function_PcMagicka: - case Function_PcFatigue: - case Function_PcHealth: + case ESM::DialogueCondition::Function_PcMagicka: + case ESM::DialogueCondition::Function_PcFatigue: + case ESM::DialogueCondition::Function_PcHealth: return std::pair(0, IntMax); - case Function_Health_Percent: - case Function_PcHealthPercent: + case ESM::DialogueCondition::Function_Health_Percent: + case ESM::DialogueCondition::Function_PcHealthPercent: return std::pair(0, 100); default: @@ -713,21 +551,21 @@ std::pair CSMWorld::ConstInfoSelectWrapper::getValidFloatRange() c const float FloatMax = std::numeric_limits::infinity(); const float FloatMin = -std::numeric_limits::infinity(); - switch (mFunctionName) + switch (getFunctionName()) { // Numeric - case Function_Global: - case Function_Local: - case Function_NotLocal: + case ESM::DialogueCondition::Function_Global: + case ESM::DialogueCondition::Function_Local: + case ESM::DialogueCondition::Function_NotLocal: return std::pair(FloatMin, FloatMax); - case Function_PcMagicka: - case Function_PcFatigue: - case Function_PcHealth: + case ESM::DialogueCondition::Function_PcMagicka: + case ESM::DialogueCondition::Function_PcFatigue: + case ESM::DialogueCondition::Function_PcHealth: return std::pair(0, FloatMax); - case Function_Health_Percent: - case Function_PcHealthPercent: + case ESM::DialogueCondition::Function_Health_Percent: + case ESM::DialogueCondition::Function_PcHealthPercent: return std::pair(0, 100); default: @@ -768,19 +606,19 @@ template bool CSMWorld::ConstInfoSelectWrapper::conditionIsAlwaysTrue( std::pair conditionRange, std::pair validRange) const { - switch (mRelationType) + switch (getRelationType()) { - case Relation_Equal: + case ESM::DialogueCondition::Comp_Eq: return false; - case Relation_NotEqual: + case ESM::DialogueCondition::Comp_Ne: // If value is not within range, it will always be true return !rangeContains(conditionRange.first, validRange); - case Relation_Greater: - case Relation_GreaterOrEqual: - case Relation_Less: - case Relation_LessOrEqual: + case ESM::DialogueCondition::Comp_Gt: + case ESM::DialogueCondition::Comp_Ge: + case ESM::DialogueCondition::Comp_Ls: + case ESM::DialogueCondition::Comp_Le: // If the valid range is completely within the condition range, it will always be true return rangeFullyContains(conditionRange, validRange); @@ -795,18 +633,18 @@ template bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue( std::pair conditionRange, std::pair validRange) const { - switch (mRelationType) + switch (getRelationType()) { - case Relation_Equal: + case ESM::DialogueCondition::Comp_Eq: return !rangeContains(conditionRange.first, validRange); - case Relation_NotEqual: + case ESM::DialogueCondition::Comp_Ne: return false; - case Relation_Greater: - case Relation_GreaterOrEqual: - case Relation_Less: - case Relation_LessOrEqual: + case ESM::DialogueCondition::Comp_Gt: + case ESM::DialogueCondition::Comp_Ge: + case ESM::DialogueCondition::Comp_Ls: + case ESM::DialogueCondition::Comp_Le: // If ranges do not overlap, it will never be true return !rangesOverlap(conditionRange, validRange); @@ -817,153 +655,47 @@ bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue( return false; } +QVariant CSMWorld::ConstInfoSelectWrapper::getValue() const +{ + return std::visit([](auto value) { return QVariant(value); }, mConstSelect.mValue); +} + // InfoSelectWrapper -CSMWorld::InfoSelectWrapper::InfoSelectWrapper(ESM::DialInfo::SelectStruct& select) +CSMWorld::InfoSelectWrapper::InfoSelectWrapper(ESM::DialogueCondition& select) : CSMWorld::ConstInfoSelectWrapper(select) , mSelect(select) { } -void CSMWorld::InfoSelectWrapper::setFunctionName(FunctionName name) +void CSMWorld::InfoSelectWrapper::setFunctionName(ESM::DialogueCondition::Function name) { - mFunctionName = name; + mSelect.mFunction = name; updateHasVariable(); updateComparisonType(); + if (getComparisonType() != ConstInfoSelectWrapper::Comparison_Numeric + && std::holds_alternative(mSelect.mValue)) + { + mSelect.mValue = std::visit([](auto value) { return static_cast(value); }, mSelect.mValue); + } } -void CSMWorld::InfoSelectWrapper::setRelationType(RelationType type) +void CSMWorld::InfoSelectWrapper::setRelationType(ESM::DialogueCondition::Comparison type) { - mRelationType = type; + mSelect.mComparison = type; } void CSMWorld::InfoSelectWrapper::setVariableName(const std::string& name) { - mVariableName = name; + mSelect.mVariable = name; } -void CSMWorld::InfoSelectWrapper::setDefaults() +void CSMWorld::InfoSelectWrapper::setValue(int value) { - if (!variantTypeIsValid()) - mSelect.mValue.setType(ESM::VT_Int); - - switch (mComparisonType) - { - case Comparison_Boolean: - setRelationType(Relation_Equal); - mSelect.mValue.setInteger(1); - break; - - case Comparison_Integer: - case Comparison_Numeric: - setRelationType(Relation_Greater); - mSelect.mValue.setInteger(0); - break; - - default: - // Do nothing - break; - } - - update(); -} - -void CSMWorld::InfoSelectWrapper::update() -{ - std::ostringstream stream; - - // Leading 0 - stream << '0'; - - // Write Function - - bool writeIndex = false; - size_t functionIndex = static_cast(mFunctionName); - - switch (mFunctionName) - { - case Function_None: - stream << '0'; - break; - case Function_Global: - stream << '2'; - break; - case Function_Local: - stream << '3'; - break; - case Function_Journal: - stream << '4'; - break; - case Function_Item: - stream << '5'; - break; - case Function_Dead: - stream << '6'; - break; - case Function_NotId: - stream << '7'; - break; - case Function_NotFaction: - stream << '8'; - break; - case Function_NotClass: - stream << '9'; - break; - case Function_NotRace: - stream << 'A'; - break; - case Function_NotCell: - stream << 'B'; - break; - case Function_NotLocal: - stream << 'C'; - break; - default: - stream << '1'; - writeIndex = true; - break; - } - - if (writeIndex && functionIndex < 10) // leading 0 - stream << '0' << functionIndex; - else if (writeIndex) - stream << functionIndex; - else - stream << "00"; - - // Write Relation - switch (mRelationType) - { - case Relation_Equal: - stream << '0'; - break; - case Relation_NotEqual: - stream << '1'; - break; - case Relation_Greater: - stream << '2'; - break; - case Relation_GreaterOrEqual: - stream << '3'; - break; - case Relation_Less: - stream << '4'; - break; - case Relation_LessOrEqual: - stream << '5'; - break; - default: - stream << '0'; - break; - } - - if (mHasVariable) - stream << mVariableName; - - mSelect.mSelectRule = stream.str(); + mSelect.mValue = value; } -ESM::Variant& CSMWorld::InfoSelectWrapper::getVariant() +void CSMWorld::InfoSelectWrapper::setValue(float value) { - return mSelect.mValue; + mSelect.mValue = value; } diff --git a/apps/opencs/model/world/infoselectwrapper.hpp b/apps/opencs/model/world/infoselectwrapper.hpp index 4ecdc676dc..b3b5abe462 100644 --- a/apps/opencs/model/world/infoselectwrapper.hpp +++ b/apps/opencs/model/world/infoselectwrapper.hpp @@ -7,133 +7,13 @@ #include -namespace ESM -{ - class Variant; -} +#include namespace CSMWorld { - // ESM::DialInfo::SelectStruct.mSelectRule - // 012345... - // ^^^ ^^ - // ||| || - // ||| |+------------- condition variable string - // ||| +-------------- comparison type, ['0'..'5']; e.g. !=, <, >=, etc - // ||+---------------- function index (encoded, where function == '1') - // |+----------------- function, ['1'..'C']; e.g. Global, Local, Not ID, etc - // +------------------ unknown - // - - // Wrapper for DialInfo::SelectStruct class ConstInfoSelectWrapper { public: - // Order matters - enum FunctionName - { - Function_RankLow = 0, - Function_RankHigh, - Function_RankRequirement, - Function_Reputation, - Function_Health_Percent, - Function_PcReputation, - Function_PcLevel, - Function_PcHealthPercent, - Function_PcMagicka, - Function_PcFatigue, - Function_PcStrength, - Function_PcBlock, - Function_PcArmorer, - Function_PcMediumArmor, - Function_PcHeavyArmor, - Function_PcBluntWeapon, - Function_PcLongBlade, - Function_PcAxe, - Function_PcSpear, - Function_PcAthletics, - Function_PcEnchant, - Function_PcDestruction, - Function_PcAlteration, - Function_PcIllusion, - Function_PcConjuration, - Function_PcMysticism, - Function_PcRestoration, - Function_PcAlchemy, - Function_PcUnarmored, - Function_PcSecurity, - Function_PcSneak, - Function_PcAcrobatics, - Function_PcLightArmor, - Function_PcShortBlade, - Function_PcMarksman, - Function_PcMerchantile, - Function_PcSpeechcraft, - Function_PcHandToHand, - Function_PcGender, - Function_PcExpelled, - Function_PcCommonDisease, - Function_PcBlightDisease, - Function_PcClothingModifier, - Function_PcCrimeLevel, - Function_SameSex, - Function_SameRace, - Function_SameFaction, - Function_FactionRankDifference, - Function_Detected, - Function_Alarmed, - Function_Choice, - Function_PcIntelligence, - Function_PcWillpower, - Function_PcAgility, - Function_PcSpeed, - Function_PcEndurance, - Function_PcPersonality, - Function_PcLuck, - Function_PcCorpus, - Function_Weather, - Function_PcVampire, - Function_Level, - Function_Attacked, - Function_TalkedToPc, - Function_PcHealth, - Function_CreatureTarget, - Function_FriendHit, - Function_Fight, - Function_Hello, - Function_Alarm, - Function_Flee, - Function_ShouldAttack, - Function_Werewolf, - Function_PcWerewolfKills = 73, - - Function_Global, - Function_Local, - Function_Journal, - Function_Item, - Function_Dead, - Function_NotId, - Function_NotFaction, - Function_NotClass, - Function_NotRace, - Function_NotCell, - Function_NotLocal, - - Function_None - }; - - enum RelationType - { - Relation_Equal, - Relation_NotEqual, - Relation_Greater, - Relation_GreaterOrEqual, - Relation_Less, - Relation_LessOrEqual, - - Relation_None - }; - enum ComparisonType { Comparison_Boolean, @@ -143,25 +23,13 @@ namespace CSMWorld Comparison_None }; - static const size_t RuleMinSize; - - static const size_t FunctionPrefixOffset; - static const size_t FunctionIndexOffset; - static const size_t RelationIndexOffset; - static const size_t VarNameOffset; - static const char* FunctionEnumStrings[]; static const char* RelationEnumStrings[]; - static const char* ComparisonEnumStrings[]; - - static std::string convertToString(FunctionName name); - static std::string convertToString(RelationType type); - static std::string convertToString(ComparisonType type); - ConstInfoSelectWrapper(const ESM::DialInfo::SelectStruct& select); + ConstInfoSelectWrapper(const ESM::DialogueCondition& select); - FunctionName getFunctionName() const; - RelationType getRelationType() const; + ESM::DialogueCondition::Function getFunctionName() const; + ESM::DialogueCondition::Comparison getRelationType() const; ComparisonType getComparisonType() const; bool hasVariable() const; @@ -169,17 +37,12 @@ namespace CSMWorld bool conditionIsAlwaysTrue() const; bool conditionIsNeverTrue() const; - bool variantTypeIsValid() const; - const ESM::Variant& getVariant() const; + QVariant getValue() const; std::string toString() const; protected: - void readRule(); - void readFunctionName(); - void readRelationType(); - void readVariableName(); void updateHasVariable(); void updateComparisonType(); @@ -207,38 +70,29 @@ namespace CSMWorld template bool conditionIsNeverTrue(std::pair conditionRange, std::pair validRange) const; - FunctionName mFunctionName; - RelationType mRelationType; ComparisonType mComparisonType; bool mHasVariable; - std::string mVariableName; private: - const ESM::DialInfo::SelectStruct& mConstSelect; + const ESM::DialogueCondition& mConstSelect; }; - // Wrapper for DialInfo::SelectStruct that can modify the wrapped select struct + // Wrapper for DialogueCondition that can modify the wrapped select struct class InfoSelectWrapper : public ConstInfoSelectWrapper { public: - InfoSelectWrapper(ESM::DialInfo::SelectStruct& select); + InfoSelectWrapper(ESM::DialogueCondition& select); // Wrapped SelectStruct will not be modified until update() is called - void setFunctionName(FunctionName name); - void setRelationType(RelationType type); + void setFunctionName(ESM::DialogueCondition::Function name); + void setRelationType(ESM::DialogueCondition::Comparison type); void setVariableName(const std::string& name); - - // Modified wrapped SelectStruct - void update(); - - // This sets properties based on the function name to its defaults and updates the wrapped object - void setDefaults(); - - ESM::Variant& getVariant(); + void setValue(int value); + void setValue(float value); private: - ESM::DialInfo::SelectStruct& mSelect; + ESM::DialogueCondition& mSelect; void writeRule(); }; diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index 13ae821a77..c844c5a18f 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -38,7 +38,6 @@ namespace CSMWorld point.mZ = 0; point.mAutogenerated = 0; point.mConnectionNum = 0; - point.mUnknown = 0; points.insert(points.begin() + position, point); pathgrid.mData.mPoints = pathgrid.mPoints.size(); @@ -414,20 +413,32 @@ namespace CSMWorld QVariant RegionSoundListAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const { - ESM::Region region = record.get(); + const ESM::Region& region = record.get(); - std::vector& soundList = region.mSoundList; + const std::vector& soundList = region.mSoundList; - if (subRowIndex < 0 || subRowIndex >= static_cast(soundList.size())) + const size_t index = static_cast(subRowIndex); + if (subRowIndex < 0 || index >= soundList.size()) throw std::runtime_error("index out of range"); - ESM::Region::SoundRef soundRef = soundList[subRowIndex]; + const ESM::Region::SoundRef& soundRef = soundList[subRowIndex]; switch (subColIndex) { case 0: return QString(soundRef.mSound.getRefIdString().c_str()); case 1: return soundRef.mChance; + case 2: + { + float probability = 1.f; + for (size_t i = 0; i < index; ++i) + { + const float p = std::min(soundList[i].mChance / 100.f, 1.f); + probability *= 1.f - p; + } + probability *= std::min(soundRef.mChance / 100.f, 1.f) * 100.f; + return QString("%1%").arg(probability, 0, 'f', 2); + } default: throw std::runtime_error("Region sounds subcolumn index out of range"); } @@ -463,7 +474,7 @@ namespace CSMWorld int RegionSoundListAdapter::getColumnsCount(const Record& record) const { - return 2; + return 3; } int RegionSoundListAdapter::getRowsCount(const Record& record) const @@ -527,13 +538,11 @@ namespace CSMWorld { Info info = record.get(); - std::vector& conditions = info.mSelects; + auto& conditions = info.mSelects; // default row - ESM::DialInfo::SelectStruct condStruct; - condStruct.mSelectRule = "01000"; - condStruct.mValue = ESM::Variant(); - condStruct.mValue.setType(ESM::VT_Int); + ESM::DialogueCondition condStruct; + condStruct.mIndex = conditions.size(); conditions.insert(conditions.begin() + position, condStruct); @@ -544,7 +553,7 @@ namespace CSMWorld { Info info = record.get(); - std::vector& conditions = info.mSelects; + auto& conditions = info.mSelects; if (rowToRemove < 0 || rowToRemove >= static_cast(conditions.size())) throw std::runtime_error("index out of range"); @@ -558,8 +567,8 @@ namespace CSMWorld { Info info = record.get(); - info.mSelects = static_cast>&>(nestedTable) - .mNestedTable; + info.mSelects + = static_cast>&>(nestedTable).mNestedTable; record.setModified(info); } @@ -567,14 +576,14 @@ namespace CSMWorld NestedTableWrapperBase* InfoConditionAdapter::table(const Record& record) const { // deleted by dtor of NestedTableStoring - return new NestedTableWrapper>(record.get().mSelects); + return new NestedTableWrapper>(record.get().mSelects); } QVariant InfoConditionAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const { Info info = record.get(); - std::vector& conditions = info.mSelects; + auto& conditions = info.mSelects; if (subRowIndex < 0 || subRowIndex >= static_cast(conditions.size())) throw std::runtime_error("index out of range"); @@ -596,23 +605,11 @@ namespace CSMWorld } case 2: { - return infoSelectWrapper.getRelationType(); + return infoSelectWrapper.getRelationType() - ESM::DialogueCondition::Comp_Eq; } case 3: { - switch (infoSelectWrapper.getVariant().getType()) - { - case ESM::VT_Int: - { - return infoSelectWrapper.getVariant().getInteger(); - } - case ESM::VT_Float: - { - return infoSelectWrapper.getVariant().getFloat(); - } - default: - return QVariant(); - } + return infoSelectWrapper.getValue(); } default: throw std::runtime_error("Info condition subcolumn index out of range"); @@ -624,7 +621,7 @@ namespace CSMWorld { Info info = record.get(); - std::vector& conditions = info.mSelects; + auto& conditions = info.mSelects; if (subRowIndex < 0 || subRowIndex >= static_cast(conditions.size())) throw std::runtime_error("index out of range"); @@ -636,27 +633,18 @@ namespace CSMWorld { case 0: // Function { - infoSelectWrapper.setFunctionName(static_cast(value.toInt())); - - if (infoSelectWrapper.getComparisonType() != ConstInfoSelectWrapper::Comparison_Numeric - && infoSelectWrapper.getVariant().getType() != ESM::VT_Int) - { - infoSelectWrapper.getVariant().setType(ESM::VT_Int); - } - - infoSelectWrapper.update(); + infoSelectWrapper.setFunctionName(static_cast(value.toInt())); break; } case 1: // Variable { infoSelectWrapper.setVariableName(value.toString().toUtf8().constData()); - infoSelectWrapper.update(); break; } case 2: // Relation { - infoSelectWrapper.setRelationType(static_cast(value.toInt())); - infoSelectWrapper.update(); + infoSelectWrapper.setRelationType( + static_cast(value.toInt() + ESM::DialogueCondition::Comp_Eq)); break; } case 3: // Value @@ -668,13 +656,11 @@ namespace CSMWorld // QVariant seems to have issues converting 0 if ((value.toInt(&conversionResult) && conversionResult) || value.toString().compare("0") == 0) { - infoSelectWrapper.getVariant().setType(ESM::VT_Int); - infoSelectWrapper.getVariant().setInteger(value.toInt()); + infoSelectWrapper.setValue(value.toInt()); } else if (value.toFloat(&conversionResult) && conversionResult) { - infoSelectWrapper.getVariant().setType(ESM::VT_Float); - infoSelectWrapper.getVariant().setFloat(value.toFloat()); + infoSelectWrapper.setValue(value.toFloat()); } break; } @@ -683,8 +669,7 @@ namespace CSMWorld { if ((value.toInt(&conversionResult) && conversionResult) || value.toString().compare("0") == 0) { - infoSelectWrapper.getVariant().setType(ESM::VT_Int); - infoSelectWrapper.getVariant().setInteger(value.toInt()); + infoSelectWrapper.setValue(value.toInt()); } break; } @@ -996,7 +981,10 @@ namespace CSMWorld case 5: { if (isInterior && interiorWater) + { cell.mWater = value.toFloat(); + cell.setHasWaterHeightSub(true); + } else return; // return without saving break; diff --git a/apps/opencs/model/world/nestedcoladapterimp.hpp b/apps/opencs/model/world/nestedcoladapterimp.hpp index 46928973fe..e18cda9611 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.hpp +++ b/apps/opencs/model/world/nestedcoladapterimp.hpp @@ -255,20 +255,22 @@ namespace CSMWorld { ESXRecordT magic = record.get(); - std::vector& effectsList = magic.mEffects.mList; + std::vector& effectsList = magic.mEffects.mList; // blank row - ESM::ENAMstruct effect; - effect.mEffectID = 0; - effect.mSkill = -1; - effect.mAttribute = -1; - effect.mRange = 0; - effect.mArea = 0; - effect.mDuration = 0; - effect.mMagnMin = 0; - effect.mMagnMax = 0; + ESM::IndexedENAMstruct effect; + effect.mIndex = position; + effect.mData.mEffectID = 0; + effect.mData.mSkill = -1; + effect.mData.mAttribute = -1; + effect.mData.mRange = 0; + effect.mData.mArea = 0; + effect.mData.mDuration = 0; + effect.mData.mMagnMin = 0; + effect.mData.mMagnMax = 0; effectsList.insert(effectsList.begin() + position, effect); + magic.mEffects.updateIndexes(); record.setModified(magic); } @@ -277,12 +279,13 @@ namespace CSMWorld { ESXRecordT magic = record.get(); - std::vector& effectsList = magic.mEffects.mList; + std::vector& effectsList = magic.mEffects.mList; if (rowToRemove < 0 || rowToRemove >= static_cast(effectsList.size())) throw std::runtime_error("index out of range"); effectsList.erase(effectsList.begin() + rowToRemove); + magic.mEffects.updateIndexes(); record.setModified(magic); } @@ -292,7 +295,7 @@ namespace CSMWorld ESXRecordT magic = record.get(); magic.mEffects.mList - = static_cast>&>(nestedTable).mNestedTable; + = static_cast>&>(nestedTable).mNestedTable; record.setModified(magic); } @@ -300,19 +303,19 @@ namespace CSMWorld NestedTableWrapperBase* table(const Record& record) const override { // deleted by dtor of NestedTableStoring - return new NestedTableWrapper>(record.get().mEffects.mList); + return new NestedTableWrapper>(record.get().mEffects.mList); } QVariant getData(const Record& record, int subRowIndex, int subColIndex) const override { ESXRecordT magic = record.get(); - std::vector& effectsList = magic.mEffects.mList; + std::vector& effectsList = magic.mEffects.mList; if (subRowIndex < 0 || subRowIndex >= static_cast(effectsList.size())) throw std::runtime_error("index out of range"); - ESM::ENAMstruct effect = effectsList[subRowIndex]; + ESM::ENAMstruct effect = effectsList[subRowIndex].mData; switch (subColIndex) { case 0: @@ -374,12 +377,12 @@ namespace CSMWorld { ESXRecordT magic = record.get(); - std::vector& effectsList = magic.mEffects.mList; + std::vector& effectsList = magic.mEffects.mList; if (subRowIndex < 0 || subRowIndex >= static_cast(effectsList.size())) throw std::runtime_error("index out of range"); - ESM::ENAMstruct effect = effectsList[subRowIndex]; + ESM::ENAMstruct effect = effectsList[subRowIndex].mData; switch (subColIndex) { case 0: @@ -438,7 +441,7 @@ namespace CSMWorld throw std::runtime_error("Magic Effects subcolumn index out of range"); } - magic.mEffects.mList[subRowIndex] = effect; + magic.mEffects.mList[subRowIndex].mData = effect; record.setModified(magic); } diff --git a/apps/opencs/model/world/record.hpp b/apps/opencs/model/world/record.hpp index d1f64fbfef..35e4c82a35 100644 --- a/apps/opencs/model/world/record.hpp +++ b/apps/opencs/model/world/record.hpp @@ -19,6 +19,11 @@ namespace CSMWorld State mState; + explicit RecordBase(State state) + : mState(state) + { + } + virtual ~RecordBase() = default; virtual std::unique_ptr clone() const = 0; @@ -69,21 +74,18 @@ namespace CSMWorld template Record::Record() - : mBase() + : RecordBase(State_BaseOnly) + , mBase() , mModified() { } template Record::Record(State state, const ESXRecordT* base, const ESXRecordT* modified) + : RecordBase(state) + , mBase(base == nullptr ? ESXRecordT{} : *base) + , mModified(modified == nullptr ? ESXRecordT{} : *modified) { - if (base) - mBase = *base; - - if (modified) - mModified = *modified; - - this->mState = state; } template diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index c6179facb8..bdccab9cda 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -33,7 +33,7 @@ QVariant CSMWorld::PotionRefIdAdapter::getData(const RefIdColumn* column, const data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Potion))); if (column == mAutoCalc) - return record.get().mData.mAutoCalc != 0; + return record.get().mData.mFlags & ESM::Potion::Autocalc; // to show nested tables in dialogue subview, see IdTree::hasChildren() if (column == mColumns.mEffects) @@ -51,7 +51,7 @@ void CSMWorld::PotionRefIdAdapter::setData( ESM::Potion potion = record.get(); if (column == mAutoCalc) - potion.mData.mAutoCalc = value.toInt(); + potion.mData.mFlags = value.toBool(); else { InventoryRefIdAdapter::setData(column, data, index, value); diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 694f67e445..e0d5799726 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -97,7 +97,7 @@ CSMWorld::RefIdCollection::RefIdCollection() inventoryColumns.mIcon = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_Weight, ColumnBase::Display_Float); inventoryColumns.mWeight = &mColumns.back(); - mColumns.emplace_back(Columns::ColumnId_StackCount, ColumnBase::Display_Integer); + mColumns.emplace_back(Columns::ColumnId_GoldValue, ColumnBase::Display_Integer); inventoryColumns.mValue = &mColumns.back(); IngredientColumns ingredientColumns(inventoryColumns); @@ -210,7 +210,6 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_AiDuration, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_AiWanderToD, CSMWorld::ColumnBase::Display_Integer)); - mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle1, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle2, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle3, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle4, CSMWorld::ColumnBase::Display_Integer)); @@ -218,6 +217,7 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle6, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle7, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle8, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle9, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_AiWanderRepeat, CSMWorld::ColumnBase::Display_Boolean)); mColumns.back().addColumn( diff --git a/apps/opencs/model/world/regionmap.cpp b/apps/opencs/model/world/regionmap.cpp index 5c22aedf4d..79a0d5474d 100644 --- a/apps/opencs/model/world/regionmap.cpp +++ b/apps/opencs/model/world/regionmap.cpp @@ -1,7 +1,9 @@ #include "regionmap.hpp" +#include #include #include +#include #include #include @@ -21,20 +23,33 @@ #include "data.hpp" #include "universalid.hpp" -CSMWorld::RegionMap::CellDescription::CellDescription() - : mDeleted(false) +namespace CSMWorld { + float getLandHeight(const CSMWorld::Cell& cell, CSMWorld::Data& data) + { + const IdCollection& lands = data.getLand(); + int landIndex = lands.searchId(cell.mId); + if (landIndex == -1) + return 0.0f; + + // If any part of land is above water, returns > 0 - otherwise returns < 0 + const Land& land = lands.getRecord(landIndex).get(); + if (land.getLandData()) + return land.getLandData()->mMaxHeight - cell.mWater; + + return 0.0f; + } } -CSMWorld::RegionMap::CellDescription::CellDescription(const Record& cell) +CSMWorld::RegionMap::CellDescription::CellDescription(const Record& cell, float landHeight) { const Cell& cell2 = cell.get(); if (!cell2.isExterior()) throw std::logic_error("Interior cell in region map"); + mMaxLandHeight = landHeight; mDeleted = cell.isDeleted(); - mRegion = cell2.mRegion; mName = cell2.mName; } @@ -92,7 +107,7 @@ void CSMWorld::RegionMap::buildMap() if (cell2.isExterior()) { - CellDescription description(cell); + CellDescription description(cell, getLandHeight(cell2, mData)); CellCoordinates index = getIndex(cell2); @@ -140,7 +155,7 @@ void CSMWorld::RegionMap::addCells(int start, int end) { CellCoordinates index = getIndex(cell2); - CellDescription description(cell); + CellDescription description(cell, getLandHeight(cell.get(), mData)); addCell(index, description); } @@ -335,10 +350,11 @@ QVariant CSMWorld::RegionMap::data(const QModelIndex& index, int role) const auto iter = mColours.find(cell->second.mRegion); if (iter != mColours.end()) - return QBrush(QColor(iter->second & 0xff, (iter->second >> 8) & 0xff, (iter->second >> 16) & 0xff)); + return QBrush(QColor(iter->second & 0xff, (iter->second >> 8) & 0xff, (iter->second >> 16) & 0xff), + cell->second.mMaxLandHeight > 0 ? Qt::SolidPattern : Qt::CrossPattern); - if (cell->second.mRegion.empty()) - return QBrush(Qt::Dense6Pattern); // no region + if (cell->second.mRegion.empty()) // no region + return QBrush(cell->second.mMaxLandHeight > 0 ? Qt::Dense3Pattern : Qt::Dense6Pattern); return QBrush(Qt::red, Qt::Dense6Pattern); // invalid region } diff --git a/apps/opencs/model/world/regionmap.hpp b/apps/opencs/model/world/regionmap.hpp index 3f62c7b61d..96281ba49c 100644 --- a/apps/opencs/model/world/regionmap.hpp +++ b/apps/opencs/model/world/regionmap.hpp @@ -40,13 +40,12 @@ namespace CSMWorld private: struct CellDescription { + float mMaxLandHeight; bool mDeleted; ESM::RefId mRegion; std::string mName; - CellDescription(); - - CellDescription(const Record& cell); + CellDescription(const Record& cell, float landHeight); }; Data& mData; diff --git a/apps/opencs/model/world/resources.cpp b/apps/opencs/model/world/resources.cpp index 9957af3d66..7082575c64 100644 --- a/apps/opencs/model/world/resources.cpp +++ b/apps/opencs/model/world/resources.cpp @@ -28,7 +28,7 @@ void CSMWorld::Resources::recreate(const VFS::Manager* vfs, const char* const* e size_t baseSize = mBaseDirectory.size(); - for (const auto& filepath : vfs->getRecursiveDirectoryIterator("")) + for (const auto& filepath : vfs->getRecursiveDirectoryIterator()) { const std::string_view view = filepath.view(); if (view.size() < baseSize + 1 || !view.starts_with(mBaseDirectory) || view[baseSize] != '/') diff --git a/apps/opencs/model/world/tablemimedata.cpp b/apps/opencs/model/world/tablemimedata.cpp index a966a53eee..a40698bc27 100644 --- a/apps/opencs/model/world/tablemimedata.cpp +++ b/apps/opencs/model/world/tablemimedata.cpp @@ -58,7 +58,7 @@ std::string CSMWorld::TableMimeData::getIcon() const if (tmpIcon != id.getIcon()) { - return ":/multitype.png"; // icon stolen from gnome TODO: get new icon + return ":multitype"; } tmpIcon = id.getIcon(); diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index 9daf87e20a..0ebccd6253 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -23,158 +23,137 @@ namespace constexpr TypeData sNoArg[] = { { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, "-", ":placeholder" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Globals, "Global Variables", - ":./global-variable.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Gmsts, "Game Settings", ":./gmst.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Skills, "Skills", ":./skill.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Classes, "Classes", ":./class.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Factions, "Factions", ":./faction.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Races, "Races", ":./race.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Sounds, "Sounds", ":./sound.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Scripts, "Scripts", ":./script.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Regions, "Regions", ":./region.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Birthsigns, "Birthsigns", - ":./birthsign.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Spells, "Spells", ":./spell.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Topics, "Topics", - ":./dialogue-topics.png" }, + ":global-variable" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Gmsts, "Game Settings", ":gmst" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Skills, "Skills", ":skill" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Classes, "Classes", ":class" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Factions, "Factions", ":faction" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Races, "Races", ":race" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Sounds, "Sounds", ":sound" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Scripts, "Scripts", ":script" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Regions, "Regions", ":region" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Birthsigns, "Birthsigns", ":birthsign" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Spells, "Spells", ":spell" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Topics, "Topics", ":dialogue-topics" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Journals, "Journals", - ":./journal-topics.png" }, + ":journal-topics" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_TopicInfos, "Topic Infos", - ":./dialogue-topic-infos.png" }, + ":dialogue-info" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_JournalInfos, "Journal Infos", - ":./journal-topic-infos.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Cells, "Cells", ":./cell.png" }, + ":journal-topic-infos" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Cells, "Cells", ":cell" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Enchantments, "Enchantments", - ":./enchantment.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_BodyParts, "Body Parts", - ":./body-part.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Referenceables, "Objects", - ":./object.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_References, "Instances", - ":./instance.png" }, - { CSMWorld::UniversalId::Class_NonRecord, CSMWorld::UniversalId::Type_RegionMap, "Region Map", - ":./region-map.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Filters, "Filters", ":./filter.png" }, - { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Meshes, "Meshes", - ":./resources-mesh" }, - { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Icons, "Icons", ":./resources-icon" }, + ":enchantment" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_BodyParts, "Body Parts", ":body-part" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Referenceables, "Objects", ":object" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_References, "Instances", ":instance" }, + { CSMWorld::UniversalId::Class_NonRecord, CSMWorld::UniversalId::Type_RegionMap, "Region Map", ":region-map" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Filters, "Filters", ":filter" }, + { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Meshes, "Meshes", ":resources-mesh" }, + { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Icons, "Icons", ":resources-icon" }, { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Musics, "Music Files", - ":./resources-music" }, + ":resources-music" }, { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_SoundsRes, "Sound Files", ":resources-sound" }, { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Textures, "Textures", - ":./resources-texture" }, - { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Videos, "Videos", - ":./resources-video" }, + ":resources-texture" }, + { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Videos, "Videos", ":resources-video" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_DebugProfiles, "Debug Profiles", - ":./debug-profile.png" }, + ":debug-profile" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_SelectionGroup, "Selection Groups", "" }, - { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_RunLog, "Run Log", ":./run-log.png" }, + { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_RunLog, "Run Log", ":run-log" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_SoundGens, "Sound Generators", - ":./sound-generator.png" }, + ":sound-generator" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_MagicEffects, "Magic Effects", - ":./magic-effect.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Lands, "Lands", - ":./land-heightmap.png" }, + ":magic-effect" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Lands, "Lands", ":land-heightmap" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_LandTextures, "Land Textures", - ":./land-texture.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Pathgrids, "Pathgrids", - ":./pathgrid.png" }, + ":land-texture" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Pathgrids, "Pathgrids", ":pathgrid" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_StartScripts, "Start Scripts", - ":./start-script.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_MetaDatas, "Metadata", - ":./metadata.png" }, + ":start-script" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_MetaDatas, "Metadata", ":metadata" }, }; constexpr TypeData sIdArg[] = { { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Global, "Global Variable", - ":./global-variable.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Gmst, "Game Setting", ":./gmst.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Skill, "Skill", ":./skill.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Class, "Class", ":./class.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Faction, "Faction", ":./faction.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Race, "Race", ":./race.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Sound, "Sound", ":./sound.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Script, "Script", ":./script.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Region, "Region", ":./region.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Birthsign, "Birthsign", ":./birthsign.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Spell, "Spell", ":./spell.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Topic, "Topic", ":./dialogue-topics.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Journal, "Journal", - ":./journal-topics.png" }, + ":global-variable" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Gmst, "Game Setting", ":gmst" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Skill, "Skill", ":skill" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Class, "Class", ":class" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Faction, "Faction", ":faction" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Race, "Race", ":race" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Sound, "Sound", ":sound" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Script, "Script", ":script" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Region, "Region", ":region" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Birthsign, "Birthsign", ":birthsign" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Spell, "Spell", ":spell" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Topic, "Topic", ":dialogue-topics" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Journal, "Journal", ":journal-topics" }, { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_TopicInfo, "TopicInfo", - ":./dialogue-topic-infos.png" }, + ":dialogue-info" }, { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_JournalInfo, "JournalInfo", - ":./journal-topic-infos.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell, "Cell", ":./cell.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell_Missing, "Cell", ":./cell.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Referenceable, "Object", ":./object.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Activator, "Activator", - ":./activator.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Potion, "Potion", ":./potion.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Apparatus, "Apparatus", - ":./apparatus.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Armor, "Armor", ":./armor.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Book, "Book", ":./book.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Clothing, "Clothing", ":./clothing.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Container, "Container", - ":./container.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Creature, "Creature", ":./creature.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Door, "Door", ":./door.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Ingredient, "Ingredient", - ":./ingredient.png" }, + ":journal-topic-infos" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell, "Cell", ":cell" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell_Missing, "Cell", ":cell" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Referenceable, "Object", ":object" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Activator, "Activator", ":activator" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Potion, "Potion", ":potion" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Apparatus, "Apparatus", ":apparatus" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Armor, "Armor", ":armor" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Book, "Book", ":book" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Clothing, "Clothing", ":clothing" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Container, "Container", ":container" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Creature, "Creature", ":creature" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Door, "Door", ":door" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Ingredient, "Ingredient", ":ingredient" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_CreatureLevelledList, - "Creature Levelled List", ":./levelled-creature.png" }, + "Creature Levelled List", ":levelled-creature" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_ItemLevelledList, "Item Levelled List", - ":./levelled-item.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Light, "Light", ":./light.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Lockpick, "Lockpick", ":./lockpick.png" }, + ":levelled-item" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Light, "Light", ":light" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Lockpick, "Lockpick", ":lockpick" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Miscellaneous, "Miscellaneous", - ":./miscellaneous.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Npc, "NPC", ":./npc.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Probe, "Probe", ":./probe.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Repair, "Repair", ":./repair.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Static, "Static", ":./static.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Weapon, "Weapon", ":./weapon.png" }, - { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_Reference, "Instance", - ":./instance.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Filter, "Filter", ":./filter.png" }, - { CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Scene, "Scene", ":./scene.png" }, - { CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Preview, "Preview", - ":./record-preview.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Enchantment, "Enchantment", - ":./enchantment.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_BodyPart, "Body Part", ":./body-part.png" }, - { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Mesh, "Mesh", ":./resources-mesh" }, - { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Icon, "Icon", ":./resources-icon" }, - { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Music, "Music", ":./resources-music" }, + ":miscellaneous" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Npc, "NPC", ":npc" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Probe, "Probe", ":probe" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Repair, "Repair", ":repair" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Static, "Static", ":static" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Weapon, "Weapon", ":weapon" }, + { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_Reference, "Instance", ":instance" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Filter, "Filter", ":filter" }, + { CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Scene, "Scene", ":scene" }, + { CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Preview, "Preview", ":edit-preview" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Enchantment, "Enchantment", ":enchantment" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_BodyPart, "Body Part", ":body-part" }, + { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Mesh, "Mesh", ":resources-mesh" }, + { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Icon, "Icon", ":resources-icon" }, + { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Music, "Music", ":resources-music" }, { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_SoundRes, "Sound File", - ":./resources-sound" }, - { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Texture, "Texture", - ":./resources-texture" }, - { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Video, "Video", ":./resources-video" }, + ":resources-sound" }, + { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Texture, "Texture", ":resources-texture" }, + { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Video, "Video", ":resources-video" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_DebugProfile, "Debug Profile", - ":./debug-profile.png" }, + ":debug-profile" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_SoundGen, "Sound Generator", - ":./sound-generator.png" }, + ":sound-generator" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_MagicEffect, "Magic Effect", - ":./magic-effect.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Land, "Land", ":./land-heightmap.png" }, + ":magic-effect" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Land, "Land", ":land-heightmap" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_LandTexture, "Land Texture", - ":./land-texture.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Pathgrid, "Pathgrid", ":./pathgrid.png" }, + ":land-texture" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Pathgrid, "Pathgrid", ":pathgrid" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_StartScript, "Start Script", - ":./start-script.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_MetaData, "Metadata", ":./metadata.png" }, + ":start-script" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_MetaData, "Metadata", ":metadata" }, }; constexpr TypeData sIndexArg[] = { { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_VerificationResults, - "Verification Results", ":./menu-verify.png" }, + "Verification Results", ":menu-verify" }, { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_LoadErrorLog, "Load Error Log", - ":./error-log.png" }, - { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_Search, "Global Search", - ":./menu-search.png" }, + ":error-log" }, + { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_Search, "Global Search", ":menu-search" }, }; struct WriteToStream diff --git a/apps/opencs/view/doc/startup.cpp b/apps/opencs/view/doc/startup.cpp index 27463b0456..e6323bf1a1 100644 --- a/apps/opencs/view/doc/startup.cpp +++ b/apps/opencs/view/doc/startup.cpp @@ -10,7 +10,7 @@ #include #include -QPushButton* CSVDoc::StartupDialogue::addButton(const QString& label, const QIcon& icon) +QPushButton* CSVDoc::StartupDialogue::addButton(const QString& label, const QString& icon) { int column = mColumn--; @@ -39,13 +39,13 @@ QWidget* CSVDoc::StartupDialogue::createButtons() mLayout = new QGridLayout(widget); /// \todo add icons - QPushButton* loadDocument = addButton("Edit A Content File", QIcon(":startup/edit-content")); + QPushButton* loadDocument = addButton("Edit A Content File", ":startup/edit-content"); connect(loadDocument, &QPushButton::clicked, this, &StartupDialogue::loadDocument); - QPushButton* createAddon = addButton("Create A New Addon", QIcon(":startup/create-addon")); + QPushButton* createAddon = addButton("Create A New Addon", ":startup/create-addon"); connect(createAddon, &QPushButton::clicked, this, &StartupDialogue::createAddon); - QPushButton* createGame = addButton("Create A New Game", QIcon(":startup/create-game")); + QPushButton* createGame = addButton("Create A New Game", ":startup/create-game"); connect(createGame, &QPushButton::clicked, this, &StartupDialogue::createGame); for (int i = 0; i < 3; ++i) diff --git a/apps/opencs/view/doc/startup.hpp b/apps/opencs/view/doc/startup.hpp index 061b91b2d1..f2cccfcd38 100644 --- a/apps/opencs/view/doc/startup.hpp +++ b/apps/opencs/view/doc/startup.hpp @@ -20,7 +20,7 @@ namespace CSVDoc int mColumn; QGridLayout* mLayout; - QPushButton* addButton(const QString& label, const QIcon& icon); + QPushButton* addButton(const QString& label, const QString& icon); QWidget* createButtons(); diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index e1bf7e6ac6..88a33108c0 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -71,30 +71,30 @@ void CSVDoc::View::setupFileMenu() { QMenu* file = menuBar()->addMenu(tr("File")); - QAction* newGame = createMenuEntry("New Game", ":./menu-new-game.png", file, "document-file-newgame"); + QAction* newGame = createMenuEntry("New Game", ":menu-new-game", file, "document-file-newgame"); connect(newGame, &QAction::triggered, this, &View::newGameRequest); - QAction* newAddon = createMenuEntry("New Addon", ":./menu-new-addon.png", file, "document-file-newaddon"); + QAction* newAddon = createMenuEntry("New Addon", ":menu-new-addon", file, "document-file-newaddon"); connect(newAddon, &QAction::triggered, this, &View::newAddonRequest); - QAction* open = createMenuEntry("Open", ":./menu-open.png", file, "document-file-open"); + QAction* open = createMenuEntry("Open", ":menu-open", file, "document-file-open"); connect(open, &QAction::triggered, this, &View::loadDocumentRequest); - QAction* save = createMenuEntry("Save", ":./menu-save.png", file, "document-file-save"); + QAction* save = createMenuEntry("Save", ":menu-save", file, "document-file-save"); connect(save, &QAction::triggered, this, &View::save); mSave = save; file->addSeparator(); - QAction* verify = createMenuEntry("Verify", ":./menu-verify.png", file, "document-file-verify"); + QAction* verify = createMenuEntry("Verify", ":menu-verify", file, "document-file-verify"); connect(verify, &QAction::triggered, this, &View::verify); mVerify = verify; - QAction* merge = createMenuEntry("Merge", ":./menu-merge.png", file, "document-file-merge"); + QAction* merge = createMenuEntry("Merge", ":menu-merge", file, "document-file-merge"); connect(merge, &QAction::triggered, this, &View::merge); mMerge = merge; - QAction* loadErrors = createMenuEntry("Error Log", ":./error-log.png", file, "document-file-errorlog"); + QAction* loadErrors = createMenuEntry("Error Log", ":error-log", file, "document-file-errorlog"); connect(loadErrors, &QAction::triggered, this, &View::loadErrorLog); QAction* meta = createMenuEntry(CSMWorld::UniversalId::Type_MetaDatas, file, "document-file-metadata"); @@ -102,10 +102,10 @@ void CSVDoc::View::setupFileMenu() file->addSeparator(); - QAction* close = createMenuEntry("Close", ":./menu-close.png", file, "document-file-close"); + QAction* close = createMenuEntry("Close", ":menu-close", file, "document-file-close"); connect(close, &QAction::triggered, this, &View::close); - QAction* exit = createMenuEntry("Exit", ":./menu-exit.png", file, "document-file-exit"); + QAction* exit = createMenuEntry("Exit", ":menu-exit", file, "document-file-exit"); connect(exit, &QAction::triggered, this, &View::exit); connect(this, &View::exitApplicationRequest, &mViewManager, &ViewManager::exitApplication); @@ -140,17 +140,16 @@ void CSVDoc::View::setupEditMenu() mUndo = mDocument->getUndoStack().createUndoAction(this, tr("Undo")); setupShortcut("document-edit-undo", mUndo); connect(mUndo, &QAction::changed, this, &View::undoActionChanged); - mUndo->setIcon(QIcon(QString::fromStdString(":./menu-undo.png"))); + mUndo->setIcon(QIcon(QString::fromStdString(":menu-undo"))); edit->addAction(mUndo); mRedo = mDocument->getUndoStack().createRedoAction(this, tr("Redo")); connect(mRedo, &QAction::changed, this, &View::redoActionChanged); setupShortcut("document-edit-redo", mRedo); - mRedo->setIcon(QIcon(QString::fromStdString(":./menu-redo.png"))); + mRedo->setIcon(QIcon(QString::fromStdString(":menu-redo"))); edit->addAction(mRedo); - QAction* userSettings - = createMenuEntry("Preferences", ":./menu-preferences.png", edit, "document-edit-preferences"); + QAction* userSettings = createMenuEntry("Preferences", ":menu-preferences", edit, "document-edit-preferences"); connect(userSettings, &QAction::triggered, this, &View::editSettingsRequest); QAction* search = createMenuEntry(CSMWorld::UniversalId::Type_Search, edit, "document-edit-search"); @@ -161,10 +160,10 @@ void CSVDoc::View::setupViewMenu() { QMenu* view = menuBar()->addMenu(tr("View")); - QAction* newWindow = createMenuEntry("New View", ":./menu-new-window.png", view, "document-view-newview"); + QAction* newWindow = createMenuEntry("New View", ":menu-new-window", view, "document-view-newview"); connect(newWindow, &QAction::triggered, this, &View::newView); - mShowStatusBar = createMenuEntry("Toggle Status Bar", ":./menu-status-bar.png", view, "document-view-statusbar"); + mShowStatusBar = createMenuEntry("Toggle Status Bar", ":menu-status-bar", view, "document-view-statusbar"); connect(mShowStatusBar, &QAction::toggled, this, &View::toggleShowStatusBar); mShowStatusBar->setCheckable(true); mShowStatusBar->setChecked(CSMPrefs::get()["Windows"]["show-statusbar"].isTrue()); @@ -289,7 +288,7 @@ void CSVDoc::View::setupAssetsMenu() { QMenu* assets = menuBar()->addMenu(tr("Assets")); - QAction* reload = createMenuEntry("Reload", ":./menu-reload.png", assets, "document-assets-reload"); + QAction* reload = createMenuEntry("Reload", ":menu-reload", assets, "document-assets-reload"); connect(reload, &QAction::triggered, &mDocument->getData(), &CSMWorld::Data::assetsChanged); assets->addSeparator(); @@ -341,9 +340,9 @@ void CSVDoc::View::setupDebugMenu() QAction* runDebug = debug->addMenu(mGlobalDebugProfileMenu); runDebug->setText(tr("Run OpenMW")); setupShortcut("document-debug-run", runDebug); - runDebug->setIcon(QIcon(QString::fromStdString(":./run-openmw.png"))); + runDebug->setIcon(QIcon(QString::fromStdString(":run-openmw"))); - QAction* stopDebug = createMenuEntry("Stop OpenMW", ":./stop-openmw.png", debug, "document-debug-shutdown"); + QAction* stopDebug = createMenuEntry("Stop OpenMW", ":stop-openmw", debug, "document-debug-shutdown"); connect(stopDebug, &QAction::triggered, this, &View::stop); mStopDebug = stopDebug; @@ -355,16 +354,16 @@ void CSVDoc::View::setupHelpMenu() { QMenu* help = menuBar()->addMenu(tr("Help")); - QAction* helpInfo = createMenuEntry("Help", ":/info.png", help, "document-help-help"); + QAction* helpInfo = createMenuEntry("Help", ":info", help, "document-help-help"); connect(helpInfo, &QAction::triggered, this, &View::openHelp); - QAction* tutorial = createMenuEntry("Tutorial", ":/info.png", help, "document-help-tutorial"); + QAction* tutorial = createMenuEntry("Tutorial", ":info", help, "document-help-tutorial"); connect(tutorial, &QAction::triggered, this, &View::tutorial); - QAction* about = createMenuEntry("About OpenMW-CS", ":./info.png", help, "document-help-about"); + QAction* about = createMenuEntry("About OpenMW-CS", ":info", help, "document-help-about"); connect(about, &QAction::triggered, this, &View::infoAbout); - QAction* aboutQt = createMenuEntry("About Qt", ":./qt.png", help, "document-help-qt"); + QAction* aboutQt = createMenuEntry("About Qt", ":qt", help, "document-help-qt"); connect(aboutQt, &QAction::triggered, this, &View::infoAboutQt); } @@ -1111,7 +1110,16 @@ void CSVDoc::View::updateWidth(bool isGrowLimit, int minSubViewWidth) { QRect rect; if (isGrowLimit) - rect = QApplication::screenAt(pos())->geometry(); + { + // Widget position can be negative, we should clamp it. + QPoint position = pos(); + if (position.x() <= 0) + position.setX(0); + if (position.y() <= 0) + position.setY(0); + + rect = QApplication::screenAt(position)->geometry(); + } else rect = desktopRect(); diff --git a/apps/opencs/view/filter/editwidget.cpp b/apps/opencs/view/filter/editwidget.cpp index 50735bf703..0d7ab679b2 100644 --- a/apps/opencs/view/filter/editwidget.cpp +++ b/apps/opencs/view/filter/editwidget.cpp @@ -44,7 +44,7 @@ CSVFilter::EditWidget::EditWidget(CSMWorld::Data& data, QWidget* parent) mHelpAction = new QAction(tr("Help"), this); connect(mHelpAction, &QAction::triggered, this, &EditWidget::openHelp); - mHelpAction->setIcon(QIcon(":/info.png")); + mHelpAction->setIcon(QIcon(":info")); addAction(mHelpAction); auto* openHelpShortcut = new CSMPrefs::Shortcut("help", this); openHelpShortcut->associateAction(mHelpAction); diff --git a/apps/opencs/view/render/actor.cpp b/apps/opencs/view/render/actor.cpp index d1bfac0ec6..8aa08e443e 100644 --- a/apps/opencs/view/render/actor.cpp +++ b/apps/opencs/view/render/actor.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -29,7 +30,7 @@ namespace CSVRender Actor::Actor(const ESM::RefId& id, CSMWorld::Data& data) : mId(id) , mData(data) - , mBaseNode(new osg::Group()) + , mBaseNode(new osg::PositionAttitudeTransform()) , mSkeleton(nullptr) { mActorData = mData.getActorAdapter()->getActorData(mId); @@ -60,6 +61,10 @@ namespace CSVRender // Attach parts to skeleton loadBodyParts(); + + const osg::Vec2f& attributes = mActorData->getRaceWeightHeight(); + + mBaseNode->setScale(osg::Vec3d(attributes.x(), attributes.x(), attributes.y())); } else { diff --git a/apps/opencs/view/render/actor.hpp b/apps/opencs/view/render/actor.hpp index 86c7e7ff2d..09d896e7e7 100644 --- a/apps/opencs/view/render/actor.hpp +++ b/apps/opencs/view/render/actor.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -59,9 +60,9 @@ namespace CSVRender CSMWorld::Data& mData; CSMWorld::ActorAdapter::ActorDataPtr mActorData; - osg::ref_ptr mBaseNode; + osg::ref_ptr mBaseNode; SceneUtil::Skeleton* mSkeleton; - SceneUtil::NodeMapVisitor::NodeMap mNodeMap; + SceneUtil::NodeMap mNodeMap; }; } diff --git a/apps/opencs/view/render/instancemovemode.cpp b/apps/opencs/view/render/instancemovemode.cpp index e4004a1537..bc999eb633 100644 --- a/apps/opencs/view/render/instancemovemode.cpp +++ b/apps/opencs/view/render/instancemovemode.cpp @@ -8,7 +8,7 @@ class QWidget; CSVRender::InstanceMoveMode::InstanceMoveMode(QWidget* parent) - : ModeButton(QIcon(QPixmap(":scenetoolbar/transform-move")), + : ModeButton(QIcon(":scenetoolbar/transform-move"), "Move selected instances" "
  • Use {scene-edit-primary} to move instances around freely
  • " "
  • Use {scene-edit-secondary} to move instances around within the grid
  • " diff --git a/apps/opencs/view/render/instanceselectionmode.cpp b/apps/opencs/view/render/instanceselectionmode.cpp index fa8998747d..d3e2379640 100644 --- a/apps/opencs/view/render/instanceselectionmode.cpp +++ b/apps/opencs/view/render/instanceselectionmode.cpp @@ -58,7 +58,8 @@ namespace CSVRender InstanceSelectionMode::~InstanceSelectionMode() { - mParentNode->removeChild(mBaseNode); + if (mBaseNode) + mParentNode->removeChild(mBaseNode); } void InstanceSelectionMode::setDragStart(const osg::Vec3d& dragStart) diff --git a/apps/opencs/view/render/mask.hpp b/apps/opencs/view/render/mask.hpp index 7f767e19ac..1c84d886d3 100644 --- a/apps/opencs/view/render/mask.hpp +++ b/apps/opencs/view/render/mask.hpp @@ -12,11 +12,10 @@ namespace CSVRender { // elements that are part of the actual scene Mask_Hidden = 0x0, - Mask_Reference = 0x2, - Mask_Pathgrid = 0x4, - Mask_Water = 0x8, - Mask_Fog = 0x10, - Mask_Terrain = 0x20, + Mask_Reference = 0x1, + Mask_Pathgrid = 0x2, + Mask_Water = 0x4, + Mask_Terrain = 0x8, // used within models Mask_ParticleSystem = 0x100, diff --git a/apps/opencs/view/render/object.hpp b/apps/opencs/view/render/object.hpp index 5c73b12211..31f0d93ac4 100644 --- a/apps/opencs/view/render/object.hpp +++ b/apps/opencs/view/render/object.hpp @@ -8,7 +8,7 @@ #include #include -#include +#include #include #include "tagbase.hpp" diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index 214618a627..58523ab595 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -160,7 +160,6 @@ void CSVRender::PagedWorldspaceWidget::addVisibilitySelectorButtons(CSVWidget::S { WorldspaceWidget::addVisibilitySelectorButtons(tool); tool->addButton(Button_Terrain, Mask_Terrain, "Terrain"); - tool->addButton(Button_Fog, Mask_Fog, "Fog", "", true); } void CSVRender::PagedWorldspaceWidget::addEditModeSelectorButtons(CSVWidget::SceneToolMode* tool) @@ -170,9 +169,10 @@ void CSVRender::PagedWorldspaceWidget::addEditModeSelectorButtons(CSVWidget::Sce /// \todo replace EditMode with suitable subclasses tool->addButton(new TerrainShapeMode(this, mRootNode, tool), "terrain-shape"); tool->addButton(new TerrainTextureMode(this, mRootNode, tool), "terrain-texture"); - tool->addButton( - new EditMode(this, QIcon(":placeholder"), Mask_Reference, "Terrain vertex paint editing"), "terrain-vertex"); - tool->addButton(new EditMode(this, QIcon(":placeholder"), Mask_Reference, "Terrain movement"), "terrain-move"); + const QIcon vertexIcon = QIcon(":scenetoolbar/editing-terrain-vertex-paint"); + const QIcon movementIcon = QIcon(":scenetoolbar/editing-terrain-movement"); + tool->addButton(new EditMode(this, vertexIcon, Mask_Reference, "Terrain vertex paint editing"), "terrain-vertex"); + tool->addButton(new EditMode(this, movementIcon, Mask_Reference, "Terrain movement"), "terrain-move"); } void CSVRender::PagedWorldspaceWidget::handleInteractionPress(const WorldspaceHitResult& hit, InteractionType type) diff --git a/apps/opencs/view/render/pathgridmode.cpp b/apps/opencs/view/render/pathgridmode.cpp index 5c45e2b31f..8550195e8c 100644 --- a/apps/opencs/view/render/pathgridmode.cpp +++ b/apps/opencs/view/render/pathgridmode.cpp @@ -36,8 +36,8 @@ class QWidget; namespace CSVRender { PathgridMode::PathgridMode(WorldspaceWidget* worldspaceWidget, QWidget* parent) - : EditMode(worldspaceWidget, QIcon(":placeholder"), Mask_Pathgrid | Mask_Terrain | Mask_Reference, getTooltip(), - parent) + : EditMode(worldspaceWidget, QIcon(":scenetoolbar/editing-pathgrid"), + Mask_Pathgrid | Mask_Terrain | Mask_Reference, getTooltip(), parent) , mDragMode(DragMode_None) , mFromNode(0) , mSelectionMode(nullptr) diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 953e3076b3..716a087d02 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #include #include "../widget/scenetoolmode.hpp" @@ -76,6 +77,8 @@ namespace CSVRender = new osgViewer::GraphicsWindowEmbedded(0, 0, width(), height()); mWidget->setGraphicsWindowEmbedded(window); + mRenderer->setRealizeOperation(new SceneUtil::GetGLExtensionsOperation()); + int frameRateLimit = CSMPrefs::get()["Rendering"]["framerate-limit"].toInt(); mRenderer->setRunMaxFrameRate(frameRateLimit); mRenderer->setUseConfigureAffinity(false); diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index 899918c3b9..fee43b5de5 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -347,7 +347,6 @@ void CSVRender::UnpagedWorldspaceWidget::addVisibilitySelectorButtons(CSVWidget: { WorldspaceWidget::addVisibilitySelectorButtons(tool); tool->addButton(Button_Terrain, Mask_Terrain, "Terrain", "", true); - tool->addButton(Button_Fog, Mask_Fog, "Fog"); } std::string CSVRender::UnpagedWorldspaceWidget::getStartupInstruction() diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index 505d985ffa..06470d2883 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -222,8 +222,7 @@ namespace CSVRender Button_Reference = 0x1, Button_Pathgrid = 0x2, Button_Water = 0x4, - Button_Fog = 0x8, - Button_Terrain = 0x10 + Button_Terrain = 0x8 }; virtual void addVisibilitySelectorButtons(CSVWidget::SceneToolToggle2* tool); diff --git a/apps/opencs/view/widget/scenetoolmode.cpp b/apps/opencs/view/widget/scenetoolmode.cpp index f2795d6de9..1fa7cfb690 100644 --- a/apps/opencs/view/widget/scenetoolmode.cpp +++ b/apps/opencs/view/widget/scenetoolmode.cpp @@ -94,7 +94,7 @@ void CSVWidget::SceneToolMode::showPanel(const QPoint& position) void CSVWidget::SceneToolMode::addButton(const std::string& icon, const std::string& id, const QString& tooltip) { - ModeButton* button = new ModeButton(QIcon(QPixmap(icon.c_str())), tooltip, mPanel); + ModeButton* button = new ModeButton(QIcon(icon.c_str()), tooltip, mPanel); addButton(button, id); } diff --git a/apps/opencs/view/widget/scenetoolshapebrush.cpp b/apps/opencs/view/widget/scenetoolshapebrush.cpp index 57b78ffc71..0e040c2385 100644 --- a/apps/opencs/view/widget/scenetoolshapebrush.cpp +++ b/apps/opencs/view/widget/scenetoolshapebrush.cpp @@ -60,10 +60,10 @@ CSVWidget::ShapeBrushWindow::ShapeBrushWindow(CSMDoc::Document& document, QWidge : QFrame(parent, Qt::Popup) , mDocument(document) { - mButtonPoint = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-point")), "", this); - mButtonSquare = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-square")), "", this); - mButtonCircle = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-circle")), "", this); - mButtonCustom = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-custom")), "", this); + mButtonPoint = new QPushButton(QIcon(":scenetoolbar/brush-point"), "", this); + mButtonSquare = new QPushButton(QIcon(":scenetoolbar/brush-square"), "", this); + mButtonCircle = new QPushButton(QIcon(":scenetoolbar/brush-circle"), "", this); + mButtonCustom = new QPushButton(QIcon(":scenetoolbar/brush-custom"), "", this); mSizeSliders = new ShapeBrushSizeControls("Brush size", this); @@ -201,25 +201,25 @@ void CSVWidget::SceneToolShapeBrush::setButtonIcon(CSVWidget::BrushShape brushSh { case BrushShape_Point: - setIcon(QIcon(QPixmap(":scenetoolbar/brush-point"))); + setIcon(QIcon(":scenetoolbar/brush-point")); tooltip += mShapeBrushWindow->toolTipPoint; break; case BrushShape_Square: - setIcon(QIcon(QPixmap(":scenetoolbar/brush-square"))); + setIcon(QIcon(":scenetoolbar/brush-square")); tooltip += mShapeBrushWindow->toolTipSquare; break; case BrushShape_Circle: - setIcon(QIcon(QPixmap(":scenetoolbar/brush-circle"))); + setIcon(QIcon(":scenetoolbar/brush-circle")); tooltip += mShapeBrushWindow->toolTipCircle; break; case BrushShape_Custom: - setIcon(QIcon(QPixmap(":scenetoolbar/brush-custom"))); + setIcon(QIcon(":scenetoolbar/brush-custom")); tooltip += mShapeBrushWindow->toolTipCustom; break; } diff --git a/apps/opencs/view/widget/scenetooltexturebrush.cpp b/apps/opencs/view/widget/scenetooltexturebrush.cpp index 9c3e723009..2e002aaf2e 100644 --- a/apps/opencs/view/widget/scenetooltexturebrush.cpp +++ b/apps/opencs/view/widget/scenetooltexturebrush.cpp @@ -90,10 +90,10 @@ CSVWidget::TextureBrushWindow::TextureBrushWindow(CSMDoc::Document& document, QW mSelectedBrush = new QLabel(QString::fromStdString(mBrushTextureLabel)); } - mButtonPoint = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-point")), "", this); - mButtonSquare = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-square")), "", this); - mButtonCircle = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-circle")), "", this); - mButtonCustom = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-custom")), "", this); + mButtonPoint = new QPushButton(QIcon(":scenetoolbar/brush-point"), "", this); + mButtonSquare = new QPushButton(QIcon(":scenetoolbar/brush-square"), "", this); + mButtonCircle = new QPushButton(QIcon(":scenetoolbar/brush-circle"), "", this); + mButtonCustom = new QPushButton(QIcon(":scenetoolbar/brush-custom"), "", this); mSizeSliders = new BrushSizeControls("Brush size", this); @@ -282,25 +282,25 @@ void CSVWidget::SceneToolTextureBrush::setButtonIcon(CSVWidget::BrushShape brush { case BrushShape_Point: - setIcon(QIcon(QPixmap(":scenetoolbar/brush-point"))); + setIcon(QIcon(":scenetoolbar/brush-point")); tooltip += mTextureBrushWindow->toolTipPoint; break; case BrushShape_Square: - setIcon(QIcon(QPixmap(":scenetoolbar/brush-square"))); + setIcon(QIcon(":scenetoolbar/brush-square")); tooltip += mTextureBrushWindow->toolTipSquare; break; case BrushShape_Circle: - setIcon(QIcon(QPixmap(":scenetoolbar/brush-circle"))); + setIcon(QIcon(":scenetoolbar/brush-circle")); tooltip += mTextureBrushWindow->toolTipCircle; break; case BrushShape_Custom: - setIcon(QIcon(QPixmap(":scenetoolbar/brush-custom"))); + setIcon(QIcon(":scenetoolbar/brush-custom")); tooltip += mTextureBrushWindow->toolTipCustom; break; } diff --git a/apps/opencs/view/widget/scenetooltoggle2.cpp b/apps/opencs/view/widget/scenetooltoggle2.cpp index 8dbd1e804c..44bffa34a6 100644 --- a/apps/opencs/view/widget/scenetooltoggle2.cpp +++ b/apps/opencs/view/widget/scenetooltoggle2.cpp @@ -88,7 +88,7 @@ void CSVWidget::SceneToolToggle2::addButton( stream << mSingleIcon << id; PushButton* button = new PushButton( - QIcon(QPixmap(stream.str().c_str())), PushButton::Type_Toggle, tooltip.isEmpty() ? name : tooltip, mPanel); + QIcon(stream.str().c_str()), PushButton::Type_Toggle, tooltip.isEmpty() ? name : tooltip, mPanel); button->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); button->setIconSize(QSize(mIconSize, mIconSize)); diff --git a/apps/opencs/view/world/dragrecordtable.cpp b/apps/opencs/view/world/dragrecordtable.cpp index 11a0c9a540..a8a3df309c 100644 --- a/apps/opencs/view/world/dragrecordtable.cpp +++ b/apps/opencs/view/world/dragrecordtable.cpp @@ -29,7 +29,7 @@ void CSVWorld::DragRecordTable::startDragFromTable(const CSVWorld::DragRecordTab mime->setIndexAtDragStart(index); QDrag* drag = new QDrag(this); drag->setMimeData(mime); - drag->setPixmap(QString::fromUtf8(mime->getIcon().c_str())); + drag->setPixmap(QIcon(mime->getIcon().c_str()).pixmap(QSize(16, 16))); drag->exec(Qt::CopyAction); } diff --git a/apps/opencs/view/world/idcompletiondelegate.cpp b/apps/opencs/view/world/idcompletiondelegate.cpp index 3e26ed9250..e39be392f4 100644 --- a/apps/opencs/view/world/idcompletiondelegate.cpp +++ b/apps/opencs/view/world/idcompletiondelegate.cpp @@ -46,41 +46,41 @@ QWidget* CSVWorld::IdCompletionDelegate::createEditor(QWidget* parent, const QSt switch (conditionFunction) { - case CSMWorld::ConstInfoSelectWrapper::Function_Global: + case ESM::DialogueCondition::Function_Global: { return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_GlobalVariable); } - case CSMWorld::ConstInfoSelectWrapper::Function_Journal: + case ESM::DialogueCondition::Function_Journal: { return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Journal); } - case CSMWorld::ConstInfoSelectWrapper::Function_Item: + case ESM::DialogueCondition::Function_Item: { return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Referenceable); } - case CSMWorld::ConstInfoSelectWrapper::Function_Dead: - case CSMWorld::ConstInfoSelectWrapper::Function_NotId: + case ESM::DialogueCondition::Function_Dead: + case ESM::DialogueCondition::Function_NotId: { return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Referenceable); } - case CSMWorld::ConstInfoSelectWrapper::Function_NotFaction: + case ESM::DialogueCondition::Function_NotFaction: { return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Faction); } - case CSMWorld::ConstInfoSelectWrapper::Function_NotClass: + case ESM::DialogueCondition::Function_NotClass: { return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Class); } - case CSMWorld::ConstInfoSelectWrapper::Function_NotRace: + case ESM::DialogueCondition::Function_NotRace: { return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Race); } - case CSMWorld::ConstInfoSelectWrapper::Function_NotCell: + case ESM::DialogueCondition::Function_NotCell: { return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Cell); } - case CSMWorld::ConstInfoSelectWrapper::Function_Local: - case CSMWorld::ConstInfoSelectWrapper::Function_NotLocal: + case ESM::DialogueCondition::Function_Local: + case ESM::DialogueCondition::Function_NotLocal: { return new CSVWidget::DropLineEdit(display, parent); } diff --git a/apps/opencs/view/world/idvalidator.cpp b/apps/opencs/view/world/idvalidator.cpp index 6f790d20cb..078bd6bce5 100644 --- a/apps/opencs/view/world/idvalidator.cpp +++ b/apps/opencs/view/world/idvalidator.cpp @@ -2,17 +2,6 @@ #include -bool CSVWorld::IdValidator::isValid(const QChar& c, bool first) const -{ - if (c.isLetter() || c == '_') - return true; - - if (!first && (c.isDigit() || c.isSpace())) - return true; - - return false; -} - CSVWorld::IdValidator::IdValidator(bool relaxed, QObject* parent) : QValidator(parent) , mRelaxed(relaxed) @@ -92,7 +81,7 @@ QValidator::State CSVWorld::IdValidator::validate(QString& input, int& pos) cons { prevScope = false; - if (!isValid(*iter, first)) + if (!iter->isPrint()) return QValidator::Invalid; } } diff --git a/apps/opencs/view/world/idvalidator.hpp b/apps/opencs/view/world/idvalidator.hpp index e831542961..6b98d35672 100644 --- a/apps/opencs/view/world/idvalidator.hpp +++ b/apps/opencs/view/world/idvalidator.hpp @@ -13,9 +13,6 @@ namespace CSVWorld std::string mNamespace; mutable std::string mError; - private: - bool isValid(const QChar& c, bool first) const; - public: IdValidator(bool relaxed = false, QObject* parent = nullptr); ///< \param relaxed Relaxed rules for IDs that also functino as user visible text diff --git a/apps/opencs/view/world/recordbuttonbar.cpp b/apps/opencs/view/world/recordbuttonbar.cpp index 67270bd1ed..1333a4a7da 100644 --- a/apps/opencs/view/world/recordbuttonbar.cpp +++ b/apps/opencs/view/world/recordbuttonbar.cpp @@ -92,7 +92,7 @@ CSVWorld::RecordButtonBar::RecordButtonBar(const CSMWorld::UniversalId& id, CSMW if (mTable.getFeatures() & CSMWorld::IdTable::Feature_View) { QToolButton* viewButton = new QToolButton(this); - viewButton->setIcon(QIcon(":/cell.png")); + viewButton->setIcon(QIcon(":cell")); viewButton->setToolTip("Open a scene view of the cell this record is located in"); buttonsLayout->addWidget(viewButton); connect(viewButton, &QToolButton::clicked, this, &RecordButtonBar::viewRecord); diff --git a/apps/opencs/view/world/regionmap.cpp b/apps/opencs/view/world/regionmap.cpp index a2847848d0..17d0016afc 100644 --- a/apps/opencs/view/world/regionmap.cpp +++ b/apps/opencs/view/world/regionmap.cpp @@ -224,6 +224,10 @@ CSVWorld::RegionMap::RegionMap(const CSMWorld::UniversalId& universalId, CSMDoc: addAction(mViewInTableAction); setAcceptDrops(true); + + // Make columns square incase QSizeHint doesnt apply + for (int column = 0; column < this->model()->columnCount(); ++column) + this->setColumnWidth(column, this->rowHeight(0)); } void CSVWorld::RegionMap::selectAll() @@ -358,12 +362,23 @@ std::vector CSVWorld::RegionMap::getDraggedRecords() cons return ids; } +void CSVWorld::RegionMap::dragMoveEvent(QDragMoveEvent* event) +{ + const CSMWorld::TableMimeData* mime = dynamic_cast(event->mimeData()); + if (mime != nullptr && (mime->holdsType(CSMWorld::UniversalId::Type_Region))) + { + event->accept(); + return; + } + + event->ignore(); +} + void CSVWorld::RegionMap::dropEvent(QDropEvent* event) { QModelIndex index = indexAt(event->pos()); bool exists = QTableView::model()->data(index, Qt::BackgroundRole) != QBrush(Qt::DiagCrossPattern); - if (!index.isValid() || !exists) { return; diff --git a/apps/opencs/view/world/regionmap.hpp b/apps/opencs/view/world/regionmap.hpp index b6c5078ea3..137b47ed83 100644 --- a/apps/opencs/view/world/regionmap.hpp +++ b/apps/opencs/view/world/regionmap.hpp @@ -59,6 +59,8 @@ namespace CSVWorld void mouseMoveEvent(QMouseEvent* event) override; + void dragMoveEvent(QDragMoveEvent* event) override; + void dropEvent(QDropEvent* event) override; public: diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 4212e952e8..b7be2b90c8 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -385,7 +385,7 @@ CSVWorld::Table::Table(const CSMWorld::UniversalId& id, bool createAndDelete, bo mViewAction = new QAction(tr("View"), this); connect(mViewAction, &QAction::triggered, this, &Table::viewRecord); - mViewAction->setIcon(QIcon(":/cell.png")); + mViewAction->setIcon(QIcon(":cell")); addAction(mViewAction); CSMPrefs::Shortcut* viewShortcut = new CSMPrefs::Shortcut("table-view", this); viewShortcut->associateAction(mViewAction); @@ -417,7 +417,7 @@ CSVWorld::Table::Table(const CSMWorld::UniversalId& id, bool createAndDelete, bo mHelpAction = new QAction(tr("Help"), this); connect(mHelpAction, &QAction::triggered, this, &Table::openHelp); - mHelpAction->setIcon(QIcon(":/info.png")); + mHelpAction->setIcon(QIcon(":info")); addAction(mHelpAction); CSMPrefs::Shortcut* openHelpShortcut = new CSMPrefs::Shortcut("help", this); openHelpShortcut->associateAction(mHelpAction); diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index 891d954ad4..b48eaec31d 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -78,8 +78,16 @@ CSVWorld::TableSubView::TableSubView( widget->setLayout(layout); setWidget(widget); + + // Widget position can be negative, we should clamp it. + QPoint position = pos(); + if (position.x() <= 0) + position.setX(0); + if (position.y() <= 0) + position.setY(0); + // prefer height of the screen and full width of the table - const QRect rect = QApplication::screenAt(pos())->geometry(); + const QRect rect = QApplication::screenAt(position)->geometry(); int frameHeight = 40; // set a reasonable default QWidget* topLevel = QApplication::topLevelAt(pos()); if (topLevel) diff --git a/apps/opencs_tests/CMakeLists.txt b/apps/opencs_tests/CMakeLists.txt index 2b7309f8b9..3bf783bb68 100644 --- a/apps/opencs_tests/CMakeLists.txt +++ b/apps/opencs_tests/CMakeLists.txt @@ -26,7 +26,7 @@ if (BUILD_WITH_CODE_COVERAGE) target_link_libraries(openmw-cs-tests PRIVATE gcov) endif() -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw-cs-tests PRIVATE ) diff --git a/apps/opencs_tests/model/world/testuniversalid.cpp b/apps/opencs_tests/model/world/testuniversalid.cpp index 54538a591d..871a5218d4 100644 --- a/apps/opencs_tests/model/world/testuniversalid.cpp +++ b/apps/opencs_tests/model/world/testuniversalid.cpp @@ -149,39 +149,36 @@ namespace CSMWorld Params{ UniversalId(UniversalId::Type_None), UniversalId::Type_None, UniversalId::Class_None, UniversalId::ArgumentType_None, "-", "-", ":placeholder" }, Params{ UniversalId(UniversalId::Type_RegionMap), UniversalId::Type_RegionMap, UniversalId::Class_NonRecord, - UniversalId::ArgumentType_None, "Region Map", "Region Map", ":./region-map.png" }, + UniversalId::ArgumentType_None, "Region Map", "Region Map", ":region-map" }, Params{ UniversalId(UniversalId::Type_RunLog), UniversalId::Type_RunLog, UniversalId::Class_Transient, - UniversalId::ArgumentType_None, "Run Log", "Run Log", ":./run-log.png" }, + UniversalId::ArgumentType_None, "Run Log", "Run Log", ":run-log" }, Params{ UniversalId(UniversalId::Type_Lands), UniversalId::Type_Lands, UniversalId::Class_RecordList, - UniversalId::ArgumentType_None, "Lands", "Lands", ":./land-heightmap.png" }, + UniversalId::ArgumentType_None, "Lands", "Lands", ":land-heightmap" }, Params{ UniversalId(UniversalId::Type_Icons), UniversalId::Type_Icons, UniversalId::Class_ResourceList, - UniversalId::ArgumentType_None, "Icons", "Icons", ":./resources-icon" }, + UniversalId::ArgumentType_None, "Icons", "Icons", ":resources-icon" }, Params{ UniversalId(UniversalId::Type_Activator, "a"), UniversalId::Type_Activator, - UniversalId::Class_RefRecord, UniversalId::ArgumentType_Id, "Activator", "Activator: a", - ":./activator.png" }, + UniversalId::Class_RefRecord, UniversalId::ArgumentType_Id, "Activator", "Activator: a", ":activator" }, Params{ UniversalId(UniversalId::Type_Gmst, "b"), UniversalId::Type_Gmst, UniversalId::Class_Record, - UniversalId::ArgumentType_Id, "Game Setting", "Game Setting: b", ":./gmst.png" }, + UniversalId::ArgumentType_Id, "Game Setting", "Game Setting: b", ":gmst" }, Params{ UniversalId(UniversalId::Type_Mesh, "c"), UniversalId::Type_Mesh, UniversalId::Class_Resource, - UniversalId::ArgumentType_Id, "Mesh", "Mesh: c", ":./resources-mesh" }, + UniversalId::ArgumentType_Id, "Mesh", "Mesh: c", ":resources-mesh" }, Params{ UniversalId(UniversalId::Type_Scene, "d"), UniversalId::Type_Scene, UniversalId::Class_Collection, - UniversalId::ArgumentType_Id, "Scene", "Scene: d", ":./scene.png" }, + UniversalId::ArgumentType_Id, "Scene", "Scene: d", ":scene" }, Params{ UniversalId(UniversalId::Type_Reference, "e"), UniversalId::Type_Reference, - UniversalId::Class_SubRecord, UniversalId::ArgumentType_Id, "Instance", "Instance: e", - ":./instance.png" }, + UniversalId::Class_SubRecord, UniversalId::ArgumentType_Id, "Instance", "Instance: e", ":instance" }, Params{ UniversalId(UniversalId::Type_Search, 42), UniversalId::Type_Search, UniversalId::Class_Transient, - UniversalId::ArgumentType_Index, "Global Search", "Global Search: 42", ":./menu-search.png" }, + UniversalId::ArgumentType_Index, "Global Search", "Global Search: 42", ":menu-search" }, Params{ UniversalId("Instance: f"), UniversalId::Type_Reference, UniversalId::Class_SubRecord, - UniversalId::ArgumentType_Id, "Instance", "Instance: f", ":./instance.png" }, + UniversalId::ArgumentType_Id, "Instance", "Instance: f", ":instance" }, Params{ UniversalId(UniversalId::Type_Reference, ESM::RefId::stringRefId("g")), UniversalId::Type_Reference, - UniversalId::Class_SubRecord, UniversalId::ArgumentType_RefId, "Instance", "Instance: g", - ":./instance.png" }, + UniversalId::Class_SubRecord, UniversalId::ArgumentType_RefId, "Instance", "Instance: g", ":instance" }, Params{ UniversalId(UniversalId::Type_Reference, ESM::RefId::index(ESM::REC_SKIL, 42)), UniversalId::Type_Reference, UniversalId::Class_SubRecord, UniversalId::ArgumentType_RefId, "Instance", - "Instance: SKIL:0x2a", ":./instance.png" }, + "Instance: SKIL:0x2a", ":instance" }, }; INSTANTIATE_TEST_SUITE_P(ValidParams, CSMWorldUniversalIdValidPerTypeTest, ValuesIn(validParams)); diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index cc0cba1a1a..c9bfa87648 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -63,8 +63,8 @@ add_openmw_dir (mwlua luamanagerimp object objectlists userdataserializer luaevents engineevents objectvariant context menuscripts globalscripts localscripts playerscripts luabindings objectbindings cellbindings mwscriptbindings camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings - postprocessingbindings stats debugbindings corebindings worldbindings worker magicbindings factionbindings - classbindings itemdata inputprocessor animationbindings + postprocessingbindings stats recordstore debugbindings corebindings worldbindings worker magicbindings factionbindings + classbindings itemdata inputprocessor animationbindings birthsignbindings racebindings markupbindings 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 @@ -88,7 +88,7 @@ add_openmw_dir (mwworld add_openmw_dir (mwphysics physicssystem trace collisiontype actor convert object heightfield closestnotmerayresultcallback - contacttestresultcallback deepestnotmecontacttestresultcallback stepper movementsolver projectile + contacttestresultcallback stepper movementsolver projectile actorconvexcallback raycasting mtphysics contacttestwrapper projectileconvexcallback ) @@ -103,7 +103,7 @@ add_openmw_dir (mwmechanics drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aibreathe aicast aiescort aiface aiactivate aicombat recharge repair enchanting pathfinding pathgrid security spellcasting spellresistance disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction summoning - character actors objects aistate trading weaponpriority spellpriority weapontype spellutil + character actors objects aistate weaponpriority spellpriority weapontype spellutil spelleffects ) @@ -161,7 +161,7 @@ target_link_libraries(openmw components ) -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw PRIVATE diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 75687ff281..179dbcdc32 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -31,6 +31,7 @@ #include #include +#include #include #include @@ -63,6 +64,7 @@ #include "mwscript/interpretercontext.hpp" #include "mwscript/scriptmanagerimp.hpp" +#include "mwsound/constants.hpp" #include "mwsound/soundmanagerimp.hpp" #include "mwworld/class.hpp" @@ -600,6 +602,7 @@ void OMW::Engine::createWindow() mViewer->setRealizeOperation(realizeOperations); osg::ref_ptr identifyOp = new IdentifyOpenGLOperation(); realizeOperations->add(identifyOp); + realizeOperations->add(new SceneUtil::GetGLExtensionsOperation()); if (Debug::shouldDebugOpenGL()) realizeOperations->add(new Debug::EnableGLDebugOperation()); @@ -780,13 +783,13 @@ void OMW::Engine::prepareEngine() // gui needs our shaders path before everything else mResourceSystem->getSceneManager()->setShaderPath(mResDir / "shaders"); - osg::ref_ptr exts = osg::GLExtensions::Get(0, false); - bool shadersSupported = exts && (exts->glslLanguageVersion >= 1.2f); + osg::GLExtensions& exts = SceneUtil::getGLExtensions(); + bool shadersSupported = exts.glslLanguageVersion >= 1.2f; #if OSG_VERSION_LESS_THAN(3, 6, 6) // hack fix for https://github.com/openscenegraph/OpenSceneGraph/issues/1028 - if (exts) - exts->glRenderbufferStorageMultisampleCoverageNV = nullptr; + if (!osg::isGLExtensionSupported(exts.contextID, "NV_framebuffer_multisample_coverage")) + exts.glRenderbufferStorageMultisampleCoverageNV = nullptr; #endif osg::ref_ptr guiRoot = new osg::Group; @@ -963,17 +966,17 @@ void OMW::Engine::go() } // Setup profiler - osg::ref_ptr statshandler = new Resource::Profiler(stats.is_open(), mVFS.get()); + osg::ref_ptr statsHandler = new Resource::Profiler(stats.is_open(), *mVFS); - initStatsHandler(*statshandler); + initStatsHandler(*statsHandler); - mViewer->addEventHandler(statshandler); + mViewer->addEventHandler(statsHandler); - osg::ref_ptr resourceshandler = new Resource::StatsHandler(stats.is_open(), mVFS.get()); - mViewer->addEventHandler(resourceshandler); + osg::ref_ptr resourcesHandler = new Resource::StatsHandler(stats.is_open(), *mVFS); + mViewer->addEventHandler(resourcesHandler); if (stats.is_open()) - Resource::CollectStatistics(mViewer); + Resource::collectStatistics(*mViewer); // Start the game if (!mSaveGameFile.empty()) @@ -985,9 +988,8 @@ void OMW::Engine::go() // start in main menu mWindowManager->pushGuiMode(MWGui::GM_MainMenu); - std::string titlefile = "music/special/morrowind title.mp3"; - if (mVFS->exists(titlefile)) - mSoundManager->streamMusic(titlefile, MWSound::MusicType::Special); + if (mVFS->exists(MWSound::titleMusic)) + mSoundManager->streamMusic(MWSound::titleMusic, MWSound::MusicType::Special); else Log(Debug::Warning) << "Title music not found"; diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 5bbc0211c1..d8095a645b 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -108,7 +109,8 @@ bool parseOptions(int argc, char** argv, OMW::Engine& engine, Files::Configurati Log(Debug::Error) << "No content file given (esm/esp, nor omwgame/omwaddon). Aborting..."; return false; } - std::set contentDedupe; + engine.addContentFile("builtin.omwscripts"); + std::set contentDedupe{ "builtin.omwscripts" }; for (const auto& contentFile : content) { if (!contentDedupe.insert(contentFile).second) @@ -228,6 +230,9 @@ int runApplication(int argc, char* argv[]) if (parseOptions(argc, argv, *engine, cfgMgr)) { + if (!Misc::checkRequiredOSGPluginsArePresent()) + return 1; + engine->go(); } diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index e865756408..a5d6fe1114 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -68,6 +68,8 @@ namespace MWBase const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult, std::string_view start, std::string_view stop, float startpoint, uint32_t loops, bool loopfallback) = 0; + virtual void skillLevelUp(const MWWorld::Ptr& actor, ESM::RefId skillId, std::string_view source) = 0; + virtual void skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale) = 0; virtual void exteriorCreated(MWWorld::CellStore& cell) = 0; virtual void actorDied(const MWWorld::Ptr& actor) = 0; virtual void questUpdated(const ESM::RefId& questId, int stage) = 0; @@ -81,6 +83,12 @@ namespace MWBase struct InputEvent { + struct WheelChange + { + int x; + int y; + }; + enum { KeyPressed, @@ -91,8 +99,11 @@ namespace MWBase TouchPressed, TouchReleased, TouchMoved, + MouseButtonPressed, + MouseButtonReleased, + MouseWheel, } mType; - std::variant mValue; + std::variant mValue; }; virtual void inputEvent(const InputEvent& event) = 0; diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index c8e353acc9..37586ed33a 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -265,7 +265,7 @@ namespace MWBase virtual bool isReadyToBlock(const MWWorld::Ptr& ptr) const = 0; virtual bool isAttackingOrSpell(const MWWorld::Ptr& ptr) const = 0; - virtual void castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool manualSpell) = 0; + virtual void castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool scriptedSpell) = 0; virtual void processChangedSettings(const std::set>& settings) = 0; diff --git a/apps/openmw/mwbase/soundmanager.hpp b/apps/openmw/mwbase/soundmanager.hpp index 1f0337869b..ab3f9c5605 100644 --- a/apps/openmw/mwbase/soundmanager.hpp +++ b/apps/openmw/mwbase/soundmanager.hpp @@ -6,6 +6,8 @@ #include #include +#include + #include "../mwsound/type.hpp" #include "../mwworld/ptr.hpp" @@ -115,7 +117,7 @@ namespace MWBase virtual void stopMusic() = 0; ///< Stops music if it's playing - virtual void streamMusic(const std::string& filename, MWSound::MusicType type, float fade = 1.f) = 0; + virtual void streamMusic(VFS::Path::NormalizedView filename, MWSound::MusicType type, float fade = 1.f) = 0; ///< Play a soundifle /// \param filename name of a sound file in the data directory. /// \param type music type. @@ -124,16 +126,16 @@ namespace MWBase virtual bool isMusicPlaying() = 0; ///< Returns true if music is playing - virtual void playPlaylist(const std::string& playlist) = 0; + virtual void playPlaylist(VFS::Path::NormalizedView playlist) = 0; ///< Start playing music from the selected folder /// \param name of the folder that contains the playlist /// Title music playlist is predefined - virtual void say(const MWWorld::ConstPtr& reference, const std::string& filename) = 0; + virtual void say(const MWWorld::ConstPtr& reference, VFS::Path::NormalizedView filename) = 0; ///< Make an actor say some text. /// \param filename name of a sound file in the VFS - virtual void say(const std::string& filename) = 0; + virtual void say(VFS::Path::NormalizedView filename) = 0; ///< Say some text, without an actor ref /// \param filename name of a sound file in the VFS diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index a7859ad9e6..14674e4503 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -136,6 +136,7 @@ namespace MWBase virtual bool isConsoleMode() const = 0; virtual bool isPostProcessorHudVisible() const = 0; + virtual bool isSettingsWindowVisible() const = 0; virtual bool isInteractiveMessageBoxActive() const = 0; virtual void toggleVisible(MWGui::GuiWindow wnd) = 0; @@ -157,7 +158,6 @@ namespace MWBase virtual MWGui::ConfirmationDialog* getConfirmationDialog() = 0; virtual MWGui::TradeWindow* getTradeWindow() = 0; virtual MWGui::PostProcessorHud* getPostProcessorHud() = 0; - virtual MWGui::SettingsWindow* getSettingsWindow() = 0; /// Make the player use an item, while updating GUI state accordingly virtual void useItem(const MWWorld::Ptr& item, bool force = false) = 0; @@ -202,9 +202,6 @@ namespace MWBase virtual bool getFullHelp() const = 0; - virtual void setActiveMap(int x, int y, bool interior) = 0; - ///< set the indices of the map texture that should be used - /// sets the visibility of the drowning bar virtual void setDrowningBarVisibility(bool visible) = 0; @@ -293,7 +290,7 @@ namespace MWBase virtual void setEnemy(const MWWorld::Ptr& enemy) = 0; - virtual int getMessagesCount() const = 0; + virtual std::size_t getMessagesCount() const = 0; virtual const Translation::Storage& getTranslationDataStorage() const = 0; @@ -345,6 +342,7 @@ namespace MWBase virtual void toggleConsole() = 0; virtual void toggleDebugWindow() = 0; virtual void togglePostProcessorHud() = 0; + virtual void toggleSettingsWindow() = 0; /// Cycle to next or previous spell virtual void cycleSpell(bool next) = 0; diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 4247ef2e3e..7d6a84023b 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -183,8 +183,6 @@ namespace MWBase /// generate a name. virtual std::string_view getCellName(const MWWorld::Cell& cell) const = 0; - virtual std::string_view getCellName(const ESM::Cell* cell) const = 0; - virtual void removeRefScript(const MWWorld::CellRef* ref) = 0; //< Remove the script attached to ref from mLocalScripts @@ -304,7 +302,7 @@ namespace MWBase virtual const MWPhysics::RayCastingInterface* getRayCasting() const = 0; virtual bool castRenderingRay(MWPhysics::RayCastingResult& res, const osg::Vec3f& from, const osg::Vec3f& to, - bool ignorePlayer, bool ignoreActors) + bool ignorePlayer, bool ignoreActors, std::span ignoreList = {}) = 0; virtual void setActorCollisionMode(const MWWorld::Ptr& ptr, bool internal, bool external) = 0; @@ -463,7 +461,7 @@ namespace MWBase */ virtual MWWorld::SpellCastState startSpellCast(const MWWorld::Ptr& actor) = 0; - virtual void castSpell(const MWWorld::Ptr& actor, bool manualSpell = false) = 0; + virtual void castSpell(const MWWorld::Ptr& actor, bool scriptedSpell = false) = 0; virtual void launchMagicBolt(const ESM::RefId& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection, ESM::RefNum item) diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index 678c4e054b..e0ee315bc1 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -104,13 +104,11 @@ namespace MWClass std::string_view name = getName(ptr); info.caption = MyGUI::TextIterator::toTagsString(MyGUI::UString(name)) + MWGui::ToolTips::getCountString(count); - std::string text; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } - info.text = std::move(text); return info; } diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index 1bf6f9c845..bf5e4dc2f1 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -102,8 +102,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = std::move(text); diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index ffa5b36a4d..3853f53fc4 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -257,8 +257,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.enchant = ref->mBase->mEnchant; diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index 2c375547d0..95453e7a58 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -121,8 +121,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.enchant = ref->mBase->mEnchant; diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 17519405de..cdf51ef663 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -164,8 +164,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.enchant = ref->mBase->mEnchant; diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index e2023ef8c3..75b8543b0a 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -265,10 +265,10 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); if (ptr.getCellRef().getRefId() == "stolen_goods") - text += "\nYou can not use evidence chests"; + info.extra += "\nYou cannot use evidence chests"; } info.text = std::move(text); diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index d19e5d5c43..a5effe8e78 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -316,11 +316,11 @@ namespace MWClass { const unsigned char* attack = nullptr; if (type == ESM::Weapon::AT_Chop) - attack = weapon.get()->mBase->mData.mChop; + attack = weapon.get()->mBase->mData.mChop.data(); else if (type == ESM::Weapon::AT_Slash) - attack = weapon.get()->mBase->mData.mSlash; + attack = weapon.get()->mBase->mData.mSlash.data(); else if (type == ESM::Weapon::AT_Thrust) - attack = weapon.get()->mBase->mData.mThrust; + attack = weapon.get()->mBase->mData.mThrust.data(); if (attack) { damage = attack[0] + ((attack[1] - attack[0]) * attackStrength); @@ -474,20 +474,17 @@ namespace MWClass } const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); - const MWMechanics::AiSequence& aiSequence = stats.getAiSequence(); - - const bool isInCombat = aiSequence.isInCombat(); if (stats.isDead()) { - // by default user can loot friendly actors during death animation - if (Settings::game().mCanLootDuringDeathAnimation && !isInCombat) + // by default user can loot non-fighting actors during death animation + if (Settings::game().mCanLootDuringDeathAnimation) return std::make_unique(ptr); // otherwise wait until death animation if (stats.isDeathAnimationFinished()) return std::make_unique(ptr); } - else if ((!isInCombat || aiSequence.isFleeing()) && !stats.getKnockedDown()) + else if (!stats.getKnockedDown()) return std::make_unique(ptr); // Tribunal and some mod companions oddly enough must use open action as fallback @@ -594,10 +591,8 @@ namespace MWClass std::string_view name = getName(ptr); info.caption = MyGUI::TextIterator::toTagsString(MyGUI::UString(name)); - std::string text; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); - info.text = std::move(text); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); return info; } diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 015c454915..c5cdc60cec 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -136,7 +136,7 @@ namespace MWClass const ESM::MagicEffect* effect = store.get().find(ESM::MagicEffect::Telekinesis); animation->addSpellCastGlow( - effect, 1); // 1 second glow to match the time taken for a door opening or closing + effect->getColor(), 1); // 1 second glow to match the time taken for a door opening or closing } } @@ -290,8 +290,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = std::move(text); diff --git a/apps/openmw/mwclass/esm4base.hpp b/apps/openmw/mwclass/esm4base.hpp index f5fd346637..f13d6007cd 100644 --- a/apps/openmw/mwclass/esm4base.hpp +++ b/apps/openmw/mwclass/esm4base.hpp @@ -103,7 +103,7 @@ namespace MWClass // Hide meshes meshes/marker/* and *LOD.nif in ESM4 cells. It is a temporarty hack. // Needed because otherwise LOD meshes are rendered on top of normal meshes. // TODO: Figure out a better way find markers and LOD meshes; show LOD only outside of active grid. - if (model.empty() || Misc::StringUtils::ciStartsWith(model, "meshes\\marker") + if (model.empty() || Misc::StringUtils::ciStartsWith(model, "marker") || Misc::StringUtils::ciEndsWith(model, "lod.nif")) return {}; diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index 9af9a5703b..e18d6ad5f3 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -117,8 +117,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index dc37b8d154..06b1901864 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -173,8 +173,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = std::move(text); diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 42b5634b64..dc3b73da63 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -118,8 +118,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = std::move(text); diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index 0470a89a16..dcd91c51af 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -163,8 +163,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = std::move(text); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index b8cd4cd23d..023e9ad768 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -23,6 +23,7 @@ #include "../mwbase/dialoguemanager.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" @@ -634,11 +635,11 @@ namespace MWClass { const unsigned char* attack = nullptr; if (type == ESM::Weapon::AT_Chop) - attack = weapon.get()->mBase->mData.mChop; + attack = weapon.get()->mBase->mData.mChop.data(); else if (type == ESM::Weapon::AT_Slash) - attack = weapon.get()->mBase->mData.mSlash; + attack = weapon.get()->mBase->mData.mSlash.data(); else if (type == ESM::Weapon::AT_Thrust) - attack = weapon.get()->mBase->mData.mThrust; + attack = weapon.get()->mBase->mData.mThrust.data(); if (attack) { damage = attack[0] + ((attack[1] - attack[0]) * attackStrength); @@ -662,7 +663,7 @@ namespace MWClass ESM::RefId weapskill = ESM::Skill::HandToHand; if (!weapon.isEmpty()) weapskill = weapon.getClass().getEquipmentSkill(weapon); - skillUsageSucceeded(ptr, weapskill, 0); + skillUsageSucceeded(ptr, weapskill, ESM::Skill::Weapon_SuccessfulHit); const MWMechanics::AiSequence& seq = victim.getClass().getCreatureStats(victim).getAiSequence(); @@ -852,7 +853,7 @@ namespace MWClass ESM::RefId skill = armor.getClass().getEquipmentSkill(armor); if (ptr == MWMechanics::getPlayer()) - skillUsageSucceeded(ptr, skill, 0); + skillUsageSucceeded(ptr, skill, ESM::Skill::Armor_HitByOpponent); if (skill == ESM::Skill::LightArmor) sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Light Armor Hit"), 1.0f, 1.0f); @@ -862,7 +863,7 @@ namespace MWClass sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Heavy Armor Hit"), 1.0f, 1.0f); } else if (ptr == MWMechanics::getPlayer()) - skillUsageSucceeded(ptr, ESM::Skill::Unarmored, 0); + skillUsageSucceeded(ptr, ESM::Skill::Unarmored, ESM::Skill::Armor_HitByOpponent); } } @@ -924,35 +925,38 @@ namespace MWClass } const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); + const MWMechanics::AiSequence& aiSequence = stats.getAiSequence(); + const bool isPursuing = aiSequence.isInPursuit() && actor == MWMechanics::getPlayer(); + const bool inCombatWithActor = aiSequence.isInCombat(actor) || isPursuing; if (stats.isDead()) { - // by default user can loot friendly actors during death animation - if (Settings::game().mCanLootDuringDeathAnimation && !stats.getAiSequence().isInCombat()) + // by default user can loot non-fighting actors during death animation + if (Settings::game().mCanLootDuringDeathAnimation) return std::make_unique(ptr); // otherwise wait until death animation if (stats.isDeathAnimationFinished()) return std::make_unique(ptr); } - else if (!stats.getAiSequence().isInCombat()) + else { - if (stats.getKnockedDown() || MWBase::Environment::get().getMechanicsManager()->isSneaking(actor)) - return std::make_unique(ptr); // stealing + const bool allowStealingFromKO + = Settings::game().mAlwaysAllowStealingFromKnockedOutActors || !inCombatWithActor; + if (stats.getKnockedDown() && allowStealingFromKO) + return std::make_unique(ptr); + + const bool allowStealingWhileSneaking = !inCombatWithActor; + if (MWBase::Environment::get().getMechanicsManager()->isSneaking(actor) && allowStealingWhileSneaking) + return std::make_unique(ptr); - // Can't talk to werewolves - if (!getNpcStats(ptr).isWerewolf()) + const bool allowTalking = !inCombatWithActor && !getNpcStats(ptr).isWerewolf(); + if (allowTalking) return std::make_unique(ptr); } - else // In combat - { - if (Settings::game().mAlwaysAllowStealingFromKnockedOutActors && stats.getKnockedDown()) - return std::make_unique(ptr); // stealing - } - // Tribunal and some mod companions oddly enough must use open action as fallback - if (!getScript(ptr).empty() && ptr.getRefData().getLocals().getIntVar(getScript(ptr), "companion")) - return std::make_unique(ptr); + if (inCombatWithActor) + return std::make_unique("#{sActorInCombat}"); return std::make_unique(); } @@ -1086,7 +1090,8 @@ namespace MWClass if (customData.mNpcStats.isDead() && customData.mNpcStats.isDeathAnimationFinished()) return true; - if (!customData.mNpcStats.getAiSequence().isInCombat()) + const MWMechanics::AiSequence& aiSeq = customData.mNpcStats.getAiSequence(); + if (!aiSeq.isInCombat() || aiSeq.isFleeing()) return true; if (Settings::game().mAlwaysAllowStealingFromKnockedOutActors && customData.mNpcStats.getKnockedDown()) @@ -1113,7 +1118,7 @@ namespace MWClass } if (fullHelp) - info.text = MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra = MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); return info; } @@ -1138,16 +1143,7 @@ namespace MWClass void Npc::skillUsageSucceeded(const MWWorld::Ptr& ptr, ESM::RefId skill, int usageType, float extraFactor) const { - MWMechanics::NpcStats& stats = getNpcStats(ptr); - - if (stats.isWerewolf()) - return; - - MWWorld::LiveCellRef* ref = ptr.get(); - - const ESM::Class* class_ = MWBase::Environment::get().getESMStore()->get().find(ref->mBase->mClass); - - stats.useSkill(skill, *class_, usageType, extraFactor); + MWBase::Environment::get().getLuaManager()->skillUse(ptr, skill, usageType, extraFactor); } float Npc::getArmorRating(const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index e5da876d06..0d98302fe6 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -19,6 +19,7 @@ #include "../mwrender/renderinginterface.hpp" #include "../mwmechanics/alchemy.hpp" +#include "../mwmechanics/spellutil.hpp" #include "classmodel.hpp" @@ -65,9 +66,7 @@ namespace MWClass int Potion::getValue(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef* ref = ptr.get(); - - return ref->mBase->mData.mValue; + return MWMechanics::getPotionValue(*ptr.get()->mBase); } const ESM::RefId& Potion::getUpSoundId(const MWWorld::ConstPtr& ptr) const @@ -101,7 +100,7 @@ namespace MWClass std::string text; text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); info.effects = MWGui::Widgets::MWEffectList::effectListFromESM(&ref->mBase->mEffects); @@ -114,8 +113,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = std::move(text); diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index 4f5e7be5cb..beab45945c 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -117,8 +117,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = std::move(text); diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index 3000ea4087..279352263e 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -119,8 +119,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = std::move(text); diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index b5a3415717..5f1f7f2772 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -239,8 +239,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = std::move(text); diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 2b7774d8c9..556b5b53d7 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -543,7 +543,8 @@ namespace MWDialogue mPermanentDispositionChange += perm; MWWorld::Ptr player = MWMechanics::getPlayer(); - player.getClass().skillUsageSucceeded(player, ESM::Skill::Speechcraft, success ? 0 : 1); + player.getClass().skillUsageSucceeded( + player, ESM::Skill::Speechcraft, success ? ESM::Skill::Speechcraft_Success : ESM::Skill::Speechcraft_Fail); if (success) { @@ -652,7 +653,7 @@ namespace MWDialogue if (Settings::gui().mSubtitles) winMgr->messageBox(info->mResponse); if (!info->mSound.empty()) - sndMgr->say(actor, Misc::ResourceHelpers::correctSoundPath(info->mSound)); + sndMgr->say(actor, Misc::ResourceHelpers::correctSoundPath(VFS::Path::Normalized(info->mSound))); if (!info->mResultScript.empty()) executeScript(info->mResultScript, actor); } diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index 8b67ea28b3..295d690ce5 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -30,16 +30,16 @@ namespace { bool matchesStaticFilters(const MWDialogue::SelectWrapper& select, const MWWorld::Ptr& actor) { - const ESM::RefId selectId = ESM::RefId::stringRefId(select.getName()); - if (select.getFunction() == MWDialogue::SelectWrapper::Function_NotId) + const ESM::RefId selectId = select.getId(); + if (select.getFunction() == ESM::DialogueCondition::Function_NotId) return actor.getCellRef().getRefId() != selectId; if (actor.getClass().isNpc()) { - if (select.getFunction() == MWDialogue::SelectWrapper::Function_NotFaction) + if (select.getFunction() == ESM::DialogueCondition::Function_NotFaction) return actor.getClass().getPrimaryFaction(actor) != selectId; - else if (select.getFunction() == MWDialogue::SelectWrapper::Function_NotClass) + else if (select.getFunction() == ESM::DialogueCondition::Function_NotClass) return actor.get()->mBase->mClass != selectId; - else if (select.getFunction() == MWDialogue::SelectWrapper::Function_NotRace) + else if (select.getFunction() == ESM::DialogueCondition::Function_NotRace) return actor.get()->mBase->mRace != selectId; } return true; @@ -47,7 +47,7 @@ namespace bool matchesStaticFilters(const ESM::DialInfo& info, const MWWorld::Ptr& actor) { - for (const ESM::DialInfo::SelectStruct& select : info.mSelects) + for (const auto& select : info.mSelects) { MWDialogue::SelectWrapper wrapper = select; if (wrapper.getType() == MWDialogue::SelectWrapper::Type_Boolean) @@ -62,7 +62,7 @@ namespace } else if (wrapper.getType() == MWDialogue::SelectWrapper::Type_Numeric) { - if (wrapper.getFunction() == MWDialogue::SelectWrapper::Function_Local) + if (wrapper.getFunction() == ESM::DialogueCondition::Function_Local) { const ESM::RefId& scriptName = actor.getClass().getScript(actor); if (scriptName.empty()) @@ -207,9 +207,8 @@ bool MWDialogue::Filter::testPlayer(const ESM::DialInfo& info) const bool MWDialogue::Filter::testSelectStructs(const ESM::DialInfo& info) const { - for (std::vector::const_iterator iter(info.mSelects.begin()); - iter != info.mSelects.end(); ++iter) - if (!testSelectStruct(*iter)) + for (const auto& select : info.mSelects) + if (!testSelectStruct(select)) return false; return true; @@ -270,11 +269,11 @@ bool MWDialogue::Filter::testSelectStruct(const SelectWrapper& select) const // If the actor is a creature, we pass all conditions only applicable to NPCs. return true; - if (select.getFunction() == SelectWrapper::Function_Choice && mChoice == -1) + if (select.getFunction() == ESM::DialogueCondition::Function_Choice && mChoice == -1) // If not currently in a choice, we reject all conditions that test against choices. return false; - if (select.getFunction() == SelectWrapper::Function_Weather + if (select.getFunction() == ESM::DialogueCondition::Function_Weather && !(MWBase::Environment::get().getWorld()->isCellExterior() || MWBase::Environment::get().getWorld()->isCellQuasiExterior())) // Reject weather conditions in interior cells @@ -305,29 +304,31 @@ bool MWDialogue::Filter::testSelectStructNumeric(const SelectWrapper& select) co { switch (select.getFunction()) { - case SelectWrapper::Function_Global: + case ESM::DialogueCondition::Function_Global: // internally all globals are float :( return select.selectCompare(MWBase::Environment::get().getWorld()->getGlobalFloat(select.getName())); - case SelectWrapper::Function_Local: + case ESM::DialogueCondition::Function_Local: { return testFunctionLocal(select); } - case SelectWrapper::Function_NotLocal: + case ESM::DialogueCondition::Function_NotLocal: { return !testFunctionLocal(select); } - case SelectWrapper::Function_PcHealthPercent: + case ESM::DialogueCondition::Function_PcHealthPercent: { MWWorld::Ptr player = MWMechanics::getPlayer(); return select.selectCompare( static_cast(player.getClass().getCreatureStats(player).getHealth().getRatio() * 100)); } - case SelectWrapper::Function_PcDynamicStat: + case ESM::DialogueCondition::Function_PcMagicka: + case ESM::DialogueCondition::Function_PcFatigue: + case ESM::DialogueCondition::Function_PcHealth: { MWWorld::Ptr player = MWMechanics::getPlayer(); @@ -336,7 +337,7 @@ bool MWDialogue::Filter::testSelectStructNumeric(const SelectWrapper& select) co return select.selectCompare(value); } - case SelectWrapper::Function_HealthPercent: + case ESM::DialogueCondition::Function_Health_Percent: { return select.selectCompare( static_cast(mActor.getClass().getCreatureStats(mActor).getHealth().getRatio() * 100)); @@ -354,27 +355,29 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons switch (select.getFunction()) { - case SelectWrapper::Function_Journal: + case ESM::DialogueCondition::Function_Journal: - return MWBase::Environment::get().getJournal()->getJournalIndex(ESM::RefId::stringRefId(select.getName())); + return MWBase::Environment::get().getJournal()->getJournalIndex(select.getId()); - case SelectWrapper::Function_Item: + case ESM::DialogueCondition::Function_Item: { MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); - return store.count(ESM::RefId::stringRefId(select.getName())); + return store.count(select.getId()); } - case SelectWrapper::Function_Dead: + case ESM::DialogueCondition::Function_Dead: - return MWBase::Environment::get().getMechanicsManager()->countDeaths( - ESM::RefId::stringRefId(select.getName())); + return MWBase::Environment::get().getMechanicsManager()->countDeaths(select.getId()); - case SelectWrapper::Function_Choice: + case ESM::DialogueCondition::Function_Choice: return mChoice; - case SelectWrapper::Function_AiSetting: + case ESM::DialogueCondition::Function_Fight: + case ESM::DialogueCondition::Function_Hello: + case ESM::DialogueCondition::Function_Alarm: + case ESM::DialogueCondition::Function_Flee: { int argument = select.getArgument(); if (argument < 0 || argument > 3) @@ -387,32 +390,65 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons .getAiSetting(static_cast(argument)) .getModified(false); } - case SelectWrapper::Function_PcAttribute: + case ESM::DialogueCondition::Function_PcStrength: + case ESM::DialogueCondition::Function_PcIntelligence: + case ESM::DialogueCondition::Function_PcWillpower: + case ESM::DialogueCondition::Function_PcAgility: + case ESM::DialogueCondition::Function_PcSpeed: + case ESM::DialogueCondition::Function_PcEndurance: + case ESM::DialogueCondition::Function_PcPersonality: + case ESM::DialogueCondition::Function_PcLuck: { ESM::RefId attribute = ESM::Attribute::indexToRefId(select.getArgument()); return player.getClass().getCreatureStats(player).getAttribute(attribute).getModified(); } - case SelectWrapper::Function_PcSkill: + case ESM::DialogueCondition::Function_PcBlock: + case ESM::DialogueCondition::Function_PcArmorer: + case ESM::DialogueCondition::Function_PcMediumArmor: + case ESM::DialogueCondition::Function_PcHeavyArmor: + case ESM::DialogueCondition::Function_PcBluntWeapon: + case ESM::DialogueCondition::Function_PcLongBlade: + case ESM::DialogueCondition::Function_PcAxe: + case ESM::DialogueCondition::Function_PcSpear: + case ESM::DialogueCondition::Function_PcAthletics: + case ESM::DialogueCondition::Function_PcEnchant: + case ESM::DialogueCondition::Function_PcDestruction: + case ESM::DialogueCondition::Function_PcAlteration: + case ESM::DialogueCondition::Function_PcIllusion: + case ESM::DialogueCondition::Function_PcConjuration: + case ESM::DialogueCondition::Function_PcMysticism: + case ESM::DialogueCondition::Function_PcRestoration: + case ESM::DialogueCondition::Function_PcAlchemy: + case ESM::DialogueCondition::Function_PcUnarmored: + case ESM::DialogueCondition::Function_PcSecurity: + case ESM::DialogueCondition::Function_PcSneak: + case ESM::DialogueCondition::Function_PcAcrobatics: + case ESM::DialogueCondition::Function_PcLightArmor: + case ESM::DialogueCondition::Function_PcShortBlade: + case ESM::DialogueCondition::Function_PcMarksman: + case ESM::DialogueCondition::Function_PcMerchantile: + case ESM::DialogueCondition::Function_PcSpeechcraft: + case ESM::DialogueCondition::Function_PcHandToHand: { ESM::RefId skill = ESM::Skill::indexToRefId(select.getArgument()); return static_cast(player.getClass().getNpcStats(player).getSkill(skill).getModified()); } - case SelectWrapper::Function_FriendlyHit: + case ESM::DialogueCondition::Function_FriendHit: { int hits = mActor.getClass().getCreatureStats(mActor).getFriendlyHits(); return hits > 4 ? 4 : hits; } - case SelectWrapper::Function_PcLevel: + case ESM::DialogueCondition::Function_PcLevel: return player.getClass().getCreatureStats(player).getLevel(); - case SelectWrapper::Function_PcGender: + case ESM::DialogueCondition::Function_PcGender: return player.get()->mBase->isMale() ? 0 : 1; - case SelectWrapper::Function_PcClothingModifier: + case ESM::DialogueCondition::Function_PcClothingModifier: { const MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); @@ -429,11 +465,11 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons return value; } - case SelectWrapper::Function_PcCrimeLevel: + case ESM::DialogueCondition::Function_PcCrimeLevel: return player.getClass().getNpcStats(player).getBounty(); - case SelectWrapper::Function_RankRequirement: + case ESM::DialogueCondition::Function_RankRequirement: { const ESM::RefId& faction = mActor.getClass().getPrimaryFaction(mActor); if (faction.empty()) @@ -455,23 +491,23 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons return result; } - case SelectWrapper::Function_Level: + case ESM::DialogueCondition::Function_Level: return mActor.getClass().getCreatureStats(mActor).getLevel(); - case SelectWrapper::Function_PCReputation: + case ESM::DialogueCondition::Function_PcReputation: return player.getClass().getNpcStats(player).getReputation(); - case SelectWrapper::Function_Weather: + case ESM::DialogueCondition::Function_Weather: return MWBase::Environment::get().getWorld()->getCurrentWeather(); - case SelectWrapper::Function_Reputation: + case ESM::DialogueCondition::Function_Reputation: return mActor.getClass().getNpcStats(mActor).getReputation(); - case SelectWrapper::Function_FactionRankDiff: + case ESM::DialogueCondition::Function_FactionRankDifference: { const ESM::RefId& faction = mActor.getClass().getPrimaryFaction(mActor); @@ -483,14 +519,14 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons return rank - npcRank; } - case SelectWrapper::Function_WerewolfKills: + case ESM::DialogueCondition::Function_PcWerewolfKills: return player.getClass().getNpcStats(player).getWerewolfKills(); - case SelectWrapper::Function_RankLow: - case SelectWrapper::Function_RankHigh: + case ESM::DialogueCondition::Function_FacReactionLowest: + case ESM::DialogueCondition::Function_FacReactionHighest: { - bool low = select.getFunction() == SelectWrapper::Function_RankLow; + bool low = select.getFunction() == ESM::DialogueCondition::Function_FacReactionLowest; const ESM::RefId& factionId = mActor.getClass().getPrimaryFaction(mActor); @@ -513,7 +549,7 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons return value; } - case SelectWrapper::Function_CreatureTargetted: + case ESM::DialogueCondition::Function_CreatureTarget: { MWWorld::Ptr target; @@ -540,53 +576,49 @@ bool MWDialogue::Filter::getSelectStructBoolean(const SelectWrapper& select) con switch (select.getFunction()) { - case SelectWrapper::Function_False: + case ESM::DialogueCondition::Function_NotId: - return false; - - case SelectWrapper::Function_NotId: - - return !(mActor.getCellRef().getRefId() == ESM::RefId::stringRefId(select.getName())); + return mActor.getCellRef().getRefId() != select.getId(); - case SelectWrapper::Function_NotFaction: + case ESM::DialogueCondition::Function_NotFaction: - return !(mActor.getClass().getPrimaryFaction(mActor) == ESM::RefId::stringRefId(select.getName())); + return mActor.getClass().getPrimaryFaction(mActor) != select.getId(); - case SelectWrapper::Function_NotClass: + case ESM::DialogueCondition::Function_NotClass: - return !(mActor.get()->mBase->mClass == ESM::RefId::stringRefId(select.getName())); + return mActor.get()->mBase->mClass != select.getId(); - case SelectWrapper::Function_NotRace: + case ESM::DialogueCondition::Function_NotRace: - return !(mActor.get()->mBase->mRace == ESM::RefId::stringRefId(select.getName())); + return mActor.get()->mBase->mRace != select.getId(); - case SelectWrapper::Function_NotCell: + case ESM::DialogueCondition::Function_NotCell: { std::string_view actorCell = MWBase::Environment::get().getWorld()->getCellName(mActor.getCell()); - return !Misc::StringUtils::ciStartsWith(actorCell, select.getName()); + return !Misc::StringUtils::ciStartsWith(actorCell, select.getCellName()); } - case SelectWrapper::Function_SameGender: + case ESM::DialogueCondition::Function_SameSex: return (player.get()->mBase->mFlags & ESM::NPC::Female) == (mActor.get()->mBase->mFlags & ESM::NPC::Female); - case SelectWrapper::Function_SameRace: + case ESM::DialogueCondition::Function_SameRace: return mActor.get()->mBase->mRace == player.get()->mBase->mRace; - case SelectWrapper::Function_SameFaction: + case ESM::DialogueCondition::Function_SameFaction: return player.getClass().getNpcStats(player).isInFaction(mActor.getClass().getPrimaryFaction(mActor)); - case SelectWrapper::Function_PcCommonDisease: + case ESM::DialogueCondition::Function_PcCommonDisease: return player.getClass().getCreatureStats(player).hasCommonDisease(); - case SelectWrapper::Function_PcBlightDisease: + case ESM::DialogueCondition::Function_PcBlightDisease: return player.getClass().getCreatureStats(player).hasBlightDisease(); - case SelectWrapper::Function_PcCorprus: + case ESM::DialogueCondition::Function_PcCorprus: return player.getClass() .getCreatureStats(player) @@ -595,7 +627,7 @@ bool MWDialogue::Filter::getSelectStructBoolean(const SelectWrapper& select) con .getMagnitude() != 0; - case SelectWrapper::Function_PcExpelled: + case ESM::DialogueCondition::Function_PcExpelled: { const ESM::RefId& faction = mActor.getClass().getPrimaryFaction(mActor); @@ -605,7 +637,7 @@ bool MWDialogue::Filter::getSelectStructBoolean(const SelectWrapper& select) con return player.getClass().getNpcStats(player).getExpelled(faction); } - case SelectWrapper::Function_PcVampire: + case ESM::DialogueCondition::Function_PcVampire: return player.getClass() .getCreatureStats(player) @@ -614,27 +646,27 @@ bool MWDialogue::Filter::getSelectStructBoolean(const SelectWrapper& select) con .getMagnitude() > 0; - case SelectWrapper::Function_TalkedToPc: + case ESM::DialogueCondition::Function_TalkedToPc: return mTalkedToPlayer; - case SelectWrapper::Function_Alarmed: + case ESM::DialogueCondition::Function_Alarmed: return mActor.getClass().getCreatureStats(mActor).isAlarmed(); - case SelectWrapper::Function_Detected: + case ESM::DialogueCondition::Function_Detected: return MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, mActor); - case SelectWrapper::Function_Attacked: + case ESM::DialogueCondition::Function_Attacked: return mActor.getClass().getCreatureStats(mActor).getAttacked(); - case SelectWrapper::Function_ShouldAttack: + case ESM::DialogueCondition::Function_ShouldAttack: return MWBase::Environment::get().getMechanicsManager()->isAggressive(mActor, MWMechanics::getPlayer()); - case SelectWrapper::Function_Werewolf: + case ESM::DialogueCondition::Function_Werewolf: return mActor.getClass().getNpcStats(mActor).isWerewolf(); diff --git a/apps/openmw/mwdialogue/keywordsearch.hpp b/apps/openmw/mwdialogue/keywordsearch.hpp index 3b784cd59c..2c98eac218 100644 --- a/apps/openmw/mwdialogue/keywordsearch.hpp +++ b/apps/openmw/mwdialogue/keywordsearch.hpp @@ -87,7 +87,7 @@ namespace MWDialogue // some keywords might be longer variations of other keywords, so we definitely need a list of // candidates the first element in the pair is length of the match, i.e. depth from the first character // on - std::vector> candidates; + std::vector> candidates; while ((j + 1) != end) { @@ -148,11 +148,11 @@ namespace MWDialogue // resolve overlapping keywords while (!matches.empty()) { - int longestKeywordSize = 0; + std::size_t longestKeywordSize = 0; typename std::vector::iterator longestKeyword = matches.begin(); for (typename std::vector::iterator it = matches.begin(); it != matches.end(); ++it) { - int size = it->mEnd - it->mBeg; + std::size_t size = it->mEnd - it->mBeg; if (size > longestKeywordSize) { longestKeywordSize = size; @@ -199,7 +199,7 @@ namespace MWDialogue void seed_impl(std::string_view keyword, value_t value, size_t depth, Entry& entry) { - int ch = Misc::StringUtils::toLower(keyword.at(depth)); + auto ch = Misc::StringUtils::toLower(keyword.at(depth)); typename Entry::childen_t::iterator j = entry.mChildren.find(ch); diff --git a/apps/openmw/mwdialogue/selectwrapper.cpp b/apps/openmw/mwdialogue/selectwrapper.cpp index 94f7f73097..02c9d29b59 100644 --- a/apps/openmw/mwdialogue/selectwrapper.cpp +++ b/apps/openmw/mwdialogue/selectwrapper.cpp @@ -10,431 +10,264 @@ namespace { template - bool selectCompareImp(char comp, T1 value1, T2 value2) + bool selectCompareImp(ESM::DialogueCondition::Comparison comp, T1 value1, T2 value2) { switch (comp) { - case '0': + case ESM::DialogueCondition::Comp_Eq: return value1 == value2; - case '1': + case ESM::DialogueCondition::Comp_Ne: return value1 != value2; - case '2': + case ESM::DialogueCondition::Comp_Gt: return value1 > value2; - case '3': + case ESM::DialogueCondition::Comp_Ge: return value1 >= value2; - case '4': + case ESM::DialogueCondition::Comp_Ls: return value1 < value2; - case '5': + case ESM::DialogueCondition::Comp_Le: return value1 <= value2; + default: + throw std::runtime_error("unknown compare type in dialogue info select"); } - - throw std::runtime_error("unknown compare type in dialogue info select"); } template - bool selectCompareImp(const ESM::DialInfo::SelectStruct& select, T value1) - { - if (select.mValue.getType() == ESM::VT_Int) - { - return selectCompareImp(select.mSelectRule[4], value1, select.mValue.getInteger()); - } - else if (select.mValue.getType() == ESM::VT_Float) - { - return selectCompareImp(select.mSelectRule[4], value1, select.mValue.getFloat()); - } - else - throw std::runtime_error("unsupported variable type in dialogue info select"); - } -} - -MWDialogue::SelectWrapper::Function MWDialogue::SelectWrapper::decodeFunction() const -{ - const int index = Misc::StringUtils::toNumeric(mSelect.mSelectRule.substr(2, 2), 0); - - switch (index) + bool selectCompareImp(const ESM::DialogueCondition& select, T value1) { - case 0: - return Function_RankLow; - case 1: - return Function_RankHigh; - case 2: - return Function_RankRequirement; - case 3: - return Function_Reputation; - case 4: - return Function_HealthPercent; - case 5: - return Function_PCReputation; - case 6: - return Function_PcLevel; - case 7: - return Function_PcHealthPercent; - case 8: - case 9: - return Function_PcDynamicStat; - case 10: - return Function_PcAttribute; - case 11: - case 12: - case 13: - case 14: - case 15: - case 16: - case 17: - case 18: - case 19: - case 20: - case 21: - case 22: - case 23: - case 24: - case 25: - case 26: - case 27: - case 28: - case 29: - case 30: - case 31: - case 32: - case 33: - case 34: - case 35: - case 36: - case 37: - return Function_PcSkill; - case 38: - return Function_PcGender; - case 39: - return Function_PcExpelled; - case 40: - return Function_PcCommonDisease; - case 41: - return Function_PcBlightDisease; - case 42: - return Function_PcClothingModifier; - case 43: - return Function_PcCrimeLevel; - case 44: - return Function_SameGender; - case 45: - return Function_SameRace; - case 46: - return Function_SameFaction; - case 47: - return Function_FactionRankDiff; - case 48: - return Function_Detected; - case 49: - return Function_Alarmed; - case 50: - return Function_Choice; - case 51: - case 52: - case 53: - case 54: - case 55: - case 56: - case 57: - return Function_PcAttribute; - case 58: - return Function_PcCorprus; - case 59: - return Function_Weather; - case 60: - return Function_PcVampire; - case 61: - return Function_Level; - case 62: - return Function_Attacked; - case 63: - return Function_TalkedToPc; - case 64: - return Function_PcDynamicStat; - case 65: - return Function_CreatureTargetted; - case 66: - return Function_FriendlyHit; - case 67: - case 68: - case 69: - case 70: - return Function_AiSetting; - case 71: - return Function_ShouldAttack; - case 72: - return Function_Werewolf; - case 73: - return Function_WerewolfKills; + return std::visit( + [&](auto value) { return selectCompareImp(select.mComparison, value1, value); }, select.mValue); } - - return Function_False; } -MWDialogue::SelectWrapper::SelectWrapper(const ESM::DialInfo::SelectStruct& select) +MWDialogue::SelectWrapper::SelectWrapper(const ESM::DialogueCondition& select) : mSelect(select) { } -MWDialogue::SelectWrapper::Function MWDialogue::SelectWrapper::getFunction() const +ESM::DialogueCondition::Function MWDialogue::SelectWrapper::getFunction() const { - char type = mSelect.mSelectRule[1]; - - switch (type) - { - case '1': - return decodeFunction(); - case '2': - return Function_Global; - case '3': - return Function_Local; - case '4': - return Function_Journal; - case '5': - return Function_Item; - case '6': - return Function_Dead; - case '7': - return Function_NotId; - case '8': - return Function_NotFaction; - case '9': - return Function_NotClass; - case 'A': - return Function_NotRace; - case 'B': - return Function_NotCell; - case 'C': - return Function_NotLocal; - } - - return Function_None; + return mSelect.mFunction; } int MWDialogue::SelectWrapper::getArgument() const { - if (mSelect.mSelectRule[1] != '1') - return 0; - - int index = 0; - - std::istringstream(mSelect.mSelectRule.substr(2, 2)) >> index; - - switch (index) + switch (mSelect.mFunction) { // AI settings - case 67: + case ESM::DialogueCondition::Function_Fight: return 1; - case 68: + case ESM::DialogueCondition::Function_Hello: return 0; - case 69: + case ESM::DialogueCondition::Function_Alarm: return 3; - case 70: + case ESM::DialogueCondition::Function_Flee: return 2; // attributes - case 10: + case ESM::DialogueCondition::Function_PcStrength: return 0; - case 51: + case ESM::DialogueCondition::Function_PcIntelligence: return 1; - case 52: + case ESM::DialogueCondition::Function_PcWillpower: return 2; - case 53: + case ESM::DialogueCondition::Function_PcAgility: return 3; - case 54: + case ESM::DialogueCondition::Function_PcSpeed: return 4; - case 55: + case ESM::DialogueCondition::Function_PcEndurance: return 5; - case 56: + case ESM::DialogueCondition::Function_PcPersonality: return 6; - case 57: + case ESM::DialogueCondition::Function_PcLuck: return 7; // skills - case 11: + case ESM::DialogueCondition::Function_PcBlock: return 0; - case 12: + case ESM::DialogueCondition::Function_PcArmorer: return 1; - case 13: + case ESM::DialogueCondition::Function_PcMediumArmor: return 2; - case 14: + case ESM::DialogueCondition::Function_PcHeavyArmor: return 3; - case 15: + case ESM::DialogueCondition::Function_PcBluntWeapon: return 4; - case 16: + case ESM::DialogueCondition::Function_PcLongBlade: return 5; - case 17: + case ESM::DialogueCondition::Function_PcAxe: return 6; - case 18: + case ESM::DialogueCondition::Function_PcSpear: return 7; - case 19: + case ESM::DialogueCondition::Function_PcAthletics: return 8; - case 20: + case ESM::DialogueCondition::Function_PcEnchant: return 9; - case 21: + case ESM::DialogueCondition::Function_PcDestruction: return 10; - case 22: + case ESM::DialogueCondition::Function_PcAlteration: return 11; - case 23: + case ESM::DialogueCondition::Function_PcIllusion: return 12; - case 24: + case ESM::DialogueCondition::Function_PcConjuration: return 13; - case 25: + case ESM::DialogueCondition::Function_PcMysticism: return 14; - case 26: + case ESM::DialogueCondition::Function_PcRestoration: return 15; - case 27: + case ESM::DialogueCondition::Function_PcAlchemy: return 16; - case 28: + case ESM::DialogueCondition::Function_PcUnarmored: return 17; - case 29: + case ESM::DialogueCondition::Function_PcSecurity: return 18; - case 30: + case ESM::DialogueCondition::Function_PcSneak: return 19; - case 31: + case ESM::DialogueCondition::Function_PcAcrobatics: return 20; - case 32: + case ESM::DialogueCondition::Function_PcLightArmor: return 21; - case 33: + case ESM::DialogueCondition::Function_PcShortBlade: return 22; - case 34: + case ESM::DialogueCondition::Function_PcMarksman: return 23; - case 35: + case ESM::DialogueCondition::Function_PcMerchantile: return 24; - case 36: + case ESM::DialogueCondition::Function_PcSpeechcraft: return 25; - case 37: + case ESM::DialogueCondition::Function_PcHandToHand: return 26; // dynamic stats - case 8: + case ESM::DialogueCondition::Function_PcMagicka: return 1; - case 9: + case ESM::DialogueCondition::Function_PcFatigue: return 2; - case 64: + case ESM::DialogueCondition::Function_PcHealth: + return 0; + default: return 0; } - - return 0; } MWDialogue::SelectWrapper::Type MWDialogue::SelectWrapper::getType() const { - static const Function integerFunctions[] = { - Function_Journal, - Function_Item, - Function_Dead, - Function_Choice, - Function_AiSetting, - Function_PcAttribute, - Function_PcSkill, - Function_FriendlyHit, - Function_PcLevel, - Function_PcGender, - Function_PcClothingModifier, - Function_PcCrimeLevel, - Function_RankRequirement, - Function_Level, - Function_PCReputation, - Function_Weather, - Function_Reputation, - Function_FactionRankDiff, - Function_WerewolfKills, - Function_RankLow, - Function_RankHigh, - Function_CreatureTargetted, - // end marker - Function_None, - }; - - static const Function numericFunctions[] = { - Function_Global, - Function_Local, - Function_NotLocal, - Function_PcDynamicStat, - Function_PcHealthPercent, - Function_HealthPercent, - // end marker - Function_None, - }; - - static const Function booleanFunctions[] = { - Function_False, - Function_SameGender, - Function_SameRace, - Function_SameFaction, - Function_PcCommonDisease, - Function_PcBlightDisease, - Function_PcCorprus, - Function_PcExpelled, - Function_PcVampire, - Function_TalkedToPc, - Function_Alarmed, - Function_Detected, - Function_Attacked, - Function_ShouldAttack, - Function_Werewolf, - // end marker - Function_None, - }; - - static const Function invertedBooleanFunctions[] = { - Function_NotId, - Function_NotFaction, - Function_NotClass, - Function_NotRace, - Function_NotCell, - // end marker - Function_None, - }; - - Function function = getFunction(); - - for (int i = 0; integerFunctions[i] != Function_None; ++i) - if (integerFunctions[i] == function) + switch (mSelect.mFunction) + { + case ESM::DialogueCondition::Function_Journal: + case ESM::DialogueCondition::Function_Item: + case ESM::DialogueCondition::Function_Dead: + case ESM::DialogueCondition::Function_Choice: + case ESM::DialogueCondition::Function_Fight: + case ESM::DialogueCondition::Function_Hello: + case ESM::DialogueCondition::Function_Alarm: + case ESM::DialogueCondition::Function_Flee: + case ESM::DialogueCondition::Function_PcStrength: + case ESM::DialogueCondition::Function_PcIntelligence: + case ESM::DialogueCondition::Function_PcWillpower: + case ESM::DialogueCondition::Function_PcAgility: + case ESM::DialogueCondition::Function_PcSpeed: + case ESM::DialogueCondition::Function_PcEndurance: + case ESM::DialogueCondition::Function_PcPersonality: + case ESM::DialogueCondition::Function_PcLuck: + case ESM::DialogueCondition::Function_PcBlock: + case ESM::DialogueCondition::Function_PcArmorer: + case ESM::DialogueCondition::Function_PcMediumArmor: + case ESM::DialogueCondition::Function_PcHeavyArmor: + case ESM::DialogueCondition::Function_PcBluntWeapon: + case ESM::DialogueCondition::Function_PcLongBlade: + case ESM::DialogueCondition::Function_PcAxe: + case ESM::DialogueCondition::Function_PcSpear: + case ESM::DialogueCondition::Function_PcAthletics: + case ESM::DialogueCondition::Function_PcEnchant: + case ESM::DialogueCondition::Function_PcDestruction: + case ESM::DialogueCondition::Function_PcAlteration: + case ESM::DialogueCondition::Function_PcIllusion: + case ESM::DialogueCondition::Function_PcConjuration: + case ESM::DialogueCondition::Function_PcMysticism: + case ESM::DialogueCondition::Function_PcRestoration: + case ESM::DialogueCondition::Function_PcAlchemy: + case ESM::DialogueCondition::Function_PcUnarmored: + case ESM::DialogueCondition::Function_PcSecurity: + case ESM::DialogueCondition::Function_PcSneak: + case ESM::DialogueCondition::Function_PcAcrobatics: + case ESM::DialogueCondition::Function_PcLightArmor: + case ESM::DialogueCondition::Function_PcShortBlade: + case ESM::DialogueCondition::Function_PcMarksman: + case ESM::DialogueCondition::Function_PcMerchantile: + case ESM::DialogueCondition::Function_PcSpeechcraft: + case ESM::DialogueCondition::Function_PcHandToHand: + case ESM::DialogueCondition::Function_FriendHit: + case ESM::DialogueCondition::Function_PcLevel: + case ESM::DialogueCondition::Function_PcGender: + case ESM::DialogueCondition::Function_PcClothingModifier: + case ESM::DialogueCondition::Function_PcCrimeLevel: + case ESM::DialogueCondition::Function_RankRequirement: + case ESM::DialogueCondition::Function_Level: + case ESM::DialogueCondition::Function_PcReputation: + case ESM::DialogueCondition::Function_Weather: + case ESM::DialogueCondition::Function_Reputation: + case ESM::DialogueCondition::Function_FactionRankDifference: + case ESM::DialogueCondition::Function_PcWerewolfKills: + case ESM::DialogueCondition::Function_FacReactionLowest: + case ESM::DialogueCondition::Function_FacReactionHighest: + case ESM::DialogueCondition::Function_CreatureTarget: return Type_Integer; - - for (int i = 0; numericFunctions[i] != Function_None; ++i) - if (numericFunctions[i] == function) + case ESM::DialogueCondition::Function_Global: + case ESM::DialogueCondition::Function_Local: + case ESM::DialogueCondition::Function_NotLocal: + case ESM::DialogueCondition::Function_PcHealth: + case ESM::DialogueCondition::Function_PcMagicka: + case ESM::DialogueCondition::Function_PcFatigue: + case ESM::DialogueCondition::Function_PcHealthPercent: + case ESM::DialogueCondition::Function_Health_Percent: return Type_Numeric; - - for (int i = 0; booleanFunctions[i] != Function_None; ++i) - if (booleanFunctions[i] == function) + case ESM::DialogueCondition::Function_SameSex: + case ESM::DialogueCondition::Function_SameRace: + case ESM::DialogueCondition::Function_SameFaction: + case ESM::DialogueCondition::Function_PcCommonDisease: + case ESM::DialogueCondition::Function_PcBlightDisease: + case ESM::DialogueCondition::Function_PcCorprus: + case ESM::DialogueCondition::Function_PcExpelled: + case ESM::DialogueCondition::Function_PcVampire: + case ESM::DialogueCondition::Function_TalkedToPc: + case ESM::DialogueCondition::Function_Alarmed: + case ESM::DialogueCondition::Function_Detected: + case ESM::DialogueCondition::Function_Attacked: + case ESM::DialogueCondition::Function_ShouldAttack: + case ESM::DialogueCondition::Function_Werewolf: return Type_Boolean; - - for (int i = 0; invertedBooleanFunctions[i] != Function_None; ++i) - if (invertedBooleanFunctions[i] == function) + case ESM::DialogueCondition::Function_NotId: + case ESM::DialogueCondition::Function_NotFaction: + case ESM::DialogueCondition::Function_NotClass: + case ESM::DialogueCondition::Function_NotRace: + case ESM::DialogueCondition::Function_NotCell: return Type_Inverted; - - return Type_None; + default: + return Type_None; + }; } bool MWDialogue::SelectWrapper::isNpcOnly() const { - static const Function functions[] = { - Function_NotFaction, - Function_NotClass, - Function_NotRace, - Function_SameGender, - Function_SameRace, - Function_SameFaction, - Function_RankRequirement, - Function_Reputation, - Function_FactionRankDiff, - Function_Werewolf, - Function_WerewolfKills, - Function_RankLow, - Function_RankHigh, - // end marker - Function_None, - }; - - Function function = getFunction(); - - for (int i = 0; functions[i] != Function_None; ++i) - if (functions[i] == function) + switch (mSelect.mFunction) + { + case ESM::DialogueCondition::Function_NotFaction: + case ESM::DialogueCondition::Function_NotClass: + case ESM::DialogueCondition::Function_NotRace: + case ESM::DialogueCondition::Function_SameSex: + case ESM::DialogueCondition::Function_SameRace: + case ESM::DialogueCondition::Function_SameFaction: + case ESM::DialogueCondition::Function_RankRequirement: + case ESM::DialogueCondition::Function_Reputation: + case ESM::DialogueCondition::Function_FactionRankDifference: + case ESM::DialogueCondition::Function_Werewolf: + case ESM::DialogueCondition::Function_PcWerewolfKills: + case ESM::DialogueCondition::Function_FacReactionLowest: + case ESM::DialogueCondition::Function_FacReactionHighest: return true; - - return false; + default: + return false; + } } bool MWDialogue::SelectWrapper::selectCompare(int value) const @@ -454,5 +287,15 @@ bool MWDialogue::SelectWrapper::selectCompare(bool value) const std::string MWDialogue::SelectWrapper::getName() const { - return Misc::StringUtils::lowerCase(std::string_view(mSelect.mSelectRule).substr(5)); + return Misc::StringUtils::lowerCase(mSelect.mVariable); +} + +std::string_view MWDialogue::SelectWrapper::getCellName() const +{ + return mSelect.mVariable; +} + +ESM::RefId MWDialogue::SelectWrapper::getId() const +{ + return ESM::RefId::stringRefId(mSelect.mVariable); } diff --git a/apps/openmw/mwdialogue/selectwrapper.hpp b/apps/openmw/mwdialogue/selectwrapper.hpp index 0d376d957c..d831b6cea0 100644 --- a/apps/openmw/mwdialogue/selectwrapper.hpp +++ b/apps/openmw/mwdialogue/selectwrapper.hpp @@ -7,62 +7,9 @@ namespace MWDialogue { class SelectWrapper { - const ESM::DialInfo::SelectStruct& mSelect; + const ESM::DialogueCondition& mSelect; public: - enum Function - { - Function_None, - Function_False, - Function_Journal, - Function_Item, - Function_Dead, - Function_NotId, - Function_NotFaction, - Function_NotClass, - Function_NotRace, - Function_NotCell, - Function_NotLocal, - Function_Local, - Function_Global, - Function_SameGender, - Function_SameRace, - Function_SameFaction, - Function_Choice, - Function_PcCommonDisease, - Function_PcBlightDisease, - Function_PcCorprus, - Function_AiSetting, - Function_PcAttribute, - Function_PcSkill, - Function_PcExpelled, - Function_PcVampire, - Function_FriendlyHit, - Function_TalkedToPc, - Function_PcLevel, - Function_PcHealthPercent, - Function_PcDynamicStat, - Function_PcGender, - Function_PcClothingModifier, - Function_PcCrimeLevel, - Function_RankRequirement, - Function_HealthPercent, - Function_Level, - Function_PCReputation, - Function_Weather, - Function_Reputation, - Function_Alarmed, - Function_FactionRankDiff, - Function_Detected, - Function_Attacked, - Function_ShouldAttack, - Function_CreatureTargetted, - Function_Werewolf, - Function_WerewolfKills, - Function_RankLow, - Function_RankHigh - }; - enum Type { Type_None, @@ -72,13 +19,10 @@ namespace MWDialogue Type_Inverted }; - private: - Function decodeFunction() const; - public: - SelectWrapper(const ESM::DialInfo::SelectStruct& select); + SelectWrapper(const ESM::DialogueCondition& select); - Function getFunction() const; + ESM::DialogueCondition::Function getFunction() const; int getArgument() const; @@ -95,6 +39,10 @@ namespace MWDialogue std::string getName() const; ///< Return case-smashed name. + + std::string_view getCellName() const; + + ESM::RefId getId() const; }; } diff --git a/apps/openmw/mwgui/charactercreation.cpp b/apps/openmw/mwgui/charactercreation.cpp index c5280d1615..be2d22ae84 100644 --- a/apps/openmw/mwgui/charactercreation.cpp +++ b/apps/openmw/mwgui/charactercreation.cpp @@ -39,7 +39,7 @@ namespace { const std::string mText; const Response mResponses[3]; - const std::string mSound; + const VFS::Path::Normalized mSound; }; Step sGenerateClassSteps(int number) diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index b430e08142..a188f3c86b 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -771,11 +771,6 @@ namespace MWGui return output.append(matches.front()); } - void Console::onResChange(int width, int height) - { - setCoord(10, 10, width - 10, height / 2); - } - void Console::updateSelectedObjectPtr(const MWWorld::Ptr& currentPtr, const MWWorld::Ptr& newPtr) { if (mPtr == currentPtr) diff --git a/apps/openmw/mwgui/console.hpp b/apps/openmw/mwgui/console.hpp index 79d18847a4..2b6ecfc8ad 100644 --- a/apps/openmw/mwgui/console.hpp +++ b/apps/openmw/mwgui/console.hpp @@ -47,8 +47,6 @@ namespace MWGui void onOpen() override; - void onResChange(int width, int height) override; - // Print a message to the console, in specified color. void print(const std::string& msg, std::string_view color = MWBase::WindowManager::sConsoleColor_Default); diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index 8264dd60b6..af4a3e8ce3 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -273,7 +273,7 @@ namespace MWGui void EnchantingDialog::notifyEffectsChanged() { - mEffectList.mList = mEffects; + mEffectList.populate(mEffects); mEnchanting.setEffect(mEffectList); updateLabels(); } diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index 1c8aad5447..0ee341c5c2 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -427,7 +427,7 @@ namespace MWGui { // use the icon of the first effect const ESM::MagicEffect* effect = MWBase::Environment::get().getESMStore()->get().find( - spell->mEffects.mList.front().mEffectID); + spell->mEffects.mList.front().mData.mEffectID); std::string icon = effect->mIcon; std::replace(icon.begin(), icon.end(), '/', '\\'); size_t slashPos = icon.rfind('\\'); diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 0fbd15dda2..4805f7f3cb 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -417,6 +417,8 @@ namespace MWGui void InventoryWindow::onWindowResize(MyGUI::Window* _sender) { + WindowBase::clampWindowCoordinates(_sender); + adjustPanes(); const WindowSettingValues settings = getModeSettings(mGuiMode); diff --git a/apps/openmw/mwgui/keyboardnavigation.cpp b/apps/openmw/mwgui/keyboardnavigation.cpp index 85c7d8ba88..9d4971951a 100644 --- a/apps/openmw/mwgui/keyboardnavigation.cpp +++ b/apps/openmw/mwgui/keyboardnavigation.cpp @@ -246,12 +246,12 @@ namespace MWGui bool forward = (direction == D_Next || direction == D_Right || direction == D_Down); - int index = found - keyFocusList.begin(); + std::ptrdiff_t index{ found - keyFocusList.begin() }; index = forward ? (index + 1) : (index - 1); if (wrap) index = (index + keyFocusList.size()) % keyFocusList.size(); else - index = std::clamp(index, 0, keyFocusList.size() - 1); + index = std::clamp(index, 0, keyFocusList.size() - 1); MyGUI::Widget* next = keyFocusList[index]; int vertdiff = next->getTop() - focus->getTop(); diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp index 2160a04b1b..f1a40a3f16 100644 --- a/apps/openmw/mwgui/levelupdialog.cpp +++ b/apps/openmw/mwgui/levelupdialog.cpp @@ -22,6 +22,8 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwsound/constants.hpp" + #include "class.hpp" namespace @@ -164,8 +166,10 @@ namespace MWGui const MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats(player); setClassImage(mClassImage, - ESM::RefId::stringRefId(getLevelupClassImage(pcStats.getSkillIncreasesForSpecialization(0), - pcStats.getSkillIncreasesForSpecialization(1), pcStats.getSkillIncreasesForSpecialization(2)))); + ESM::RefId::stringRefId( + getLevelupClassImage(pcStats.getSkillIncreasesForSpecialization(ESM::Class::Specialization::Combat), + pcStats.getSkillIncreasesForSpecialization(ESM::Class::Specialization::Magic), + pcStats.getSkillIncreasesForSpecialization(ESM::Class::Specialization::Stealth)))); int level = creatureStats.getLevel() + 1; mLevelText->setCaptionWithReplacing("#{sLevelUpMenu1} " + MyGUI::utility::toString(level)); @@ -214,8 +218,7 @@ namespace MWGui center(); // Play LevelUp Music - MWBase::Environment::get().getSoundManager()->streamMusic( - "Music/Special/MW_Triumph.mp3", MWSound::MusicType::Special); + MWBase::Environment::get().getSoundManager()->streamMusic(MWSound::triumphMusic, MWSound::MusicType::Special); } void LevelupDialog::onOkButtonClicked(MyGUI::Widget* sender) diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 8ba2bb8312..3a9aba7828 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -67,7 +67,8 @@ namespace MWGui != supported_extensions.end(); }; - for (const auto& name : mResourceSystem->getVFS()->getRecursiveDirectoryIterator("Splash/")) + constexpr VFS::Path::NormalizedView splash("splash/"); + for (const auto& name : mResourceSystem->getVFS()->getRecursiveDirectoryIterator(splash)) { if (isSupportedExtension(Misc::getFileExtension(name))) mSplashScreens.push_back(name); diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index d0c55f432e..53f791fdac 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include "../mwbase/environment.hpp" @@ -37,7 +38,9 @@ namespace MWGui getWidget(mVersionText, "VersionText"); mVersionText->setCaption(versionDescription); - mHasAnimatedMenu = mVFS->exists("video/menu_background.bik"); + constexpr VFS::Path::NormalizedView menuBackgroundVideo("video/menu_background.bik"); + + mHasAnimatedMenu = mVFS->exists(menuBackgroundVideo); updateMenu(); } @@ -99,7 +102,7 @@ namespace MWGui } else if (name == "options") { - winMgr->getSettingsWindow()->setVisible(true); + winMgr->toggleSettingsWindow(); } else if (name == "credits") winMgr->playVideo("mw_credits.bik", true); @@ -212,6 +215,12 @@ namespace MWGui bool MainMenu::exit() { + if (MWBase::Environment::get().getWindowManager()->isSettingsWindowVisible()) + { + MWBase::Environment::get().getWindowManager()->toggleSettingsWindow(); + return false; + } + return MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_Running; } diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index cb6ba79f9e..02a38fa640 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -95,6 +95,13 @@ namespace return std::clamp( viewingDistanceInCells, Constants::CellGridRadius, Settings::map().mMaxLocalViewingDistance.get()); } + + ESM::RefId getCellIdInWorldSpace(const MWWorld::Cell& cell, int x, int y) + { + if (cell.isExterior()) + return ESM::Cell::generateIdForCell(true, {}, x, y); + return cell.getId(); + } } namespace MWGui @@ -170,12 +177,9 @@ namespace MWGui LocalMapBase::LocalMapBase( CustomMarkerCollection& markers, MWRender::LocalMap* localMapRender, bool fogOfWarEnabled) : mLocalMapRender(localMapRender) - , mCurX(0) - , mCurY(0) - , mInterior(false) + , mActiveCell(nullptr) , mLocalMap(nullptr) , mCompass(nullptr) - , mChanged(true) , mFogOfWarToggled(true) , mFogOfWarEnabled(fogOfWarEnabled) , mNumCells(1) @@ -231,12 +235,6 @@ namespace MWGui } } - void LocalMapBase::setCellPrefix(const std::string& prefix) - { - mPrefix = prefix; - mChanged = true; - } - bool LocalMapBase::toggleFogOfWar() { mFogOfWarToggled = !mFogOfWarToggled; @@ -262,8 +260,8 @@ namespace MWGui { // normalized cell coordinates auto mapWidgetSize = getWidgetSize(); - return MyGUI::IntPoint(std::round(nX * mapWidgetSize + (mCellDistance + (cellX - mCurX)) * mapWidgetSize), - std::round(nY * mapWidgetSize + (mCellDistance - (cellY - mCurY)) * mapWidgetSize)); + return MyGUI::IntPoint(std::round((nX + mCellDistance + cellX - mActiveCell->getGridX()) * mapWidgetSize), + std::round((nY + mCellDistance - cellY + mActiveCell->getGridY()) * mapWidgetSize)); } MyGUI::IntPoint LocalMapBase::getMarkerPosition(float worldX, float worldY, MarkerUserData& markerPos) const @@ -272,7 +270,7 @@ namespace MWGui // normalized cell coordinates float nX, nY; - if (!mInterior) + if (mActiveCell->isExterior()) { ESM::ExteriorCellLocation cellPos = ESM::positionToExteriorCellLocation(worldX, worldY); cellIndex.x() = cellPos.mX; @@ -336,7 +334,7 @@ namespace MWGui std::vector& LocalMapBase::currentDoorMarkersWidgets() { - return mInterior ? mInteriorDoorMarkerWidgets : mExteriorDoorMarkerWidgets; + return mActiveCell->isExterior() ? mExteriorDoorMarkerWidgets : mInteriorDoorMarkerWidgets; } void LocalMapBase::updateCustomMarkers() @@ -344,12 +342,14 @@ namespace MWGui for (MyGUI::Widget* widget : mCustomMarkerWidgets) MyGUI::Gui::getInstance().destroyWidget(widget); mCustomMarkerWidgets.clear(); - + if (!mActiveCell) + return; for (int dX = -mCellDistance; dX <= mCellDistance; ++dX) { for (int dY = -mCellDistance; dY <= mCellDistance; ++dY) { - ESM::RefId cellRefId = ESM::Cell::generateIdForCell(!mInterior, mPrefix, mCurX + dX, mCurY + dY); + ESM::RefId cellRefId + = getCellIdInWorldSpace(*mActiveCell, mActiveCell->getGridX() + dX, mActiveCell->getGridY() + dY); CustomMarkerCollection::RangeType markers = mCustomMarkers.getMarkers(cellRefId); for (CustomMarkerCollection::ContainerType::const_iterator it = markers.first; it != markers.second; @@ -377,16 +377,25 @@ namespace MWGui redraw(); } - void LocalMapBase::setActiveCell(const int x, const int y, bool interior) + void LocalMapBase::setActiveCell(const MWWorld::Cell& cell) { - if (x == mCurX && y == mCurY && mInterior == interior && !mChanged) + if (&cell == mActiveCell) return; // don't do anything if we're still in the same cell - if (!interior && !(x == mCurX && y == mCurY)) + const int x = cell.getGridX(); + const int y = cell.getGridY(); + + if (cell.isExterior()) { - const MyGUI::IntRect intersection - = { std::max(x, mCurX) - mCellDistance, std::max(y, mCurY) - mCellDistance, - std::min(x, mCurX) + mCellDistance, std::min(y, mCurY) + mCellDistance }; + int curX = 0; + int curY = 0; + if (mActiveCell) + { + curX = mActiveCell->getGridX(); + curY = mActiveCell->getGridY(); + } + const MyGUI::IntRect intersection = { std::max(x, curX) - mCellDistance, std::max(y, curY) - mCellDistance, + std::min(x, curX) + mCellDistance, std::min(y, curY) + mCellDistance }; const MyGUI::IntRect activeGrid = createRect({ x, y }, Constants::CellGridRadius); const MyGUI::IntRect currentView = createRect({ x, y }, mCellDistance); @@ -407,17 +416,14 @@ namespace MWGui for (auto& widget : mDoorMarkersToRecycle) widget->setVisible(false); - for (auto const& cell : mMaps) + for (auto const& entry : mMaps) { - if (mHasALastActiveCell && !intersection.inside({ cell.mCellX, cell.mCellY })) - mLocalMapRender->removeExteriorCell(cell.mCellX, cell.mCellY); + if (mHasALastActiveCell && !intersection.inside({ entry.mCellX, entry.mCellY })) + mLocalMapRender->removeExteriorCell(entry.mCellX, entry.mCellY); } } - mCurX = x; - mCurY = y; - mInterior = interior; - mChanged = false; + mActiveCell = &cell; for (int mx = 0; mx < mNumCells; ++mx) { @@ -441,7 +447,7 @@ namespace MWGui for (MyGUI::Widget* widget : currentDoorMarkersWidgets()) widget->setCoord(getMarkerCoordinates(widget, 8)); - if (!mInterior) + if (mActiveCell->isExterior()) mHasALastActiveCell = true; updateMagicMarkers(); @@ -580,7 +586,7 @@ namespace MWGui if (!entry.mMapTexture) { - if (!mInterior) + if (mActiveCell->isExterior()) requestMapRender(&MWBase::Environment::get().getWorldModel()->getExterior( ESM::ExteriorCellLocation(entry.mCellX, entry.mCellY, ESM::Cell::sDefaultWorldspaceId))); @@ -626,12 +632,12 @@ namespace MWGui mDoorMarkersToRecycle.end(), mInteriorDoorMarkerWidgets.begin(), mInteriorDoorMarkerWidgets.end()); mInteriorDoorMarkerWidgets.clear(); - if (mInterior) + if (!mActiveCell->isExterior()) { for (MyGUI::Widget* widget : mExteriorDoorMarkerWidgets) widget->setVisible(false); - MWWorld::CellStore& cell = worldModel->getInterior(mPrefix); + MWWorld::CellStore& cell = worldModel->getInterior(mActiveCell->getNameId()); world->getDoorMarkers(cell, doors); } else @@ -678,7 +684,7 @@ namespace MWGui } currentDoorMarkersWidgets().push_back(markerWidget); - if (!mInterior) + if (mActiveCell->isExterior()) mExteriorDoorsByCell[{ data->cellX, data->cellY }].push_back(markerWidget); } @@ -701,8 +707,7 @@ namespace MWGui MWWorld::CellStore* markedCell = nullptr; ESM::Position markedPosition; MWBase::Environment::get().getWorld()->getPlayer().getMarkedPosition(markedCell, markedPosition); - if (markedCell && markedCell->isExterior() == !mInterior - && (!mInterior || Misc::StringUtils::ciEqual(markedCell->getCell()->getNameId(), mPrefix))) + if (markedCell && markedCell->getCell()->getWorldSpace() == mActiveCell->getWorldSpace()) { MarkerUserData markerPos(mLocalMapRender); MyGUI::ImageBox* markerWidget = mLocalMap->createWidget("ImageBox", @@ -870,11 +875,11 @@ namespace MWGui int y = (int(widgetPos.top / float(mapWidgetSize)) - mCellDistance) * -1; float nX = widgetPos.left / float(mapWidgetSize) - int(widgetPos.left / float(mapWidgetSize)); float nY = widgetPos.top / float(mapWidgetSize) - int(widgetPos.top / float(mapWidgetSize)); - x += mCurX; - y += mCurY; + x += mActiveCell->getGridX(); + y += mActiveCell->getGridY(); osg::Vec2f worldPos; - if (mInterior) + if (!mActiveCell->isExterior()) { worldPos = mLocalMapRender->interiorMapToWorldPosition(nX, nY, x, y); } @@ -886,7 +891,7 @@ namespace MWGui mEditingMarker.mWorldX = worldPos.x(); mEditingMarker.mWorldY = worldPos.y(); - ESM::RefId clickedId = ESM::Cell::generateIdForCell(!mInterior, LocalMapBase::mPrefix, x, y); + ESM::RefId clickedId = getCellIdInWorldSpace(*mActiveCell, x, y); mEditingMarker.mCell = clickedId; @@ -977,7 +982,7 @@ namespace MWGui resizeGlobalMap(); float x = mCurPos.x(), y = mCurPos.y(); - if (mInterior) + if (!mActiveCell->isExterior()) { auto pos = MWBase::Environment::get().getWorld()->getPlayer().getLastKnownExteriorPosition(); x = pos.x(); @@ -1020,7 +1025,7 @@ namespace MWGui resizeGlobalMap(); } - MapWindow::~MapWindow() {} + MapWindow::~MapWindow() = default; void MapWindow::setCellName(const std::string& cellName) { @@ -1289,7 +1294,7 @@ namespace MWGui mMarkers.clear(); mGlobalMapRender->clear(); - mChanged = true; + mActiveCell = nullptr; for (auto& widgetPair : mGlobalMapMarkers) MyGUI::Gui::getInstance().destroyWidget(widgetPair.first.widget); diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index 29759a4365..8066256437 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -27,6 +27,7 @@ namespace ESM namespace MWWorld { + class Cell; class CellStore; } @@ -77,8 +78,7 @@ namespace MWGui virtual ~LocalMapBase(); void init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass, int cellDistance = Constants::CellGridRadius); - void setCellPrefix(const std::string& prefix); - void setActiveCell(const int x, const int y, bool interior = false); + void setActiveCell(const MWWorld::Cell& cell); void requestMapRender(const MWWorld::CellStore* cell); void setPlayerDir(const float x, const float y); void setPlayerPos(int cellX, int cellY, const float nx, const float ny); @@ -115,15 +115,12 @@ namespace MWGui float mLocalMapZoom = 1.f; MWRender::LocalMap* mLocalMapRender; - int mCurX, mCurY; // the position of the active cell on the global map (in cell coords) + const MWWorld::Cell* mActiveCell; bool mHasALastActiveCell = false; osg::Vec2f mCurPos; // the position of the player in the world (in cell coords) - bool mInterior; MyGUI::ScrollView* mLocalMap; MyGUI::ImageBox* mCompass; - std::string mPrefix; - bool mChanged; bool mFogOfWarToggled; bool mFogOfWarEnabled; diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index b27adacd0f..1d6e1511c4 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -28,7 +28,7 @@ namespace MWGui MessageBoxManager::clear(); } - int MessageBoxManager::getMessagesCount() + std::size_t MessageBoxManager::getMessagesCount() { return mMessageBoxes.size(); } diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index bb61bd6bd9..feb717e0ad 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -29,7 +29,7 @@ namespace MWGui bool immediate = false, int defaultFocus = -1); bool isInteractiveMessageBox(); - int getMessagesCount(); + std::size_t getMessagesCount(); const InteractiveMessageBox* getInteractiveMessageBox() const { return mInterMessageBoxe.get(); } diff --git a/apps/openmw/mwgui/pickpocketitemmodel.cpp b/apps/openmw/mwgui/pickpocketitemmodel.cpp index bfaf011835..fa7bce449b 100644 --- a/apps/openmw/mwgui/pickpocketitemmodel.cpp +++ b/apps/openmw/mwgui/pickpocketitemmodel.cpp @@ -134,7 +134,7 @@ namespace MWGui return false; } else - player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, 1); + player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, ESM::Skill::Sneak_PickPocket); return true; } diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 204fa00492..df7236242f 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -299,7 +299,8 @@ namespace MWGui mSelected->button->setUserString("Spell", spellId.serialize()); // use the icon of the first effect - const ESM::MagicEffect* effect = esmStore.get().find(spell->mEffects.mList.front().mEffectID); + const ESM::MagicEffect* effect + = esmStore.get().find(spell->mEffects.mList.front().mData.mEffectID); std::string path = effect->mIcon; std::replace(path.begin(), path.end(), '/', '\\'); diff --git a/apps/openmw/mwgui/screenfader.cpp b/apps/openmw/mwgui/screenfader.cpp index e22517a360..0068ba7960 100644 --- a/apps/openmw/mwgui/screenfader.cpp +++ b/apps/openmw/mwgui/screenfader.cpp @@ -96,9 +96,10 @@ namespace MWGui { imageBox->setImageTexture(texturePath); const MyGUI::IntSize imageSize = imageBox->getImageSize(); - imageBox->setImageCoord( - MyGUI::IntCoord(texCoordOverride.left * imageSize.width, texCoordOverride.top * imageSize.height, - texCoordOverride.width * imageSize.width, texCoordOverride.height * imageSize.height)); + imageBox->setImageCoord(MyGUI::IntCoord(static_cast(texCoordOverride.left * imageSize.width), + static_cast(texCoordOverride.top * imageSize.height), + static_cast(texCoordOverride.width * imageSize.width), + static_cast(texCoordOverride.height * imageSize.height))); } } diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 6c6a34595e..075a338592 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -241,7 +241,7 @@ namespace MWGui } SettingsWindow::SettingsWindow() - : WindowModal("openmw_settings_window.layout") + : WindowBase("openmw_settings_window.layout") , mKeyboardMode(true) , mCurrentPage(-1) { @@ -266,6 +266,9 @@ namespace MWGui getWidget(mResetControlsButton, "ResetControlsButton"); getWidget(mKeyboardSwitch, "KeyboardButton"); getWidget(mControllerSwitch, "ControllerButton"); + getWidget(mWaterRefractionButton, "WaterRefractionButton"); + getWidget(mSunlightScatteringButton, "SunlightScatteringButton"); + getWidget(mWobblyShoresButton, "WobblyShoresButton"); getWidget(mWaterTextureSize, "WaterTextureSize"); getWidget(mWaterReflectionDetail, "WaterReflectionDetail"); getWidget(mWaterRainRippleDetail, "WaterRainRippleDetail"); @@ -306,6 +309,8 @@ namespace MWGui += MyGUI::newDelegate(this, &SettingsWindow::onTextureFilteringChanged); mResolutionList->eventListChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onResolutionSelected); + mWaterRefractionButton->eventMouseButtonClick + += MyGUI::newDelegate(this, &SettingsWindow::onRefractionButtonClicked); mWaterTextureSize->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterTextureSizeChanged); mWaterReflectionDetail->eventComboChangePosition @@ -377,6 +382,10 @@ namespace MWGui const int waterRainRippleDetail = Settings::water().mRainRippleDetail; mWaterRainRippleDetail->setIndexSelected(waterRainRippleDetail); + const bool waterRefraction = Settings::water().mRefraction; + mSunlightScatteringButton->setEnabled(waterRefraction); + mWobblyShoresButton->setEnabled(waterRefraction); + updateMaxLightsComboBox(mMaxLights); const Settings::WindowMode windowMode = Settings::video().mWindowMode; @@ -393,7 +402,8 @@ namespace MWGui std::vector availableLanguages; const VFS::Manager* vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - for (const auto& path : vfs->getRecursiveDirectoryIterator("l10n/")) + constexpr VFS::Path::NormalizedView l10n("l10n/"); + for (const auto& path : vfs->getRecursiveDirectoryIterator(l10n)) { if (Misc::getFileExtension(path) == "yaml") { @@ -504,6 +514,14 @@ namespace MWGui } } + void SettingsWindow::onRefractionButtonClicked(MyGUI::Widget* _sender) + { + const bool refractionEnabled = Settings::water().mRefraction; + + mSunlightScatteringButton->setEnabled(refractionEnabled); + mWobblyShoresButton->setEnabled(refractionEnabled); + } + void SettingsWindow::onWaterTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos) { int size = 0; @@ -1042,8 +1060,6 @@ namespace MWGui void SettingsWindow::onOpen() { - WindowModal::onOpen(); - highlightCurrentResolution(); updateControlsBox(); updateLightSettings(); diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 47951ef121..dc4e09f8ac 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -7,7 +7,7 @@ namespace MWGui { - class SettingsWindow : public WindowModal + class SettingsWindow : public WindowBase { public: SettingsWindow(); @@ -37,6 +37,9 @@ namespace MWGui MyGUI::Button* mWindowBorderButton; MyGUI::ComboBox* mTextureFilteringButton; + MyGUI::Button* mWaterRefractionButton; + MyGUI::Button* mSunlightScatteringButton; + MyGUI::Button* mWobblyShoresButton; MyGUI::ComboBox* mWaterTextureSize; MyGUI::ComboBox* mWaterReflectionDetail; MyGUI::ComboBox* mWaterRainRippleDetail; @@ -76,6 +79,7 @@ namespace MWGui void onResolutionCancel(); void highlightCurrentResolution(); + void onRefractionButtonClicked(MyGUI::Widget* _sender); void onWaterTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos); void onWaterReflectionDetailChanged(MyGUI::ComboBox* _sender, size_t pos); void onWaterRainRippleDetailChanged(MyGUI::ComboBox* _sender, size_t pos); diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index d668db1dec..d8302df87c 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -470,9 +470,7 @@ namespace MWGui y *= 1.5; } - ESM::EffectList effectList; - effectList.mList = mEffects; - mSpell.mEffects = std::move(effectList); + mSpell.mEffects.populate(mEffects); mSpell.mData.mCost = int(y); mSpell.mData.mType = ESM::Spell::ST_Spell; mSpell.mData.mFlags = 0; @@ -528,10 +526,11 @@ namespace MWGui if (spell->mData.mType != ESM::Spell::ST_Spell) continue; - for (const ESM::ENAMstruct& effectInfo : spell->mEffects.mList) + for (const ESM::IndexedENAMstruct& effectInfo : spell->mEffects.mList) { + int16_t effectId = effectInfo.mData.mEffectID; const ESM::MagicEffect* effect - = MWBase::Environment::get().getESMStore()->get().find(effectInfo.mEffectID); + = MWBase::Environment::get().getESMStore()->get().find(effectId); // skip effects that do not allow spellmaking/enchanting int requiredFlags @@ -539,8 +538,8 @@ namespace MWGui if (!(effect->mData.mFlags & requiredFlags)) continue; - if (std::find(knownEffects.begin(), knownEffects.end(), effectInfo.mEffectID) == knownEffects.end()) - knownEffects.push_back(effectInfo.mEffectID); + if (std::find(knownEffects.begin(), knownEffects.end(), effectId) == knownEffects.end()) + knownEffects.push_back(effectId); } } diff --git a/apps/openmw/mwgui/spellmodel.cpp b/apps/openmw/mwgui/spellmodel.cpp index 385464da24..3d70c391c9 100644 --- a/apps/openmw/mwgui/spellmodel.cpp +++ b/apps/openmw/mwgui/spellmodel.cpp @@ -48,14 +48,14 @@ namespace MWGui for (const auto& effect : effects.mList) { - short effectId = effect.mEffectID; + short effectId = effect.mData.mEffectID; if (effectId != -1) { const ESM::MagicEffect* magicEffect = store.get().find(effectId); const ESM::Attribute* attribute - = store.get().search(ESM::Attribute::indexToRefId(effect.mAttribute)); - const ESM::Skill* skill = store.get().search(ESM::Skill::indexToRefId(effect.mSkill)); + = store.get().search(ESM::Attribute::indexToRefId(effect.mData.mAttribute)); + const ESM::Skill* skill = store.get().search(ESM::Skill::indexToRefId(effect.mData.mSkill)); std::string fullEffectName = MWMechanics::getMagicEffectString(*magicEffect, attribute, skill); std::string convert = Utf8Stream::lowerCaseUtf8(fullEffectName); diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index 6e7d2c2ba2..69f0b4b449 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -582,9 +582,8 @@ namespace MWGui const std::set& expelled = PCstats.getExpelled(); bool firstFaction = true; - for (auto& factionPair : mFactions) + for (const auto& [factionId, factionRank] : mFactions) { - const ESM::RefId& factionId = factionPair.first; const ESM::Faction* faction = store.get().find(factionId); if (faction->mData.mIsHidden == 1) continue; @@ -611,10 +610,10 @@ namespace MWGui text += "\n#{fontcolourhtml=normal}#{sExpelled}"; else { - const int rank = std::clamp(factionPair.second, 0, 9); - text += std::string("\n#{fontcolourhtml=normal}") + faction->mRanks[rank]; - - if (rank < 9) + const auto rank = static_cast(std::max(0, factionRank)); + if (rank < faction->mRanks.size()) + text += std::string("\n#{fontcolourhtml=normal}") + faction->mRanks[rank]; + if (rank + 1 < faction->mRanks.size() && !faction->mRanks[rank + 1].empty()) { // player doesn't have max rank yet text += std::string("\n\n#{fontcolourhtml=header}#{sNextRank} ") + faction->mRanks[rank + 1]; diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 0a0343831d..960a4a5a21 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -222,17 +222,17 @@ namespace MWGui = store->get().find(ESM::RefId::deserialize(focus->getUserString("Spell"))); info.caption = spell->mName; Widgets::SpellEffectList effects; - for (const ESM::ENAMstruct& spellEffect : spell->mEffects.mList) + for (const ESM::IndexedENAMstruct& spellEffect : spell->mEffects.mList) { Widgets::SpellEffectParams params; - params.mEffectID = spellEffect.mEffectID; - params.mSkill = ESM::Skill::indexToRefId(spellEffect.mSkill); - params.mAttribute = ESM::Attribute::indexToRefId(spellEffect.mAttribute); - params.mDuration = spellEffect.mDuration; - params.mMagnMin = spellEffect.mMagnMin; - params.mMagnMax = spellEffect.mMagnMax; - params.mRange = spellEffect.mRange; - params.mArea = spellEffect.mArea; + params.mEffectID = spellEffect.mData.mEffectID; + params.mSkill = ESM::Skill::indexToRefId(spellEffect.mData.mSkill); + params.mAttribute = ESM::Attribute::indexToRefId(spellEffect.mData.mAttribute); + params.mDuration = spellEffect.mData.mDuration; + params.mMagnMin = spellEffect.mData.mMagnMin; + params.mMagnMax = spellEffect.mData.mMagnMax; + params.mRange = spellEffect.mData.mRange; + params.mArea = spellEffect.mData.mArea; params.mIsConstant = (spell->mData.mType == ESM::Spell::ST_Ability); params.mNoTarget = false; effects.push_back(params); @@ -410,10 +410,13 @@ namespace MWGui const std::string& image = info.icon; int imageSize = (!image.empty()) ? info.imageSize : 0; std::string text = info.text; + std::string_view extra = info.extra; // remove the first newline (easier this way) - if (text.size() > 0 && text[0] == '\n') + if (!text.empty() && text[0] == '\n') text.erase(0, 1); + if (!extra.empty() && extra[0] == '\n') + extra = extra.substr(1); const ESM::Enchantment* enchant = nullptr; const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); @@ -572,6 +575,24 @@ namespace MWGui } } + if (!extra.empty()) + { + Gui::EditBox* extraWidget = mDynamicToolTipBox->createWidget("SandText", + MyGUI::IntCoord(padding.left, totalSize.height + 12, 300 - padding.left, 300 - totalSize.height), + MyGUI::Align::Stretch, "ToolTipExtraText"); + + extraWidget->setEditStatic(true); + extraWidget->setEditMultiLine(true); + extraWidget->setEditWordWrap(info.wordWrap); + extraWidget->setCaptionWithReplacing(extra); + extraWidget->setTextAlign(MyGUI::Align::HCenter | MyGUI::Align::Top); + extraWidget->setNeedKeyFocus(false); + + MyGUI::IntSize extraTextSize = extraWidget->getTextSize(); + totalSize.height += extraTextSize.height + 4; + totalSize.width = std::max(totalSize.width, extraTextSize.width); + } + captionWidget->setCoord((totalSize.width - captionSize.width) / 2 + imageSize, (captionHeight - captionSize.height) / 2, captionSize.width - imageSize, captionSize.height); diff --git a/apps/openmw/mwgui/tooltips.hpp b/apps/openmw/mwgui/tooltips.hpp index 69f6856840..132698475f 100644 --- a/apps/openmw/mwgui/tooltips.hpp +++ b/apps/openmw/mwgui/tooltips.hpp @@ -29,6 +29,7 @@ namespace MWGui std::string caption; std::string text; + std::string extra; std::string icon; int imageSize; diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index c62d360412..ba752303d2 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -42,6 +43,75 @@ namespace return static_cast(price * count); } + bool haggle(const MWWorld::Ptr& player, const MWWorld::Ptr& merchant, int playerOffer, int merchantOffer) + { + // accept if merchant offer is better than player offer + if (playerOffer <= merchantOffer) + { + return true; + } + + // reject if npc is a creature + if (merchant.getType() != ESM::NPC::sRecordId) + { + return false; + } + + const MWWorld::Store& gmst + = MWBase::Environment::get().getESMStore()->get(); + + // Is the player buying? + bool buying = (merchantOffer < 0); + int a = std::abs(merchantOffer); + int b = std::abs(playerOffer); + int d = (buying) ? int(100 * (a - b) / a) : int(100 * (b - a) / b); + + int clampedDisposition = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(merchant); + + const MWMechanics::CreatureStats& merchantStats = merchant.getClass().getCreatureStats(merchant); + const MWMechanics::CreatureStats& playerStats = player.getClass().getCreatureStats(player); + + float a1 = static_cast(player.getClass().getSkill(player, ESM::Skill::Mercantile)); + float b1 = 0.1f * playerStats.getAttribute(ESM::Attribute::Luck).getModified(); + float c1 = 0.2f * playerStats.getAttribute(ESM::Attribute::Personality).getModified(); + float d1 = static_cast(merchant.getClass().getSkill(merchant, ESM::Skill::Mercantile)); + float e1 = 0.1f * merchantStats.getAttribute(ESM::Attribute::Luck).getModified(); + float f1 = 0.2f * merchantStats.getAttribute(ESM::Attribute::Personality).getModified(); + + float dispositionTerm = gmst.find("fDispositionMod")->mValue.getFloat() * (clampedDisposition - 50); + float pcTerm = (dispositionTerm + a1 + b1 + c1) * playerStats.getFatigueTerm(); + float npcTerm = (d1 + e1 + f1) * merchantStats.getFatigueTerm(); + float x = gmst.find("fBargainOfferMulti")->mValue.getFloat() * d + + gmst.find("fBargainOfferBase")->mValue.getFloat() + int(pcTerm - npcTerm); + + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + int roll = Misc::Rng::rollDice(100, prng) + 1; + + // reject if roll fails + // (or if player tries to buy things and get money) + if (roll > x || (merchantOffer < 0 && 0 < playerOffer)) + { + return false; + } + + // apply skill gain on successful barter + float skillGain = 0.f; + int finalPrice = std::abs(playerOffer); + int initialMerchantOffer = std::abs(merchantOffer); + + if (!buying && (finalPrice > initialMerchantOffer)) + { + skillGain = std::floor(100.f * (finalPrice - initialMerchantOffer) / finalPrice); + } + else if (buying && (finalPrice < initialMerchantOffer)) + { + skillGain = std::floor(100.f * (initialMerchantOffer - finalPrice) / initialMerchantOffer); + } + player.getClass().skillUsageSucceeded( + player, ESM::Skill::Mercantile, ESM::Skill::Mercantile_Success, skillGain); + + return true; + } } namespace MWGui @@ -328,7 +398,7 @@ namespace MWGui } } - bool offerAccepted = mTrading.haggle(player, mPtr, mCurrentBalance, mCurrentMerchantOffer); + bool offerAccepted = haggle(player, mPtr, mCurrentBalance, mCurrentMerchantOffer); // apply disposition change if merchant is NPC if (mPtr.getClass().isNpc()) diff --git a/apps/openmw/mwgui/tradewindow.hpp b/apps/openmw/mwgui/tradewindow.hpp index 7d5fd399df..33c39cb269 100644 --- a/apps/openmw/mwgui/tradewindow.hpp +++ b/apps/openmw/mwgui/tradewindow.hpp @@ -1,8 +1,6 @@ #ifndef MWGUI_TRADEWINDOW_H #define MWGUI_TRADEWINDOW_H -#include "../mwmechanics/trading.hpp" - #include "referenceinterface.hpp" #include "windowbase.hpp" @@ -53,7 +51,6 @@ namespace MWGui ItemView* mItemView; SortFilterItemModel* mSortModel; TradeItemModel* mTradeModel; - MWMechanics::Trading mTrading; static const float sBalanceChangeInitialPause; // in seconds static const float sBalanceChangeInterval; // in seconds diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index 5395f6db1c..890aa0ba68 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -5,6 +5,7 @@ #include #include "../mwbase/environment.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" @@ -174,10 +175,7 @@ namespace MWGui } // increase skill - MWWorld::LiveCellRef* playerRef = player.get(); - - const ESM::Class* class_ = store.get().find(playerRef->mBase->mClass); - pcStats.increaseSkill(skill->mId, *class_, true); + MWBase::Environment::get().getLuaManager()->skillLevelUp(player, skill->mId, "trainer"); // remove gold player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price); @@ -191,8 +189,8 @@ namespace MWGui mProgressBar.setProgress(0, 2); mTimeAdvancer.run(2); - MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.2); - MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.2, false, 0.2); + MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.2f); + MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.2f, false, 0.2f); } void TrainingWindow::onTrainingProgressChanged(int cur, int total) diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index d824682308..6cc5bdfdf5 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -195,18 +195,18 @@ namespace MWGui::Widgets const ESM::Spell* spell = store.get().search(mId); MYGUI_ASSERT(spell, "spell with id '" << mId << "' not found"); - for (const ESM::ENAMstruct& effectInfo : spell->mEffects.mList) + for (const ESM::IndexedENAMstruct& effectInfo : spell->mEffects.mList) { MWSpellEffectPtr effect = creator->createWidget("MW_EffectImage", coord, MyGUI::Align::Default); SpellEffectParams params; - params.mEffectID = effectInfo.mEffectID; - params.mSkill = ESM::Skill::indexToRefId(effectInfo.mSkill); - params.mAttribute = ESM::Attribute::indexToRefId(effectInfo.mAttribute); - params.mDuration = effectInfo.mDuration; - params.mMagnMin = effectInfo.mMagnMin; - params.mMagnMax = effectInfo.mMagnMax; - params.mRange = effectInfo.mRange; + params.mEffectID = effectInfo.mData.mEffectID; + params.mSkill = ESM::Skill::indexToRefId(effectInfo.mData.mSkill); + params.mAttribute = ESM::Attribute::indexToRefId(effectInfo.mData.mAttribute); + params.mDuration = effectInfo.mData.mDuration; + params.mMagnMin = effectInfo.mData.mMagnMin; + params.mMagnMax = effectInfo.mData.mMagnMax; + params.mRange = effectInfo.mData.mRange; params.mIsConstant = (flags & MWEffectList::EF_Constant) != 0; params.mNoTarget = (flags & MWEffectList::EF_NoTarget); params.mNoMagnitude = (flags & MWEffectList::EF_NoMagnitude); @@ -308,17 +308,17 @@ namespace MWGui::Widgets SpellEffectList MWEffectList::effectListFromESM(const ESM::EffectList* effects) { SpellEffectList result; - for (const ESM::ENAMstruct& effectInfo : effects->mList) + for (const ESM::IndexedENAMstruct& effectInfo : effects->mList) { SpellEffectParams params; - params.mEffectID = effectInfo.mEffectID; - params.mSkill = ESM::Skill::indexToRefId(effectInfo.mSkill); - params.mAttribute = ESM::Attribute::indexToRefId(effectInfo.mAttribute); - params.mDuration = effectInfo.mDuration; - params.mMagnMin = effectInfo.mMagnMin; - params.mMagnMax = effectInfo.mMagnMax; - params.mRange = effectInfo.mRange; - params.mArea = effectInfo.mArea; + params.mEffectID = effectInfo.mData.mEffectID; + params.mSkill = ESM::Skill::indexToRefId(effectInfo.mData.mSkill); + params.mAttribute = ESM::Attribute::indexToRefId(effectInfo.mData.mAttribute); + params.mDuration = effectInfo.mData.mDuration; + params.mMagnMin = effectInfo.mData.mMagnMin; + params.mMagnMax = effectInfo.mData.mMagnMax; + params.mRange = effectInfo.mData.mRange; + params.mArea = effectInfo.mData.mArea; result.push_back(params); } return result; @@ -371,7 +371,7 @@ namespace MWGui::Widgets std::string spellLine = MWMechanics::getMagicEffectString(*magicEffect, attribute, skill); - if (mEffectParams.mMagnMin || mEffectParams.mMagnMax) + if ((mEffectParams.mMagnMin || mEffectParams.mMagnMax) && !mEffectParams.mNoMagnitude) { ESM::MagicEffect::MagnitudeDisplayType displayType = magicEffect->getMagnitudeDisplayType(); if (displayType == ESM::MagicEffect::MDT_TimesInt) @@ -386,7 +386,7 @@ namespace MWGui::Widgets spellLine += formatter.str(); } - else if (displayType != ESM::MagicEffect::MDT_None && !mEffectParams.mNoMagnitude) + else if (displayType != ESM::MagicEffect::MDT_None) { spellLine += " " + MyGUI::utility::toString(mEffectParams.mMagnMin); if (mEffectParams.mMagnMin != mEffectParams.mMagnMax) diff --git a/apps/openmw/mwgui/windowbase.cpp b/apps/openmw/mwgui/windowbase.cpp index a680e38cf8..f5d90590f8 100644 --- a/apps/openmw/mwgui/windowbase.cpp +++ b/apps/openmw/mwgui/windowbase.cpp @@ -77,6 +77,34 @@ void WindowBase::center() mMainWidget->setCoord(coord); } +void WindowBase::clampWindowCoordinates(MyGUI::Window* window) +{ + MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + if (window->getLayer()) + viewSize = window->getLayer()->getSize(); + + // Window's minimum size is larger than the screen size, can not clamp coordinates + auto minSize = window->getMinSize(); + if (minSize.width > viewSize.width || minSize.height > viewSize.height) + return; + + int left = std::max(0, window->getPosition().left); + int top = std::max(0, window->getPosition().top); + int width = std::clamp(window->getSize().width, 0, viewSize.width); + int height = std::clamp(window->getSize().height, 0, viewSize.height); + if (left + width > viewSize.width) + left = viewSize.width - width; + + if (top + height > viewSize.height) + top = viewSize.height - height; + + if (window->getSize().width != width || window->getSize().height != height) + window->setSize(width, height); + + if (window->getPosition().left != left || window->getPosition().top != top) + window->setPosition(left, top); +} + WindowModal::WindowModal(const std::string& parLayout) : WindowBase(parLayout) { diff --git a/apps/openmw/mwgui/windowbase.hpp b/apps/openmw/mwgui/windowbase.hpp index 54fb269305..466060c6ad 100644 --- a/apps/openmw/mwgui/windowbase.hpp +++ b/apps/openmw/mwgui/windowbase.hpp @@ -52,6 +52,8 @@ namespace MWGui virtual std::string_view getWindowIdForLua() const { return ""; } void setDisabledByLua(bool disabled) { mDisabledByLua = disabled; } + static void clampWindowCoordinates(MyGUI::Window* window); + protected: virtual void onTitleDoubleClicked(); diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index e463443b0c..3212e8f02b 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -356,7 +356,8 @@ namespace MWGui mWindows.push_back(std::move(console)); trackWindow(mConsole, makeConsoleWindowSettingValues()); - bool questList = mResourceSystem->getVFS()->exists("textures/tx_menubook_options_over.dds"); + constexpr VFS::Path::NormalizedView menubookOptionsOverTexture("textures/tx_menubook_options_over.dds"); + const bool questList = mResourceSystem->getVFS()->exists(menubookOptionsOverTexture); auto journal = JournalWindow::create(JournalViewModel::create(), questList, mEncoding); mGuiModeStates[GM_Journal] = GuiModeState(journal.get()); mWindows.push_back(std::move(journal)); @@ -844,7 +845,7 @@ namespace MWGui if (!player.getCell()->isExterior()) { - setActiveMap(x, y, true); + setActiveMap(*player.getCell()->getCell()); } // else: need to know the current grid center, call setActiveMap from changeCell @@ -914,6 +915,9 @@ namespace MWGui if (isConsoleMode()) mConsole->onFrame(frameDuration); + if (isSettingsWindowVisible()) + mSettingsWindow->onFrame(frameDuration); + if (!gameRunning) return; @@ -982,29 +986,23 @@ namespace MWGui mMap->addVisitedLocation(name, cellCommon->getGridX(), cellCommon->getGridY()); mMap->cellExplored(cellCommon->getGridX(), cellCommon->getGridY()); - - setActiveMap(cellCommon->getGridX(), cellCommon->getGridY(), false); } else { - mMap->setCellPrefix(std::string(cellCommon->getNameId())); - mHud->setCellPrefix(std::string(cellCommon->getNameId())); - osg::Vec3f worldPos; if (!MWBase::Environment::get().getWorld()->findInteriorPositionInWorldSpace(cell, worldPos)) worldPos = MWBase::Environment::get().getWorld()->getPlayer().getLastKnownExteriorPosition(); else MWBase::Environment::get().getWorld()->getPlayer().setLastKnownExteriorPosition(worldPos); mMap->setGlobalMapPlayerPosition(worldPos.x(), worldPos.y()); - - setActiveMap(0, 0, true); } + setActiveMap(*cellCommon); } - void WindowManager::setActiveMap(int x, int y, bool interior) + void WindowManager::setActiveMap(const MWWorld::Cell& cell) { - mMap->setActiveCell(x, y, interior); - mHud->setActiveCell(x, y, interior); + mMap->setActiveCell(cell); + mHud->setActiveCell(cell); } void WindowManager::setDrowningBarVisibility(bool visible) @@ -1203,6 +1201,8 @@ namespace MWGui const WindowRectSettingValues& rect = settings.mIsMaximized ? settings.mMaximized : settings.mRegular; window->setPosition(MyGUI::IntPoint(static_cast(rect.mX * x), static_cast(rect.mY * y))); window->setSize(MyGUI::IntSize(static_cast(rect.mW * x), static_cast(rect.mH * y))); + + WindowBase::clampWindowCoordinates(window); } for (const auto& window : mWindows) @@ -1476,10 +1476,6 @@ namespace MWGui { return mPostProcessorHud; } - MWGui::SettingsWindow* WindowManager::getSettingsWindow() - { - return mSettingsWindow; - } void WindowManager::useItem(const MWWorld::Ptr& item, bool bypassBeastRestrictions) { @@ -1555,6 +1551,11 @@ namespace MWGui return mPostProcessorHud && mPostProcessorHud->isVisible(); } + bool WindowManager::isSettingsWindowVisible() const + { + return mSettingsWindow && mSettingsWindow->isVisible(); + } + bool WindowManager::isInteractiveMessageBoxActive() const { return mMessageBoxManager && mMessageBoxManager->isInteractiveMessageBox(); @@ -1691,9 +1692,9 @@ namespace MWGui mHud->setEnemy(enemy); } - int WindowManager::getMessagesCount() const + std::size_t WindowManager::getMessagesCount() const { - int count = 0; + std::size_t count = 0; if (mMessageBoxManager) count = mMessageBoxManager->getMessagesCount(); @@ -1716,13 +1717,15 @@ namespace MWGui const WindowRectSettingValues& rect = settings.mIsMaximized ? settings.mMaximized : settings.mRegular; - layout->mMainWidget->setPosition( + MyGUI::Window* window = layout->mMainWidget->castType(); + window->setPosition( MyGUI::IntPoint(static_cast(rect.mX * viewSize.width), static_cast(rect.mY * viewSize.height))); - layout->mMainWidget->setSize( + window->setSize( MyGUI::IntSize(static_cast(rect.mW * viewSize.width), static_cast(rect.mH * viewSize.height))); - MyGUI::Window* window = layout->mMainWidget->castType(); window->eventWindowChangeCoord += MyGUI::newDelegate(this, &WindowManager::onWindowChangeCoord); + WindowBase::clampWindowCoordinates(window); + mTrackedWindows.emplace(window, settings); } @@ -1752,6 +1755,8 @@ namespace MWGui if (it == mTrackedWindows.end()) return; + WindowBase::clampWindowCoordinates(window); + const WindowSettingValues& settings = it->second; MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); @@ -2136,6 +2141,21 @@ namespace MWGui updateVisible(); } + void WindowManager::toggleSettingsWindow() + { + bool visible = mSettingsWindow->isVisible(); + + if (!visible && !mGuiModes.empty()) + mKeyboardNavigation->saveFocus(mGuiModes.back()); + + mSettingsWindow->setVisible(!visible); + + if (visible && !mGuiModes.empty()) + mKeyboardNavigation->restoreFocus(mGuiModes.back()); + + updateVisible(); + } + void WindowManager::cycleSpell(bool next) { if (!isGuiMode()) diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index d6a286632c..6f880efd7e 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -50,6 +50,7 @@ namespace MyGUI namespace MWWorld { + class Cell; class ESMStore; } @@ -161,6 +162,7 @@ namespace MWGui bool isConsoleMode() const override; bool isPostProcessorHudVisible() const override; + bool isSettingsWindowVisible() const override; bool isInteractiveMessageBoxActive() const override; void toggleVisible(GuiWindow wnd) override; @@ -182,7 +184,6 @@ namespace MWGui MWGui::ConfirmationDialog* getConfirmationDialog() override; MWGui::TradeWindow* getTradeWindow() override; MWGui::PostProcessorHud* getPostProcessorHud() override; - MWGui::SettingsWindow* getSettingsWindow() override; /// Make the player use an item, while updating GUI state accordingly void useItem(const MWWorld::Ptr& item, bool bypassBeastRestrictions = false) override; @@ -216,9 +217,6 @@ namespace MWGui bool toggleFullHelp() override; ///< show extra info in item tooltips (owner, script) bool getFullHelp() const override; - void setActiveMap(int x, int y, bool interior) override; - ///< set the indices of the map texture that should be used - /// sets the visibility of the drowning bar void setDrowningBarVisibility(bool visible) override; @@ -315,7 +313,7 @@ namespace MWGui void setEnemy(const MWWorld::Ptr& enemy) override; - int getMessagesCount() const override; + std::size_t getMessagesCount() const override; const Translation::Storage& getTranslationDataStorage() const override; @@ -366,6 +364,7 @@ namespace MWGui void toggleConsole() override; void toggleDebugWindow() override; void togglePostProcessorHud() override; + void toggleSettingsWindow() override; /// Cycle to next or previous spell void cycleSpell(bool next) override; @@ -589,6 +588,9 @@ namespace MWGui void setCullMask(uint32_t mask) override; uint32_t getCullMask() override; + void setActiveMap(const MWWorld::Cell& cell); + ///< set the indices of the map texture that should be used + Files::ConfigurationManager& mCfgMgr; }; } diff --git a/apps/openmw/mwinput/bindingsmanager.cpp b/apps/openmw/mwinput/bindingsmanager.cpp index 3f505896f4..a6bab19673 100644 --- a/apps/openmw/mwinput/bindingsmanager.cpp +++ b/apps/openmw/mwinput/bindingsmanager.cpp @@ -627,12 +627,12 @@ namespace MWInput return mInputBinder->detectingBindingState(); } - void BindingsManager::mousePressed(const SDL_MouseButtonEvent& arg, int deviceID) + void BindingsManager::mousePressed(const SDL_MouseButtonEvent& arg, Uint8 deviceID) { mInputBinder->mousePressed(arg, deviceID); } - void BindingsManager::mouseReleased(const SDL_MouseButtonEvent& arg, int deviceID) + void BindingsManager::mouseReleased(const SDL_MouseButtonEvent& arg, Uint8 deviceID) { mInputBinder->mouseReleased(arg, deviceID); } diff --git a/apps/openmw/mwinput/bindingsmanager.hpp b/apps/openmw/mwinput/bindingsmanager.hpp index a11baf74de..bee9e07cf7 100644 --- a/apps/openmw/mwinput/bindingsmanager.hpp +++ b/apps/openmw/mwinput/bindingsmanager.hpp @@ -47,8 +47,8 @@ namespace MWInput SDL_GameController* getControllerOrNull() const; - void mousePressed(const SDL_MouseButtonEvent& evt, int deviceID); - void mouseReleased(const SDL_MouseButtonEvent& arg, int deviceID); + void mousePressed(const SDL_MouseButtonEvent& evt, Uint8 deviceID); + void mouseReleased(const SDL_MouseButtonEvent& arg, Uint8 deviceID); void mouseMoved(const SDLUtil::MouseMotionEvent& arg); void mouseWheelMoved(const SDL_MouseWheelEvent& arg); diff --git a/apps/openmw/mwinput/mousemanager.cpp b/apps/openmw/mwinput/mousemanager.cpp index 55b50b91ae..9a8cada25b 100644 --- a/apps/openmw/mwinput/mousemanager.cpp +++ b/apps/openmw/mwinput/mousemanager.cpp @@ -10,6 +10,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" @@ -119,15 +120,22 @@ namespace MWInput mBindingsManager->setPlayerControlsEnabled(!guiMode); mBindingsManager->mouseReleased(arg, id); } + + MWBase::Environment::get().getLuaManager()->inputEvent( + { MWBase::LuaManager::InputEvent::MouseButtonReleased, arg.button }); } void MouseManager::mouseWheelMoved(const SDL_MouseWheelEvent& arg) { MWBase::InputManager* input = MWBase::Environment::get().getInputManager(); if (mBindingsManager->isDetectingBindingState() || !input->controlsDisabled()) + { mBindingsManager->mouseWheelMoved(arg); + } input->setJoystickLastUsed(false); + MWBase::Environment::get().getLuaManager()->inputEvent({ MWBase::LuaManager::InputEvent::MouseWheel, + MWBase::LuaManager::InputEvent::WheelChange{ arg.x, arg.y } }); } void MouseManager::mousePressed(const SDL_MouseButtonEvent& arg, Uint8 id) @@ -158,10 +166,12 @@ namespace MWInput // Don't trigger any mouse bindings while in settings menu, otherwise rebinding controls becomes impossible // Also do not trigger bindings when input controls are disabled, e.g. during save loading - const MWGui::SettingsWindow* settingsWindow - = MWBase::Environment::get().getWindowManager()->getSettingsWindow(); - if ((!settingsWindow || !settingsWindow->isVisible()) && !input->controlsDisabled()) + if (!MWBase::Environment::get().getWindowManager()->isSettingsWindowVisible() && !input->controlsDisabled()) + { mBindingsManager->mousePressed(arg, id); + } + MWBase::Environment::get().getLuaManager()->inputEvent( + { MWBase::LuaManager::InputEvent::MouseButtonPressed, arg.button }); } void MouseManager::updateCursorMode() @@ -208,14 +218,14 @@ namespace MWInput }; // Only actually turn player when we're not in vanity mode - bool controls = MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols"); - if (!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot) && controls) + bool playerLooking = MWBase::Environment::get().getInputManager()->getControlSwitch("playerlooking"); + if (!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot) && playerLooking) { MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); player.yaw(-rot[2]); player.pitch(-rot[0]); } - else if (!controls) + else if (!playerLooking) MWBase::Environment::get().getWorld()->disableDeferredPreviewRotation(); MWBase::Environment::get().getInputManager()->resetIdleTime(); diff --git a/apps/openmw/mwlua/animationbindings.cpp b/apps/openmw/mwlua/animationbindings.cpp index d024c41307..fb3e64ba73 100644 --- a/apps/openmw/mwlua/animationbindings.cpp +++ b/apps/openmw/mwlua/animationbindings.cpp @@ -1,3 +1,5 @@ +#include "animationbindings.hpp" + #include #include #include @@ -18,27 +20,6 @@ #include "luamanagerimp.hpp" #include "objectvariant.hpp" -#include "animationbindings.hpp" -#include - -namespace MWLua -{ - struct AnimationGroup; - struct TextKeyCallback; -} - -namespace sol -{ - template <> - struct is_automagical : std::false_type - { - }; - template <> - struct is_automagical> : std::false_type - { - }; -} - namespace MWLua { using BlendMask = MWRender::Animation::BlendMask; diff --git a/apps/openmw/mwlua/animationbindings.hpp b/apps/openmw/mwlua/animationbindings.hpp index d28dda9208..251de42ee8 100644 --- a/apps/openmw/mwlua/animationbindings.hpp +++ b/apps/openmw/mwlua/animationbindings.hpp @@ -5,6 +5,8 @@ namespace MWLua { + struct Context; + sol::table initAnimationPackage(const Context& context); sol::table initCoreVfxBindings(const Context& context); } diff --git a/apps/openmw/mwlua/birthsignbindings.cpp b/apps/openmw/mwlua/birthsignbindings.cpp new file mode 100644 index 0000000000..f65d50bc5a --- /dev/null +++ b/apps/openmw/mwlua/birthsignbindings.cpp @@ -0,0 +1,47 @@ +#include "birthsignbindings.hpp" + +#include +#include +#include +#include + +#include "../mwbase/environment.hpp" + +#include "idcollectionbindings.hpp" +#include "types/types.hpp" + +namespace sol +{ + template <> + struct is_automagical : 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(birthSigns, context); + + auto signT = lua.new_usertype("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 { + return createReadOnlyRefIdTable(lua, rec.mPowers.mList); + }); + + return LuaUtil::makeReadOnly(birthSigns); + } +} diff --git a/apps/openmw/mwlua/birthsignbindings.hpp b/apps/openmw/mwlua/birthsignbindings.hpp new file mode 100644 index 0000000000..bf41707d47 --- /dev/null +++ b/apps/openmw/mwlua/birthsignbindings.hpp @@ -0,0 +1,13 @@ +#ifndef MWLUA_BIRTHSIGNBINDINGS_H +#define MWLUA_BIRTHSIGNBINDINGS_H + +#include + +#include "context.hpp" + +namespace MWLua +{ + sol::table initBirthSignRecordBindings(const Context& context); +} + +#endif // MWLUA_BIRTHSIGNBINDINGS_H diff --git a/apps/openmw/mwlua/camerabindings.cpp b/apps/openmw/mwlua/camerabindings.cpp index e3470eb853..ed75b4b198 100644 --- a/apps/openmw/mwlua/camerabindings.cpp +++ b/apps/openmw/mwlua/camerabindings.cpp @@ -1,3 +1,4 @@ +#include "camerabindings.hpp" #include #include diff --git a/apps/openmw/mwlua/classbindings.cpp b/apps/openmw/mwlua/classbindings.cpp index 339b724f19..84864781d2 100644 --- a/apps/openmw/mwlua/classbindings.cpp +++ b/apps/openmw/mwlua/classbindings.cpp @@ -1,13 +1,9 @@ +#include "classbindings.hpp" + #include #include -#include "../mwbase/environment.hpp" -#include "../mwworld/class.hpp" -#include "../mwworld/esmstore.hpp" - -#include "classbindings.hpp" -#include "luamanagerimp.hpp" -#include "stats.hpp" +#include "idcollectionbindings.hpp" #include "types/types.hpp" namespace sol @@ -16,16 +12,12 @@ namespace sol struct is_automagical : std::false_type { }; - template <> - struct is_automagical> : std::false_type - { - }; } namespace MWLua { - sol::table initCoreClassBindings(const Context& context) + sol::table initClassRecordBindings(const Context& context) { sol::state_view& lua = context.mLua->sol(); sol::table classes(context.mLua->sol(), sol::create); @@ -40,34 +32,15 @@ namespace MWLua = sol::readonly_property([](const ESM::Class& rec) -> std::string_view { return rec.mDescription; }); classT["attributes"] = sol::readonly_property([lua](const ESM::Class& rec) -> sol::table { - sol::table res(lua, sol::create); - auto attribute = rec.mData.mAttribute; - for (size_t i = 0; i < attribute.size(); ++i) - { - ESM::RefId attributeId = ESM::Attribute::indexToRefId(attribute[i]); - res[i + 1] = attributeId.serializeText(); - } - return res; + return createReadOnlyRefIdTable(lua, rec.mData.mAttribute, ESM::Attribute::indexToRefId); }); classT["majorSkills"] = sol::readonly_property([lua](const ESM::Class& rec) -> sol::table { - sol::table res(lua, sol::create); - auto skills = rec.mData.mSkills; - for (size_t i = 0; i < skills.size(); ++i) - { - ESM::RefId skillId = ESM::Skill::indexToRefId(skills[i][1]); - res[i + 1] = skillId.serializeText(); - } - return res; + return createReadOnlyRefIdTable( + lua, rec.mData.mSkills, [](const auto& pair) { return ESM::Skill::indexToRefId(pair[1]); }); }); classT["minorSkills"] = sol::readonly_property([lua](const ESM::Class& rec) -> sol::table { - sol::table res(lua, sol::create); - auto skills = rec.mData.mSkills; - for (size_t i = 0; i < skills.size(); ++i) - { - ESM::RefId skillId = ESM::Skill::indexToRefId(skills[i][0]); - res[i + 1] = skillId.serializeText(); - } - return res; + return createReadOnlyRefIdTable( + lua, rec.mData.mSkills, [](const auto& pair) { return ESM::Skill::indexToRefId(pair[0]); }); }); classT["specialization"] = sol::readonly_property([](const ESM::Class& rec) -> std::string_view { diff --git a/apps/openmw/mwlua/classbindings.hpp b/apps/openmw/mwlua/classbindings.hpp index 9dd9befae4..1acb0a9ad3 100644 --- a/apps/openmw/mwlua/classbindings.hpp +++ b/apps/openmw/mwlua/classbindings.hpp @@ -7,7 +7,7 @@ namespace MWLua { - sol::table initCoreClassBindings(const Context& context); + sol::table initClassRecordBindings(const Context& context); } #endif // MWLUA_CLASSBINDINGS_H diff --git a/apps/openmw/mwlua/corebindings.cpp b/apps/openmw/mwlua/corebindings.cpp index 8d8e97ed07..b212d4d01c 100644 --- a/apps/openmw/mwlua/corebindings.cpp +++ b/apps/openmw/mwlua/corebindings.cpp @@ -97,8 +97,7 @@ namespace MWLua api["magic"] = initCoreMagicBindings(context); api["stats"] = initCoreStatsBindings(context); - initCoreFactionBindings(context); - api["factions"] = &MWBase::Environment::get().getESMStore()->get(); + api["factions"] = initCoreFactionBindings(context); api["l10n"] = LuaUtil::initL10nLoader(lua->sol(), MWBase::Environment::get().getL10nManager()); const MWWorld::Store* gmstStore diff --git a/apps/openmw/mwlua/debugbindings.cpp b/apps/openmw/mwlua/debugbindings.cpp index 0aa1f4ace5..97ca080e5c 100644 --- a/apps/openmw/mwlua/debugbindings.cpp +++ b/apps/openmw/mwlua/debugbindings.cpp @@ -1,4 +1,5 @@ #include "debugbindings.hpp" + #include "context.hpp" #include "luamanagerimp.hpp" diff --git a/apps/openmw/mwlua/engineevents.cpp b/apps/openmw/mwlua/engineevents.cpp index 43507ff1a5..6c652bccba 100644 --- a/apps/openmw/mwlua/engineevents.cpp +++ b/apps/openmw/mwlua/engineevents.cpp @@ -95,6 +95,24 @@ namespace MWLua scripts->onAnimationTextKey(event.mGroupname, event.mKey); } + void operator()(const OnSkillUse& event) const + { + MWWorld::Ptr actor = getPtr(event.mActor); + if (actor.isEmpty()) + return; + if (auto* scripts = getLocalScripts(actor)) + scripts->onSkillUse(event.mSkill, event.useType, event.scale); + } + + void operator()(const OnSkillLevelUp& event) const + { + MWWorld::Ptr actor = getPtr(event.mActor); + if (actor.isEmpty()) + return; + if (auto* scripts = getLocalScripts(actor)) + scripts->onSkillLevelUp(event.mSkill, event.mSource); + } + private: MWWorld::Ptr getPtr(ESM::RefNum id) const { diff --git a/apps/openmw/mwlua/engineevents.hpp b/apps/openmw/mwlua/engineevents.hpp index bf8d219fd5..fb9183eb7c 100644 --- a/apps/openmw/mwlua/engineevents.hpp +++ b/apps/openmw/mwlua/engineevents.hpp @@ -57,8 +57,21 @@ namespace MWLua std::string mGroupname; std::string mKey; }; + struct OnSkillUse + { + ESM::RefNum mActor; + std::string mSkill; + int useType; + float scale; + }; + struct OnSkillLevelUp + { + ESM::RefNum mActor; + std::string mSkill; + std::string mSource; + }; using Event = std::variant; + OnAnimationTextKey, OnSkillUse, OnSkillLevelUp>; void clear() { mQueue.clear(); } void addToQueue(Event e) { mQueue.push_back(std::move(e)); } diff --git a/apps/openmw/mwlua/factionbindings.cpp b/apps/openmw/mwlua/factionbindings.cpp index 87ce6ced39..83b9cfc5e8 100644 --- a/apps/openmw/mwlua/factionbindings.cpp +++ b/apps/openmw/mwlua/factionbindings.cpp @@ -1,15 +1,12 @@ #include "factionbindings.hpp" +#include "recordstore.hpp" #include #include -#include "../mwbase/environment.hpp" -#include "../mwworld/class.hpp" -#include "../mwworld/esmstore.hpp" +#include "../mwworld/store.hpp" -#include "../mwmechanics/npcstats.hpp" - -#include "luamanagerimp.hpp" +#include "idcollectionbindings.hpp" namespace { @@ -36,10 +33,6 @@ namespace sol { }; template <> - struct is_automagical> : std::false_type - { - }; - template <> struct is_automagical> : std::false_type { }; @@ -47,27 +40,11 @@ namespace sol namespace MWLua { - using FactionStore = MWWorld::Store; - - void initCoreFactionBindings(const Context& context) + sol::table initCoreFactionBindings(const Context& context) { sol::state_view& lua = context.mLua->sol(); - sol::usertype factionStoreT = lua.new_usertype("ESM3_FactionStore"); - factionStoreT[sol::meta_function::to_string] = [](const FactionStore& store) { - return "ESM3_FactionStore{" + std::to_string(store.getSize()) + " factions}"; - }; - factionStoreT[sol::meta_function::length] = [](const FactionStore& store) { return store.getSize(); }; - factionStoreT[sol::meta_function::index] = sol::overload( - [](const FactionStore& store, size_t index) -> const ESM::Faction* { - if (index == 0 || index > store.getSize()) - return nullptr; - return store.at(index - 1); - }, - [](const FactionStore& store, std::string_view factionId) -> const ESM::Faction* { - return store.search(ESM::RefId::deserializeText(factionId)); - }); - factionStoreT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); - factionStoreT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); + sol::table factions(lua, sol::create); + addRecordFunctionBinding(factions, context); // Faction record auto factionT = lua.new_usertype("ESM3_Faction"); factionT[sol::meta_function::to_string] @@ -96,26 +73,10 @@ namespace MWLua return res; }); factionT["attributes"] = sol::readonly_property([&lua](const ESM::Faction& rec) { - sol::table res(lua, sol::create); - for (auto attributeIndex : rec.mData.mAttribute) - { - ESM::RefId id = ESM::Attribute::indexToRefId(attributeIndex); - if (!id.empty()) - res.add(id.serializeText()); - } - - return res; + return createReadOnlyRefIdTable(lua, rec.mData.mAttribute, ESM::Attribute::indexToRefId); }); factionT["skills"] = sol::readonly_property([&lua](const ESM::Faction& rec) { - sol::table res(lua, sol::create); - for (auto skillIndex : rec.mData.mSkills) - { - ESM::RefId id = ESM::Skill::indexToRefId(skillIndex); - if (!id.empty()) - res.add(id.serializeText()); - } - - return res; + return createReadOnlyRefIdTable(lua, rec.mData.mSkills, ESM::Skill::indexToRefId); }); auto rankT = lua.new_usertype("ESM3_FactionRank"); rankT[sol::meta_function::to_string] = [](const FactionRank& rec) -> std::string { @@ -133,5 +94,6 @@ namespace MWLua res.add(rec.mAttribute2); return res; }); + return LuaUtil::makeReadOnly(factions); } } diff --git a/apps/openmw/mwlua/factionbindings.hpp b/apps/openmw/mwlua/factionbindings.hpp index fe37133dbe..0dc06ceaf2 100644 --- a/apps/openmw/mwlua/factionbindings.hpp +++ b/apps/openmw/mwlua/factionbindings.hpp @@ -7,7 +7,7 @@ namespace MWLua { - void initCoreFactionBindings(const Context& context); + sol::table initCoreFactionBindings(const Context& context); } #endif // MWLUA_FACTIONBINDINGS_H diff --git a/apps/openmw/mwlua/idcollectionbindings.hpp b/apps/openmw/mwlua/idcollectionbindings.hpp new file mode 100644 index 0000000000..15e2b14fb9 --- /dev/null +++ b/apps/openmw/mwlua/idcollectionbindings.hpp @@ -0,0 +1,25 @@ +#ifndef MWLUA_IDCOLLECTIONBINDINGS_H +#define MWLUA_IDCOLLECTIONBINDINGS_H + +#include + +#include +#include + +namespace MWLua +{ + template + sol::table createReadOnlyRefIdTable(const sol::state_view& lua, const C& container, P projection = {}) + { + sol::table res(lua, sol::create); + for (const auto& element : container) + { + ESM::RefId id = projection(element); + if (!id.empty()) + res.add(id.serializeText()); + } + return LuaUtil::makeReadOnly(res); + } +} + +#endif diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp index ae54061cb6..09a5a0babb 100644 --- a/apps/openmw/mwlua/inputbindings.cpp +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -11,6 +11,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" #include "../mwinput/actions.hpp" + #include "luamanagerimp.hpp" namespace sol @@ -220,7 +221,7 @@ namespace MWLua api["getControlSwitch"] = [input](std::string_view key) { return input->getControlSwitch(key); }; api["setControlSwitch"] = [input](std::string_view key, bool v) { input->toggleControlSwitch(key, v); }; - api["getKeyName"] = [](SDL_Scancode code) { return SDL_GetKeyName(SDL_GetKeyFromScancode(code)); }; + api["getKeyName"] = [](SDL_Scancode code) { return SDL_GetScancodeName(code); }; api["ACTION"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ { "GameMenu", MWInput::A_GameMenu }, diff --git a/apps/openmw/mwlua/inputprocessor.hpp b/apps/openmw/mwlua/inputprocessor.hpp index e005183098..dcd19ae8cd 100644 --- a/apps/openmw/mwlua/inputprocessor.hpp +++ b/apps/openmw/mwlua/inputprocessor.hpp @@ -18,7 +18,7 @@ namespace MWLua { mScriptsContainer->registerEngineHandlers({ &mKeyPressHandlers, &mKeyReleaseHandlers, &mControllerButtonPressHandlers, &mControllerButtonReleaseHandlers, &mActionHandlers, &mTouchpadPressed, - &mTouchpadReleased, &mTouchpadMoved }); + &mTouchpadReleased, &mTouchpadMoved, &mMouseButtonPress, &mMouseButtonRelease, &mMouseWheel }); } void processInputEvent(const MWBase::LuaManager::InputEvent& event) @@ -53,6 +53,16 @@ namespace MWLua case InputEvent::TouchMoved: mScriptsContainer->callEngineHandlers(mTouchpadMoved, std::get(event.mValue)); break; + case InputEvent::MouseButtonPressed: + mScriptsContainer->callEngineHandlers(mMouseButtonPress, std::get(event.mValue)); + break; + case InputEvent::MouseButtonReleased: + mScriptsContainer->callEngineHandlers(mMouseButtonRelease, std::get(event.mValue)); + break; + case InputEvent::MouseWheel: + auto wheelEvent = std::get(event.mValue); + mScriptsContainer->callEngineHandlers(mMouseWheel, wheelEvent.y, wheelEvent.x); + break; } } @@ -66,6 +76,9 @@ namespace MWLua typename Container::EngineHandlerList mTouchpadPressed{ "onTouchPress" }; typename Container::EngineHandlerList mTouchpadReleased{ "onTouchRelease" }; typename Container::EngineHandlerList mTouchpadMoved{ "onTouchMove" }; + typename Container::EngineHandlerList mMouseButtonPress{ "onMouseButtonPress" }; + typename Container::EngineHandlerList mMouseButtonRelease{ "onMouseButtonRelease" }; + typename Container::EngineHandlerList mMouseWheel{ "onMouseWheel" }; }; } diff --git a/apps/openmw/mwlua/itemdata.cpp b/apps/openmw/mwlua/itemdata.cpp index d7ced755ea..3e2b755af8 100644 --- a/apps/openmw/mwlua/itemdata.cpp +++ b/apps/openmw/mwlua/itemdata.cpp @@ -1,13 +1,11 @@ #include "itemdata.hpp" #include "context.hpp" - #include "luamanagerimp.hpp" +#include "objectvariant.hpp" #include "../mwworld/class.hpp" -#include "objectvariant.hpp" - namespace { using SelfObject = MWLua::SelfObject; diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index 1d5e710869..8fa0571afc 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -176,7 +176,8 @@ namespace MWLua { this->addPackage("openmw.self", sol::make_object(lua->sol(), &mData)); registerEngineHandlers({ &mOnActiveHandlers, &mOnInactiveHandlers, &mOnConsumeHandlers, &mOnActivatedHandlers, - &mOnTeleportedHandlers, &mOnAnimationTextKeyHandlers, &mOnPlayAnimationHandlers }); + &mOnTeleportedHandlers, &mOnAnimationTextKeyHandlers, &mOnPlayAnimationHandlers, &mOnSkillUse, + &mOnSkillLevelUp }); } void LocalScripts::setActive(bool active) diff --git a/apps/openmw/mwlua/localscripts.hpp b/apps/openmw/mwlua/localscripts.hpp index 230ec93d3c..2ec78860d1 100644 --- a/apps/openmw/mwlua/localscripts.hpp +++ b/apps/openmw/mwlua/localscripts.hpp @@ -79,6 +79,14 @@ namespace MWLua { callEngineHandlers(mOnPlayAnimationHandlers, groupname, options); } + void onSkillUse(std::string_view skillId, int useType, float scale) + { + callEngineHandlers(mOnSkillUse, skillId, useType, scale); + } + void onSkillLevelUp(std::string_view skillId, std::string_view source) + { + callEngineHandlers(mOnSkillLevelUp, skillId, source); + } void applyStatsCache(); @@ -93,6 +101,8 @@ namespace MWLua EngineHandlerList mOnTeleportedHandlers{ "onTeleported" }; EngineHandlerList mOnAnimationTextKeyHandlers{ "_onAnimationTextKey" }; EngineHandlerList mOnPlayAnimationHandlers{ "_onPlayAnimation" }; + EngineHandlerList mOnSkillUse{ "_onSkillUse" }; + EngineHandlerList mOnSkillLevelUp{ "_onSkillLevelUp" }; }; } diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 0de10827e0..553b8af8f6 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -14,6 +14,7 @@ #include "debugbindings.hpp" #include "inputbindings.hpp" #include "localscripts.hpp" +#include "markupbindings.hpp" #include "menuscripts.hpp" #include "nearbybindings.hpp" #include "objectbindings.hpp" @@ -35,6 +36,7 @@ namespace MWLua { "openmw.async", LuaUtil::getAsyncPackageInitializer( lua, [tm] { return tm->getSimulationTime(); }, [tm] { return tm->getGameTime(); }) }, + { "openmw.markup", initMarkupPackage(context) }, { "openmw.util", LuaUtil::initUtilPackage(lua) }, { "openmw.vfs", initVFSPackage(context) }, }; diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index bb7d73a808..256a11a0b6 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -112,13 +112,12 @@ namespace MWLua mPlayerPackages.insert(mLocalPackages.begin(), mLocalPackages.end()); LuaUtil::LuaStorage::initLuaBindings(mLua.sol()); - mGlobalScripts.addPackage( - "openmw.storage", LuaUtil::LuaStorage::initGlobalPackage(mLua.sol(), &mGlobalStorage)); + mGlobalScripts.addPackage("openmw.storage", LuaUtil::LuaStorage::initGlobalPackage(mLua, &mGlobalStorage)); mMenuScripts.addPackage( - "openmw.storage", LuaUtil::LuaStorage::initMenuPackage(mLua.sol(), &mGlobalStorage, &mPlayerStorage)); - mLocalPackages["openmw.storage"] = LuaUtil::LuaStorage::initLocalPackage(mLua.sol(), &mGlobalStorage); + "openmw.storage", LuaUtil::LuaStorage::initMenuPackage(mLua, &mGlobalStorage, &mPlayerStorage)); + mLocalPackages["openmw.storage"] = LuaUtil::LuaStorage::initLocalPackage(mLua, &mGlobalStorage); mPlayerPackages["openmw.storage"] - = LuaUtil::LuaStorage::initPlayerPackage(mLua.sol(), &mGlobalStorage, &mPlayerStorage); + = LuaUtil::LuaStorage::initPlayerPackage(mLua, &mGlobalStorage, &mPlayerStorage); mPlayerStorage.setActive(true); mGlobalStorage.setActive(false); @@ -456,6 +455,17 @@ namespace MWLua scripts->onPlayAnimation(groupname, options); } + void LuaManager::skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale) + { + mEngineEvents.addToQueue(EngineEvents::OnSkillUse{ getId(actor), skillId.serializeText(), useType, scale }); + } + + void LuaManager::skillLevelUp(const MWWorld::Ptr& actor, ESM::RefId skillId, std::string_view source) + { + mEngineEvents.addToQueue( + EngineEvents::OnSkillLevelUp{ getId(actor), skillId.serializeText(), std::string(source) }); + } + void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr) { mObjectLists.objectAddedToScene(ptr); // assigns generated RefNum if it is not set yet. diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index a9657e4d61..d245b3035b 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -88,6 +88,8 @@ namespace MWLua const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult, std::string_view start, std::string_view stop, float startpoint, uint32_t loops, bool loopfallback) override; + void skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale) override; + void skillLevelUp(const MWWorld::Ptr& actor, ESM::RefId skillId, std::string_view source) override; void exteriorCreated(MWWorld::CellStore& cell) override { mEngineEvents.addToQueue(EngineEvents::OnNewExterior{ cell }); diff --git a/apps/openmw/mwlua/magicbindings.cpp b/apps/openmw/mwlua/magicbindings.cpp index 1e3cb2ab69..da689558a2 100644 --- a/apps/openmw/mwlua/magicbindings.cpp +++ b/apps/openmw/mwlua/magicbindings.cpp @@ -13,12 +13,14 @@ #include #include #include +#include #include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwmechanics/activespells.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/magiceffects.hpp" #include "../mwmechanics/spellutil.hpp" @@ -31,6 +33,7 @@ #include "luamanagerimp.hpp" #include "object.hpp" #include "objectvariant.hpp" +#include "recordstore.hpp" namespace MWLua { @@ -135,16 +138,12 @@ namespace MWLua namespace sol { - template - struct is_automagical> : std::false_type - { - }; template <> struct is_automagical : std::false_type { }; template <> - struct is_automagical : std::false_type + struct is_automagical : std::false_type { }; template <> @@ -192,6 +191,26 @@ namespace MWLua return ESM::RefId::deserializeText(LuaUtil::cast(recordOrId)); } + static const ESM::Spell* toSpell(const sol::object& spellOrId) + { + if (spellOrId.is()) + return spellOrId.as(); + else + { + auto& store = MWBase::Environment::get().getWorld()->getStore(); + auto refId = ESM::RefId::deserializeText(LuaUtil::cast(spellOrId)); + return store.get().find(refId); + } + } + + static sol::table effectParamsListToTable(sol::state_view& lua, const std::vector& effects) + { + sol::table res(lua, sol::create); + for (size_t i = 0; i < effects.size(); ++i) + res[i + 1] = effects[i]; // ESM::IndexedENAMstruct (effect params) + return res; + } + sol::table initCoreMagicBindings(const Context& context) { sol::state_view& lua = context.mLua->sol(); @@ -228,50 +247,18 @@ namespace MWLua } // Spell store - using SpellStore = MWWorld::Store; - const SpellStore* spellStore = &MWBase::Environment::get().getWorld()->getStore().get(); - sol::usertype spellStoreT = lua.new_usertype("ESM3_SpellStore"); - spellStoreT[sol::meta_function::to_string] - = [](const SpellStore& store) { return "ESM3_SpellStore{" + std::to_string(store.getSize()) + " spells}"; }; - spellStoreT[sol::meta_function::length] = [](const SpellStore& store) { return store.getSize(); }; - spellStoreT[sol::meta_function::index] = sol::overload( - [](const SpellStore& store, size_t index) -> const ESM::Spell* { - if (index == 0 || index > store.getSize()) - return nullptr; - return store.at(index - 1); - }, - [](const SpellStore& store, std::string_view spellId) -> const ESM::Spell* { - return store.search(ESM::RefId::deserializeText(spellId)); - }); - spellStoreT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); - spellStoreT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); - - magicApi["spells"] = spellStore; + sol::table spells(lua, sol::create); + addRecordFunctionBinding(spells, context); + magicApi["spells"] = LuaUtil::makeReadOnly(spells); // Enchantment store - using EnchantmentStore = MWWorld::Store; - const EnchantmentStore* enchantmentStore - = &MWBase::Environment::get().getWorld()->getStore().get(); - sol::usertype enchantmentStoreT = lua.new_usertype("ESM3_EnchantmentStore"); - enchantmentStoreT[sol::meta_function::to_string] = [](const EnchantmentStore& store) { - return "ESM3_EnchantmentStore{" + std::to_string(store.getSize()) + " enchantments}"; - }; - enchantmentStoreT[sol::meta_function::length] = [](const EnchantmentStore& store) { return store.getSize(); }; - enchantmentStoreT[sol::meta_function::index] = sol::overload( - [](const EnchantmentStore& store, size_t index) -> const ESM::Enchantment* { - if (index == 0 || index > store.getSize()) - return nullptr; - return store.at(index - 1); - }, - [](const EnchantmentStore& store, std::string_view enchantmentId) -> const ESM::Enchantment* { - return store.search(ESM::RefId::deserializeText(enchantmentId)); - }); - enchantmentStoreT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); - enchantmentStoreT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); - - magicApi["enchantments"] = enchantmentStore; + sol::table enchantments(lua, sol::create); + addRecordFunctionBinding(enchantments, context); + magicApi["enchantments"] = LuaUtil::makeReadOnly(enchantments); // MagicEffect store + sol::table magicEffects(lua, sol::create); + magicApi["effects"] = LuaUtil::makeReadOnly(magicEffects); using MagicEffectStore = MWWorld::Store; const MagicEffectStore* magicEffectStore = &MWBase::Environment::get().getWorld()->getStore().get(); @@ -303,8 +290,10 @@ namespace MWLua }; magicEffectStoreT[sol::meta_function::pairs] = [iter = sol::make_object(lua, magicEffectsIter)] { return iter; }; + magicEffectStoreT[sol::meta_function::ipairs] + = [iter = sol::make_object(lua, magicEffectsIter)] { return iter; }; - magicApi["effects"] = magicEffectStore; + magicEffects["records"] = magicEffectStore; // Spell record auto spellT = lua.new_usertype("ESM3_Spell"); @@ -314,12 +303,12 @@ namespace MWLua spellT["name"] = sol::readonly_property([](const ESM::Spell& rec) -> std::string_view { return rec.mName; }); spellT["type"] = sol::readonly_property([](const ESM::Spell& rec) -> int { return rec.mData.mType; }); spellT["cost"] = sol::readonly_property([](const ESM::Spell& rec) -> int { return rec.mData.mCost; }); - spellT["effects"] = sol::readonly_property([&lua](const ESM::Spell& rec) -> sol::table { - sol::table res(lua, sol::create); - for (size_t i = 0; i < rec.mEffects.mList.size(); ++i) - res[i + 1] = rec.mEffects.mList[i]; // ESM::ENAMstruct (effect params) - return res; - }); + spellT["alwaysSucceedFlag"] = sol::readonly_property( + [](const ESM::Spell& rec) -> bool { return !!(rec.mData.mFlags & ESM::Spell::F_Always); }); + spellT["autocalcFlag"] = sol::readonly_property( + [](const ESM::Spell& rec) -> bool { return !!(rec.mData.mFlags & ESM::Spell::F_Autocalc); }); + spellT["effects"] = sol::readonly_property( + [&lua](const ESM::Spell& rec) -> sol::table { return effectParamsListToTable(lua, rec.mEffects.mList); }); // Enchantment record auto enchantT = lua.new_usertype("ESM3_Enchantment"); @@ -334,46 +323,49 @@ namespace MWLua enchantT["charge"] = sol::readonly_property([](const ESM::Enchantment& rec) -> int { return rec.mData.mCharge; }); enchantT["effects"] = sol::readonly_property([&lua](const ESM::Enchantment& rec) -> sol::table { - sol::table res(lua, sol::create); - for (size_t i = 0; i < rec.mEffects.mList.size(); ++i) - res[i + 1] = rec.mEffects.mList[i]; // ESM::ENAMstruct (effect params) - return res; + return effectParamsListToTable(lua, rec.mEffects.mList); }); // Effect params - auto effectParamsT = lua.new_usertype("ESM3_EffectParams"); - effectParamsT[sol::meta_function::to_string] = [magicEffectStore](const ESM::ENAMstruct& params) { - const ESM::MagicEffect* const rec = magicEffectStore->find(params.mEffectID); + auto effectParamsT = lua.new_usertype("ESM3_EffectParams"); + effectParamsT[sol::meta_function::to_string] = [magicEffectStore](const ESM::IndexedENAMstruct& params) { + const ESM::MagicEffect* const rec = magicEffectStore->find(params.mData.mEffectID); return "ESM3_EffectParams[" + ESM::MagicEffect::indexToGmstString(rec->mIndex) + "]"; }; - effectParamsT["effect"] - = sol::readonly_property([magicEffectStore](const ESM::ENAMstruct& params) -> const ESM::MagicEffect* { - return magicEffectStore->find(params.mEffectID); - }); + effectParamsT["effect"] = sol::readonly_property( + [magicEffectStore](const ESM::IndexedENAMstruct& params) -> const ESM::MagicEffect* { + return magicEffectStore->find(params.mData.mEffectID); + }); + effectParamsT["id"] = sol::readonly_property([](const ESM::IndexedENAMstruct& params) -> std::string { + auto name = ESM::MagicEffect::indexToName(params.mData.mEffectID); + return Misc::StringUtils::lowerCase(name); + }); effectParamsT["affectedSkill"] - = sol::readonly_property([](const ESM::ENAMstruct& params) -> sol::optional { - ESM::RefId id = ESM::Skill::indexToRefId(params.mSkill); + = sol::readonly_property([](const ESM::IndexedENAMstruct& params) -> sol::optional { + ESM::RefId id = ESM::Skill::indexToRefId(params.mData.mSkill); if (!id.empty()) return id.serializeText(); return sol::nullopt; }); effectParamsT["affectedAttribute"] - = sol::readonly_property([](const ESM::ENAMstruct& params) -> sol::optional { - ESM::RefId id = ESM::Attribute::indexToRefId(params.mAttribute); + = sol::readonly_property([](const ESM::IndexedENAMstruct& params) -> sol::optional { + ESM::RefId id = ESM::Attribute::indexToRefId(params.mData.mAttribute); if (!id.empty()) return id.serializeText(); return sol::nullopt; }); effectParamsT["range"] - = sol::readonly_property([](const ESM::ENAMstruct& params) -> int { return params.mRange; }); + = sol::readonly_property([](const ESM::IndexedENAMstruct& params) -> int { return params.mData.mRange; }); effectParamsT["area"] - = sol::readonly_property([](const ESM::ENAMstruct& params) -> int { return params.mArea; }); + = sol::readonly_property([](const ESM::IndexedENAMstruct& params) -> int { return params.mData.mArea; }); effectParamsT["magnitudeMin"] - = sol::readonly_property([](const ESM::ENAMstruct& params) -> int { return params.mMagnMin; }); + = sol::readonly_property([](const ESM::IndexedENAMstruct& params) -> int { return params.mData.mMagnMin; }); effectParamsT["magnitudeMax"] - = sol::readonly_property([](const ESM::ENAMstruct& params) -> int { return params.mMagnMax; }); - effectParamsT["duration"] - = sol::readonly_property([](const ESM::ENAMstruct& params) -> int { return params.mDuration; }); + = sol::readonly_property([](const ESM::IndexedENAMstruct& params) -> int { return params.mData.mMagnMax; }); + effectParamsT["duration"] = sol::readonly_property( + [](const ESM::IndexedENAMstruct& params) -> int { return params.mData.mDuration; }); + effectParamsT["index"] + = sol::readonly_property([](const ESM::IndexedENAMstruct& params) -> int { return params.mIndex; }); // MagicEffect record auto magicEffectT = context.mLua->sol().new_usertype("ESM3_MagicEffect"); @@ -394,12 +386,22 @@ namespace MWLua magicEffectT["continuousVfx"] = sol::readonly_property([](const ESM::MagicEffect& rec) -> bool { return (rec.mData.mFlags & ESM::MagicEffect::ContinuousVfx) != 0; }); - magicEffectT["castingStatic"] = sol::readonly_property( + magicEffectT["areaSound"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> std::string { return rec.mAreaSound.serializeText(); }); + magicEffectT["boltSound"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> std::string { return rec.mBoltSound.serializeText(); }); + magicEffectT["castSound"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> std::string { return rec.mCastSound.serializeText(); }); + magicEffectT["hitSound"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> std::string { return rec.mHitSound.serializeText(); }); + magicEffectT["areaStatic"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> std::string { return rec.mArea.serializeText(); }); + magicEffectT["boltStatic"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> std::string { return rec.mBolt.serializeText(); }); + magicEffectT["castStatic"] = sol::readonly_property( [](const ESM::MagicEffect& rec) -> std::string { return rec.mCasting.serializeText(); }); magicEffectT["hitStatic"] = sol::readonly_property( [](const ESM::MagicEffect& rec) -> std::string { return rec.mHit.serializeText(); }); - magicEffectT["areaStatic"] = sol::readonly_property( - [](const ESM::MagicEffect& rec) -> std::string { return rec.mArea.serializeText(); }); magicEffectT["name"] = sol::readonly_property([](const ESM::MagicEffect& rec) -> std::string_view { return MWBase::Environment::get() .getWorld() @@ -415,8 +417,20 @@ namespace MWLua magicEffectT["color"] = sol::readonly_property([](const ESM::MagicEffect& rec) -> Misc::Color { return Misc::Color(rec.mData.mRed / 255.f, rec.mData.mGreen / 255.f, rec.mData.mBlue / 255.f, 1.f); }); + magicEffectT["hasDuration"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> bool { return !(rec.mData.mFlags & ESM::MagicEffect::NoDuration); }); + magicEffectT["hasMagnitude"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> bool { return !(rec.mData.mFlags & ESM::MagicEffect::NoMagnitude); }); + // TODO: Not self-explanatory. Needs either a better name or documentation. The description in + // loadmgef.hpp is uninformative. + magicEffectT["isAppliedOnce"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> bool { return rec.mData.mFlags & ESM::MagicEffect::AppliedOnce; }); magicEffectT["harmful"] = sol::readonly_property( [](const ESM::MagicEffect& rec) -> bool { return rec.mData.mFlags & ESM::MagicEffect::Harmful; }); + magicEffectT["casterLinked"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> bool { return rec.mData.mFlags & ESM::MagicEffect::CasterLinked; }); + magicEffectT["nonRecastable"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> bool { return rec.mData.mFlags & ESM::MagicEffect::NonRecastable; }); // TODO: Should we expose it? What happens if a spell has several effects with different projectileSpeed? // magicEffectT["projectileSpeed"] @@ -430,6 +444,8 @@ namespace MWLua auto name = ESM::MagicEffect::indexToName(effect.mEffectId); return Misc::StringUtils::lowerCase(name); }); + activeSpellEffectT["index"] + = sol::readonly_property([](const ESM::ActiveEffect& effect) -> int { return effect.mEffectIndex; }); activeSpellEffectT["name"] = sol::readonly_property([](const ESM::ActiveEffect& effect) -> std::string { return MWMechanics::EffectKey(effect.mEffectId, effect.getSkillOrAttribute()).toString(); }); @@ -493,12 +509,13 @@ namespace MWLua auto activeSpellT = context.mLua->sol().new_usertype("ActiveSpellParams"); activeSpellT[sol::meta_function::to_string] = [](const ActiveSpell& activeSpell) { - return "ActiveSpellParams[" + activeSpell.mParams.getId().serializeText() + "]"; + return "ActiveSpellParams[" + activeSpell.mParams.getSourceSpellId().serializeText() + "]"; }; activeSpellT["name"] = sol::readonly_property( [](const ActiveSpell& activeSpell) -> std::string_view { return activeSpell.mParams.getDisplayName(); }); - activeSpellT["id"] = sol::readonly_property( - [](const ActiveSpell& activeSpell) -> std::string { return activeSpell.mParams.getId().serializeText(); }); + activeSpellT["id"] = sol::readonly_property([](const ActiveSpell& activeSpell) -> std::string { + return activeSpell.mParams.getSourceSpellId().serializeText(); + }); activeSpellT["item"] = sol::readonly_property([&lua](const ActiveSpell& activeSpell) -> sol::object { auto item = activeSpell.mParams.getItem(); if (!item.isSet()) @@ -535,6 +552,21 @@ namespace MWLua } return res; }); + activeSpellT["fromEquipment"] = sol::readonly_property([](const ActiveSpell& activeSpell) -> bool { + return activeSpell.mParams.hasFlag(ESM::ActiveSpells::Flag_Equipment); + }); + activeSpellT["temporary"] = sol::readonly_property([](const ActiveSpell& activeSpell) -> bool { + return activeSpell.mParams.hasFlag(ESM::ActiveSpells::Flag_Temporary); + }); + activeSpellT["affectsBaseValues"] = sol::readonly_property([](const ActiveSpell& activeSpell) -> bool { + return activeSpell.mParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues); + }); + activeSpellT["stackable"] = sol::readonly_property([](const ActiveSpell& activeSpell) -> bool { + return activeSpell.mParams.hasFlag(ESM::ActiveSpells::Flag_Stackable); + }); + activeSpellT["activeSpellId"] = sol::readonly_property([](const ActiveSpell& activeSpell) -> std::string { + return activeSpell.mParams.getActiveSpellId().serializeText(); + }); auto activeEffectT = context.mLua->sol().new_usertype("ActiveEffect"); @@ -573,6 +605,78 @@ namespace MWLua return LuaUtil::makeReadOnly(magicApi); } + static std::pair> getNameAndMagicEffects( + const MWWorld::Ptr& actor, ESM::RefId id, const sol::table& effects, bool quiet) + { + std::vector effectIndexes; + + for (const auto& entry : effects) + { + if (entry.second.is()) + effectIndexes.push_back(entry.second.as()); + else if (entry.second.is()) + throw std::runtime_error("Error: Adding effects as enam structs is not implemented, use indexes."); + else + throw std::runtime_error("Unexpected entry in 'effects' table while trying to add to active effects"); + } + + const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); + + auto getEffectsFromIndexes = [&](const ESM::EffectList& effects) { + std::vector enams; + for (auto index : effectIndexes) + enams.push_back(effects.mList.at(index)); + return enams; + }; + + auto getNameAndEffects = [&](auto* record) { + return std::pair>( + record->mName, getEffectsFromIndexes(record->mEffects)); + }; + auto getNameAndEffectsEnch = [&](auto* record) { + auto* enchantment = esmStore.get().find(record->mEnchant); + return std::pair>( + record->mName, getEffectsFromIndexes(enchantment->mEffects)); + }; + switch (esmStore.find(id)) + { + case ESM::REC_ALCH: + return getNameAndEffects(esmStore.get().find(id)); + case ESM::REC_INGR: + { + // Ingredients are a special case as their effect list is calculated on consumption. + const ESM::Ingredient* ingredient = esmStore.get().find(id); + std::vector enams; + quiet = quiet || actor != MWMechanics::getPlayer(); + for (uint32_t i = 0; i < effectIndexes.size(); i++) + { + if (auto effect = MWMechanics::rollIngredientEffect(actor, ingredient, effectIndexes[i])) + enams.push_back(effect->mList[0]); + } + if (enams.empty() && !quiet) + { + // "X has no effect on you" + std::string message = esmStore.get().find("sNotifyMessage50")->mValue.getString(); + message = Misc::StringUtils::format(message, ingredient->mName); + MWBase::Environment::get().getWindowManager()->messageBox(message); + } + return { ingredient->mName, std::move(enams) }; + } + case ESM::REC_ARMO: + return getNameAndEffectsEnch(esmStore.get().find(id)); + case ESM::REC_BOOK: + return getNameAndEffectsEnch(esmStore.get().find(id)); + case ESM::REC_CLOT: + return getNameAndEffectsEnch(esmStore.get().find(id)); + case ESM::REC_WEAP: + return getNameAndEffectsEnch(esmStore.get().find(id)); + default: + // esmStore.find doesn't find REC_SPELs + case ESM::REC_SPEL: + return getNameAndEffects(esmStore.get().find(id)); + } + } + void addActorMagicBindings(sol::table& actor, const Context& context) { const MWWorld::Store* spellStore @@ -731,6 +835,16 @@ namespace MWLua }); }; + // types.Actor.spells(o):canUsePower() + spellsT["canUsePower"] = [](const ActorSpells& spells, const sol::object& spellOrId) -> bool { + if (spells.mActor.isLObject()) + throw std::runtime_error("Local scripts can modify only spells of the actor they are attached to."); + auto* spell = toSpell(spellOrId); + if (auto* store = spells.getStore()) + return store->canUsePower(spell); + return false; + }; + // pairs(types.Actor.activeSpells(o)) activeSpellsT["__pairs"] = [](sol::this_state ts, ActorActiveSpells& self) { sol::state_view lua(ts); @@ -738,7 +852,7 @@ namespace MWLua return sol::as_function([lua, self]() mutable -> std::pair { if (!self.isEnd()) { - auto id = sol::make_object(lua, self.mIterator->getId().serializeText()); + auto id = sol::make_object(lua, self.mIterator->getSourceSpellId().serializeText()); auto params = sol::make_object(lua, ActiveSpell{ self.mActor, *self.mIterator }); self.advance(); return { id, params }; @@ -762,14 +876,97 @@ namespace MWLua }; // types.Actor.activeSpells(o):remove(id) - activeSpellsT["remove"] = [](const ActorActiveSpells& spells, const sol::object& spellOrId) { + activeSpellsT["remove"] = [context](const ActorActiveSpells& spells, std::string_view idStr) { + if (spells.isLObject()) + throw std::runtime_error("Local scripts can modify effect only on the actor they are attached to."); + + context.mLuaManager->addAction([spells = spells, id = ESM::RefId::deserializeText(idStr)]() { + if (auto* store = spells.getStore()) + { + auto it = store->getActiveSpellById(id); + if (it != store->end()) + { + if (it->hasFlag(ESM::ActiveSpells::Flag_Temporary)) + store->removeEffectsByActiveSpellId(spells.mActor.ptr(), id); + else + throw std::runtime_error("Can only remove temporary effects."); + } + } + }); + }; + + // types.Actor.activeSpells(o):add(id, spellid, effects, options) + activeSpellsT["add"] = [](const ActorActiveSpells& spells, const sol::table& options) { if (spells.isLObject()) throw std::runtime_error("Local scripts can modify effect only on the actor they are attached to."); - auto id = toSpellId(spellOrId); if (auto* store = spells.getStore()) { - store->removeEffects(spells.mActor.ptr(), id); + ESM::RefId id = ESM::RefId::deserializeText(options.get("id")); + sol::optional item = options.get>("item"); + ESM::RefNum itemId; + if (item) + itemId = item->id(); + sol::optional caster = options.get>("caster"); + bool stackable = options.get_or("stackable", false); + bool ignoreReflect = options.get_or("ignoreReflect", false); + bool ignoreSpellAbsorption = options.get_or("ignoreSpellAbsorption", false); + bool ignoreResistances = options.get_or("ignoreResistances", false); + sol::table effects = options.get("effects"); + bool quiet = options.get_or("quiet", false); + if (effects.empty()) + throw std::runtime_error("Error: Parameter 'effects': cannot be an empty list/table"); + const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); + auto [name, enams] = getNameAndMagicEffects(spells.mActor.ptr(), id, effects, quiet); + name = options.get_or("name", name); + + MWWorld::Ptr casterPtr; + if (caster) + casterPtr = caster->ptrOrEmpty(); + + bool affectsHealth = false; + MWMechanics::ActiveSpells::ActiveSpellParams params(casterPtr, id, name, itemId); + params.setFlag(ESM::ActiveSpells::Flag_Lua); + params.setFlag(ESM::ActiveSpells::Flag_Temporary); + if (stackable) + params.setFlag(ESM::ActiveSpells::Flag_Stackable); + + for (auto enam : enams) + { + const ESM::MagicEffect* mgef = esmStore.get().find(enam.mData.mEffectID); + MWMechanics::ActiveSpells::ActiveEffect effect; + effect.mEffectId = enam.mData.mEffectID; + effect.mArg = MWMechanics::EffectKey(enam.mData).mArg; + effect.mMagnitude = 0.f; + effect.mMinMagnitude = enam.mData.mMagnMin; + effect.mMaxMagnitude = enam.mData.mMagnMax; + effect.mEffectIndex = enam.mIndex; + effect.mFlags = ESM::ActiveEffect::Flag_None; + if (ignoreReflect) + effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_Reflect; + if (ignoreSpellAbsorption) + effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_SpellAbsorption; + if (ignoreResistances) + effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_Resistances; + + bool hasDuration = !(mgef->mData.mFlags & ESM::MagicEffect::NoDuration); + effect.mDuration = hasDuration ? static_cast(enam.mData.mDuration) : 1.f; + + bool appliedOnce = mgef->mData.mFlags & ESM::MagicEffect::AppliedOnce; + if (!appliedOnce) + effect.mDuration = std::max(1.f, effect.mDuration); + effect.mTimeLeft = effect.mDuration; + params.getEffects().emplace_back(effect); + + affectsHealth = affectsHealth || mgef->mData.mFlags & ESM::MagicEffect::Harmful + || effect.mEffectId == ESM::MagicEffect::RestoreHealth; + } + store->addSpell(params); + if (affectsHealth && casterPtr == MWMechanics::getPlayer()) + // If player is attempting to cast a harmful spell on or is healing a living target, show the + // target's HP bar. + // TODO: This should be moved to Lua once the HUD has been dehardcoded + MWBase::Environment::get().getWindowManager()->setEnemy(spells.mActor.ptr()); } }; diff --git a/apps/openmw/mwlua/markupbindings.cpp b/apps/openmw/mwlua/markupbindings.cpp new file mode 100644 index 0000000000..f0b9d67a51 --- /dev/null +++ b/apps/openmw/mwlua/markupbindings.cpp @@ -0,0 +1,31 @@ +#include "markupbindings.hpp" + +#include +#include +#include +#include +#include + +#include "../mwbase/environment.hpp" + +#include "context.hpp" + +namespace MWLua +{ + sol::table initMarkupPackage(const Context& context) + { + sol::table api(context.mLua->sol(), sol::create); + + auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + + api["loadYaml"] = [lua = context.mLua, vfs](std::string_view fileName) { + Files::IStreamPtr file = vfs->get(VFS::Path::Normalized(fileName)); + return LuaUtil::loadYaml(*file, lua->sol()); + }; + api["decodeYaml"] = [lua = context.mLua](std::string_view inputData) { + return LuaUtil::loadYaml(std::string(inputData), lua->sol()); + }; + + return LuaUtil::makeReadOnly(api); + } +} diff --git a/apps/openmw/mwlua/markupbindings.hpp b/apps/openmw/mwlua/markupbindings.hpp new file mode 100644 index 0000000000..9105ab5edf --- /dev/null +++ b/apps/openmw/mwlua/markupbindings.hpp @@ -0,0 +1,13 @@ +#ifndef MWLUA_MARKUPBINDINGS_H +#define MWLUA_MARKUPBINDINGS_H + +#include + +#include "context.hpp" + +namespace MWLua +{ + sol::table initMarkupPackage(const Context&); +} + +#endif // MWLUA_MARKUPBINDINGS_H diff --git a/apps/openmw/mwlua/mwscriptbindings.cpp b/apps/openmw/mwlua/mwscriptbindings.cpp index a41ef30a44..1f0a081fe4 100644 --- a/apps/openmw/mwlua/mwscriptbindings.cpp +++ b/apps/openmw/mwlua/mwscriptbindings.cpp @@ -53,6 +53,33 @@ namespace sol namespace MWLua { + float getGlobalVariableValue(const std::string_view globalId) + { + char varType = MWBase::Environment::get().getWorld()->getGlobalVariableType(globalId); + if (varType == 'f') + { + return MWBase::Environment::get().getWorld()->getGlobalFloat(globalId); + } + else if (varType == 's' || varType == 'l') + { + return static_cast(MWBase::Environment::get().getWorld()->getGlobalInt(globalId)); + } + return 0; + } + + void setGlobalVariableValue(const std::string_view globalId, float value) + { + char varType = MWBase::Environment::get().getWorld()->getGlobalVariableType(globalId); + if (varType == 'f') + { + MWBase::Environment::get().getWorld()->setGlobalFloat(globalId, value); + } + else if (varType == 's' || varType == 'l') + { + MWBase::Environment::get().getWorld()->setGlobalInt(globalId, value); + } + } + sol::table initMWScriptBindings(const Context& context) { sol::table api(context.mLua->sol(), sol::create); @@ -125,37 +152,55 @@ namespace MWLua return "ESM3_GlobalStore{" + std::to_string(store.getSize()) + " globals}"; }; globalStoreT[sol::meta_function::length] = [](const GlobalStore& store) { return store.getSize(); }; - globalStoreT[sol::meta_function::index] - = sol::overload([](const GlobalStore& store, std::string_view globalId) -> sol::optional { - auto g = store.search(ESM::RefId::deserializeText(globalId)); - if (g == nullptr) - return sol::nullopt; - char varType = MWBase::Environment::get().getWorld()->getGlobalVariableType(globalId); - if (varType == 's' || varType == 'l') - { - return static_cast(MWBase::Environment::get().getWorld()->getGlobalInt(globalId)); - } - else - { - return MWBase::Environment::get().getWorld()->getGlobalFloat(globalId); - } - }); - globalStoreT[sol::meta_function::new_index] - = sol::overload([](const GlobalStore& store, std::string_view globalId, float val) { - auto g = store.search(ESM::RefId::deserializeText(globalId)); - if (g == nullptr) - throw std::runtime_error("No variable \"" + std::string(globalId) + "\" in GlobalStore"); - char varType = MWBase::Environment::get().getWorld()->getGlobalVariableType(globalId); - if (varType == 's' || varType == 'l') - { - MWBase::Environment::get().getWorld()->setGlobalInt(globalId, static_cast(val)); - } - else - { - MWBase::Environment::get().getWorld()->setGlobalFloat(globalId, val); - } - }); - globalStoreT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); + globalStoreT[sol::meta_function::index] = sol::overload( + [](const GlobalStore& store, std::string_view globalId) -> sol::optional { + auto g = store.search(ESM::RefId::deserializeText(globalId)); + if (g == nullptr) + return sol::nullopt; + return getGlobalVariableValue(globalId); + }, + [](const GlobalStore& store, size_t index) -> sol::optional { + if (index < 1 || store.getSize() < index) + return sol::nullopt; + auto g = store.at(index - 1); + if (g == nullptr) + return sol::nullopt; + std::string globalId = g->mId.serializeText(); + return getGlobalVariableValue(globalId); + }); + globalStoreT[sol::meta_function::new_index] = sol::overload( + [](const GlobalStore& store, std::string_view globalId, float val) -> void { + auto g = store.search(ESM::RefId::deserializeText(globalId)); + if (g == nullptr) + throw std::runtime_error("No variable \"" + std::string(globalId) + "\" in GlobalStore"); + setGlobalVariableValue(globalId, val); + }, + [](const GlobalStore& store, size_t index, float val) { + if (index < 1 || store.getSize() < index) + return; + auto g = store.at(index - 1); + if (g == nullptr) + return; + std::string globalId = g->mId.serializeText(); + setGlobalVariableValue(globalId, val); + }); + globalStoreT[sol::meta_function::pairs] = [](const GlobalStore& store) { + size_t index = 0; + return sol::as_function( + [index, &store](sol::this_state ts) mutable -> sol::optional> { + if (index >= store.getSize()) + return sol::nullopt; + + const ESM::Global* global = store.at(index++); + if (!global) + return sol::nullopt; + + std::string globalId = global->mId.serializeText(); + float value = getGlobalVariableValue(globalId); + + return std::make_tuple(globalId, value); + }); + }; globalStoreT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); api["getGlobalVariables"] = [globalStore](sol::optional player) { if (player.has_value() && player->ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr()) diff --git a/apps/openmw/mwlua/nearbybindings.cpp b/apps/openmw/mwlua/nearbybindings.cpp index 7e1845aeac..af6980fb7f 100644 --- a/apps/openmw/mwlua/nearbybindings.cpp +++ b/apps/openmw/mwlua/nearbybindings.cpp @@ -16,6 +16,31 @@ #include "luamanagerimp.hpp" #include "objectlists.hpp" +namespace +{ + template + std::vector parseIgnoreList(const sol::table& options) + { + std::vector ignore; + + if (const auto& ignoreObj = options.get>("ignore")) + { + ignore.push_back(ignoreObj->ptr()); + } + else if (const auto& ignoreTable = options.get>("ignore")) + { + ignoreTable->for_each([&](const auto& _, const sol::object& value) { + if (value.is()) + { + ignore.push_back(value.as().ptr()); + } + }); + } + + return ignore; + } +} + namespace sol { template <> @@ -71,24 +96,27 @@ namespace MWLua })); api["castRay"] = [](const osg::Vec3f& from, const osg::Vec3f& to, sol::optional options) { - MWWorld::Ptr ignore; + std::vector ignore; int collisionType = MWPhysics::CollisionType_Default; float radius = 0; if (options) { - sol::optional ignoreObj = options->get>("ignore"); - if (ignoreObj) - ignore = ignoreObj->ptr(); + ignore = parseIgnoreList(*options); collisionType = options->get>("collisionType").value_or(collisionType); radius = options->get>("radius").value_or(0); } const MWPhysics::RayCastingInterface* rayCasting = MWBase::Environment::get().getWorld()->getRayCasting(); if (radius <= 0) - return rayCasting->castRay(from, to, ignore, std::vector(), collisionType); + { + return rayCasting->castRay(from, to, ignore, {}, collisionType); + } else { - if (!ignore.isEmpty()) - throw std::logic_error("Currently castRay doesn't support `ignore` when radius > 0"); + for (const auto& ptr : ignore) + { + if (!ptr.isEmpty()) + throw std::logic_error("Currently castRay doesn't support `ignore` when radius > 0"); + } return rayCasting->castSphere(from, to, radius, collisionType); } }; @@ -108,22 +136,37 @@ namespace MWLua // and use this callback from the main thread at the beginning of the next frame processing. rayCasting->asyncCastRay(callback, from, to, ignore, std::vector(), collisionType); };*/ - api["castRenderingRay"] = [manager = context.mLuaManager](const osg::Vec3f& from, const osg::Vec3f& to) { + api["castRenderingRay"] = [manager = context.mLuaManager](const osg::Vec3f& from, const osg::Vec3f& to, + const sol::optional& options) { if (!manager->isProcessingInputEvents()) { throw std::logic_error( "castRenderingRay can be used only in player scripts during processing of input events; " "use asyncCastRenderingRay instead."); } + + std::vector ignore; + if (options.has_value()) + { + ignore = parseIgnoreList(*options); + } + MWPhysics::RayCastingResult res; - MWBase::Environment::get().getWorld()->castRenderingRay(res, from, to, false, false); + MWBase::Environment::get().getWorld()->castRenderingRay(res, from, to, false, false, ignore); return res; }; - api["asyncCastRenderingRay"] = [context]( - const sol::table& callback, const osg::Vec3f& from, const osg::Vec3f& to) { - context.mLuaManager->addAction([context, callback = LuaUtil::Callback::fromLua(callback), from, to] { + api["asyncCastRenderingRay"] = [context](const sol::table& callback, const osg::Vec3f& from, + const osg::Vec3f& to, const sol::optional& options) { + std::vector ignore; + if (options.has_value()) + { + ignore = parseIgnoreList(*options); + } + + context.mLuaManager->addAction([context, ignore = std::move(ignore), + callback = LuaUtil::Callback::fromLua(callback), from, to] { MWPhysics::RayCastingResult res; - MWBase::Environment::get().getWorld()->castRenderingRay(res, from, to, false, false); + MWBase::Environment::get().getWorld()->castRenderingRay(res, from, to, false, false, ignore); context.mLuaManager->queueCallback(callback, sol::main_object(context.mLua->sol(), sol::in_place, res)); }); }; diff --git a/apps/openmw/mwlua/objectlists.cpp b/apps/openmw/mwlua/objectlists.cpp index e7b3bb2e06..d0bda5a644 100644 --- a/apps/openmw/mwlua/objectlists.cpp +++ b/apps/openmw/mwlua/objectlists.cpp @@ -7,7 +7,6 @@ #include #include "../mwbase/environment.hpp" -#include "../mwbase/windowmanager.hpp" #include "../mwclass/container.hpp" diff --git a/apps/openmw/mwlua/racebindings.cpp b/apps/openmw/mwlua/racebindings.cpp new file mode 100644 index 0000000000..ea23e883e1 --- /dev/null +++ b/apps/openmw/mwlua/racebindings.cpp @@ -0,0 +1,116 @@ +#include "racebindings.hpp" + +#include +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwworld/esmstore.hpp" + +#include "idcollectionbindings.hpp" +#include "types/types.hpp" + +namespace +{ + struct RaceAttributes + { + const ESM::Race& mRace; + const sol::state_view mLua; + + sol::table getAttribute(ESM::RefId id) const + { + sol::table res(mLua, sol::create); + res["male"] = mRace.mData.getAttribute(id, true); + res["female"] = mRace.mData.getAttribute(id, false); + return LuaUtil::makeReadOnly(res); + } + }; +} + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; + template <> + struct is_automagical : std::false_type + { + }; +} + +namespace MWLua +{ + sol::table initRaceRecordBindings(const Context& context) + { + sol::state_view& lua = context.mLua->sol(); + sol::table races(context.mLua->sol(), sol::create); + addRecordFunctionBinding(races, context); + + auto raceT = lua.new_usertype("ESM3_Race"); + raceT[sol::meta_function::to_string] + = [](const ESM::Race& rec) -> std::string { return "ESM3_Race[" + rec.mId.toDebugString() + "]"; }; + raceT["id"] = sol::readonly_property([](const ESM::Race& rec) { return rec.mId.serializeText(); }); + raceT["name"] = sol::readonly_property([](const ESM::Race& rec) -> std::string_view { return rec.mName; }); + raceT["description"] + = sol::readonly_property([](const ESM::Race& rec) -> std::string_view { return rec.mDescription; }); + raceT["spells"] = sol::readonly_property( + [lua](const ESM::Race& rec) -> sol::table { return createReadOnlyRefIdTable(lua, rec.mPowers.mList); }); + raceT["skills"] = sol::readonly_property([lua](const ESM::Race& rec) -> sol::table { + sol::table res(lua, sol::create); + for (const auto& skillBonus : rec.mData.mBonus) + { + ESM::RefId skill = ESM::Skill::indexToRefId(skillBonus.mSkill); + if (!skill.empty()) + res[skill.serializeText()] = skillBonus.mBonus; + } + return res; + }); + raceT["isPlayable"] = sol::readonly_property( + [](const ESM::Race& rec) -> bool { return rec.mData.mFlags & ESM::Race::Playable; }); + raceT["isBeast"] + = sol::readonly_property([](const ESM::Race& rec) -> bool { return rec.mData.mFlags & ESM::Race::Beast; }); + raceT["height"] = sol::readonly_property([lua](const ESM::Race& rec) -> sol::table { + sol::table res(lua, sol::create); + res["male"] = rec.mData.mMaleHeight; + res["female"] = rec.mData.mFemaleHeight; + return LuaUtil::makeReadOnly(res); + }); + raceT["weight"] = sol::readonly_property([lua](const ESM::Race& rec) -> sol::table { + sol::table res(lua, sol::create); + res["male"] = rec.mData.mMaleWeight; + res["female"] = rec.mData.mFemaleWeight; + return LuaUtil::makeReadOnly(res); + }); + + raceT["attributes"] = sol::readonly_property([lua](const ESM::Race& rec) -> RaceAttributes { + return { rec, lua }; + }); + + auto attributesT = lua.new_usertype("ESM3_RaceAttributes"); + const auto& store = MWBase::Environment::get().getESMStore()->get(); + attributesT[sol::meta_function::index] + = [&](const RaceAttributes& attributes, std::string_view stringId) -> sol::optional { + ESM::RefId id = ESM::RefId::deserializeText(stringId); + if (!store.search(id)) + return sol::nullopt; + return attributes.getAttribute(id); + }; + attributesT[sol::meta_function::pairs] = [&](sol::this_state ts, RaceAttributes& attributes) { + auto iterator = store.begin(); + return sol::as_function( + [iterator, attributes, + &store]() mutable -> std::pair, sol::optional> { + if (iterator != store.end()) + { + ESM::RefId id = iterator->mId; + ++iterator; + return { id.serializeText(), attributes.getAttribute(id) }; + } + return { sol::nullopt, sol::nullopt }; + }); + }; + + return LuaUtil::makeReadOnly(races); + } +} diff --git a/apps/openmw/mwlua/racebindings.hpp b/apps/openmw/mwlua/racebindings.hpp new file mode 100644 index 0000000000..43ba9237c5 --- /dev/null +++ b/apps/openmw/mwlua/racebindings.hpp @@ -0,0 +1,13 @@ +#ifndef MWLUA_RACEBINDINGS_H +#define MWLUA_RACEBINDINGS_H + +#include + +#include "context.hpp" + +namespace MWLua +{ + sol::table initRaceRecordBindings(const Context& context); +} + +#endif // MWLUA_RACEBINDINGS_H diff --git a/apps/openmw/mwlua/recordstore.hpp b/apps/openmw/mwlua/recordstore.hpp new file mode 100644 index 0000000000..3d04de396b --- /dev/null +++ b/apps/openmw/mwlua/recordstore.hpp @@ -0,0 +1,63 @@ +#ifndef MWLUA_RECORDSTORE_H +#define MWLUA_RECORDSTORE_H + +#include + +#include +#include + +#include "apps/openmw/mwbase/environment.hpp" +#include "apps/openmw/mwbase/world.hpp" +#include "apps/openmw/mwworld/esmstore.hpp" +#include "apps/openmw/mwworld/store.hpp" + +#include "context.hpp" +#include "object.hpp" + +namespace sol +{ + // Ensure sol does not try to create the automatic Container or usertype bindings for Store. + // They include write operations and we want the store to be read-only. + template + struct is_automagical> : std::false_type + { + }; +} + +namespace MWLua +{ + template + void addRecordFunctionBinding( + sol::table& table, const Context& context, const std::string& recordName = std::string(T::getRecordType())) + { + const MWWorld::Store& store = MWBase::Environment::get().getESMStore()->get(); + + table["record"] = sol::overload([](const Object& obj) -> const T* { return obj.ptr().get()->mBase; }, + [&store](std::string_view id) -> const T* { return store.search(ESM::RefId::deserializeText(id)); }); + + // Define a custom user type for the store. + // Provide the interface of a read-only array. + using StoreT = MWWorld::Store; + sol::state_view& lua = context.mLua->sol(); + sol::usertype storeT = lua.new_usertype(recordName + "WorldStore"); + storeT[sol::meta_function::to_string] = [recordName](const StoreT& store) { + return "{" + std::to_string(store.getSize()) + " " + recordName + " records}"; + }; + storeT[sol::meta_function::length] = [](const StoreT& store) { return store.getSize(); }; + storeT[sol::meta_function::index] = sol::overload( + [](const StoreT& store, size_t index) -> const T* { + if (index == 0 || index > store.getSize()) + return nullptr; + return store.at(index - 1); // Translate from Lua's 1-based indexing. + }, + [](const StoreT& store, std::string_view id) -> const T* { + return store.search(ESM::RefId::deserializeText(id)); + }); + storeT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); + storeT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); + + // Provide access to the store. + table["records"] = &store; + } +} +#endif // MWLUA_RECORDSTORE_H diff --git a/apps/openmw/mwlua/soundbindings.cpp b/apps/openmw/mwlua/soundbindings.cpp index e8b7089eb8..a5ec69b4fc 100644 --- a/apps/openmw/mwlua/soundbindings.cpp +++ b/apps/openmw/mwlua/soundbindings.cpp @@ -1,4 +1,5 @@ #include "soundbindings.hpp" +#include "recordstore.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" @@ -11,6 +12,7 @@ #include #include "luamanagerimp.hpp" +#include "objectvariant.hpp" namespace { @@ -28,6 +30,27 @@ namespace float mFade = 1.f; }; + MWWorld::Ptr getMutablePtrOrThrow(const MWLua::ObjectVariant& variant) + { + if (variant.isLObject()) + throw std::runtime_error("Local scripts can only modify object they are attached to."); + + MWWorld::Ptr ptr = variant.ptr(); + if (ptr.isEmpty()) + throw std::runtime_error("Invalid object"); + + return ptr; + } + + MWWorld::Ptr getPtrOrThrow(const MWLua::ObjectVariant& variant) + { + MWWorld::Ptr ptr = variant.ptr(); + if (ptr.isEmpty()) + throw std::runtime_error("Invalid object"); + + return ptr; + } + PlaySoundArgs getPlaySoundArgs(const sol::optional& options) { PlaySoundArgs args; @@ -118,9 +141,20 @@ namespace MWLua api["streamMusic"] = [](std::string_view fileName, const sol::optional& options) { auto args = getStreamMusicArgs(options); MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); - sndMgr->streamMusic(std::string(fileName), MWSound::MusicType::Scripted, args.mFade); + sndMgr->streamMusic(VFS::Path::Normalized(fileName), MWSound::MusicType::Scripted, args.mFade); }; + api["say"] + = [luaManager = context.mLuaManager](std::string_view fileName, sol::optional text) { + MWBase::Environment::get().getSoundManager()->say(VFS::Path::Normalized(fileName)); + if (text) + luaManager->addUIMessage(*text); + }; + + api["stopSay"] = []() { MWBase::Environment::get().getSoundManager()->stopSay(MWWorld::ConstPtr()); }; + api["isSayActive"] + = []() { return MWBase::Environment::get().getSoundManager()->sayActive(MWWorld::ConstPtr()); }; + api["isMusicPlaying"] = []() { return MWBase::Environment::get().getSoundManager()->isMusicPlaying(); }; api["stopMusic"] = []() { MWBase::Environment::get().getSoundManager()->stopMusic(); }; @@ -137,83 +171,63 @@ namespace MWLua api["isEnabled"] = []() { return MWBase::Environment::get().getSoundManager()->isEnabled(); }; api["playSound3d"] - = [](std::string_view soundId, const Object& object, const sol::optional& options) { + = [](std::string_view soundId, const sol::object& object, const sol::optional& options) { auto args = getPlaySoundArgs(options); auto playMode = getPlayMode(args, true); ESM::RefId sound = ESM::RefId::deserializeText(soundId); + MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object)); MWBase::Environment::get().getSoundManager()->playSound3D( - object.ptr(), sound, args.mVolume, args.mPitch, MWSound::Type::Sfx, playMode, args.mTimeOffset); + ptr, sound, args.mVolume, args.mPitch, MWSound::Type::Sfx, playMode, args.mTimeOffset); }; api["playSoundFile3d"] - = [](std::string_view fileName, const Object& object, const sol::optional& options) { + = [](std::string_view fileName, const sol::object& object, const sol::optional& options) { auto args = getPlaySoundArgs(options); auto playMode = getPlayMode(args, true); + MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object)); - MWBase::Environment::get().getSoundManager()->playSound3D(object.ptr(), fileName, args.mVolume, - args.mPitch, MWSound::Type::Sfx, playMode, args.mTimeOffset); + MWBase::Environment::get().getSoundManager()->playSound3D( + ptr, fileName, args.mVolume, args.mPitch, MWSound::Type::Sfx, playMode, args.mTimeOffset); }; - api["stopSound3d"] = [](std::string_view soundId, const Object& object) { + api["stopSound3d"] = [](std::string_view soundId, const sol::object& object) { ESM::RefId sound = ESM::RefId::deserializeText(soundId); - MWBase::Environment::get().getSoundManager()->stopSound3D(object.ptr(), sound); + MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object)); + MWBase::Environment::get().getSoundManager()->stopSound3D(ptr, sound); }; - api["stopSoundFile3d"] = [](std::string_view fileName, const Object& object) { - MWBase::Environment::get().getSoundManager()->stopSound3D(object.ptr(), fileName); + api["stopSoundFile3d"] = [](std::string_view fileName, const sol::object& object) { + MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object)); + MWBase::Environment::get().getSoundManager()->stopSound3D(ptr, fileName); }; - api["isSoundPlaying"] = [](std::string_view soundId, const Object& object) { + api["isSoundPlaying"] = [](std::string_view soundId, const sol::object& object) { ESM::RefId sound = ESM::RefId::deserializeText(soundId); - return MWBase::Environment::get().getSoundManager()->getSoundPlaying(object.ptr(), sound); + const MWWorld::Ptr& ptr = getPtrOrThrow(ObjectVariant(object)); + return MWBase::Environment::get().getSoundManager()->getSoundPlaying(ptr, sound); + }; + api["isSoundFilePlaying"] = [](std::string_view fileName, const sol::object& object) { + const MWWorld::Ptr& ptr = getPtrOrThrow(ObjectVariant(object)); + return MWBase::Environment::get().getSoundManager()->getSoundPlaying(ptr, fileName); + }; + + api["say"] = [luaManager = context.mLuaManager]( + std::string_view fileName, const sol::object& object, sol::optional text) { + MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object)); + MWBase::Environment::get().getSoundManager()->say(ptr, VFS::Path::Normalized(fileName)); + if (text) + luaManager->addUIMessage(*text); + }; + api["stopSay"] = [](const sol::object& object) { + MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object)); + MWBase::Environment::get().getSoundManager()->stopSay(ptr); }; - api["isSoundFilePlaying"] = [](std::string_view fileName, const Object& object) { - return MWBase::Environment::get().getSoundManager()->getSoundPlaying(object.ptr(), fileName); + api["isSayActive"] = [](const sol::object& object) { + const MWWorld::Ptr& ptr = getPtrOrThrow(ObjectVariant(object)); + return MWBase::Environment::get().getSoundManager()->sayActive(ptr); }; - api["say"] = sol::overload( - [luaManager = context.mLuaManager]( - std::string_view fileName, const Object& object, sol::optional text) { - MWBase::Environment::get().getSoundManager()->say(object.ptr(), std::string(fileName)); - if (text) - luaManager->addUIMessage(*text); - }, - [luaManager = context.mLuaManager](std::string_view fileName, sol::optional text) { - MWBase::Environment::get().getSoundManager()->say(std::string(fileName)); - if (text) - luaManager->addUIMessage(*text); - }); - api["stopSay"] = sol::overload( - [](const Object& object) { - const MWWorld::Ptr& objPtr = object.ptr(); - MWBase::Environment::get().getSoundManager()->stopSay(objPtr); - }, - []() { MWBase::Environment::get().getSoundManager()->stopSay(MWWorld::ConstPtr()); }); - api["isSayActive"] = sol::overload( - [](const Object& object) { - const MWWorld::Ptr& objPtr = object.ptr(); - return MWBase::Environment::get().getSoundManager()->sayActive(objPtr); - }, - []() { return MWBase::Environment::get().getSoundManager()->sayActive(MWWorld::ConstPtr()); }); - - using SoundStore = MWWorld::Store; - sol::usertype soundStoreT = lua.new_usertype("ESM3_SoundStore"); - soundStoreT[sol::meta_function::to_string] - = [](const SoundStore& store) { return "ESM3_SoundStore{" + std::to_string(store.getSize()) + " sounds}"; }; - soundStoreT[sol::meta_function::length] = [](const SoundStore& store) { return store.getSize(); }; - soundStoreT[sol::meta_function::index] = sol::overload( - [](const SoundStore& store, size_t index) -> const ESM::Sound* { - if (index == 0 || index > store.getSize()) - return nullptr; - return store.at(index - 1); - }, - [](const SoundStore& store, std::string_view soundId) -> const ESM::Sound* { - return store.search(ESM::RefId::deserializeText(soundId)); - }); - soundStoreT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); - soundStoreT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); - - api["sounds"] = &MWBase::Environment::get().getWorld()->getStore().get(); + addRecordFunctionBinding(api, context); // Sound record auto soundT = lua.new_usertype("ESM3_Sound"); @@ -227,7 +241,7 @@ namespace MWLua soundT["maxRange"] = sol::readonly_property([](const ESM::Sound& rec) -> unsigned char { return rec.mData.mMaxRange; }); soundT["fileName"] = sol::readonly_property([](const ESM::Sound& rec) -> std::string { - return VFS::Path::normalizeFilename(Misc::ResourceHelpers::correctSoundPath(rec.mSound)); + return Misc::ResourceHelpers::correctSoundPath(VFS::Path::Normalized(rec.mSound)).value(); }); return LuaUtil::makeReadOnly(api); diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp index 02bed00bf5..209a852697 100644 --- a/apps/openmw/mwlua/stats.cpp +++ b/apps/openmw/mwlua/stats.cpp @@ -22,7 +22,7 @@ #include "../mwworld/esmstore.hpp" #include "objectvariant.hpp" -#include "types/types.hpp" +#include "recordstore.hpp" namespace { @@ -31,7 +31,7 @@ namespace using Index = const SelfObject::CachedStat::Index&; template - auto addIndexedAccessor(Index index) + auto addIndexedAccessor(auto index) { return [index](const sol::object& o) { return T::create(ObjectVariant(o), index); }; } @@ -73,6 +73,96 @@ namespace MWLua "StatUpdateAction"); } + static void setCreatureValue(Index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) + { + auto& stats = ptr.getClass().getCreatureStats(ptr); + if (prop == "current") + stats.setLevel(LuaUtil::cast(value)); + } + + static void setNpcValue(Index index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) + { + auto& stats = ptr.getClass().getNpcStats(ptr); + if (prop == "progress") + stats.setLevelProgress(LuaUtil::cast(value)); + else if (prop == "skillIncreasesForAttribute") + stats.setSkillIncreasesForAttribute( + *std::get(index).getIf(), LuaUtil::cast(value)); + else if (prop == "skillIncreasesForSpecialization") + stats.setSkillIncreasesForSpecialization( + static_cast(std::get(index)), LuaUtil::cast(value)); + } + + class SkillIncreasesForAttributeStats + { + ObjectVariant mObject; + + public: + SkillIncreasesForAttributeStats(ObjectVariant object) + : mObject(std::move(object)) + { + } + + sol::object get(const Context& context, ESM::StringRefId attributeId) const + { + const auto& ptr = mObject.ptr(); + if (!ptr.getClass().isNpc()) + return sol::nil; + + return getValue(context, mObject, &setNpcValue, attributeId, "skillIncreasesForAttribute", + [attributeId](const MWWorld::Ptr& ptr) { + return ptr.getClass().getNpcStats(ptr).getSkillIncreasesForAttribute(attributeId); + }); + } + + void set(const Context& context, ESM::StringRefId attributeId, const sol::object& value) const + { + const auto& ptr = mObject.ptr(); + if (!ptr.getClass().isNpc()) + return; + + SelfObject* obj = mObject.asSelfObject(); + addStatUpdateAction(context.mLuaManager, *obj); + obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, attributeId, "skillIncreasesForAttribute" }] = value; + } + }; + + class SkillIncreasesForSpecializationStats + { + ObjectVariant mObject; + + public: + SkillIncreasesForSpecializationStats(ObjectVariant object) + : mObject(std::move(object)) + { + } + + sol::object get(const Context& context, int specialization) const + { + const auto& ptr = mObject.ptr(); + if (!ptr.getClass().isNpc()) + return sol::nil; + + return getValue(context, mObject, &setNpcValue, specialization, "skillIncreasesForSpecialization", + [specialization](const MWWorld::Ptr& ptr) { + return ptr.getClass().getNpcStats(ptr).getSkillIncreasesForSpecialization( + static_cast(specialization)); + }); + } + + void set(const Context& context, int specialization, const sol::object& value) const + { + const auto& ptr = mObject.ptr(); + if (!ptr.getClass().isNpc()) + return; + + SelfObject* obj = mObject.asSelfObject(); + addStatUpdateAction(context.mLuaManager, *obj); + obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, specialization, "skillIncreasesForSpecialization" }] + = value; + } + }; + class LevelStat { ObjectVariant mObject; @@ -85,7 +175,7 @@ namespace MWLua public: sol::object getCurrent(const Context& context) const { - return getValue(context, mObject, &LevelStat::setValue, std::monostate{}, "current", + return getValue(context, mObject, &setCreatureValue, std::monostate{}, "current", [](const MWWorld::Ptr& ptr) { return ptr.getClass().getCreatureStats(ptr).getLevel(); }); } @@ -93,7 +183,7 @@ namespace MWLua { SelfObject* obj = mObject.asSelfObject(); addStatUpdateAction(context.mLuaManager, *obj); - obj->mStatsCache[SelfObject::CachedStat{ &LevelStat::setValue, std::monostate{}, "current" }] = value; + obj->mStatsCache[SelfObject::CachedStat{ &setCreatureValue, std::monostate{}, "current" }] = value; } sol::object getProgress(const Context& context) const @@ -101,7 +191,30 @@ namespace MWLua const auto& ptr = mObject.ptr(); if (!ptr.getClass().isNpc()) return sol::nil; - return sol::make_object(context.mLua->sol(), ptr.getClass().getNpcStats(ptr).getLevelProgress()); + + return getValue(context, mObject, &setNpcValue, std::monostate{}, "progress", + [](const MWWorld::Ptr& ptr) { return ptr.getClass().getNpcStats(ptr).getLevelProgress(); }); + } + + void setProgress(const Context& context, const sol::object& value) const + { + const auto& ptr = mObject.ptr(); + if (!ptr.getClass().isNpc()) + return; + + SelfObject* obj = mObject.asSelfObject(); + addStatUpdateAction(context.mLuaManager, *obj); + obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, std::monostate{}, "progress" }] = value; + } + + SkillIncreasesForAttributeStats getSkillIncreasesForAttributeStats() const + { + return SkillIncreasesForAttributeStats{ mObject }; + } + + SkillIncreasesForSpecializationStats getSkillIncreasesForSpecializationStats() const + { + return SkillIncreasesForSpecializationStats{ mObject }; } static std::optional create(ObjectVariant object, Index) @@ -110,13 +223,6 @@ namespace MWLua return {}; return LevelStat{ std::move(object) }; } - - static void setValue(Index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) - { - auto& stats = ptr.getClass().getCreatureStats(ptr); - if (prop == "current") - stats.setLevel(LuaUtil::cast(value)); - } }; class DynamicStat @@ -319,10 +425,74 @@ namespace MWLua stats.setSkill(id, stat); } }; + + class AIStat + { + ObjectVariant mObject; + MWMechanics::AiSetting mIndex; + + AIStat(ObjectVariant object, MWMechanics::AiSetting index) + : mObject(std::move(object)) + , mIndex(index) + { + } + + public: + template + sol::object get(const Context& context, std::string_view prop, G getter) const + { + return getValue(context, mObject, &AIStat::setValue, static_cast(mIndex), prop, + [this, getter](const MWWorld::Ptr& ptr) { + return (ptr.getClass().getCreatureStats(ptr).getAiSetting(mIndex).*getter)(); + }); + } + + int getModified(const Context& context) const + { + auto base = LuaUtil::cast(get(context, "base", &MWMechanics::Stat::getBase)); + auto modifier = LuaUtil::cast(get(context, "modifier", &MWMechanics::Stat::getModifier)); + return std::max(0, base + modifier); + } + + static std::optional create(ObjectVariant object, MWMechanics::AiSetting index) + { + if (!object.ptr().getClass().isActor()) + return {}; + return AIStat{ std::move(object), index }; + } + + void cache(const Context& context, std::string_view prop, const sol::object& value) const + { + SelfObject* obj = mObject.asSelfObject(); + addStatUpdateAction(context.mLuaManager, *obj); + obj->mStatsCache[SelfObject::CachedStat{ &AIStat::setValue, static_cast(mIndex), prop }] = value; + } + + static void setValue(Index i, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) + { + auto index = static_cast(std::get(i)); + auto& stats = ptr.getClass().getCreatureStats(ptr); + auto stat = stats.getAiSetting(index); + int intValue = LuaUtil::cast(value); + if (prop == "base") + stat.setBase(intValue); + else if (prop == "modifier") + stat.setModifier(intValue); + stats.setAiSetting(index, stat); + } + }; } namespace sol { + template <> + struct is_automagical : std::false_type + { + }; + template <> + struct is_automagical : std::false_type + { + }; template <> struct is_automagical : std::false_type { @@ -351,6 +521,10 @@ namespace sol struct is_automagical : std::false_type { }; + template <> + struct is_automagical : std::false_type + { + }; } namespace MWLua @@ -360,10 +534,39 @@ namespace MWLua sol::table stats(context.mLua->sol(), sol::create); actor["stats"] = LuaUtil::makeReadOnly(stats); + auto skillIncreasesForAttributeStatsT + = context.mLua->sol().new_usertype("SkillIncreasesForAttributeStats"); + for (const auto& attribute : MWBase::Environment::get().getESMStore()->get()) + { + skillIncreasesForAttributeStatsT[ESM::RefId(attribute.mId).serializeText()] = sol::property( + [=](const SkillIncreasesForAttributeStats& stat) { return stat.get(context, attribute.mId); }, + [=](const SkillIncreasesForAttributeStats& stat, const sol::object& value) { + stat.set(context, attribute.mId, value); + }); + } + // ESM::Class::specializationIndexToLuaId.at(rec.mData.mSpecialization) + auto skillIncreasesForSpecializationStatsT + = context.mLua->sol().new_usertype( + "skillIncreasesForSpecializationStats"); + for (int i = 0; i < 3; i++) + { + std::string_view index = ESM::Class::specializationIndexToLuaId.at(i); + skillIncreasesForSpecializationStatsT[index] + = sol::property([=](const SkillIncreasesForSpecializationStats& stat) { return stat.get(context, i); }, + [=](const SkillIncreasesForSpecializationStats& stat, const sol::object& value) { + stat.set(context, i, value); + }); + } + auto levelStatT = context.mLua->sol().new_usertype("LevelStat"); levelStatT["current"] = sol::property([context](const LevelStat& stat) { return stat.getCurrent(context); }, [context](const LevelStat& stat, const sol::object& value) { stat.setCurrent(context, value); }); - levelStatT["progress"] = sol::property([context](const LevelStat& stat) { return stat.getProgress(context); }); + levelStatT["progress"] = sol::property([context](const LevelStat& stat) { return stat.getProgress(context); }, + [context](const LevelStat& stat, const sol::object& value) { stat.setProgress(context, value); }); + levelStatT["skillIncreasesForAttribute"] + = sol::readonly_property([](const LevelStat& stat) { return stat.getSkillIncreasesForAttributeStats(); }); + levelStatT["skillIncreasesForSpecialization"] = sol::readonly_property( + [](const LevelStat& stat) { return stat.getSkillIncreasesForSpecializationStats(); }); stats["level"] = addIndexedAccessor(0); auto dynamicStatT = context.mLua->sol().new_usertype("DynamicStat"); @@ -386,6 +589,17 @@ namespace MWLua stats["attributes"] = LuaUtil::makeReadOnly(attributes); for (const ESM::Attribute& attribute : MWBase::Environment::get().getESMStore()->get()) attributes[ESM::RefId(attribute.mId).serializeText()] = addIndexedAccessor(attribute.mId); + + auto aiStatT = context.mLua->sol().new_usertype("AIStat"); + addProp(context, aiStatT, "base", &MWMechanics::Stat::getBase); + addProp(context, aiStatT, "modifier", &MWMechanics::Stat::getModifier); + aiStatT["modified"] = sol::readonly_property([=](const AIStat& stat) { return stat.getModified(context); }); + sol::table ai(context.mLua->sol(), sol::create); + stats["ai"] = LuaUtil::makeReadOnly(ai); + ai["alarm"] = addIndexedAccessor(MWMechanics::AiSetting::Alarm); + ai["fight"] = addIndexedAccessor(MWMechanics::AiSetting::Fight); + ai["flee"] = addIndexedAccessor(MWMechanics::AiSetting::Flee); + ai["hello"] = addIndexedAccessor(MWMechanics::AiSetting::Hello); } void addNpcStatsBindings(sol::table& npc, const Context& context) @@ -461,6 +675,13 @@ namespace MWLua skillT["attribute"] = sol::readonly_property([](const ESM::Skill& rec) -> std::string { return ESM::Attribute::indexToRefId(rec.mData.mAttribute).serializeText(); }); + skillT["skillGain"] = sol::readonly_property([lua](const ESM::Skill& rec) -> sol::table { + sol::table res(lua, sol::create); + int index = 1; + for (auto skillGain : rec.mData.mUseValue) + res[index++] = skillGain; + return res; + }); auto schoolT = context.mLua->sol().new_usertype("MagicSchool"); schoolT[sol::meta_function::to_string] diff --git a/apps/openmw/mwlua/types/actor.cpp b/apps/openmw/mwlua/types/actor.cpp index 4fda04e7c5..3b0142e441 100644 --- a/apps/openmw/mwlua/types/actor.cpp +++ b/apps/openmw/mwlua/types/actor.cpp @@ -403,6 +403,11 @@ namespace MWLua return target.getClass().getCreatureStats(target).isDead(); }; + actor["isDeathFinished"] = [](const Object& o) { + const auto& target = o.ptr(); + return target.getClass().getCreatureStats(target).isDeathAnimationFinished(); + }; + actor["getEncumbrance"] = [](const Object& actor) -> float { const MWWorld::Ptr ptr = actor.ptr(); return ptr.getClass().getEncumbrance(ptr); diff --git a/apps/openmw/mwlua/types/ingredient.cpp b/apps/openmw/mwlua/types/ingredient.cpp index abfd2329ce..f7c3a8a050 100644 --- a/apps/openmw/mwlua/types/ingredient.cpp +++ b/apps/openmw/mwlua/types/ingredient.cpp @@ -47,15 +47,16 @@ namespace MWLua { if (rec.mData.mEffectID[i] < 0) continue; - ESM::ENAMstruct effect; - effect.mEffectID = rec.mData.mEffectID[i]; - effect.mSkill = rec.mData.mSkills[i]; - effect.mAttribute = rec.mData.mAttributes[i]; - effect.mRange = ESM::RT_Self; - effect.mArea = 0; - effect.mDuration = 0; - effect.mMagnMin = 0; - effect.mMagnMax = 0; + ESM::IndexedENAMstruct effect; + effect.mData.mEffectID = rec.mData.mEffectID[i]; + effect.mData.mSkill = rec.mData.mSkills[i]; + effect.mData.mAttribute = rec.mData.mAttributes[i]; + effect.mData.mRange = ESM::RT_Self; + effect.mData.mArea = 0; + effect.mData.mDuration = 0; + effect.mData.mMagnMin = 0; + effect.mData.mMagnMax = 0; + effect.mIndex = i; res[i + 1] = effect; } return res; diff --git a/apps/openmw/mwlua/types/item.cpp b/apps/openmw/mwlua/types/item.cpp index d1f80e44f4..8f05ce8e93 100644 --- a/apps/openmw/mwlua/types/item.cpp +++ b/apps/openmw/mwlua/types/item.cpp @@ -24,6 +24,8 @@ namespace MWLua item["isRestocking"] = [](const Object& object) -> bool { return object.ptr().getCellRef().getCount(false) < 0; }; + item["isCarriable"] = [](const Object& object) -> bool { return object.ptr().getClass().isItem(object.ptr()); }; + addItemDataBindings(item, context); } } diff --git a/apps/openmw/mwlua/types/npc.cpp b/apps/openmw/mwlua/types/npc.cpp index b1ac3d994a..29147a826b 100644 --- a/apps/openmw/mwlua/types/npc.cpp +++ b/apps/openmw/mwlua/types/npc.cpp @@ -15,6 +15,7 @@ #include "../classbindings.hpp" #include "../localscripts.hpp" +#include "../racebindings.hpp" #include "../stats.hpp" namespace sol @@ -85,7 +86,8 @@ namespace MWLua record["baseGold"] = sol::readonly_property([](const ESM::NPC& rec) -> int { return rec.mNpdt.mGold; }); addActorServicesBindings(record, context); - npc["classes"] = initCoreClassBindings(context); + npc["classes"] = initClassRecordBindings(context); + npc["races"] = initRaceRecordBindings(context); // This function is game-specific, in future we should replace it with something more universal. npc["isWerewolf"] = [](const Object& o) { diff --git a/apps/openmw/mwlua/types/player.cpp b/apps/openmw/mwlua/types/player.cpp index d2a9c5d920..b2befe89de 100644 --- a/apps/openmw/mwlua/types/player.cpp +++ b/apps/openmw/mwlua/types/player.cpp @@ -1,5 +1,6 @@ #include "types.hpp" +#include "../birthsignbindings.hpp" #include "../luamanagerimp.hpp" #include "apps/openmw/mwbase/inputmanager.hpp" @@ -7,7 +8,11 @@ #include "apps/openmw/mwbase/world.hpp" #include "apps/openmw/mwmechanics/npcstats.hpp" #include "apps/openmw/mwworld/class.hpp" +#include "apps/openmw/mwworld/esmstore.hpp" #include "apps/openmw/mwworld/globals.hpp" +#include "apps/openmw/mwworld/player.hpp" + +#include namespace MWLua { @@ -34,6 +39,20 @@ namespace sol }; } +namespace +{ + ESM::RefId toBirthSignId(const sol::object& recordOrId) + { + if (recordOrId.is()) + return recordOrId.as()->mId; + std::string_view textId = LuaUtil::cast(recordOrId); + ESM::RefId id = ESM::RefId::deserializeText(textId); + if (!MWBase::Environment::get().getESMStore()->get().search(id)) + throw std::runtime_error("Failed to find birth sign: " + std::string(textId)); + return id; + } +} + namespace MWLua { static void verifyPlayer(const Object& player) @@ -167,8 +186,29 @@ namespace MWLua const MWWorld::Class& cls = o.ptr().getClass(); return cls.getNpcStats(o.ptr()).getBounty(); }; + player["setCrimeLevel"] = [](const Object& o, int amount) { + verifyPlayer(o); + if (!dynamic_cast(&o)) + throw std::runtime_error("Only global scripts can change crime level"); + const MWWorld::Class& cls = o.ptr().getClass(); + cls.getNpcStats(o.ptr()).setBounty(amount); + if (amount == 0) + MWBase::Environment::get().getWorld()->getPlayer().recordCrimeId(); + }; player["isCharGenFinished"] = [](const Object&) -> bool { return MWBase::Environment::get().getWorld()->getGlobalFloat(MWWorld::Globals::sCharGenState) == -1; }; + + player["birthSigns"] = initBirthSignRecordBindings(context); + player["getBirthSign"] = [](const Object& player) -> std::string { + verifyPlayer(player); + return MWBase::Environment::get().getWorld()->getPlayer().getBirthSign().serializeText(); + }; + player["setBirthSign"] = [](const Object& player, const sol::object& recordOrId) { + verifyPlayer(player); + if (!dynamic_cast(&player)) + throw std::runtime_error("Only global scripts can change birth signs"); + MWBase::Environment::get().getWorld()->getPlayer().setBirthSign(toBirthSignId(recordOrId)); + }; } } diff --git a/apps/openmw/mwlua/types/potion.cpp b/apps/openmw/mwlua/types/potion.cpp index 50aca6d9e7..d686bdb1f7 100644 --- a/apps/openmw/mwlua/types/potion.cpp +++ b/apps/openmw/mwlua/types/potion.cpp @@ -46,7 +46,10 @@ namespace size_t numEffects = effectsTable.size(); potion.mEffects.mList.resize(numEffects); for (size_t i = 0; i < numEffects; ++i) - potion.mEffects.mList[i] = LuaUtil::cast(effectsTable[i + 1]); + { + potion.mEffects.mList[i] = LuaUtil::cast(effectsTable[i + 1]); + } + potion.mEffects.updateIndexes(); } return potion; } @@ -83,7 +86,7 @@ namespace MWLua record["effects"] = sol::readonly_property([context](const ESM::Potion& rec) -> sol::table { sol::table res(context.mLua->sol(), sol::create); for (size_t i = 0; i < rec.mEffects.mList.size(); ++i) - res[i + 1] = rec.mEffects.mList[i]; // ESM::ENAMstruct (effect params) + res[i + 1] = rec.mEffects.mList[i]; // ESM::IndexedENAMstruct (effect params) return res; }); } diff --git a/apps/openmw/mwlua/types/types.hpp b/apps/openmw/mwlua/types/types.hpp index b52846508a..76bd2848e0 100644 --- a/apps/openmw/mwlua/types/types.hpp +++ b/apps/openmw/mwlua/types/types.hpp @@ -6,22 +6,8 @@ #include #include -#include "apps/openmw/mwbase/environment.hpp" -#include "apps/openmw/mwworld/esmstore.hpp" -#include "apps/openmw/mwworld/store.hpp" - #include "../context.hpp" -#include "../object.hpp" - -namespace sol -{ - // Ensure sol does not try to create the automatic Container or usertype bindings for Store. - // They include write operations and we want the store to be read-only. - template - struct is_automagical> : std::false_type - { - }; -} +#include "../recordstore.hpp" namespace MWLua { @@ -68,36 +54,6 @@ namespace MWLua void addESM4DoorBindings(sol::table door, const Context& context); void addESM4TerminalBindings(sol::table term, const Context& context); - - template - void addRecordFunctionBinding( - sol::table& table, const Context& context, const std::string& recordName = std::string(T::getRecordType())) - { - const MWWorld::Store& store = MWBase::Environment::get().getESMStore()->get(); - - table["record"] = sol::overload([](const Object& obj) -> const T* { return obj.ptr().get()->mBase; }, - [&store](std::string_view id) -> const T* { return store.search(ESM::RefId::deserializeText(id)); }); - - // Define a custom user type for the store. - // Provide the interface of a read-only array. - using StoreT = MWWorld::Store; - sol::state_view& lua = context.mLua->sol(); - sol::usertype storeT = lua.new_usertype(recordName + "WorldStore"); - storeT[sol::meta_function::to_string] = [recordName](const StoreT& store) { - return "{" + std::to_string(store.getSize()) + " " + recordName + " records}"; - }; - storeT[sol::meta_function::length] = [](const StoreT& store) { return store.getSize(); }; - storeT[sol::meta_function::index] = [](const StoreT& store, size_t index) -> const T* { - if (index == 0 || index > store.getSize()) - return nullptr; - return store.at(index - 1); // Translate from Lua's 1-based indexing. - }; - storeT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); - storeT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); - - // Provide access to the store. - table["records"] = &store; - } } #endif // MWLUA_TYPES_H diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index f21fdb337a..a8df03ba25 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -134,7 +134,10 @@ namespace MWLua }; api["updateAll"] = [luaManager = context.mLuaManager, menu]() { - LuaUi::Element::forEach(menu, [](LuaUi::Element* e) { e->mUpdate = true; }); + LuaUi::Element::forEach(menu, [](LuaUi::Element* e) { + if (e->mState == LuaUi::Element::Created) + e->mState = LuaUi::Element::Update; + }); luaManager->addAction([menu]() { LuaUi::Element::forEach(menu, [](LuaUi::Element* e) { e->update(); }); }, "Update all menu UI elements"); }; @@ -305,15 +308,15 @@ namespace MWLua element["layout"] = sol::property([](const LuaUi::Element& element) { return element.mLayout; }, [](LuaUi::Element& element, const sol::table& layout) { element.mLayout = layout; }); element["update"] = [luaManager = context.mLuaManager](const std::shared_ptr& element) { - if (element->mDestroy || element->mUpdate) + if (element->mState != LuaUi::Element::Created) return; - element->mUpdate = true; + element->mState = LuaUi::Element::Update; luaManager->addAction([element] { wrapAction(element, [&] { element->update(); }); }, "Update UI"); }; element["destroy"] = [luaManager = context.mLuaManager](const std::shared_ptr& element) { - if (element->mDestroy) + if (element->mState == LuaUi::Element::Destroyed) return; - element->mDestroy = true; + element->mState = LuaUi::Element::Destroy; luaManager->addAction( [element] { wrapAction(element, [&] { LuaUi::Element::erase(element.get()); }); }, "Destroy UI"); }; diff --git a/apps/openmw/mwlua/vfsbindings.cpp b/apps/openmw/mwlua/vfsbindings.cpp index c9b1a45fe2..0e13c07ef9 100644 --- a/apps/openmw/mwlua/vfsbindings.cpp +++ b/apps/openmw/mwlua/vfsbindings.cpp @@ -1,6 +1,9 @@ #include "vfsbindings.hpp" +#include + #include +#include #include #include #include @@ -10,7 +13,6 @@ #include "../mwbase/environment.hpp" #include "context.hpp" -#include "luamanagerimp.hpp" namespace MWLua { @@ -328,7 +330,8 @@ namespace MWLua }, [](const sol::object&) -> sol::object { return sol::nil; }); - api["fileExists"] = [vfs](std::string_view fileName) -> bool { return vfs->exists(fileName); }; + api["fileExists"] + = [vfs](std::string_view fileName) -> bool { return vfs->exists(VFS::Path::Normalized(fileName)); }; api["pathsWithPrefix"] = [vfs](std::string_view prefix) { auto iterator = vfs->getRecursiveDirectoryIterator(prefix); return sol::as_function([iterator, current = iterator.begin()]() mutable -> sol::optional { diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 937e7a6658..2fb7df61c1 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -8,6 +8,7 @@ #include +#include #include #include #include @@ -49,16 +50,15 @@ namespace void addEffects( std::vector& effects, const ESM::EffectList& list, bool ignoreResistances = false) { - int currentEffectIndex = 0; for (const auto& enam : list.mList) { ESM::ActiveEffect effect; - effect.mEffectId = enam.mEffectID; - effect.mArg = MWMechanics::EffectKey(enam).mArg; + effect.mEffectId = enam.mData.mEffectID; + effect.mArg = MWMechanics::EffectKey(enam.mData).mArg; effect.mMagnitude = 0.f; - effect.mMinMagnitude = enam.mMagnMin; - effect.mMaxMagnitude = enam.mMagnMax; - effect.mEffectIndex = currentEffectIndex++; + effect.mMinMagnitude = enam.mData.mMagnMin; + effect.mMaxMagnitude = enam.mData.mMagnMax; + effect.mEffectIndex = enam.mIndex; effect.mFlags = ESM::ActiveEffect::Flag_None; if (ignoreResistances) effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_Resistances; @@ -82,12 +82,13 @@ namespace MWMechanics mActiveSpells.mIterating = false; } - ActiveSpells::ActiveSpellParams::ActiveSpellParams(const CastSpell& cast, const MWWorld::Ptr& caster) - : mId(cast.mId) - , mDisplayName(cast.mSourceName) + ActiveSpells::ActiveSpellParams::ActiveSpellParams( + const MWWorld::Ptr& caster, const ESM::RefId& id, std::string_view sourceName, ESM::RefNum item) + : mSourceSpellId(id) + , mDisplayName(sourceName) , mCasterActorId(-1) - , mItem(cast.mItem) - , mType(cast.mType) + , mItem(item) + , mFlags() , mWorsenings(-1) { if (!caster.isEmpty() && caster.getClass().isActor()) @@ -96,48 +97,52 @@ namespace MWMechanics ActiveSpells::ActiveSpellParams::ActiveSpellParams( const ESM::Spell* spell, const MWWorld::Ptr& actor, bool ignoreResistances) - : mId(spell->mId) + : mSourceSpellId(spell->mId) , mDisplayName(spell->mName) , mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId()) - , mType(spell->mData.mType == ESM::Spell::ST_Ability ? ESM::ActiveSpells::Type_Ability - : ESM::ActiveSpells::Type_Permanent) + , mFlags() , mWorsenings(-1) { assert(spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power); + setFlag(ESM::ActiveSpells::Flag_SpellStore); + if (spell->mData.mType == ESM::Spell::ST_Ability) + setFlag(ESM::ActiveSpells::Flag_AffectsBaseValues); addEffects(mEffects, spell->mEffects, ignoreResistances); } ActiveSpells::ActiveSpellParams::ActiveSpellParams( const MWWorld::ConstPtr& item, const ESM::Enchantment* enchantment, const MWWorld::Ptr& actor) - : mId(item.getCellRef().getRefId()) + : mSourceSpellId(item.getCellRef().getRefId()) , mDisplayName(item.getClass().getName(item)) , mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId()) , mItem(item.getCellRef().getRefNum()) - , mType(ESM::ActiveSpells::Type_Enchantment) + , mFlags() , mWorsenings(-1) { assert(enchantment->mData.mType == ESM::Enchantment::ConstantEffect); addEffects(mEffects, enchantment->mEffects); + setFlag(ESM::ActiveSpells::Flag_Equipment); } ActiveSpells::ActiveSpellParams::ActiveSpellParams(const ESM::ActiveSpells::ActiveSpellParams& params) - : mId(params.mId) + : mActiveSpellId(params.mActiveSpellId) + , mSourceSpellId(params.mSourceSpellId) , mEffects(params.mEffects) , mDisplayName(params.mDisplayName) , mCasterActorId(params.mCasterActorId) , mItem(params.mItem) - , mType(params.mType) + , mFlags(params.mFlags) , mWorsenings(params.mWorsenings) , mNextWorsening({ params.mNextWorsening }) { } ActiveSpells::ActiveSpellParams::ActiveSpellParams(const ActiveSpellParams& params, const MWWorld::Ptr& actor) - : mId(params.mId) + : mSourceSpellId(params.mSourceSpellId) , mDisplayName(params.mDisplayName) , mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId()) , mItem(params.mItem) - , mType(params.mType) + , mFlags(params.mFlags) , mWorsenings(-1) { } @@ -145,17 +150,23 @@ namespace MWMechanics ESM::ActiveSpells::ActiveSpellParams ActiveSpells::ActiveSpellParams::toEsm() const { ESM::ActiveSpells::ActiveSpellParams params; - params.mId = mId; + params.mActiveSpellId = mActiveSpellId; + params.mSourceSpellId = mSourceSpellId; params.mEffects = mEffects; params.mDisplayName = mDisplayName; params.mCasterActorId = mCasterActorId; params.mItem = mItem; - params.mType = mType; + params.mFlags = mFlags; params.mWorsenings = mWorsenings; params.mNextWorsening = mNextWorsening.toEsm(); return params; } + void ActiveSpells::ActiveSpellParams::setFlag(ESM::ActiveSpells::Flags flag) + { + mFlags = static_cast(mFlags | flag); + } + void ActiveSpells::ActiveSpellParams::worsen() { ++mWorsenings; @@ -178,21 +189,31 @@ namespace MWMechanics { // Enchantment id is not stored directly. Instead the enchanted item is stored. const auto& store = MWBase::Environment::get().getESMStore(); - switch (store->find(mId)) + switch (store->find(mSourceSpellId)) { case ESM::REC_ARMO: - return store->get().find(mId)->mEnchant; + return store->get().find(mSourceSpellId)->mEnchant; case ESM::REC_BOOK: - return store->get().find(mId)->mEnchant; + return store->get().find(mSourceSpellId)->mEnchant; case ESM::REC_CLOT: - return store->get().find(mId)->mEnchant; + return store->get().find(mSourceSpellId)->mEnchant; case ESM::REC_WEAP: - return store->get().find(mId)->mEnchant; + return store->get().find(mSourceSpellId)->mEnchant; default: return {}; } } + const ESM::Spell* ActiveSpells::ActiveSpellParams::getSpell() const + { + return MWBase::Environment::get().getESMStore()->get().search(getSourceSpellId()); + } + + bool ActiveSpells::ActiveSpellParams::hasFlag(ESM::ActiveSpells::Flags flags) const + { + return static_cast(mFlags & flags) == flags; + } + void ActiveSpells::update(const MWWorld::Ptr& ptr, float duration) { if (mIterating) @@ -203,8 +224,7 @@ namespace MWMechanics // Erase no longer active spells and effects for (auto spellIt = mSpells.begin(); spellIt != mSpells.end();) { - if (spellIt->mType != ESM::ActiveSpells::Type_Temporary - && spellIt->mType != ESM::ActiveSpells::Type_Consumable) + if (!spellIt->hasFlag(ESM::ActiveSpells::Flag_Temporary)) { ++spellIt; continue; @@ -244,7 +264,10 @@ namespace MWMechanics { if (spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power && !isSpellActive(spell->mId)) + { mSpells.emplace_back(ActiveSpellParams{ spell, ptr }); + mSpells.back().setActiveSpellId(MWBase::Environment::get().getESMStore()->generateId()); + } } bool updateSpellWindow = false; @@ -270,8 +293,8 @@ namespace MWMechanics if (std::find_if(mSpells.begin(), mSpells.end(), [&](const ActiveSpellParams& params) { return params.mItem == slot->getCellRef().getRefNum() - && params.mType == ESM::ActiveSpells::Type_Enchantment - && params.mId == slot->getCellRef().getRefId(); + && params.hasFlag(ESM::ActiveSpells::Flag_Equipment) + && params.mSourceSpellId == slot->getCellRef().getRefId(); }) != mSpells.end()) continue; @@ -279,8 +302,8 @@ namespace MWMechanics // invisibility manually purgeEffect(ptr, ESM::MagicEffect::Invisibility); applyPurges(ptr); - const ActiveSpellParams& params - = mSpells.emplace_back(ActiveSpellParams{ *slot, enchantment, ptr }); + ActiveSpellParams& params = mSpells.emplace_back(ActiveSpellParams{ *slot, enchantment, ptr }); + params.setActiveSpellId(MWBase::Environment::get().getESMStore()->generateId()); for (const auto& effect : params.mEffects) MWMechanics::playEffects( ptr, *world->getStore().get().find(effect.mEffectId), playNonLooping); @@ -350,12 +373,11 @@ namespace MWMechanics continue; bool remove = false; - if (spellIt->mType == ESM::ActiveSpells::Type_Ability - || spellIt->mType == ESM::ActiveSpells::Type_Permanent) + if (spellIt->hasFlag(ESM::ActiveSpells::Flag_SpellStore)) { try { - remove = !spells.hasSpell(spellIt->mId); + remove = !spells.hasSpell(spellIt->mSourceSpellId); } catch (const std::runtime_error& e) { @@ -363,9 +385,9 @@ namespace MWMechanics Log(Debug::Error) << "Removing active effect: " << e.what(); } } - else if (spellIt->mType == ESM::ActiveSpells::Type_Enchantment) + else if (spellIt->hasFlag(ESM::ActiveSpells::Flag_Equipment)) { - // Remove constant effect enchantments that have been unequipped + // Remove effects tied to equipment that has been unequipped const auto& store = ptr.getClass().getInventoryStore(ptr); remove = true; for (int slotIndex = 0; slotIndex < MWWorld::InventoryStore::Slots; slotIndex++) @@ -411,11 +433,11 @@ namespace MWMechanics void ActiveSpells::addToSpells(const MWWorld::Ptr& ptr, const ActiveSpellParams& spell) { - if (spell.mType != ESM::ActiveSpells::Type_Consumable) + if (!spell.hasFlag(ESM::ActiveSpells::Flag_Stackable)) { auto found = std::find_if(mSpells.begin(), mSpells.end(), [&](const auto& existing) { - return spell.mId == existing.mId && spell.mCasterActorId == existing.mCasterActorId - && spell.mItem == existing.mItem; + return spell.mSourceSpellId == existing.mSourceSpellId + && spell.mCasterActorId == existing.mCasterActorId && spell.mItem == existing.mItem; }); if (found != mSpells.end()) { @@ -428,6 +450,7 @@ namespace MWMechanics } } mSpells.emplace_back(spell); + mSpells.back().setActiveSpellId(MWBase::Environment::get().getESMStore()->generateId()); } ActiveSpells::ActiveSpells() @@ -445,10 +468,19 @@ namespace MWMechanics return mSpells.end(); } + ActiveSpells::TIterator ActiveSpells::getActiveSpellById(const ESM::RefId& id) + { + for (TIterator it = begin(); it != end(); it++) + if (it->getActiveSpellId() == id) + return it; + return end(); + } + bool ActiveSpells::isSpellActive(const ESM::RefId& id) const { - return std::find_if(mSpells.begin(), mSpells.end(), [&](const auto& spell) { return spell.mId == id; }) - != mSpells.end(); + return std::find_if(mSpells.begin(), mSpells.end(), [&](const auto& spell) { + return spell.mSourceSpellId == id; + }) != mSpells.end(); } bool ActiveSpells::isEnchantmentActive(const ESM::RefId& id) const @@ -557,9 +589,14 @@ namespace MWMechanics return removedCurrentSpell; } - void ActiveSpells::removeEffects(const MWWorld::Ptr& ptr, const ESM::RefId& id) + void ActiveSpells::removeEffectsBySourceSpellId(const MWWorld::Ptr& ptr, const ESM::RefId& id) + { + purge([=](const ActiveSpellParams& params) { return params.mSourceSpellId == id; }, ptr); + } + + void ActiveSpells::removeEffectsByActiveSpellId(const MWWorld::Ptr& ptr, const ESM::RefId& id) { - purge([=](const ActiveSpellParams& params) { return params.mId == id; }, ptr); + purge([=](const ActiveSpellParams& params) { return params.mActiveSpellId == id; }, ptr); } void ActiveSpells::purgeEffect(const MWWorld::Ptr& ptr, int effectId, ESM::RefId effectArg) @@ -604,19 +641,19 @@ namespace MWMechanics void ActiveSpells::readState(const ESM::ActiveSpells& state) { for (const ESM::ActiveSpells::ActiveSpellParams& spell : state.mSpells) + { mSpells.emplace_back(ActiveSpellParams{ spell }); + // Generate ID for older saves that didn't have any. + if (mSpells.back().getActiveSpellId().empty()) + mSpells.back().setActiveSpellId(MWBase::Environment::get().getESMStore()->generateId()); + } for (const ESM::ActiveSpells::ActiveSpellParams& spell : state.mQueue) mQueue.emplace_back(ActiveSpellParams{ spell }); } void ActiveSpells::unloadActor(const MWWorld::Ptr& ptr) { - purge( - [](const auto& spell) { - return spell.getType() == ESM::ActiveSpells::Type_Consumable - || spell.getType() == ESM::ActiveSpells::Type_Temporary; - }, - ptr); + purge([](const auto& spell) { return spell.hasFlag(ESM::ActiveSpells::Flag_Temporary); }, ptr); mQueue.clear(); } } diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index a505b8990a..e4fa60ddb6 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -33,12 +33,13 @@ namespace MWMechanics using ActiveEffect = ESM::ActiveEffect; class ActiveSpellParams { - ESM::RefId mId; + ESM::RefId mActiveSpellId; + ESM::RefId mSourceSpellId; std::vector mEffects; std::string mDisplayName; int mCasterActorId; ESM::RefNum mItem; - ESM::ActiveSpells::EffectType mType; + ESM::ActiveSpells::Flags mFlags; int mWorsenings; MWWorld::TimeStamp mNextWorsening; MWWorld::Ptr mSource; @@ -57,15 +58,17 @@ namespace MWMechanics friend class ActiveSpells; public: - ActiveSpellParams(const CastSpell& cast, const MWWorld::Ptr& caster); + ActiveSpellParams( + const MWWorld::Ptr& caster, const ESM::RefId& id, std::string_view sourceName, ESM::RefNum item); + + ESM::RefId getActiveSpellId() const { return mActiveSpellId; } + void setActiveSpellId(ESM::RefId id) { mActiveSpellId = id; } - const ESM::RefId& getId() const { return mId; } + const ESM::RefId& getSourceSpellId() const { return mSourceSpellId; } const std::vector& getEffects() const { return mEffects; } std::vector& getEffects() { return mEffects; } - ESM::ActiveSpells::EffectType getType() const { return mType; } - int getCasterActorId() const { return mCasterActorId; } int getWorsenings() const { return mWorsenings; } @@ -75,6 +78,10 @@ namespace MWMechanics ESM::RefNum getItem() const { return mItem; } ESM::RefId getEnchantment() const; + const ESM::Spell* getSpell() const; + bool hasFlag(ESM::ActiveSpells::Flags flags) const; + void setFlag(ESM::ActiveSpells::Flags flags); + // Increments worsenings count and sets the next timestamp void worsen(); @@ -93,6 +100,8 @@ namespace MWMechanics TIterator end() const; + TIterator getActiveSpellById(const ESM::RefId& id); + void update(const MWWorld::Ptr& ptr, float duration); private: @@ -132,7 +141,9 @@ namespace MWMechanics void addSpell(const ESM::Spell* spell, const MWWorld::Ptr& actor); /// Removes the active effects from this spell/potion/.. with \a id - void removeEffects(const MWWorld::Ptr& ptr, const ESM::RefId& id); + void removeEffectsBySourceSpellId(const MWWorld::Ptr& ptr, const ESM::RefId& id); + /// Removes the active effects of a specific active spell + void removeEffectsByActiveSpellId(const MWWorld::Ptr& ptr, const ESM::RefId& id); /// Remove all active effects with this effect id void purgeEffect(const MWWorld::Ptr& ptr, int effectId, ESM::RefId effectArg = {}); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index d463fa729b..32f81c398e 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -39,6 +39,8 @@ #include "../mwrender/vismask.hpp" +#include "../mwsound/constants.hpp" + #include "actor.hpp" #include "actorutil.hpp" #include "aicombataction.hpp" @@ -1228,11 +1230,11 @@ namespace MWMechanics } } - void Actors::castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool manualSpell) const + void Actors::castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool scriptedSpell) const { const auto iter = mIndex.find(ptr.mRef); if (iter != mIndex.end()) - iter->second->getCharacterController().castSpell(spellId, manualSpell); + iter->second->getCharacterController().castSpell(spellId, scriptedSpell); } bool Actors::isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) const @@ -1309,7 +1311,8 @@ namespace MWMechanics if (inProcessingRange) { MWMechanics::CreatureStats& stats = actor.getPtr().getClass().getCreatureStats(actor.getPtr()); - if (!stats.isDead() && stats.getAiSequence().isInCombat()) + bool isDead = stats.isDead() && stats.isDeathAnimationFinished(); + if (!isDead && stats.getAiSequence().isInCombat()) { hasHostiles = true; break; @@ -1797,7 +1800,7 @@ namespace MWMechanics MWBase::Environment::get().getStateManager()->askLoadRecent(); // Play Death Music if it was the player dying MWBase::Environment::get().getSoundManager()->streamMusic( - "Music/Special/MW_Death.mp3", MWSound::MusicType::Special); + MWSound::deathMusic, MWSound::MusicType::Special); } else { @@ -1955,7 +1958,7 @@ namespace MWMechanics mSneakSkillTimer = 0.f; if (avoidedNotice && mSneakSkillTimer == 0.f) - player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, 0); + player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, ESM::Skill::Sneak_AvoidNotice); if (!detected) MWBase::Environment::get().getWindowManager()->setSneakVisibility(true); diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 14c55c4e45..2821df43e6 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -67,7 +67,7 @@ namespace MWMechanics void resurrect(const MWWorld::Ptr& ptr) const; - void castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool manualSpell = false) const; + void castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool scriptedSpell = false) const; void updateActor(const MWWorld::Ptr& old, const MWWorld::Ptr& ptr) const; ///< Updates an actor with a new Ptr diff --git a/apps/openmw/mwmechanics/aicast.cpp b/apps/openmw/mwmechanics/aicast.cpp index 249ca97326..6384d70c06 100644 --- a/apps/openmw/mwmechanics/aicast.cpp +++ b/apps/openmw/mwmechanics/aicast.cpp @@ -25,11 +25,11 @@ namespace MWMechanics } } -MWMechanics::AiCast::AiCast(const ESM::RefId& targetId, const ESM::RefId& spellId, bool manualSpell) +MWMechanics::AiCast::AiCast(const ESM::RefId& targetId, const ESM::RefId& spellId, bool scriptedSpell) : mTargetId(targetId) , mSpellId(spellId) , mCasting(false) - , mManual(manualSpell) + , mScripted(scriptedSpell) , mDistance(getInitialDistance(spellId)) { } @@ -49,7 +49,7 @@ bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::Charac if (target.isEmpty()) return true; - if (!mManual + if (!mScripted && !pathTo(actor, target.getRefData().getPosition().asVec3(), duration, characterController.getSupportedMovementDirections(), mDistance)) { @@ -85,7 +85,7 @@ bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::Charac if (!mCasting) { - MWBase::Environment::get().getMechanicsManager()->castSpell(actor, mSpellId, mManual); + MWBase::Environment::get().getMechanicsManager()->castSpell(actor, mSpellId, mScripted); mCasting = true; return false; } diff --git a/apps/openmw/mwmechanics/aicast.hpp b/apps/openmw/mwmechanics/aicast.hpp index 435458cc0f..649c5a4d34 100644 --- a/apps/openmw/mwmechanics/aicast.hpp +++ b/apps/openmw/mwmechanics/aicast.hpp @@ -15,7 +15,7 @@ namespace MWMechanics class AiCast final : public TypedAiPackage { public: - AiCast(const ESM::RefId& targetId, const ESM::RefId& spellId, bool manualSpell = false); + AiCast(const ESM::RefId& targetId, const ESM::RefId& spellId, bool scriptedSpell = false); bool execute(const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) override; @@ -37,7 +37,7 @@ namespace MWMechanics const ESM::RefId mTargetId; const ESM::RefId mSpellId; bool mCasting; - const bool mManual; + const bool mScripted; const float mDistance; }; } diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 0b3c2b8bd2..2399961a3a 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -275,7 +275,7 @@ namespace MWMechanics if (!spellId.empty()) { const ESM::Spell* spell = MWBase::Environment::get().getESMStore()->get().find(spellId); - if (spell->mEffects.mList.empty() || spell->mEffects.mList[0].mRange != ESM::RT_Target) + if (spell->mEffects.mList.empty() || spell->mEffects.mList[0].mData.mRange != ESM::RT_Target) canShout = false; } storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat, canShout); diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index 563bd8b8cd..91d2a9bbb8 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -355,14 +355,14 @@ namespace MWMechanics { const ESM::Spell* spell = MWBase::Environment::get().getESMStore()->get().find(selectedSpellId); - for (std::vector::const_iterator effectIt = spell->mEffects.mList.begin(); + for (std::vector::const_iterator effectIt = spell->mEffects.mList.begin(); effectIt != spell->mEffects.mList.end(); ++effectIt) { - if (effectIt->mRange == ESM::RT_Target) + if (effectIt->mData.mRange == ESM::RT_Target) { const ESM::MagicEffect* effect = MWBase::Environment::get().getESMStore()->get().find( - effectIt->mEffectID); + effectIt->mData.mEffectID); dist = effect->mData.mSpeed; break; } @@ -375,14 +375,14 @@ namespace MWMechanics { const ESM::Enchantment* ench = MWBase::Environment::get().getESMStore()->get().find(enchId); - for (std::vector::const_iterator effectIt = ench->mEffects.mList.begin(); + for (std::vector::const_iterator effectIt = ench->mEffects.mList.begin(); effectIt != ench->mEffects.mList.end(); ++effectIt) { - if (effectIt->mRange == ESM::RT_Target) + if (effectIt->mData.mRange == ESM::RT_Target) { const ESM::MagicEffect* effect = MWBase::Environment::get().getESMStore()->get().find( - effectIt->mEffectID); + effectIt->mData.mEffectID); dist = effect->mData.mSpeed; break; } diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index be2601dc37..38466c0c4c 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -86,7 +86,7 @@ namespace MWMechanics return MWBase::Environment::get() .getWorld() ->getRayCasting() - ->castRay(position, visibleDestination, actor, {}, mask) + ->castRay(position, visibleDestination, { actor }, {}, mask) .mHit; } diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index aea3e36632..4be48296a9 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -250,7 +250,7 @@ const ESM::Potion* MWMechanics::Alchemy::getRecord(const ESM::Potion& toFind) co if (iter->mName != toFind.mName || iter->mScript != toFind.mScript || iter->mData.mWeight != toFind.mData.mWeight || iter->mData.mValue != toFind.mData.mValue - || iter->mData.mAutoCalc != toFind.mData.mAutoCalc) + || iter->mData.mFlags != toFind.mData.mFlags) continue; // Don't choose an ID that came from the content files, would have unintended side effects @@ -262,13 +262,13 @@ const ESM::Potion* MWMechanics::Alchemy::getRecord(const ESM::Potion& toFind) co for (size_t i = 0; i < iter->mEffects.mList.size(); ++i) { - const ESM::ENAMstruct& first = iter->mEffects.mList[i]; + const ESM::IndexedENAMstruct& first = iter->mEffects.mList[i]; const ESM::ENAMstruct& second = mEffects[i]; - if (first.mEffectID != second.mEffectID || first.mArea != second.mArea || first.mRange != second.mRange - || first.mSkill != second.mSkill || first.mAttribute != second.mAttribute - || first.mMagnMin != second.mMagnMin || first.mMagnMax != second.mMagnMax - || first.mDuration != second.mDuration) + if (first.mData.mEffectID != second.mEffectID || first.mData.mArea != second.mArea + || first.mData.mRange != second.mRange || first.mData.mSkill != second.mSkill + || first.mData.mAttribute != second.mAttribute || first.mData.mMagnMin != second.mMagnMin + || first.mData.mMagnMax != second.mMagnMax || first.mData.mDuration != second.mDuration) { mismatch = true; break; @@ -310,7 +310,7 @@ void MWMechanics::Alchemy::addPotion(const std::string& name) newRecord.mData.mWeight /= countIngredients(); newRecord.mData.mValue = mValue; - newRecord.mData.mAutoCalc = 0; + newRecord.mData.mFlags = 0; newRecord.mRecordFlags = 0; newRecord.mName = name; @@ -324,7 +324,7 @@ void MWMechanics::Alchemy::addPotion(const std::string& name) newRecord.mModel = "m\\misc_potion_" + std::string(meshes[index]) + "_01.nif"; newRecord.mIcon = "m\\tx_potion_" + std::string(meshes[index]) + "_01.dds"; - newRecord.mEffects.mList = mEffects; + newRecord.mEffects.populate(mEffects); const ESM::Potion* record = getRecord(newRecord); if (!record) @@ -335,7 +335,7 @@ void MWMechanics::Alchemy::addPotion(const std::string& name) void MWMechanics::Alchemy::increaseSkill() { - mAlchemist.getClass().skillUsageSucceeded(mAlchemist, ESM::Skill::Alchemy, 0); + mAlchemist.getClass().skillUsageSucceeded(mAlchemist, ESM::Skill::Alchemy, ESM::Skill::Alchemy_CreatePotion); } float MWMechanics::Alchemy::getAlchemyFactor() const diff --git a/apps/openmw/mwmechanics/autocalcspell.cpp b/apps/openmw/mwmechanics/autocalcspell.cpp index a2f6d479f1..5bab25fbe5 100644 --- a/apps/openmw/mwmechanics/autocalcspell.cpp +++ b/apps/openmw/mwmechanics/autocalcspell.cpp @@ -221,7 +221,7 @@ namespace MWMechanics for (const auto& spellEffect : spell->mEffects.mList) { const ESM::MagicEffect* magicEffect - = MWBase::Environment::get().getESMStore()->get().find(spellEffect.mEffectID); + = MWBase::Environment::get().getESMStore()->get().find(spellEffect.mData.mEffectID); static const int iAutoSpellAttSkillMin = MWBase::Environment::get() .getESMStore() ->get() @@ -230,7 +230,7 @@ namespace MWMechanics if ((magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill)) { - ESM::RefId skill = ESM::Skill::indexToRefId(spellEffect.mSkill); + ESM::RefId skill = ESM::Skill::indexToRefId(spellEffect.mData.mSkill); auto found = actorSkills.find(skill); if (found == actorSkills.end() || found->second.getBase() < iAutoSpellAttSkillMin) return false; @@ -238,7 +238,7 @@ namespace MWMechanics if ((magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute)) { - ESM::RefId attribute = ESM::Attribute::indexToRefId(spellEffect.mAttribute); + ESM::RefId attribute = ESM::Attribute::indexToRefId(spellEffect.mData.mAttribute); auto found = actorAttributes.find(attribute); if (found == actorAttributes.end() || found->second.getBase() < iAutoSpellAttSkillMin) return false; @@ -253,22 +253,22 @@ namespace MWMechanics { // Morrowind for some reason uses a formula slightly different from magicka cost calculation float minChance = std::numeric_limits::max(); - for (const ESM::ENAMstruct& effect : spell->mEffects.mList) + for (const ESM::IndexedENAMstruct& effect : spell->mEffects.mList) { const ESM::MagicEffect* magicEffect - = MWBase::Environment::get().getESMStore()->get().find(effect.mEffectID); + = MWBase::Environment::get().getESMStore()->get().find(effect.mData.mEffectID); int minMagn = 1; int maxMagn = 1; if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) { - minMagn = effect.mMagnMin; - maxMagn = effect.mMagnMax; + minMagn = effect.mData.mMagnMin; + maxMagn = effect.mData.mMagnMax; } int duration = 0; if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) - duration = effect.mDuration; + duration = effect.mData.mDuration; if (!(magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce)) duration = std::max(1, duration); @@ -281,10 +281,10 @@ namespace MWMechanics float x = 0.5 * (std::max(1, minMagn) + std::max(1, maxMagn)); x *= 0.1 * magicEffect->mData.mBaseCost; x *= 1 + duration; - x += 0.05 * std::max(1, effect.mArea) * magicEffect->mData.mBaseCost; + x += 0.05 * std::max(1, effect.mData.mArea) * magicEffect->mData.mBaseCost; x *= fEffectCostMult; - if (effect.mRange == ESM::RT_Target) + if (effect.mData.mRange == ESM::RT_Target) x *= 1.5f; float s = 0.f; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index c94393fad0..646cee8a23 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1060,17 +1060,23 @@ namespace MWMechanics std::string_view action = evt.substr(groupname.size() + 2); if (action == "equip attach") { - if (groupname == "shield") - mAnimation->showCarriedLeft(true); - else - mAnimation->showWeapons(true); + if (mUpperBodyState == UpperBodyState::Equipping) + { + if (groupname == "shield") + mAnimation->showCarriedLeft(true); + else + mAnimation->showWeapons(true); + } } else if (action == "unequip detach") { - if (groupname == "shield") - mAnimation->showCarriedLeft(false); - else - mAnimation->showWeapons(false); + if (mUpperBodyState == UpperBodyState::Unequipping) + { + if (groupname == "shield") + mAnimation->showCarriedLeft(false); + else + mAnimation->showWeapons(false); + } } else if (action == "chop hit" || action == "slash hit" || action == "thrust hit" || action == "hit") { @@ -1155,8 +1161,8 @@ namespace MWMechanics else if (groupname == "spellcast" && action == mAttackType + " release") { if (mCanCast) - MWBase::Environment::get().getWorld()->castSpell(mPtr, mCastingManualSpell); - mCastingManualSpell = false; + MWBase::Environment::get().getWorld()->castSpell(mPtr, mCastingScriptedSpell); + mCastingScriptedSpell = false; mCanCast = false; } else if (groupname == "containeropen" && action == "loot") @@ -1526,9 +1532,9 @@ namespace MWMechanics bool isMagicItem = false; // Play hand VFX and allow castSpell use (assuming an animation is going to be played) if - // spellcasting is successful. Manual spellcasting bypasses restrictions. + // spellcasting is successful. Scripted spellcasting bypasses restrictions. MWWorld::SpellCastState spellCastResult = MWWorld::SpellCastState::Success; - if (!mCastingManualSpell) + if (!mCastingScriptedSpell) spellCastResult = world->startSpellCast(mPtr); mCanCast = spellCastResult == MWWorld::SpellCastState::Success; @@ -1558,9 +1564,9 @@ namespace MWMechanics else if (!spellid.empty() && spellCastResult != MWWorld::SpellCastState::PowerAlreadyUsed) { world->breakInvisibility(mPtr); - MWMechanics::CastSpell cast(mPtr, {}, false, mCastingManualSpell); + MWMechanics::CastSpell cast(mPtr, {}, false, mCastingScriptedSpell); - const std::vector* effects{ nullptr }; + const std::vector* effects{ nullptr }; const MWWorld::ESMStore& store = world->getStore(); if (isMagicItem) { @@ -1579,7 +1585,7 @@ namespace MWMechanics if (mCanCast) { const ESM::MagicEffect* effect = store.get().find( - effects->back().mEffectID); // use last effect of list for color of VFX_Hands + effects->back().mData.mEffectID); // use last effect of list for color of VFX_Hands const ESM::Static* castStatic = world->getStore().get().find(ESM::RefId::stringRefId("VFX_Hands")); @@ -1593,7 +1599,7 @@ namespace MWMechanics "", false, "Bip01 R Hand", effect->mParticle); } // first effect used for casting animation - const ESM::ENAMstruct& firstEffect = effects->front(); + const ESM::ENAMstruct& firstEffect = effects->front().mData; std::string startKey; std::string stopKey; @@ -1602,9 +1608,9 @@ namespace MWMechanics startKey = "start"; stopKey = "stop"; if (mCanCast) - world->castSpell( - mPtr, mCastingManualSpell); // No "release" text key to use, so cast immediately - mCastingManualSpell = false; + world->castSpell(mPtr, + mCastingScriptedSpell); // No "release" text key to use, so cast immediately + mCastingScriptedSpell = false; mCanCast = false; } else @@ -2070,7 +2076,7 @@ namespace MWMechanics vec.x() *= speed; vec.y() *= speed; - if (isKnockedOut() || isKnockedDown() || isRecovery()) + if (isKnockedOut() || isKnockedDown() || isRecovery() || isScriptedAnimPlaying()) vec = osg::Vec3f(); CharacterState movestate = CharState_None; @@ -2088,7 +2094,7 @@ namespace MWMechanics mSecondsOfSwimming += duration; while (mSecondsOfSwimming > 1) { - cls.skillUsageSucceeded(mPtr, ESM::Skill::Athletics, 1); + cls.skillUsageSucceeded(mPtr, ESM::Skill::Athletics, ESM::Skill::Athletics_SwimOneSecond); mSecondsOfSwimming -= 1; } } @@ -2097,7 +2103,7 @@ namespace MWMechanics mSecondsOfRunning += duration; while (mSecondsOfRunning > 1) { - cls.skillUsageSucceeded(mPtr, ESM::Skill::Athletics, 0); + cls.skillUsageSucceeded(mPtr, ESM::Skill::Athletics, ESM::Skill::Athletics_RunOneSecond); mSecondsOfRunning -= 1; } } @@ -2144,6 +2150,15 @@ namespace MWMechanics bool wasInJump = mInJump; mInJump = false; + const float jumpHeight = cls.getJump(mPtr); + if (jumpHeight <= 0.f || sneak || inwater || flying || !solid) + { + vec.z() = 0.f; + // Following code might assign some vertical movement regardless, need to reset this manually + // This is used for jumping detection + movementSettings.mPosition[2] = 0; + } + if (!inwater && !flying && solid) { // In the air (either getting up —ascending part of jump— or falling). @@ -2162,20 +2177,16 @@ namespace MWMechanics vec.z() = 0.0f; } // Started a jump. - else if (mJumpState != JumpState_InAir && vec.z() > 0.f && !sneak) + else if (mJumpState != JumpState_InAir && vec.z() > 0.f) { - float z = cls.getJump(mPtr); - if (z > 0.f) + mInJump = true; + if (vec.x() == 0 && vec.y() == 0) + vec.z() = jumpHeight; + else { - mInJump = true; - if (vec.x() == 0 && vec.y() == 0) - vec.z() = z; - else - { - osg::Vec3f lat(vec.x(), vec.y(), 0.0f); - lat.normalize(); - vec = osg::Vec3f(lat.x(), lat.y(), 1.0f) * z * 0.707f; - } + osg::Vec3f lat(vec.x(), vec.y(), 0.0f); + lat.normalize(); + vec = osg::Vec3f(lat.x(), lat.y(), 1.0f) * jumpHeight * 0.707f; } } } @@ -2215,7 +2226,7 @@ namespace MWMechanics { // report acrobatics progression if (isPlayer) - cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 1); + cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, ESM::Skill::Acrobatics_Fall); } } @@ -2237,8 +2248,6 @@ namespace MWMechanics if (mAnimation->isPlaying(mCurrentJump)) jumpstate = JumpState_Landing; - vec.x() *= scale; - vec.y() *= scale; vec.z() = 0.0f; if (movementSettings.mIsStrafing) @@ -2371,7 +2380,8 @@ namespace MWMechanics const float speedMult = speed / mMovementAnimSpeed; mAnimation->adjustSpeedMult(mCurrentMovement, std::min(maxSpeedMult, speedMult)); // Make sure the actual speed is the "expected" speed even though the animation is slower - scale *= std::max(1.f, speedMult / maxSpeedMult); + if (isMovementAnimationControlled()) + scale *= std::max(1.f, speedMult / maxSpeedMult); } if (!mSkipAnim) @@ -2390,20 +2400,17 @@ namespace MWMechanics } } - if (!isMovementAnimationControlled() && !isScriptedAnimPlaying()) - world->queueMovement(mPtr, vec); + updateHeadTracking(duration); } movement = vec; movementSettings.mPosition[0] = movementSettings.mPosition[1] = 0; + + // Can't reset jump state (mPosition[2]) here in full; we don't know for sure whether the PhysicsSystem will + // actually handle it in this frame due to the fixed minimum timestep used for the physics update. It will + // be reset in PhysicsSystem::move once the jump is handled. if (movement.z() == 0.f) movementSettings.mPosition[2] = 0; - // Can't reset jump state (mPosition[2]) here in full; we don't know for sure whether the PhysicSystem will - // actually handle it in this frame due to the fixed minimum timestep used for the physics update. It will - // be reset in PhysicSystem::move once the jump is handled. - - if (!mSkipAnim) - updateHeadTracking(duration); } else if (cls.getCreatureStats(mPtr).isDead()) { @@ -2420,35 +2427,42 @@ namespace MWMechanics osg::Vec3f movementFromAnimation = mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration); - if (mPtr.getClass().isActor() && isMovementAnimationControlled() && !isScriptedAnimPlaying()) + if (mPtr.getClass().isActor() && !isScriptedAnimPlaying()) { - if (duration > 0.0f) - movementFromAnimation /= duration; - else - movementFromAnimation = osg::Vec3f(0.f, 0.f, 0.f); - - movementFromAnimation.x() *= scale; - movementFromAnimation.y() *= scale; + if (isMovementAnimationControlled()) + { + if (duration != 0.f && movementFromAnimation != osg::Vec3f()) + { + movementFromAnimation /= duration; + + // Ensure we're moving in the right general direction. + // In vanilla, all horizontal movement is taken from animations, even when moving diagonally (which + // doesn't have a corresponding animation). So to achieve diagonal movement, we have to rotate the + // movement taken from the animation to the intended direction. + // + // Note that while a complete movement animation cycle will have a well defined direction, no + // individual frame will, and therefore we have to determine the direction based on the currently + // playing cycle instead. + if (speed > 0.f) + { + float animMovementAngle = getAnimationMovementDirection(); + float targetMovementAngle = std::atan2(-movement.x(), movement.y()); + float diff = targetMovementAngle - animMovementAngle; + movementFromAnimation = osg::Quat(diff, osg::Vec3f(0, 0, 1)) * movementFromAnimation; + } - if (speed > 0.f && movementFromAnimation != osg::Vec3f()) + movement = movementFromAnimation; + } + else + { + movement = osg::Vec3f(); + } + } + else if (mSkipAnim) { - // Ensure we're moving in the right general direction. In vanilla, all horizontal movement is taken from - // animations, even when moving diagonally (which doesn't have a corresponding animation). So to acheive - // diagonal movement, we have to rotate the movement taken from the animation to the intended - // direction. - // - // Note that while a complete movement animation cycle will have a well defined direction, no individual - // frame will, and therefore we have to determine the direction based on the currently playing cycle - // instead. - float animMovementAngle = getAnimationMovementDirection(); - float targetMovementAngle = std::atan2(-movement.x(), movement.y()); - float diff = targetMovementAngle - animMovementAngle; - movementFromAnimation = osg::Quat(diff, osg::Vec3f(0, 0, 1)) * movementFromAnimation; + movement = osg::Vec3f(); } - if (!(isPlayer && Settings::game().mPlayerMovementIgnoresAnimation)) - movement = movementFromAnimation; - if (mFloatToSurface) { if (cls.getCreatureStats(mPtr).isDead() @@ -2463,7 +2477,8 @@ namespace MWMechanics } } - // Update movement + movement.x() *= scale; + movement.y() *= scale; world->queueMovement(mPtr, movement); } @@ -2681,11 +2696,18 @@ namespace MWMechanics bool CharacterController::isMovementAnimationControlled() const { + if (mHitState != CharState_None) + return true; + + if (Settings::game().mPlayerMovementIgnoresAnimation && mPtr == getPlayer()) + return false; + + if (mInJump) + return false; + bool movementAnimationControlled = mIdleState != CharState_None; if (mMovementState != CharState_None) movementAnimationControlled = mMovementAnimationHasMovement; - if (mInJump) - movementAnimationControlled = false; return movementAnimationControlled; } @@ -2720,7 +2742,7 @@ namespace MWMechanics // Make sure we canceled the current attack or spellcasting, // because we disabled attack animations anyway. mCanCast = false; - mCastingManualSpell = false; + mCastingScriptedSpell = false; setAttackingOrSpell(false); if (mUpperBodyState != UpperBodyState::None) mUpperBodyState = UpperBodyState::WeaponEquipped; @@ -2872,7 +2894,7 @@ namespace MWMechanics bool CharacterController::isCastingSpell() const { - return mCastingManualSpell || mUpperBodyState == UpperBodyState::Casting; + return mCastingScriptedSpell || mUpperBodyState == UpperBodyState::Casting; } bool CharacterController::isReadyToBlock() const @@ -2926,10 +2948,10 @@ namespace MWMechanics mPtr.getClass().getCreatureStats(mPtr).setAttackingOrSpell(attackingOrSpell); } - void CharacterController::castSpell(const ESM::RefId& spellId, bool manualSpell) + void CharacterController::castSpell(const ESM::RefId& spellId, bool scriptedSpell) { setAttackingOrSpell(true); - mCastingManualSpell = manualSpell; + mCastingScriptedSpell = scriptedSpell; ActionSpell action = ActionSpell(spellId); action.prepare(mPtr); } diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 430635fff5..8ead23f659 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -192,7 +192,7 @@ namespace MWMechanics bool mCanCast{ false }; - bool mCastingManualSpell{ false }; + bool mCastingScriptedSpell{ false }; bool mIsMovingBackward{ false }; osg::Vec2f mSmoothedSpeed; @@ -312,7 +312,7 @@ namespace MWMechanics bool isAttackingOrSpell() const; void setVisibility(float visibility) const; - void castSpell(const ESM::RefId& spellId, bool manualSpell = false); + void castSpell(const ESM::RefId& spellId, bool scriptedSpell = false); void setAIAttackType(std::string_view attackType); static std::string_view getRandomAttackType(); diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 06de3b64f4..5d283214a3 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -167,7 +167,7 @@ namespace MWMechanics blockerStats.setBlock(true); if (blocker == getPlayer()) - blocker.getClass().skillUsageSucceeded(blocker, ESM::Skill::Block, 0); + blocker.getClass().skillUsageSucceeded(blocker, ESM::Skill::Block, ESM::Skill::Block_Success); return true; } @@ -246,14 +246,16 @@ namespace MWMechanics return; } - const unsigned char* attack = weapon.get()->mBase->mData.mChop; - damage = attack[0] + ((attack[1] - attack[0]) * attackStrength); // Bow/crossbow damage - - // Arrow/bolt damage - // NB in case of thrown weapons, we are applying the damage twice since projectile == weapon - attack = projectile.get()->mBase->mData.mChop; - damage += attack[0] + ((attack[1] - attack[0]) * attackStrength); - + { + const auto& attack = weapon.get()->mBase->mData.mChop; + damage = attack[0] + ((attack[1] - attack[0]) * attackStrength); // Bow/crossbow damage + } + { + // Arrow/bolt damage + // NB in case of thrown weapons, we are applying the damage twice since projectile == weapon + const auto& attack = projectile.get()->mBase->mData.mChop; + damage += attack[0] + ((attack[1] - attack[0]) * attackStrength); + } adjustWeaponDamage(damage, weapon, attacker); } @@ -267,7 +269,7 @@ namespace MWMechanics applyWerewolfDamageMult(victim, projectile, damage); if (attacker == getPlayer()) - attacker.getClass().skillUsageSucceeded(attacker, weaponSkill, 0); + attacker.getClass().skillUsageSucceeded(attacker, weaponSkill, ESM::Skill::Weapon_SuccessfulHit); const MWMechanics::AiSequence& sequence = victim.getClass().getCreatureStats(victim).getAiSequence(); bool unaware = attacker == getPlayer() && !sequence.isInCombat() @@ -585,18 +587,20 @@ namespace MWMechanics MWBase::World* world = MWBase::Environment::get().getWorld(); const MWWorld::Store& store = world->getStore().get(); + // These GMSTs are not in degrees. They're tolerance angle sines multiplied by 90. + // With the default values of 60, the actual tolerance angles are roughly 41.8 degrees. + // Don't think too hard about it. In this place, thinking can cause permanent damage to your mental health. + const float fCombatAngleXY = store.find("fCombatAngleXY")->mValue.getFloat() / 90.f; + const float fCombatAngleZ = store.find("fCombatAngleZ")->mValue.getFloat() / 90.f; + const ESM::Position& posdata = actor.getRefData().getPosition(); const osg::Vec3f actorPos(posdata.asVec3()); - - // Morrowind uses body orientation or camera orientation if available - // The difference between that and this is subtle - osg::Quat actorRot - = osg::Quat(posdata.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(posdata.rot[2], osg::Vec3f(0, 0, -1)); - - const float fCombatAngleXY = store.find("fCombatAngleXY")->mValue.getFloat(); - const float fCombatAngleZ = store.find("fCombatAngleZ")->mValue.getFloat(); - const float combatAngleXYcos = std::cos(osg::DegreesToRadians(fCombatAngleXY)); - const float combatAngleZcos = std::cos(osg::DegreesToRadians(fCombatAngleZ)); + const osg::Vec3f actorDirXY = osg::Quat(posdata.rot[2], osg::Vec3(0, 0, -1)) * osg::Vec3f(0, 1, 0); + // Only the player can look up, apparently. + const float actorVerticalAngle = actor == getPlayer() ? -std::sin(posdata.rot[0]) : 0.f; + const float actorEyeLevel = world->getHalfExtents(actor, true).z() * 2.f * 0.85f; + const osg::Vec3f actorEyePos{ actorPos.x(), actorPos.y(), actorPos.z() + actorEyeLevel }; + const bool canMoveByZ = canActorMoveByZAxis(actor); // The player can target any active actor, non-playable actors only target their targets std::vector targets; @@ -610,26 +614,40 @@ namespace MWMechanics { if (actor == target || target.getClass().getCreatureStats(target).isDead()) continue; - float dist = getDistanceToBounds(actor, target); - osg::Vec3f targetPos(target.getRefData().getPosition().asVec3()); - osg::Vec3f dirToTarget = targetPos - actorPos; - if (dist >= reach || dist >= minDist || std::abs(dirToTarget.z()) >= reach) + const float dist = getDistanceToBounds(actor, target); + const osg::Vec3f targetPos(target.getRefData().getPosition().asVec3()); + if (dist >= reach || dist >= minDist || std::abs(targetPos.z() - actorPos.z()) >= reach) continue; - dirToTarget.normalize(); + // Horizontal angle checks. + osg::Vec2f actorToTargetXY{ targetPos.x() - actorPos.x(), targetPos.y() - actorPos.y() }; + actorToTargetXY.normalize(); - // The idea is to use fCombatAngleXY and fCombatAngleZ as tolerance angles - // in XY and YZ planes of the coordinate system where the actor's orientation - // corresponds to (0, 1, 0) vector. This is not exactly what Morrowind does - // but Morrowind does something (even more) stupid here - osg::Vec3f hitDir = actorRot.inverse() * dirToTarget; - if (combatAngleXYcos * std::abs(hitDir.x()) > hitDir.y()) + // Use dot product to check if the target is behind first... + if (actorToTargetXY.x() * actorDirXY.x() + actorToTargetXY.y() * actorDirXY.y() <= 0.f) continue; - // Nice cliff racer hack Todd - if (combatAngleZcos * std::abs(hitDir.z()) > hitDir.y() && !MWMechanics::canActorMoveByZAxis(target)) + // And then perp dot product to calculate the hit angle sine. + // This gives us a horizontal hit range of [-asin(fCombatAngleXY / 90); asin(fCombatAngleXY / 90)] + if (std::abs(actorToTargetXY.x() * actorDirXY.y() - actorToTargetXY.y() * actorDirXY.x()) > fCombatAngleXY) continue; + // Vertical angle checks. Nice cliff racer hack, Todd. + if (!canMoveByZ) + { + // The idea is that the body should always be possible to hit. + // fCombatAngleZ is the tolerance for hitting the target's feet or head. + osg::Vec3f actorToTargetFeet = targetPos - actorEyePos; + osg::Vec3f actorToTargetHead = actorToTargetFeet; + actorToTargetFeet.normalize(); + actorToTargetHead.z() += world->getHalfExtents(target, true).z() * 2.f; + actorToTargetHead.normalize(); + + if (actorVerticalAngle - actorToTargetHead.z() > fCombatAngleZ + || actorVerticalAngle - actorToTargetFeet.z() < -fCombatAngleZ) + continue; + } + // Gotta use physics somehow! if (!world->getLOS(actor, target)) continue; diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index 13894146b3..7d0007f9e3 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -84,7 +84,8 @@ namespace MWMechanics if (getEnchantChance() <= (Misc::Rng::roll0to99(prng))) return false; - mEnchanter.getClass().skillUsageSucceeded(mEnchanter, ESM::Skill::Enchant, 2); + mEnchanter.getClass().skillUsageSucceeded( + mEnchanter, ESM::Skill::Enchant, ESM::Skill::Enchant_CreateMagicItem); } enchantment.mEffects = mEffectList; @@ -197,13 +198,13 @@ namespace MWMechanics float enchantmentCost = 0.f; float cost = 0.f; - for (const ESM::ENAMstruct& effect : mEffectList.mList) + for (const ESM::IndexedENAMstruct& effect : mEffectList.mList) { - float baseCost = (store.get().find(effect.mEffectID))->mData.mBaseCost; - int magMin = std::max(1, effect.mMagnMin); - int magMax = std::max(1, effect.mMagnMax); - int area = std::max(1, effect.mArea); - float duration = static_cast(effect.mDuration); + float baseCost = (store.get().find(effect.mData.mEffectID))->mData.mBaseCost; + int magMin = std::max(1, effect.mData.mMagnMin); + int magMax = std::max(1, effect.mData.mMagnMax); + int area = std::max(1, effect.mData.mArea); + float duration = static_cast(effect.mData.mDuration); if (mCastStyle == ESM::Enchantment::ConstantEffect) duration = fEnchantmentConstantDurationMult; @@ -211,7 +212,7 @@ namespace MWMechanics cost = std::max(1.f, cost); - if (effect.mRange == ESM::RT_Target) + if (effect.mData.mRange == ESM::RT_Target) cost *= 1.5f; enchantmentCost += precise ? cost : std::floor(cost); @@ -243,13 +244,7 @@ namespace MWMechanics for (int i = 0; i < static_cast(iter->mEffects.mList.size()); ++i) { - const ESM::ENAMstruct& first = iter->mEffects.mList[i]; - const ESM::ENAMstruct& second = toFind.mEffects.mList[i]; - - if (first.mEffectID != second.mEffectID || first.mArea != second.mArea || first.mRange != second.mRange - || first.mSkill != second.mSkill || first.mAttribute != second.mAttribute - || first.mMagnMin != second.mMagnMin || first.mMagnMax != second.mMagnMax - || first.mDuration != second.mDuration) + if (iter->mEffects.mList[i] != toFind.mEffects.mList[i]) { mismatch = true; break; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 9319d030b8..048476c6ef 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -29,6 +29,8 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" +#include "../mwsound/constants.hpp" + #include "actor.hpp" #include "actors.hpp" #include "actorutil.hpp" @@ -261,10 +263,10 @@ namespace MWMechanics mObjects.addObject(ptr); } - void MechanicsManager::castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool manualSpell) + void MechanicsManager::castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool scriptedSpell) { if (ptr.getClass().isActor()) - mActors.castSpell(ptr, spellId, manualSpell); + mActors.castSpell(ptr, spellId, scriptedSpell); } void MechanicsManager::remove(const MWWorld::Ptr& ptr, bool keepActive) @@ -1678,12 +1680,12 @@ namespace MWMechanics if (mMusicType != MWSound::MusicType::Explore && !hasHostiles && !(player.getClass().getCreatureStats(player).isDead() && musicPlaying)) { - MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore")); + MWBase::Environment::get().getSoundManager()->playPlaylist(MWSound::explorePlaylist); mMusicType = MWSound::MusicType::Explore; } else if (mMusicType != MWSound::MusicType::Battle && hasHostiles) { - MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Battle")); + MWBase::Environment::get().getSoundManager()->playPlaylist(MWSound::battlePlaylist); mMusicType = MWSound::MusicType::Battle; } } @@ -1978,11 +1980,7 @@ namespace MWMechanics // Transforming removes all temporary effects actor.getClass().getCreatureStats(actor).getActiveSpells().purge( - [](const auto& params) { - return params.getType() == ESM::ActiveSpells::Type_Consumable - || params.getType() == ESM::ActiveSpells::Type_Temporary; - }, - actor); + [](const auto& params) { return params.hasFlag(ESM::ActiveSpells::Flag_Temporary); }, actor); mActors.updateActor(actor, 0.f); if (werewolf) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 516b778f1e..bf94589309 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -202,7 +202,7 @@ namespace MWMechanics /// Is \a ptr casting spell or using weapon now? bool isAttackingOrSpell(const MWWorld::Ptr& ptr) const override; - void castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool manualSpell = false) override; + void castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool scriptedSpell = false) override; void processChangedSettings(const Settings::CategorySettingVector& settings) override; diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 808059fccd..eceaf8b482 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -209,93 +209,14 @@ float MWMechanics::NpcStats::getSkillProgressRequirement(ESM::RefId id, const ES return progressRequirement; } -void MWMechanics::NpcStats::useSkill(ESM::RefId id, const ESM::Class& class_, int usageType, float extraFactor) -{ - const ESM::Skill* skill = MWBase::Environment::get().getESMStore()->get().find(id); - float skillGain = 1; - if (usageType >= 4) - throw std::runtime_error("skill usage type out of range"); - if (usageType >= 0) - { - skillGain = skill->mData.mUseValue[usageType]; - if (skillGain < 0) - throw std::runtime_error("invalid skill gain factor"); - } - skillGain *= extraFactor; - - MWMechanics::SkillValue& value = getSkill(skill->mId); - - value.setProgress(value.getProgress() + skillGain); - - if (int(value.getProgress()) >= int(getSkillProgressRequirement(skill->mId, class_))) - { - // skill levelled up - increaseSkill(skill->mId, class_, false); - } -} - -void MWMechanics::NpcStats::increaseSkill(ESM::RefId id, const ESM::Class& class_, bool preserveProgress, bool readBook) +int MWMechanics::NpcStats::getLevelProgress() const { - const ESM::Skill* skill = MWBase::Environment::get().getESMStore()->get().find(id); - float base = getSkill(skill->mId).getBase(); - - if (base >= 100.f) - return; - - base += 1; - - const MWWorld::Store& gmst = MWBase::Environment::get().getESMStore()->get(); - - // is this a minor or major skill? - int increase = gmst.find("iLevelupMiscMultAttriubte")->mValue.getInteger(); // Note: GMST has a typo - int index = ESM::Skill::refIdToIndex(skill->mId); - for (const auto& skills : class_.mData.mSkills) - { - if (skills[0] == index) - { - mLevelProgress += gmst.find("iLevelUpMinorMult")->mValue.getInteger(); - increase = gmst.find("iLevelUpMinorMultAttribute")->mValue.getInteger(); - break; - } - else if (skills[1] == index) - { - mLevelProgress += gmst.find("iLevelUpMajorMult")->mValue.getInteger(); - increase = gmst.find("iLevelUpMajorMultAttribute")->mValue.getInteger(); - break; - } - } - - mSkillIncreases[ESM::Attribute::indexToRefId(skill->mData.mAttribute)] += increase; - - mSpecIncreases[skill->mData.mSpecialization] += gmst.find("iLevelupSpecialization")->mValue.getInteger(); - - // Play sound & skill progress notification - /// \todo check if character is the player, if levelling is ever implemented for NPCs - MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("skillraise")); - - std::string message{ MWBase::Environment::get().getWindowManager()->getGameSettingString("sNotifyMessage39", {}) }; - message = Misc::StringUtils::format( - message, MyGUI::TextIterator::toTagsString(skill->mName).asUTF8(), static_cast(base)); - - if (readBook) - message = "#{sBookSkillMessage}\n" + message; - - MWBase::Environment::get().getWindowManager()->messageBox(message, MWGui::ShowInDialogueMode_Never); - - if (mLevelProgress >= gmst.find("iLevelUpTotal")->mValue.getInteger()) - { - // levelup is possible now - MWBase::Environment::get().getWindowManager()->messageBox("#{sLevelUpMsg}", MWGui::ShowInDialogueMode_Never); - } - - getSkill(skill->mId).setBase(base); - if (!preserveProgress) - getSkill(skill->mId).setProgress(0); + return mLevelProgress; } -int MWMechanics::NpcStats::getLevelProgress() const +void MWMechanics::NpcStats::setLevelProgress(int progress) { - return mLevelProgress; + mLevelProgress = progress; } void MWMechanics::NpcStats::levelUp() @@ -344,11 +265,33 @@ int MWMechanics::NpcStats::getLevelupAttributeMultiplier(ESM::Attribute::Attribu return MWBase::Environment::get().getESMStore()->get().find(gmst.str())->mValue.getInteger(); } -int MWMechanics::NpcStats::getSkillIncreasesForSpecialization(int spec) const +int MWMechanics::NpcStats::getSkillIncreasesForAttribute(ESM::Attribute::AttributeID attribute) const +{ + auto it = mSkillIncreases.find(attribute); + if (it == mSkillIncreases.end()) + return 0; + return it->second; +} + +void MWMechanics::NpcStats::setSkillIncreasesForAttribute(ESM::Attribute::AttributeID attribute, int increases) +{ + if (increases == 0) + mSkillIncreases.erase(attribute); + else + mSkillIncreases[attribute] = increases; +} + +int MWMechanics::NpcStats::getSkillIncreasesForSpecialization(ESM::Class::Specialization spec) const { return mSpecIncreases[spec]; } +void MWMechanics::NpcStats::setSkillIncreasesForSpecialization(ESM::Class::Specialization spec, int increases) +{ + assert(spec >= 0 && spec < 3); + mSpecIncreases[spec] = increases; +} + void MWMechanics::NpcStats::flagAsUsed(const ESM::RefId& id) { mUsedIds.insert(id); diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index 7113ee6207..f94744cb71 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -3,6 +3,7 @@ #include "creaturestats.hpp" #include +#include #include #include #include @@ -86,16 +87,15 @@ namespace MWMechanics float getSkillProgressRequirement(ESM::RefId id, const ESM::Class& class_) const; - void useSkill(ESM::RefId id, const ESM::Class& class_, int usageType = -1, float extraFactor = 1.f); - ///< Increase skill by usage. - - void increaseSkill(ESM::RefId id, const ESM::Class& class_, bool preserveProgress, bool readBook = false); - int getLevelProgress() const; + void setLevelProgress(int progress); int getLevelupAttributeMultiplier(ESM::Attribute::AttributeID attribute) const; + int getSkillIncreasesForAttribute(ESM::Attribute::AttributeID attribute) const; + void setSkillIncreasesForAttribute(ESM::Attribute::AttributeID, int increases); - int getSkillIncreasesForSpecialization(int spec) const; + int getSkillIncreasesForSpecialization(ESM::Class::Specialization spec) const; + void setSkillIncreasesForSpecialization(ESM::Class::Specialization spec, int increases); void levelUp(); diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 44566d3b45..0f688686cd 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include namespace MWWorld diff --git a/apps/openmw/mwmechanics/recharge.cpp b/apps/openmw/mwmechanics/recharge.cpp index 6e16436bcc..7b0ad75d3c 100644 --- a/apps/openmw/mwmechanics/recharge.cpp +++ b/apps/openmw/mwmechanics/recharge.cpp @@ -84,7 +84,7 @@ namespace MWMechanics MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Enchant Fail")); } - player.getClass().skillUsageSucceeded(player, ESM::Skill::Enchant, 0); + player.getClass().skillUsageSucceeded(player, ESM::Skill::Enchant, ESM::Skill::Enchant_Recharge); gem.getContainerStore()->remove(gem, 1); if (gem.getCellRef().getCount() == 0) diff --git a/apps/openmw/mwmechanics/repair.cpp b/apps/openmw/mwmechanics/repair.cpp index 3011004244..914fa0b542 100644 --- a/apps/openmw/mwmechanics/repair.cpp +++ b/apps/openmw/mwmechanics/repair.cpp @@ -70,7 +70,7 @@ namespace MWMechanics stacked->getRefData().getLocals().setVarByInt(script, "onpcrepair", 1); // increase skill - player.getClass().skillUsageSucceeded(player, ESM::Skill::Armorer, 0); + player.getClass().skillUsageSucceeded(player, ESM::Skill::Armorer, ESM::Skill::Armorer_Repair); MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Repair")); MWBase::Environment::get().getWindowManager()->messageBox("#{sRepairSuccess}"); diff --git a/apps/openmw/mwmechanics/security.cpp b/apps/openmw/mwmechanics/security.cpp index a13131cae6..0fb8a95699 100644 --- a/apps/openmw/mwmechanics/security.cpp +++ b/apps/openmw/mwmechanics/security.cpp @@ -64,7 +64,7 @@ namespace MWMechanics lock.getCellRef().unlock(); resultMessage = "#{sLockSuccess}"; resultSound = "Open Lock"; - mActor.getClass().skillUsageSucceeded(mActor, ESM::Skill::Security, 1); + mActor.getClass().skillUsageSucceeded(mActor, ESM::Skill::Security, ESM::Skill::Security_PickLock); } else resultMessage = "#{sLockFail}"; @@ -115,7 +115,7 @@ namespace MWMechanics resultSound = "Disarm Trap"; resultMessage = "#{sTrapSuccess}"; - mActor.getClass().skillUsageSucceeded(mActor, ESM::Skill::Security, 0); + mActor.getClass().skillUsageSucceeded(mActor, ESM::Skill::Security, ESM::Skill::Security_DisarmTrap); } else resultMessage = "#{sTrapFail}"; diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 0496033c70..a7e27c9ddd 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -29,11 +29,11 @@ namespace MWMechanics { CastSpell::CastSpell( - const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const bool fromProjectile, const bool manualSpell) + const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const bool fromProjectile, const bool scriptedSpell) : mCaster(caster) , mTarget(target) , mFromProjectile(fromProjectile) - , mManualSpell(manualSpell) + , mScriptedSpell(scriptedSpell) { } @@ -41,21 +41,21 @@ namespace MWMechanics const ESM::EffectList& effects, const MWWorld::Ptr& ignore, ESM::RangeType rangeType) const { const auto world = MWBase::Environment::get().getWorld(); - std::map> toApply; + std::map> toApply; int index = -1; - for (const ESM::ENAMstruct& effectInfo : effects.mList) + for (const ESM::IndexedENAMstruct& effectInfo : effects.mList) { ++index; - const ESM::MagicEffect* effect = world->getStore().get().find(effectInfo.mEffectID); + const ESM::MagicEffect* effect = world->getStore().get().find(effectInfo.mData.mEffectID); - if (effectInfo.mRange != rangeType - || (effectInfo.mArea <= 0 && !ignore.isEmpty() && ignore.getClass().isActor())) + if (effectInfo.mData.mRange != rangeType + || (effectInfo.mData.mArea <= 0 && !ignore.isEmpty() && ignore.getClass().isActor())) continue; // Not right range type, or not area effect and hit an actor - if (mFromProjectile && effectInfo.mArea <= 0) + if (mFromProjectile && effectInfo.mData.mArea <= 0) continue; // Don't play explosion for projectiles with 0-area effects - if (!mFromProjectile && effectInfo.mRange == ESM::RT_Touch && !ignore.isEmpty() + if (!mFromProjectile && effectInfo.mData.mRange == ESM::RT_Touch && !ignore.isEmpty() && !ignore.getClass().isActor() && !ignore.getClass().hasToolTip(ignore) && (mCaster.isEmpty() || mCaster.getClass().isActor())) continue; // Don't play explosion for touch spells on non-activatable objects except when spell is from @@ -70,16 +70,16 @@ namespace MWMechanics const std::string& texture = effect->mParticle; - if (effectInfo.mArea <= 0) + if (effectInfo.mData.mArea <= 0) { - if (effectInfo.mRange == ESM::RT_Target) + if (effectInfo.mData.mRange == ESM::RT_Target) world->spawnEffect( Misc::ResourceHelpers::correctMeshPath(areaStatic->mModel), texture, mHitPosition, 1.0f); continue; } else world->spawnEffect(Misc::ResourceHelpers::correctMeshPath(areaStatic->mModel), texture, mHitPosition, - static_cast(effectInfo.mArea * 2)); + static_cast(effectInfo.mData.mArea * 2)); // Play explosion sound (make sure to use NoTrack, since we will delete the projectile now) { @@ -95,7 +95,7 @@ namespace MWMechanics std::vector objects; static const int unitsPerFoot = ceil(Constants::UnitsPerFoot); MWBase::Environment::get().getMechanicsManager()->getObjectsInRange( - mHitPosition, static_cast(effectInfo.mArea * unitsPerFoot), objects); + mHitPosition, static_cast(effectInfo.mData.mArea * unitsPerFoot), objects); for (const MWWorld::Ptr& affected : objects) { // Ignore actors without collisions here, otherwise it will be possible to hit actors outside processing @@ -104,13 +104,6 @@ namespace MWMechanics continue; auto& list = toApply[affected]; - while (list.size() < static_cast(index)) - { - // Insert dummy effects to preserve indices - auto& dummy = list.emplace_back(effectInfo); - dummy.mRange = ESM::RT_Self; - assert(dummy.mRange != rangeType); - } list.push_back(effectInfo); } } @@ -151,45 +144,34 @@ namespace MWMechanics void CastSpell::inflict( const MWWorld::Ptr& target, const ESM::EffectList& effects, ESM::RangeType range, bool exploded) const { + bool targetIsDeadActor = false; const bool targetIsActor = !target.isEmpty() && target.getClass().isActor(); if (targetIsActor) { - // Early-out for characters that have departed. const auto& stats = target.getClass().getCreatureStats(target); if (stats.isDead() && stats.isDeathAnimationFinished()) - return; + targetIsDeadActor = true; } // If none of the effects need to apply, we can early-out bool found = false; bool containsRecastable = false; - std::vector magicEffects; - magicEffects.reserve(effects.mList.size()); const auto& store = MWBase::Environment::get().getESMStore()->get(); - for (const ESM::ENAMstruct& effect : effects.mList) + for (const ESM::IndexedENAMstruct& effect : effects.mList) { - if (effect.mRange == range) + if (effect.mData.mRange == range) { found = true; - const ESM::MagicEffect* magicEffect = store.find(effect.mEffectID); - // caster needs to be an actor for linked effects (e.g. Absorb) - if (magicEffect->mData.mFlags & ESM::MagicEffect::CasterLinked - && (mCaster.isEmpty() || !mCaster.getClass().isActor())) - { - magicEffects.push_back(nullptr); - continue; - } + const ESM::MagicEffect* magicEffect = store.find(effect.mData.mEffectID); if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NonRecastable)) containsRecastable = true; - magicEffects.push_back(magicEffect); } - else - magicEffects.push_back(nullptr); } if (!found) return; - ActiveSpells::ActiveSpellParams params(*this, mCaster); + ActiveSpells::ActiveSpellParams params(mCaster, mId, mSourceName, mItem); + params.setFlag(mFlags); bool castByPlayer = (!mCaster.isEmpty() && mCaster == getPlayer()); const ActiveSpells* targetSpells = nullptr; @@ -204,31 +186,35 @@ namespace MWMechanics return; } - for (size_t currentEffectIndex = 0; !target.isEmpty() && currentEffectIndex < effects.mList.size(); - ++currentEffectIndex) + for (auto& enam : effects.mList) { - const ESM::ENAMstruct& enam = effects.mList[currentEffectIndex]; - if (enam.mRange != range) - continue; + if (target.isEmpty()) + break; - const ESM::MagicEffect* magicEffect = magicEffects[currentEffectIndex]; + if (enam.mData.mRange != range) + continue; + const ESM::MagicEffect* magicEffect = store.find(enam.mData.mEffectID); if (!magicEffect) continue; + // caster needs to be an actor for linked effects (e.g. Absorb) + if (magicEffect->mData.mFlags & ESM::MagicEffect::CasterLinked + && (mCaster.isEmpty() || !mCaster.getClass().isActor())) + continue; ActiveSpells::ActiveEffect effect; - effect.mEffectId = enam.mEffectID; - effect.mArg = MWMechanics::EffectKey(enam).mArg; + effect.mEffectId = enam.mData.mEffectID; + effect.mArg = MWMechanics::EffectKey(enam.mData).mArg; effect.mMagnitude = 0.f; - effect.mMinMagnitude = enam.mMagnMin; - effect.mMaxMagnitude = enam.mMagnMax; + effect.mMinMagnitude = enam.mData.mMagnMin; + effect.mMaxMagnitude = enam.mData.mMagnMax; effect.mTimeLeft = 0.f; - effect.mEffectIndex = static_cast(currentEffectIndex); + effect.mEffectIndex = enam.mIndex; effect.mFlags = ESM::ActiveEffect::Flag_None; - if (mManualSpell) + if (mScriptedSpell) effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_Reflect; bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration); - effect.mDuration = hasDuration ? static_cast(enam.mDuration) : 1.f; + effect.mDuration = hasDuration ? static_cast(enam.mData.mDuration) : 1.f; bool appliedOnce = magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce; if (!appliedOnce) @@ -240,8 +226,8 @@ namespace MWMechanics params.getEffects().emplace_back(effect); bool effectAffectsHealth = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful - || enam.mEffectID == ESM::MagicEffect::RestoreHealth; - if (castByPlayer && target != mCaster && targetIsActor && effectAffectsHealth) + || enam.mData.mEffectID == ESM::MagicEffect::RestoreHealth; + if (castByPlayer && target != mCaster && targetIsActor && !targetIsDeadActor && effectAffectsHealth) { // If player is attempting to cast a harmful spell on or is healing a living target, show the target's // HP bar. @@ -262,7 +248,10 @@ namespace MWMechanics if (!params.getEffects().empty()) { if (targetIsActor) - target.getClass().getCreatureStats(target).getActiveSpells().addSpell(params); + { + if (!targetIsDeadActor) + target.getClass().getCreatureStats(target).getActiveSpells().addSpell(params); + } else { // Apply effects instantly. We can ignore effect deletion since the entire params object gets @@ -336,7 +325,7 @@ namespace MWMechanics ESM::RefId school = ESM::Skill::Alteration; if (!enchantment->mEffects.mList.empty()) { - short effectId = enchantment->mEffects.mList.front().mEffectID; + short effectId = enchantment->mEffects.mList.front().mData.mEffectID; const ESM::MagicEffect* magicEffect = store->get().find(effectId); school = magicEffect->mData.mSchool; } @@ -354,7 +343,7 @@ namespace MWMechanics if (type == ESM::Enchantment::WhenUsed) { if (mCaster == getPlayer()) - mCaster.getClass().skillUsageSucceeded(mCaster, ESM::Skill::Enchant, 1); + mCaster.getClass().skillUsageSucceeded(mCaster, ESM::Skill::Enchant, ESM::Skill::Enchant_UseMagicItem); } else if (type == ESM::Enchantment::CastOnce) { @@ -364,7 +353,7 @@ namespace MWMechanics else if (type == ESM::Enchantment::WhenStrikes) { if (mCaster == getPlayer()) - mCaster.getClass().skillUsageSucceeded(mCaster, ESM::Skill::Enchant, 3); + mCaster.getClass().skillUsageSucceeded(mCaster, ESM::Skill::Enchant, ESM::Skill::Enchant_CastOnStrike); } if (isProjectile) @@ -387,7 +376,8 @@ namespace MWMechanics { mSourceName = potion->mName; mId = potion->mId; - mType = ESM::ActiveSpells::Type_Consumable; + mFlags = static_cast( + ESM::ActiveSpells::Flag_Temporary | ESM::ActiveSpells::Flag_Stackable); inflict(mCaster, potion->mEffects, ESM::RT_Self); @@ -403,7 +393,7 @@ namespace MWMechanics bool godmode = mCaster == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); - if (mCaster.getClass().isActor() && !mAlwaysSucceed && !mManualSpell) + if (mCaster.getClass().isActor() && !mAlwaysSucceed && !mScriptedSpell) { school = getSpellSchool(spell, mCaster); @@ -438,8 +428,8 @@ namespace MWMechanics stats.getSpells().usePower(spell); } - if (!mManualSpell && mCaster == getPlayer() && spellIncreasesSkill(spell)) - mCaster.getClass().skillUsageSucceeded(mCaster, school, 0); + if (!mScriptedSpell && mCaster == getPlayer() && spellIncreasesSkill(spell)) + mCaster.getClass().skillUsageSucceeded(mCaster, school, ESM::Skill::Spellcast_Success); // A non-actor doesn't play its spell cast effects from a character controller, so play them here if (!mCaster.getClass().isActor()) @@ -458,62 +448,27 @@ namespace MWMechanics bool CastSpell::cast(const ESM::Ingredient* ingredient) { mId = ingredient->mId; - mType = ESM::ActiveSpells::Type_Consumable; + mFlags = static_cast( + ESM::ActiveSpells::Flag_Temporary | ESM::ActiveSpells::Flag_Stackable); mSourceName = ingredient->mName; - ESM::ENAMstruct effect; - effect.mEffectID = ingredient->mData.mEffectID[0]; - effect.mSkill = ingredient->mData.mSkills[0]; - effect.mAttribute = ingredient->mData.mAttributes[0]; - effect.mRange = ESM::RT_Self; - effect.mArea = 0; - - const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); - const auto magicEffect = store.get().find(effect.mEffectID); - const MWMechanics::CreatureStats& creatureStats = mCaster.getClass().getCreatureStats(mCaster); - - float x = (mCaster.getClass().getSkill(mCaster, ESM::Skill::Alchemy) - + 0.2f * creatureStats.getAttribute(ESM::Attribute::Intelligence).getModified() - + 0.1f * creatureStats.getAttribute(ESM::Attribute::Luck).getModified()) - * creatureStats.getFatigueTerm(); + auto effect = rollIngredientEffect(mCaster, ingredient, mCaster != getPlayer()); - auto& prng = MWBase::Environment::get().getWorld()->getPrng(); - int roll = Misc::Rng::roll0to99(prng); - if (roll > x) + if (effect) + inflict(mCaster, *effect, ESM::RT_Self); + else { // "X has no effect on you" - std::string message = store.get().find("sNotifyMessage50")->mValue.getString(); + std::string message = MWBase::Environment::get() + .getESMStore() + ->get() + .find("sNotifyMessage50") + ->mValue.getString(); message = Misc::StringUtils::format(message, ingredient->mName); MWBase::Environment::get().getWindowManager()->messageBox(message); return false; } - float magnitude = 0; - float y = roll / std::min(x, 100.f); - y *= 0.25f * x; - if (magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) - effect.mDuration = 1; - else - effect.mDuration = static_cast(y); - if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) - { - if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) - magnitude = floor((0.05f * y) / (0.1f * magicEffect->mData.mBaseCost)); - else - magnitude = floor(y / (0.1f * magicEffect->mData.mBaseCost)); - magnitude = std::max(1.f, magnitude); - } - else - magnitude = 1; - - effect.mMagnMax = static_cast(magnitude); - effect.mMagnMin = static_cast(magnitude); - - ESM::EffectList effects; - effects.mList.push_back(effect); - - inflict(mCaster, effects, ESM::RT_Self); - return true; } @@ -527,14 +482,14 @@ namespace MWMechanics playSpellCastingEffects(spell->mEffects.mList); } - void CastSpell::playSpellCastingEffects(const std::vector& effects) const + void CastSpell::playSpellCastingEffects(const std::vector& effects) const { const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); std::vector addedEffects; - for (const ESM::ENAMstruct& effectData : effects) + for (const ESM::IndexedENAMstruct& effectData : effects) { - const auto effect = store.get().find(effectData.mEffectID); + const auto effect = store.get().find(effectData.mData.mEffectID); const ESM::Static* castStatic; @@ -587,7 +542,7 @@ namespace MWMechanics } if (animation && !mCaster.getClass().isActor()) - animation->addSpellCastGlow(effect); + animation->addSpellCastGlow(effect->getColor()); addedEffects.push_back(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel)); diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index 8f10066e04..23d3b80713 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -26,7 +26,7 @@ namespace MWMechanics MWWorld::Ptr mCaster; // May be empty MWWorld::Ptr mTarget; // May be empty - void playSpellCastingEffects(const std::vector& effects) const; + void playSpellCastingEffects(const std::vector& effects) const; void explodeSpell(const ESM::EffectList& effects, const MWWorld::Ptr& ignore, ESM::RangeType rangeType) const; @@ -41,13 +41,13 @@ namespace MWMechanics false }; // Always succeed spells casted by NPCs/creatures regardless of their chance (default: false) bool mFromProjectile; // True if spell is cast by enchantment of some projectile (arrow, bolt or thrown weapon) - bool mManualSpell; // True if spell is casted from script and ignores some checks (mana level, success chance, - // etc.) + bool mScriptedSpell; // True if spell is casted from script and ignores some checks (mana level, success chance, + // etc.) ESM::RefNum mItem; - ESM::ActiveSpells::EffectType mType{ ESM::ActiveSpells::Type_Temporary }; + ESM::ActiveSpells::Flags mFlags{ ESM::ActiveSpells::Flag_Temporary }; CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const bool fromProjectile = false, - const bool manualSpell = false); + const bool scriptedSpell = false); bool cast(const ESM::Spell* spell); diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index 8c415949f5..96044ebc5b 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -289,7 +289,7 @@ namespace animation->addEffect(Misc::ResourceHelpers::correctMeshPath(absorbStatic->mModel), ESM::MagicEffect::indexToName(ESM::MagicEffect::SpellAbsorption), false); int spellCost = 0; - if (const ESM::Spell* spell = esmStore.get().search(spellParams.getId())) + if (const ESM::Spell* spell = esmStore.get().search(spellParams.getSourceSpellId())) { spellCost = MWMechanics::calcSpellCost(*spell); } @@ -314,8 +314,7 @@ namespace auto& stats = target.getClass().getCreatureStats(target); auto& magnitudes = stats.getMagicEffects(); // Apply reflect and spell absorption - if (target != caster && spellParams.getType() != ESM::ActiveSpells::Type_Enchantment - && spellParams.getType() != ESM::ActiveSpells::Type_Permanent) + if (target != caster && spellParams.hasFlag(ESM::ActiveSpells::Flag_Temporary)) { bool canReflect = !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable) && !(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_Reflect) @@ -358,9 +357,8 @@ namespace // Apply resistances if (!(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_Resistances)) { - const ESM::Spell* spell = nullptr; - if (spellParams.getType() == ESM::ActiveSpells::Type_Temporary) - spell = MWBase::Environment::get().getESMStore()->get().search(spellParams.getId()); + const ESM::Spell* spell + = spellParams.hasFlag(ESM::ActiveSpells::Flag_Temporary) ? spellParams.getSpell() : nullptr; float magnitudeMult = MWMechanics::getEffectMultiplier(effect.mEffectId, target, caster, spell, &magnitudes); if (magnitudeMult == 0) @@ -429,10 +427,9 @@ namespace MWMechanics // Dispel removes entire spells at once target.getClass().getCreatureStats(target).getActiveSpells().purge( [magnitude = effect.mMagnitude](const ActiveSpells::ActiveSpellParams& params) { - if (params.getType() == ESM::ActiveSpells::Type_Temporary) + if (params.hasFlag(ESM::ActiveSpells::Flag_Temporary)) { - const ESM::Spell* spell - = MWBase::Environment::get().getESMStore()->get().search(params.getId()); + const ESM::Spell* spell = params.getSpell(); if (spell && spell->mData.mType == ESM::Spell::ST_Spell) { auto& prng = MWBase::Environment::get().getWorld()->getPrng(); @@ -645,7 +642,7 @@ namespace MWMechanics else if (effect.mEffectId == ESM::MagicEffect::DamageFatigue) index = 2; // Damage "Dynamic" abilities reduce the base value - if (spellParams.getType() == ESM::ActiveSpells::Type_Ability) + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) modDynamicStat(target, index, -effect.mMagnitude); else { @@ -666,7 +663,7 @@ namespace MWMechanics else if (!godmode) { // Damage Skill abilities reduce base skill :todd: - if (spellParams.getType() == ESM::ActiveSpells::Type_Ability) + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) { auto& npcStats = target.getClass().getNpcStats(target); SkillValue& skill = npcStats.getSkill(effect.getSkillOrAttribute()); @@ -725,7 +722,7 @@ namespace MWMechanics case ESM::MagicEffect::FortifyHealth: case ESM::MagicEffect::FortifyMagicka: case ESM::MagicEffect::FortifyFatigue: - if (spellParams.getType() == ESM::ActiveSpells::Type_Ability) + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) modDynamicStat(target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, effect.mMagnitude); else adjustDynamicStat( @@ -737,7 +734,7 @@ namespace MWMechanics break; case ESM::MagicEffect::FortifyAttribute: // Abilities affect base stats, but not for drain - if (spellParams.getType() == ESM::ActiveSpells::Type_Ability) + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) { auto& creatureStats = target.getClass().getCreatureStats(target); auto attribute = effect.getSkillOrAttribute(); @@ -757,7 +754,7 @@ namespace MWMechanics case ESM::MagicEffect::FortifySkill: if (!target.getClass().isNpc()) invalid = true; - else if (spellParams.getType() == ESM::ActiveSpells::Type_Ability) + else if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) { // Abilities affect base stats, but not for drain auto& npcStats = target.getClass().getNpcStats(target); @@ -922,7 +919,7 @@ namespace MWMechanics { MWRender::Animation* animation = world->getAnimation(target); if (animation) - animation->addSpellCastGlow(magicEffect); + animation->addSpellCastGlow(magicEffect->getColor()); int magnitude = static_cast(roll(effect)); if (target.getCellRef().getLockLevel() < magnitude) // If the door is not already locked to a higher value, lock it to spell magnitude @@ -947,7 +944,7 @@ namespace MWMechanics MWRender::Animation* animation = world->getAnimation(target); if (animation) - animation->addSpellCastGlow(magicEffect); + animation->addSpellCastGlow(magicEffect->getColor()); int magnitude = static_cast(roll(effect)); if (target.getCellRef().getLockLevel() <= magnitude) { @@ -985,7 +982,7 @@ namespace MWMechanics return { MagicApplicationResult::Type::APPLIED, receivedMagicDamage, affectedHealth }; auto& stats = target.getClass().getCreatureStats(target); auto& magnitudes = stats.getMagicEffects(); - if (spellParams.getType() != ESM::ActiveSpells::Type_Ability + if (!spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues) && !(effect.mFlags & ESM::ActiveEffect::Flag_Applied)) { MagicApplicationResult::Type result @@ -998,10 +995,9 @@ namespace MWMechanics oldMagnitude = effect.mMagnitude; else { - if (spellParams.getType() != ESM::ActiveSpells::Type_Enchantment) - playEffects(target, *magicEffect, - spellParams.getType() == ESM::ActiveSpells::Type_Consumable - || spellParams.getType() == ESM::ActiveSpells::Type_Temporary); + if (!spellParams.hasFlag(ESM::ActiveSpells::Flag_Equipment) + && !spellParams.hasFlag(ESM::ActiveSpells::Flag_Lua)) + playEffects(target, *magicEffect, spellParams.hasFlag(ESM::ActiveSpells::Flag_Temporary)); if (effect.mEffectId == ESM::MagicEffect::Soultrap && !target.getClass().isNpc() && target.getType() == ESM::Creature::sRecordId && target.get()->mBase->mData.mSoul == 0 && caster == getPlayer()) @@ -1016,8 +1012,7 @@ namespace MWMechanics if (effect.mDuration != 0) { float mult = dt; - if (spellParams.getType() == ESM::ActiveSpells::Type_Consumable - || spellParams.getType() == ESM::ActiveSpells::Type_Temporary) + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_Temporary)) mult = std::min(effect.mTimeLeft, dt); effect.mMagnitude *= mult; } @@ -1195,7 +1190,7 @@ namespace MWMechanics case ESM::MagicEffect::FortifyHealth: case ESM::MagicEffect::FortifyMagicka: case ESM::MagicEffect::FortifyFatigue: - if (spellParams.getType() == ESM::ActiveSpells::Type_Ability) + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) modDynamicStat(target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, -effect.mMagnitude); else adjustDynamicStat( @@ -1206,7 +1201,7 @@ namespace MWMechanics break; case ESM::MagicEffect::FortifyAttribute: // Abilities affect base stats, but not for drain - if (spellParams.getType() == ESM::ActiveSpells::Type_Ability) + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) { auto& creatureStats = target.getClass().getCreatureStats(target); auto attribute = effect.getSkillOrAttribute(); @@ -1222,7 +1217,7 @@ namespace MWMechanics break; case ESM::MagicEffect::FortifySkill: // Abilities affect base stats, but not for drain - if (spellParams.getType() == ESM::ActiveSpells::Type_Ability) + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) { auto& npcStats = target.getClass().getNpcStats(target); auto& skill = npcStats.getSkill(effect.getSkillOrAttribute()); diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp index 776381e6b2..d1407c4cbd 100644 --- a/apps/openmw/mwmechanics/spellpriority.cpp +++ b/apps/openmw/mwmechanics/spellpriority.cpp @@ -34,7 +34,7 @@ namespace if (effectFilter == -1) { const ESM::Spell* spell - = MWBase::Environment::get().getESMStore()->get().search(it->getId()); + = MWBase::Environment::get().getESMStore()->get().search(it->getSourceSpellId()); if (!spell || spell->mData.mType != ESM::Spell::ST_Spell) continue; } @@ -67,7 +67,7 @@ namespace const MWMechanics::ActiveSpells& activeSpells = actor.getClass().getCreatureStats(actor).getActiveSpells(); for (MWMechanics::ActiveSpells::TIterator it = activeSpells.begin(); it != activeSpells.end(); ++it) { - if (it->getId() != spellId) + if (it->getSourceSpellId() != spellId) continue; const MWMechanics::ActiveSpells::ActiveSpellParams& params = *it; @@ -85,7 +85,7 @@ namespace int actorId = caster.getClass().getCreatureStats(caster).getActorId(); const auto& active = target.getClass().getCreatureStats(target).getActiveSpells(); return std::find_if(active.begin(), active.end(), [&](const auto& spell) { - return spell.getCasterActorId() == actorId && spell.getId() == id; + return spell.getCasterActorId() == actorId && spell.getSourceSpellId() == id; }) != active.end(); } @@ -110,13 +110,13 @@ namespace MWMechanics int getRangeTypes(const ESM::EffectList& effects) { int types = 0; - for (std::vector::const_iterator it = effects.mList.begin(); it != effects.mList.end(); ++it) + for (const ESM::IndexedENAMstruct& effect : effects.mList) { - if (it->mRange == ESM::RT_Self) + if (effect.mData.mRange == ESM::RT_Self) types |= RangeTypes::Self; - else if (it->mRange == ESM::RT_Touch) + else if (effect.mData.mRange == ESM::RT_Touch) types |= RangeTypes::Touch; - else if (it->mRange == ESM::RT_Target) + else if (effect.mData.mRange == ESM::RT_Target) types |= RangeTypes::Target; } return types; @@ -735,12 +735,12 @@ namespace MWMechanics static const float fAIMagicSpellMult = gmst.find("fAIMagicSpellMult")->mValue.getFloat(); static const float fAIRangeMagicSpellMult = gmst.find("fAIRangeMagicSpellMult")->mValue.getFloat(); - for (const ESM::ENAMstruct& effect : list.mList) + for (const ESM::IndexedENAMstruct& effect : list.mList) { - float effectRating = rateEffect(effect, actor, enemy); + float effectRating = rateEffect(effect.mData, actor, enemy); if (useSpellMult) { - if (effect.mRange == ESM::RT_Target) + if (effect.mData.mRange == ESM::RT_Target) effectRating *= fAIRangeMagicSpellMult; else effectRating *= fAIMagicSpellMult; @@ -760,10 +760,10 @@ namespace MWMechanics float mult = fAIMagicSpellMult; - for (std::vector::const_iterator effectIt = spell->mEffects.mList.begin(); + for (std::vector::const_iterator effectIt = spell->mEffects.mList.begin(); effectIt != spell->mEffects.mList.end(); ++effectIt) { - if (effectIt->mRange == ESM::RT_Target) + if (effectIt->mData.mRange == ESM::RT_Target) { if (!MWBase::Environment::get().getWorld()->isSwimming(enemy)) mult = fAIRangeMagicSpellMult; diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index 12da7cdde8..26f0ebe4a2 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -174,7 +174,7 @@ namespace MWMechanics { for (const auto& effectIt : spell->mEffects.mList) { - if (effectIt.mEffectID == ESM::MagicEffect::Corprus) + if (effectIt.mData.mEffectID == ESM::MagicEffect::Corprus) { return true; } diff --git a/apps/openmw/mwmechanics/spellutil.cpp b/apps/openmw/mwmechanics/spellutil.cpp index 2a63a3a444..022aaec262 100644 --- a/apps/openmw/mwmechanics/spellutil.cpp +++ b/apps/openmw/mwmechanics/spellutil.cpp @@ -2,7 +2,9 @@ #include +#include #include +#include #include #include "../mwbase/environment.hpp" @@ -22,13 +24,13 @@ namespace MWMechanics { float cost = 0; - for (const ESM::ENAMstruct& effect : list.mList) + for (const ESM::IndexedENAMstruct& effect : list.mList) { - float effectCost = std::max(0.f, MWMechanics::calcEffectCost(effect, nullptr, method)); + float effectCost = std::max(0.f, MWMechanics::calcEffectCost(effect.mData, nullptr, method)); // This is applied to the whole spell cost for each effect when // creating spells, but is only applied on the effect itself in TES:CS. - if (effect.mRange == ESM::RT_Target) + if (effect.mData.mRange == ESM::RT_Target) effectCost *= 1.5; cost += effectCost; @@ -48,7 +50,7 @@ namespace MWMechanics bool appliedOnce = magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce; int minMagn = hasMagnitude ? effect.mMagnMin : 1; int maxMagn = hasMagnitude ? effect.mMagnMax : 1; - if (method != EffectCostMethod::GameEnchantment) + if (method == EffectCostMethod::PlayerSpell || method == EffectCostMethod::GameSpell) { minMagn = std::max(1, minMagn); maxMagn = std::max(1, maxMagn); @@ -57,21 +59,28 @@ namespace MWMechanics if (!appliedOnce) duration = std::max(1, duration); static const float fEffectCostMult = store.get().find("fEffectCostMult")->mValue.getFloat(); + static const float iAlchemyMod = store.get().find("iAlchemyMod")->mValue.getFloat(); int durationOffset = 0; int minArea = 0; + float costMult = fEffectCostMult; if (method == EffectCostMethod::PlayerSpell) { durationOffset = 1; minArea = 1; } + else if (method == EffectCostMethod::GamePotion) + { + minArea = 1; + costMult = iAlchemyMod; + } float x = 0.5 * (minMagn + maxMagn); x *= 0.1 * magicEffect->mData.mBaseCost; x *= durationOffset + duration; x += 0.05 * std::max(minArea, effect.mArea) * magicEffect->mData.mBaseCost; - return x * fEffectCostMult; + return x * costMult; } int calcSpellCost(const ESM::Spell& spell) @@ -140,25 +149,93 @@ namespace MWMechanics return enchantment.mData.mCharge; } + int getPotionValue(const ESM::Potion& potion) + { + if (potion.mData.mFlags & ESM::Potion::Autocalc) + { + float cost = getTotalCost(potion.mEffects, EffectCostMethod::GamePotion); + return std::round(cost); + } + return potion.mData.mValue; + } + + std::optional rollIngredientEffect( + MWWorld::Ptr caster, const ESM::Ingredient* ingredient, uint32_t index) + { + if (index >= 4) + throw std::range_error("Index out of range"); + + ESM::ENAMstruct effect; + effect.mEffectID = ingredient->mData.mEffectID[index]; + effect.mSkill = ingredient->mData.mSkills[index]; + effect.mAttribute = ingredient->mData.mAttributes[index]; + effect.mRange = ESM::RT_Self; + effect.mArea = 0; + + if (effect.mEffectID < 0) + return std::nullopt; + + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + const auto magicEffect = store.get().find(effect.mEffectID); + const MWMechanics::CreatureStats& creatureStats = caster.getClass().getCreatureStats(caster); + + float x = (caster.getClass().getSkill(caster, ESM::Skill::Alchemy) + + 0.2f * creatureStats.getAttribute(ESM::Attribute::Intelligence).getModified() + + 0.1f * creatureStats.getAttribute(ESM::Attribute::Luck).getModified()) + * creatureStats.getFatigueTerm(); + + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + int roll = Misc::Rng::roll0to99(prng); + if (roll > x) + { + return std::nullopt; + } + + float magnitude = 0; + float y = roll / std::min(x, 100.f); + y *= 0.25f * x; + if (magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) + effect.mDuration = 1; + else + effect.mDuration = static_cast(y); + if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) + { + if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) + magnitude = floor((0.05f * y) / (0.1f * magicEffect->mData.mBaseCost)); + else + magnitude = floor(y / (0.1f * magicEffect->mData.mBaseCost)); + magnitude = std::max(1.f, magnitude); + } + else + magnitude = 1; + + effect.mMagnMax = static_cast(magnitude); + effect.mMagnMin = static_cast(magnitude); + + ESM::EffectList effects; + effects.mList.push_back({ effect, index }); + return effects; + } + float calcSpellBaseSuccessChance(const ESM::Spell* spell, const MWWorld::Ptr& actor, ESM::RefId* effectiveSchool) { // Morrowind for some reason uses a formula slightly different from magicka cost calculation float y = std::numeric_limits::max(); float lowestSkill = 0; - for (const ESM::ENAMstruct& effect : spell->mEffects.mList) + for (const ESM::IndexedENAMstruct& effect : spell->mEffects.mList) { - float x = static_cast(effect.mDuration); + float x = static_cast(effect.mData.mDuration); const auto magicEffect - = MWBase::Environment::get().getESMStore()->get().find(effect.mEffectID); + = MWBase::Environment::get().getESMStore()->get().find(effect.mData.mEffectID); if (!(magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce)) x = std::max(1.f, x); x *= 0.1f * magicEffect->mData.mBaseCost; - x *= 0.5f * (effect.mMagnMin + effect.mMagnMax); - x += effect.mArea * 0.05f * magicEffect->mData.mBaseCost; - if (effect.mRange == ESM::RT_Target) + x *= 0.5f * (effect.mData.mMagnMin + effect.mData.mMagnMax); + x += effect.mData.mArea * 0.05f * magicEffect->mData.mBaseCost; + if (effect.mData.mRange == ESM::RT_Target) x *= 1.5f; static const float fEffectCostMult = MWBase::Environment::get() .getESMStore() diff --git a/apps/openmw/mwmechanics/spellutil.hpp b/apps/openmw/mwmechanics/spellutil.hpp index a332a231e6..fb9d14c8a5 100644 --- a/apps/openmw/mwmechanics/spellutil.hpp +++ b/apps/openmw/mwmechanics/spellutil.hpp @@ -3,11 +3,16 @@ #include +#include + namespace ESM { + struct EffectList; struct ENAMstruct; struct Enchantment; + struct Ingredient; struct MagicEffect; + struct Potion; struct Spell; } @@ -23,6 +28,7 @@ namespace MWMechanics GameSpell, PlayerSpell, GameEnchantment, + GamePotion, }; float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect = nullptr, @@ -33,6 +39,10 @@ namespace MWMechanics int getEffectiveEnchantmentCastCost(const ESM::Enchantment& enchantment, const MWWorld::Ptr& actor); int getEnchantmentCharge(const ESM::Enchantment& enchantment); + int getPotionValue(const ESM::Potion& potion); + std::optional rollIngredientEffect( + MWWorld::Ptr caster, const ESM::Ingredient* ingredient, uint32_t index = 0); + /** * @param spell spell to cast * @param actor calculate spell success chance for this actor (depends on actor's skills) diff --git a/apps/openmw/mwmechanics/trading.cpp b/apps/openmw/mwmechanics/trading.cpp deleted file mode 100644 index 9500897f25..0000000000 --- a/apps/openmw/mwmechanics/trading.cpp +++ /dev/null @@ -1,86 +0,0 @@ -#include "trading.hpp" - -#include - -#include "../mwbase/environment.hpp" -#include "../mwbase/mechanicsmanager.hpp" -#include "../mwbase/world.hpp" - -#include "../mwworld/class.hpp" -#include "../mwworld/esmstore.hpp" - -#include "creaturestats.hpp" - -namespace MWMechanics -{ - Trading::Trading() {} - - bool Trading::haggle(const MWWorld::Ptr& player, const MWWorld::Ptr& merchant, int playerOffer, int merchantOffer) - { - // accept if merchant offer is better than player offer - if (playerOffer <= merchantOffer) - { - return true; - } - - // reject if npc is a creature - if (merchant.getType() != ESM::NPC::sRecordId) - { - return false; - } - - const MWWorld::Store& gmst - = MWBase::Environment::get().getESMStore()->get(); - - // Is the player buying? - bool buying = (merchantOffer < 0); - int a = std::abs(merchantOffer); - int b = std::abs(playerOffer); - int d = (buying) ? int(100 * (a - b) / a) : int(100 * (b - a) / b); - - int clampedDisposition = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(merchant); - - const MWMechanics::CreatureStats& merchantStats = merchant.getClass().getCreatureStats(merchant); - const MWMechanics::CreatureStats& playerStats = player.getClass().getCreatureStats(player); - - float a1 = static_cast(player.getClass().getSkill(player, ESM::Skill::Mercantile)); - float b1 = 0.1f * playerStats.getAttribute(ESM::Attribute::Luck).getModified(); - float c1 = 0.2f * playerStats.getAttribute(ESM::Attribute::Personality).getModified(); - float d1 = static_cast(merchant.getClass().getSkill(merchant, ESM::Skill::Mercantile)); - float e1 = 0.1f * merchantStats.getAttribute(ESM::Attribute::Luck).getModified(); - float f1 = 0.2f * merchantStats.getAttribute(ESM::Attribute::Personality).getModified(); - - float dispositionTerm = gmst.find("fDispositionMod")->mValue.getFloat() * (clampedDisposition - 50); - float pcTerm = (dispositionTerm + a1 + b1 + c1) * playerStats.getFatigueTerm(); - float npcTerm = (d1 + e1 + f1) * merchantStats.getFatigueTerm(); - float x = gmst.find("fBargainOfferMulti")->mValue.getFloat() * d - + gmst.find("fBargainOfferBase")->mValue.getFloat() + int(pcTerm - npcTerm); - - auto& prng = MWBase::Environment::get().getWorld()->getPrng(); - int roll = Misc::Rng::rollDice(100, prng) + 1; - - // reject if roll fails - // (or if player tries to buy things and get money) - if (roll > x || (merchantOffer < 0 && 0 < playerOffer)) - { - return false; - } - - // apply skill gain on successful barter - float skillGain = 0.f; - int finalPrice = std::abs(playerOffer); - int initialMerchantOffer = std::abs(merchantOffer); - - if (!buying && (finalPrice > initialMerchantOffer)) - { - skillGain = std::floor(100.f * (finalPrice - initialMerchantOffer) / finalPrice); - } - else if (buying && (finalPrice < initialMerchantOffer)) - { - skillGain = std::floor(100.f * (initialMerchantOffer - finalPrice) / initialMerchantOffer); - } - player.getClass().skillUsageSucceeded(player, ESM::Skill::Mercantile, 0, skillGain); - - return true; - } -} diff --git a/apps/openmw/mwmechanics/trading.hpp b/apps/openmw/mwmechanics/trading.hpp deleted file mode 100644 index e30b82f5e8..0000000000 --- a/apps/openmw/mwmechanics/trading.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef OPENMW_MECHANICS_TRADING_H -#define OPENMW_MECHANICS_TRADING_H - -namespace MWWorld -{ - class Ptr; -} - -namespace MWMechanics -{ - class Trading - { - public: - Trading(); - - bool haggle(const MWWorld::Ptr& player, const MWWorld::Ptr& merchant, int playerOffer, int merchantOffer); - }; -} - -#endif diff --git a/apps/openmw/mwphysics/actorconvexcallback.cpp b/apps/openmw/mwphysics/actorconvexcallback.cpp index db077beb31..72bb0eff46 100644 --- a/apps/openmw/mwphysics/actorconvexcallback.cpp +++ b/apps/openmw/mwphysics/actorconvexcallback.cpp @@ -9,35 +9,28 @@ namespace MWPhysics { - class ActorOverlapTester : public btCollisionWorld::ContactResultCallback + namespace { - public: - bool overlapping = false; - - btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* colObj0Wrap, int partId0, - int index0, const btCollisionObjectWrapper* colObj1Wrap, int partId1, int index1) override + struct ActorOverlapTester : public btCollisionWorld::ContactResultCallback { - if (cp.getDistance() <= 0.0f) - overlapping = true; - return btScalar(1); - } - }; + bool mOverlapping = false; - ActorConvexCallback::ActorConvexCallback( - const btCollisionObject* me, const btVector3& motion, btScalar minCollisionDot, const btCollisionWorld* world) - : btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)) - , mMe(me) - , mMotion(motion) - , mMinCollisionDot(minCollisionDot) - , mWorld(world) - { + btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* /*colObj0Wrap*/, + int /*partId0*/, int /*index0*/, const btCollisionObjectWrapper* /*colObj1Wrap*/, int /*partId1*/, + int /*index1*/) override + { + if (cp.getDistance() <= 0.0f) + mOverlapping = true; + return 1; + } + }; } btScalar ActorConvexCallback::addSingleResult( btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace) { if (convexResult.m_hitCollisionObject == mMe) - return btScalar(1); + return 1; // override data for actor-actor collisions // vanilla Morrowind seems to make overlapping actors collide as though they are both cylinders with a diameter @@ -52,7 +45,7 @@ namespace MWPhysics const_cast(mMe), const_cast(convexResult.m_hitCollisionObject), isOverlapping); - if (isOverlapping.overlapping) + if (isOverlapping.mOverlapping) { auto originA = Misc::Convert::toOsg(mMe->getWorldTransform().getOrigin()); auto originB = Misc::Convert::toOsg(convexResult.m_hitCollisionObject->getWorldTransform().getOrigin()); @@ -73,7 +66,7 @@ namespace MWPhysics } else { - return btScalar(1); + return 1; } } } @@ -82,10 +75,10 @@ namespace MWPhysics { auto* projectileHolder = static_cast(convexResult.m_hitCollisionObject->getUserPointer()); if (!projectileHolder->isActive()) - return btScalar(1); + return 1; if (projectileHolder->isValidTarget(mMe)) projectileHolder->hit(mMe, convexResult.m_hitPointLocal, convexResult.m_hitNormalLocal); - return btScalar(1); + return 1; } btVector3 hitNormalWorld; @@ -101,7 +94,7 @@ namespace MWPhysics // dot product of the motion vector against the collision contact normal btScalar dotCollision = mMotion.dot(hitNormalWorld); if (dotCollision <= mMinCollisionDot) - return btScalar(1); + return 1; return ClosestConvexResultCallback::addSingleResult(convexResult, normalInWorldSpace); } diff --git a/apps/openmw/mwphysics/actorconvexcallback.hpp b/apps/openmw/mwphysics/actorconvexcallback.hpp index 4b9ab1a8a4..8442097a09 100644 --- a/apps/openmw/mwphysics/actorconvexcallback.hpp +++ b/apps/openmw/mwphysics/actorconvexcallback.hpp @@ -10,8 +10,15 @@ namespace MWPhysics class ActorConvexCallback : public btCollisionWorld::ClosestConvexResultCallback { public: - ActorConvexCallback(const btCollisionObject* me, const btVector3& motion, btScalar minCollisionDot, - const btCollisionWorld* world); + explicit ActorConvexCallback(const btCollisionObject* me, const btVector3& motion, btScalar minCollisionDot, + const btCollisionWorld* world) + : btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)) + , mMe(me) + , mMotion(motion) + , mMinCollisionDot(minCollisionDot) + , mWorld(world) + { + } btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace) override; diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp index 875704d790..b63cd568a8 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp @@ -1,7 +1,6 @@ #include "closestnotmerayresultcallback.hpp" #include -#include #include @@ -9,19 +8,11 @@ namespace MWPhysics { - ClosestNotMeRayResultCallback::ClosestNotMeRayResultCallback(const btCollisionObject* me, - const std::vector& targets, const btVector3& from, const btVector3& to) - : btCollisionWorld::ClosestRayResultCallback(from, to) - , mMe(me) - , mTargets(targets) - { - } - btScalar ClosestNotMeRayResultCallback::addSingleResult( btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) { const auto* hitObject = rayResult.m_collisionObject; - if (hitObject == mMe) + if (std::find(mIgnoreList.begin(), mIgnoreList.end(), hitObject) != mIgnoreList.end()) return 1.f; if (hitObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor && !mTargets.empty()) diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp index 37bda3bd52..660f24424d 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_MWPHYSICS_CLOSESTNOTMERAYRESULTCALLBACK_H #define OPENMW_MWPHYSICS_CLOSESTNOTMERAYRESULTCALLBACK_H -#include +#include #include @@ -14,14 +14,19 @@ namespace MWPhysics class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback { public: - ClosestNotMeRayResultCallback(const btCollisionObject* me, const std::vector& targets, - const btVector3& from, const btVector3& to); + explicit ClosestNotMeRayResultCallback(std::span ignore, + std::span targets, const btVector3& from, const btVector3& to) + : btCollisionWorld::ClosestRayResultCallback(from, to) + , mIgnoreList(ignore) + , mTargets(targets) + { + } btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override; private: - const btCollisionObject* mMe; - const std::vector mTargets; + const std::span mIgnoreList; + const std::span mTargets; }; } diff --git a/apps/openmw/mwphysics/contacttestresultcallback.cpp b/apps/openmw/mwphysics/contacttestresultcallback.cpp index dae0a65af0..45d1127de4 100644 --- a/apps/openmw/mwphysics/contacttestresultcallback.cpp +++ b/apps/openmw/mwphysics/contacttestresultcallback.cpp @@ -8,13 +8,8 @@ namespace MWPhysics { - ContactTestResultCallback::ContactTestResultCallback(const btCollisionObject* testedAgainst) - : mTestedAgainst(testedAgainst) - { - } - btScalar ContactTestResultCallback::addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* col0Wrap, - int partId0, int index0, const btCollisionObjectWrapper* col1Wrap, int partId1, int index1) + int /*partId0*/, int /*index0*/, const btCollisionObjectWrapper* col1Wrap, int /*partId1*/, int /*index1*/) { const btCollisionObject* collisionObject = col0Wrap->m_collisionObject; if (collisionObject == mTestedAgainst) diff --git a/apps/openmw/mwphysics/contacttestresultcallback.hpp b/apps/openmw/mwphysics/contacttestresultcallback.hpp index ae900e0208..a9ba06368c 100644 --- a/apps/openmw/mwphysics/contacttestresultcallback.hpp +++ b/apps/openmw/mwphysics/contacttestresultcallback.hpp @@ -14,15 +14,19 @@ namespace MWPhysics { class ContactTestResultCallback : public btCollisionWorld::ContactResultCallback { - const btCollisionObject* mTestedAgainst; - public: - ContactTestResultCallback(const btCollisionObject* testedAgainst); + explicit ContactTestResultCallback(const btCollisionObject* testedAgainst) + : mTestedAgainst(testedAgainst) + { + } btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* col0Wrap, int partId0, int index0, const btCollisionObjectWrapper* col1Wrap, int partId1, int index1) override; std::vector mResult; + + private: + const btCollisionObject* mTestedAgainst; }; } diff --git a/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.cpp b/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.cpp deleted file mode 100644 index 766ca79796..0000000000 --- a/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "deepestnotmecontacttestresultcallback.hpp" - -#include - -#include - -#include "collisiontype.hpp" - -namespace MWPhysics -{ - - DeepestNotMeContactTestResultCallback::DeepestNotMeContactTestResultCallback( - const btCollisionObject* me, const std::vector& targets, const btVector3& origin) - : mMe(me) - , mTargets(targets) - , mOrigin(origin) - , mLeastDistSqr(std::numeric_limits::max()) - { - } - - btScalar DeepestNotMeContactTestResultCallback::addSingleResult(btManifoldPoint& cp, - const btCollisionObjectWrapper* col0Wrap, int partId0, int index0, const btCollisionObjectWrapper* col1Wrap, - int partId1, int index1) - { - const btCollisionObject* collisionObject = col1Wrap->m_collisionObject; - if (collisionObject != mMe) - { - if (collisionObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor - && !mTargets.empty()) - { - if ((std::find(mTargets.begin(), mTargets.end(), collisionObject) == mTargets.end())) - return 0.f; - } - - btScalar distsqr = mOrigin.distance2(cp.getPositionWorldOnA()); - if (!mObject || distsqr < mLeastDistSqr) - { - mObject = collisionObject; - mLeastDistSqr = distsqr; - mContactPoint = cp.getPositionWorldOnA(); - mContactNormal = cp.m_normalWorldOnB; - } - } - - return 0.f; - } -} diff --git a/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.hpp b/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.hpp deleted file mode 100644 index d22a79e643..0000000000 --- a/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef OPENMW_MWPHYSICS_DEEPESTNOTMECONTACTTESTRESULTCALLBACK_H -#define OPENMW_MWPHYSICS_DEEPESTNOTMECONTACTTESTRESULTCALLBACK_H - -#include - -#include - -class btCollisionObject; - -namespace MWPhysics -{ - class DeepestNotMeContactTestResultCallback : public btCollisionWorld::ContactResultCallback - { - const btCollisionObject* mMe; - const std::vector mTargets; - - // Store the real origin, since the shape's origin is its center - btVector3 mOrigin; - - public: - const btCollisionObject* mObject{ nullptr }; - btVector3 mContactPoint{ 0, 0, 0 }; - btVector3 mContactNormal{ 0, 0, 0 }; - btScalar mLeastDistSqr; - - DeepestNotMeContactTestResultCallback( - const btCollisionObject* me, const std::vector& targets, const btVector3& origin); - - btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* col0Wrap, int partId0, int index0, - const btCollisionObjectWrapper* col1Wrap, int partId1, int index1) override; - }; -} - -#endif diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index fe5c1a955e..05b9f44654 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -32,53 +32,58 @@ namespace MWPhysics return obj->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor; } - class ContactCollectionCallback : public btCollisionWorld::ContactResultCallback + namespace { - public: - ContactCollectionCallback(const btCollisionObject* me, osg::Vec3f velocity) - : mMe(me) + class ContactCollectionCallback : public btCollisionWorld::ContactResultCallback { - m_collisionFilterGroup = me->getBroadphaseHandle()->m_collisionFilterGroup; - m_collisionFilterMask = me->getBroadphaseHandle()->m_collisionFilterMask & ~CollisionType_Projectile; - mVelocity = Misc::Convert::toBullet(velocity); - } - btScalar addSingleResult(btManifoldPoint& contact, const btCollisionObjectWrapper* colObj0Wrap, int partId0, - int index0, const btCollisionObjectWrapper* colObj1Wrap, int partId1, int index1) override - { - if (isActor(colObj0Wrap->getCollisionObject()) && isActor(colObj1Wrap->getCollisionObject())) - return 0.0; - // ignore overlap if we're moving in the same direction as it would push us out (don't change this to >=, - // that would break detection when not moving) - if (contact.m_normalWorldOnB.dot(mVelocity) > 0.0) - return 0.0; - auto delta = contact.m_normalWorldOnB * -contact.m_distance1; - mContactSum += delta; - mMaxX = std::max(std::abs(delta.x()), mMaxX); - mMaxY = std::max(std::abs(delta.y()), mMaxY); - mMaxZ = std::max(std::abs(delta.z()), mMaxZ); - if (contact.m_distance1 < mDistance) + public: + explicit ContactCollectionCallback(const btCollisionObject& me, const osg::Vec3f& velocity) + : mVelocity(Misc::Convert::toBullet(velocity)) { - mDistance = contact.m_distance1; - mNormal = contact.m_normalWorldOnB; - mDelta = delta; - return mDistance; + m_collisionFilterGroup = me.getBroadphaseHandle()->m_collisionFilterGroup; + m_collisionFilterMask = me.getBroadphaseHandle()->m_collisionFilterMask & ~CollisionType_Projectile; } - else + + btScalar addSingleResult(btManifoldPoint& contact, const btCollisionObjectWrapper* colObj0Wrap, + int /*partId0*/, int /*index0*/, const btCollisionObjectWrapper* colObj1Wrap, int /*partId1*/, + int /*index1*/) override { - return 0.0; + if (isActor(colObj0Wrap->getCollisionObject()) && isActor(colObj1Wrap->getCollisionObject())) + return 0.0; + // ignore overlap if we're moving in the same direction as it would push us out (don't change this to + // >=, that would break detection when not moving) + if (contact.m_normalWorldOnB.dot(mVelocity) > 0.0) + return 0.0; + auto delta = contact.m_normalWorldOnB * -contact.m_distance1; + mContactSum += delta; + mMaxX = std::max(std::abs(delta.x()), mMaxX); + mMaxY = std::max(std::abs(delta.y()), mMaxY); + mMaxZ = std::max(std::abs(delta.z()), mMaxZ); + if (contact.m_distance1 < mDistance) + { + mDistance = contact.m_distance1; + mNormal = contact.m_normalWorldOnB; + mDelta = delta; + return mDistance; + } + else + { + return 0.0; + } } - } - btScalar mMaxX = 0.0; - btScalar mMaxY = 0.0; - btScalar mMaxZ = 0.0; - btVector3 mContactSum{ 0.0, 0.0, 0.0 }; - btVector3 mNormal{ 0.0, 0.0, 0.0 }; // points towards "me" - btVector3 mDelta{ 0.0, 0.0, 0.0 }; // points towards "me" - btScalar mDistance = 0.0; // negative or zero - protected: - btVector3 mVelocity; - const btCollisionObject* mMe; - }; + + btScalar mMaxX = 0.0; + btScalar mMaxY = 0.0; + btScalar mMaxZ = 0.0; + btVector3 mContactSum{ 0.0, 0.0, 0.0 }; + btVector3 mNormal{ 0.0, 0.0, 0.0 }; // points towards "me" + btVector3 mDelta{ 0.0, 0.0, 0.0 }; // points towards "me" + btScalar mDistance = 0.0; // negative or zero + + protected: + btVector3 mVelocity; + }; + } osg::Vec3f MovementSolver::traceDown(const MWWorld::Ptr& ptr, const osg::Vec3f& position, Actor* actor, btCollisionWorld* collisionWorld, float maxHeight) @@ -454,8 +459,10 @@ namespace MWPhysics if (btFrom == btTo) return; + assert(projectile.mProjectile != nullptr); + ProjectileConvexCallback resultCallback( - projectile.mCaster, projectile.mCollisionObject, btFrom, btTo, projectile.mProjectile); + projectile.mCaster, projectile.mCollisionObject, btFrom, btTo, *projectile.mProjectile); resultCallback.m_collisionFilterMask = CollisionType_AnyPhysical; resultCallback.m_collisionFilterGroup = CollisionType_Projectile; @@ -524,7 +531,7 @@ namespace MWPhysics newTransform.setOrigin(Misc::Convert::toBullet(goodPosition)); actor.mCollisionObject->setWorldTransform(newTransform); - ContactCollectionCallback callback{ actor.mCollisionObject, velocity }; + ContactCollectionCallback callback(*actor.mCollisionObject, velocity); ContactTestWrapper::contactTest( const_cast(collisionWorld), actor.mCollisionObject, callback); return callback; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 149113dfb1..20a9c38b0f 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -49,7 +49,6 @@ #include "closestnotmerayresultcallback.hpp" #include "contacttestresultcallback.hpp" -#include "deepestnotmecontacttestresultcallback.hpp" #include "hasspherecollisioncallback.hpp" #include "heightfield.hpp" #include "movementsolver.hpp" @@ -69,7 +68,7 @@ namespace // Advance acrobatics and set flag for GetPCJumping if (isPlayer) { - ptr.getClass().skillUsageSucceeded(ptr, ESM::Skill::Acrobatics, 0); + ptr.getClass().skillUsageSucceeded(ptr, ESM::Skill::Acrobatics, ESM::Skill::Acrobatics_Jump); MWBase::Environment::get().getWorld()->getPlayer().setJumping(true); } @@ -193,7 +192,8 @@ namespace MWPhysics } RayCastingResult PhysicsSystem::castRay(const osg::Vec3f& from, const osg::Vec3f& to, - const MWWorld::ConstPtr& ignore, const std::vector& targets, int mask, int group) const + const std::vector& ignore, const std::vector& targets, int mask, + int group) const { if (from == to) { @@ -204,19 +204,22 @@ namespace MWPhysics btVector3 btFrom = Misc::Convert::toBullet(from); btVector3 btTo = Misc::Convert::toBullet(to); - const btCollisionObject* me = nullptr; + std::vector ignoreList; std::vector targetCollisionObjects; - if (!ignore.isEmpty()) + for (const auto& ptr : ignore) { - const Actor* actor = getActor(ignore); - if (actor) - me = actor->getCollisionObject(); - else + if (!ptr.isEmpty()) { - const Object* object = getObject(ignore); - if (object) - me = object->getCollisionObject(); + const Actor* actor = getActor(ptr); + if (actor) + ignoreList.push_back(actor->getCollisionObject()); + else + { + const Object* object = getObject(ptr); + if (object) + ignoreList.push_back(object->getCollisionObject()); + } } } @@ -230,7 +233,7 @@ namespace MWPhysics } } - ClosestNotMeRayResultCallback resultCallback(me, targetCollisionObjects, btFrom, btTo); + ClosestNotMeRayResultCallback resultCallback(ignoreList, targetCollisionObjects, btFrom, btTo); resultCallback.m_collisionFilterGroup = group; resultCallback.m_collisionFilterMask = mask; diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 6734682092..9cc55fecc6 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -209,12 +209,11 @@ namespace MWPhysics const MWWorld::ConstPtr& ptr, int collisionGroup, int collisionMask) const; osg::Vec3f traceDown(const MWWorld::Ptr& ptr, const osg::Vec3f& position, float maxHeight); - /// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all - /// other actors. + /// @param ignore Optional, a list of Ptr to ignore in the list of results. targets are actors to filter for, + /// ignoring all other actors. RayCastingResult castRay(const osg::Vec3f& from, const osg::Vec3f& to, - const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(), - const std::vector& targets = std::vector(), int mask = CollisionType_Default, - int group = 0xff) const override; + const std::vector& ignore = {}, const std::vector& targets = {}, + int mask = CollisionType_Default, int group = 0xff) const override; using RayCastingInterface::castRay; RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius, diff --git a/apps/openmw/mwphysics/projectileconvexcallback.cpp b/apps/openmw/mwphysics/projectileconvexcallback.cpp index d7e80b4698..913a3edb0c 100644 --- a/apps/openmw/mwphysics/projectileconvexcallback.cpp +++ b/apps/openmw/mwphysics/projectileconvexcallback.cpp @@ -6,16 +6,6 @@ namespace MWPhysics { - ProjectileConvexCallback::ProjectileConvexCallback(const btCollisionObject* caster, const btCollisionObject* me, - const btVector3& from, const btVector3& to, Projectile* proj) - : btCollisionWorld::ClosestConvexResultCallback(from, to) - , mCaster(caster) - , mMe(me) - , mProjectile(proj) - { - assert(mProjectile); - } - btScalar ProjectileConvexCallback::addSingleResult( btCollisionWorld::LocalConvexResult& result, bool normalInWorldSpace) { @@ -33,25 +23,25 @@ namespace MWPhysics { case CollisionType_Actor: { - if (!mProjectile->isValidTarget(hitObject)) + if (!mProjectile.isValidTarget(hitObject)) return 1.f; break; } case CollisionType_Projectile: { auto* target = static_cast(hitObject->getUserPointer()); - if (!mProjectile->isValidTarget(target->getCasterCollisionObject())) + if (!mProjectile.isValidTarget(target->getCasterCollisionObject())) return 1.f; target->hit(mMe, m_hitPointWorld, m_hitNormalWorld); break; } case CollisionType_Water: { - mProjectile->setHitWater(); + mProjectile.setHitWater(); break; } } - mProjectile->hit(hitObject, m_hitPointWorld, m_hitNormalWorld); + mProjectile.hit(hitObject, m_hitPointWorld, m_hitNormalWorld); return result.m_hitFraction; } diff --git a/apps/openmw/mwphysics/projectileconvexcallback.hpp b/apps/openmw/mwphysics/projectileconvexcallback.hpp index 3cd304bab0..d75ace22af 100644 --- a/apps/openmw/mwphysics/projectileconvexcallback.hpp +++ b/apps/openmw/mwphysics/projectileconvexcallback.hpp @@ -12,15 +12,21 @@ namespace MWPhysics class ProjectileConvexCallback : public btCollisionWorld::ClosestConvexResultCallback { public: - ProjectileConvexCallback(const btCollisionObject* caster, const btCollisionObject* me, const btVector3& from, - const btVector3& to, Projectile* proj); + explicit ProjectileConvexCallback(const btCollisionObject* caster, const btCollisionObject* me, + const btVector3& from, const btVector3& to, Projectile& projectile) + : btCollisionWorld::ClosestConvexResultCallback(from, to) + , mCaster(caster) + , mMe(me) + , mProjectile(projectile) + { + } btScalar addSingleResult(btCollisionWorld::LocalConvexResult& result, bool normalInWorldSpace) override; private: const btCollisionObject* mCaster; const btCollisionObject* mMe; - Projectile* mProjectile; + Projectile& mProjectile; }; } diff --git a/apps/openmw/mwphysics/raycasting.hpp b/apps/openmw/mwphysics/raycasting.hpp index 6b1a743d54..78b6ab4678 100644 --- a/apps/openmw/mwphysics/raycasting.hpp +++ b/apps/openmw/mwphysics/raycasting.hpp @@ -23,16 +23,15 @@ namespace MWPhysics public: virtual ~RayCastingInterface() = default; - /// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all - /// other actors. + /// @param ignore Optional, a list of Ptr to ignore in the list of results. targets are actors to filter for, + /// ignoring all other actors. virtual RayCastingResult castRay(const osg::Vec3f& from, const osg::Vec3f& to, - const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(), - const std::vector& targets = std::vector(), int mask = CollisionType_Default, - int group = 0xff) const = 0; + const std::vector& ignore = {}, const std::vector& targets = {}, + int mask = CollisionType_Default, int group = 0xff) const = 0; RayCastingResult castRay(const osg::Vec3f& from, const osg::Vec3f& to, int mask) const { - return castRay(from, to, MWWorld::ConstPtr(), std::vector(), mask); + return castRay(from, to, {}, {}, mask); } virtual RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius, diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index 7da29a1cf0..6e18fb51a1 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -35,6 +35,7 @@ #include "../mwworld/inventorystore.hpp" #include "../mwworld/ptr.hpp" +#include "actorutil.hpp" #include "vismask.hpp" namespace MWRender @@ -144,8 +145,7 @@ namespace MWRender if (mesh.empty()) return mesh; - std::string holsteredName = mesh; - holsteredName = holsteredName.replace(holsteredName.size() - 4, 4, "_sh.nif"); + const std::string holsteredName = addSuffixBeforeExtension(mesh, "_sh"); if (mResourceSystem->getVFS()->exists(holsteredName)) { osg::ref_ptr shieldTemplate = mResourceSystem->getSceneManager()->getInstance(holsteredName); @@ -222,8 +222,7 @@ namespace MWRender std::string_view boneName = "Bip01 AttachShield"; osg::Vec4f glowColor = shield->getClass().getEnchantmentColor(*shield); - std::string holsteredName = mesh; - holsteredName = holsteredName.replace(holsteredName.size() - 4, 4, "_sh.nif"); + const std::string holsteredName = addSuffixBeforeExtension(mesh, "_sh"); bool isEnchanted = !shield->getClass().getEnchantment(*shield).empty(); // If we have no dedicated sheath model, use basic shield model as fallback. @@ -340,14 +339,12 @@ namespace MWRender showHolsteredWeapons = false; std::string mesh = weapon->getClass().getCorrectedModel(*weapon); - std::string scabbardName = mesh; - std::string_view boneName = getHolsteredWeaponBoneName(*weapon); if (mesh.empty() || boneName.empty()) return; // If the scabbard is not found, use the weapon mesh as fallback. - scabbardName = scabbardName.replace(scabbardName.size() - 4, 4, "_sh.nif"); + const std::string scabbardName = addSuffixBeforeExtension(mesh, "_sh"); bool isEnchanted = !weapon->getClass().getEnchantment(*weapon).empty(); if (!mResourceSystem->getVFS()->exists(scabbardName)) { diff --git a/apps/openmw/mwrender/actorutil.cpp b/apps/openmw/mwrender/actorutil.cpp index 8da921e532..7739d5a6f6 100644 --- a/apps/openmw/mwrender/actorutil.cpp +++ b/apps/openmw/mwrender/actorutil.cpp @@ -37,4 +37,16 @@ namespace MWRender || VFS::Path::pathEqual(Settings::models().mBaseanimfemale.get(), model) || VFS::Path::pathEqual(Settings::models().mBaseanim.get(), model); } + + std::string addSuffixBeforeExtension(const std::string& filename, const std::string& suffix) + { + size_t dotPos = filename.rfind('.'); + + // No extension found; return the original filename with suffix appended + if (dotPos == std::string::npos) + return filename + suffix; + + // Insert the suffix before the dot (extension) and return the new filename + return filename.substr(0, dotPos) + suffix + filename.substr(dotPos); + } } diff --git a/apps/openmw/mwrender/actorutil.hpp b/apps/openmw/mwrender/actorutil.hpp index 3107bf0183..6a5ab12dea 100644 --- a/apps/openmw/mwrender/actorutil.hpp +++ b/apps/openmw/mwrender/actorutil.hpp @@ -8,6 +8,7 @@ namespace MWRender { const std::string& getActorSkeleton(bool firstPerson, bool female, bool beast, bool werewolf); bool isDefaultActorSkeleton(std::string_view model); + std::string addSuffixBeforeExtension(const std::string& filename, const std::string& suffix); } #endif diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 9cdbb19a98..31b5d36c79 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -5,7 +5,6 @@ #include #include -#include #include #include #include @@ -531,6 +530,7 @@ namespace MWRender , mHasMagicEffects(false) , mAlpha(1.f) , mPlayScriptedOnly(false) + , mRequiresBoneMap(false) { for (size_t i = 0; i < sNumBlendMasks; i++) mAnimationTimePtr[i] = std::make_shared(); @@ -965,8 +965,17 @@ namespace MWRender { if (!mNodeMapCreated && mObjectRoot) { - SceneUtil::NodeMapVisitor visitor(mNodeMap); - mObjectRoot->accept(visitor); + // If the base of this animation is an osgAnimation, we should map the bones not matrix transforms + if (mRequiresBoneMap) + { + SceneUtil::NodeMapVisitorBoneOnly visitor(mNodeMap); + mObjectRoot->accept(visitor); + } + else + { + SceneUtil::NodeMapVisitor visitor(mNodeMap); + mObjectRoot->accept(visitor); + } mNodeMapCreated = true; } return mNodeMap; @@ -1480,6 +1489,10 @@ namespace MWRender mInsert->addChild(mObjectRoot); } + // osgAnimation formats with skeletons should have their nodemap be bone instances + // FIXME: better way to detect osgAnimation here instead of relying on extension? + mRequiresBoneMap = mSkeleton != nullptr && !Misc::StringUtils::ciEndsWith(model, ".nif"); + if (previousStateset) mObjectRoot->setStateSet(previousStateset); @@ -1512,7 +1525,7 @@ namespace MWRender return mObjectRoot.get(); } - void Animation::addSpellCastGlow(const ESM::MagicEffect* effect, float glowDuration) + void Animation::addSpellCastGlow(const osg::Vec4f& color, float glowDuration) { if (!mGlowUpdater || (mGlowUpdater->isDone() || (mGlowUpdater->isPermanentGlowUpdater() == true))) { @@ -1521,12 +1534,11 @@ namespace MWRender if (mGlowUpdater && mGlowUpdater->isPermanentGlowUpdater()) { - mGlowUpdater->setColor(effect->getColor()); + mGlowUpdater->setColor(color); mGlowUpdater->setDuration(glowDuration); } else - mGlowUpdater - = SceneUtil::addEnchantedGlow(mObjectRoot, mResourceSystem, effect->getColor(), glowDuration); + mGlowUpdater = SceneUtil::addEnchantedGlow(mObjectRoot, mResourceSystem, color, glowDuration); } } @@ -1594,8 +1606,7 @@ namespace MWRender // Morrowind has a white ambient light attached to the root VFX node of the scenegraph node->getOrCreateStateSet()->setAttributeAndModes( getVFXLightModelInstance(), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); - if (mResourceSystem->getSceneManager()->getSupportsNormalsRT()) - node->getOrCreateStateSet()->setAttribute(new osg::ColorMaski(1, false, false, false, false)); + mResourceSystem->getSceneManager()->setUpNormalsRTForStateSet(node->getOrCreateStateSet(), false); SceneUtil::FindMaxControllerLengthVisitor findMaxLengthVisitor; node->accept(findMaxLengthVisitor); diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index a2226a3054..4cff658011 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -246,6 +246,7 @@ namespace MWRender osg::ref_ptr mLightListCallback; bool mPlayScriptedOnly; + bool mRequiresBoneMap; const NodeMap& getNodeMap() const; @@ -345,7 +346,7 @@ namespace MWRender // Add a spell casting glow to an object. From measuring video taken from the original engine, // the glow seems to be about 1.5 seconds except for telekinesis, which is 1 second. - void addSpellCastGlow(const ESM::MagicEffect* effect, float glowDuration = 1.5); + void addSpellCastGlow(const osg::Vec4f& color, float glowDuration = 1.5); virtual void updatePtr(const MWWorld::Ptr& ptr); diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index 86d5699d75..1c163a6701 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -109,8 +109,7 @@ namespace MWRender void Camera::updateCamera(osg::Camera* cam) { - osg::Quat orient = osg::Quat(mRoll + mExtraRoll, osg::Vec3d(0, 1, 0)) - * osg::Quat(mPitch + mExtraPitch, osg::Vec3d(1, 0, 0)) * osg::Quat(mYaw + mExtraYaw, osg::Vec3d(0, 0, 1)); + osg::Quat orient = getOrient(); osg::Vec3d forward = orient * osg::Vec3d(0, 1, 0); osg::Vec3d up = orient * osg::Vec3d(0, 0, 1); @@ -209,6 +208,12 @@ namespace MWRender mPosition = focal + offset; } + osg::Quat Camera::getOrient() const + { + return osg::Quat(mRoll + mExtraRoll, osg::Vec3d(0, 1, 0)) * osg::Quat(mPitch + mExtraPitch, osg::Vec3d(1, 0, 0)) + * osg::Quat(mYaw + mExtraYaw, osg::Vec3d(0, 0, 1)); + } + void Camera::setMode(Mode newMode, bool force) { if (mMode == newMode) diff --git a/apps/openmw/mwrender/camera.hpp b/apps/openmw/mwrender/camera.hpp index c6500160fd..e09a265293 100644 --- a/apps/openmw/mwrender/camera.hpp +++ b/apps/openmw/mwrender/camera.hpp @@ -72,6 +72,8 @@ namespace MWRender void setExtraYaw(float angle) { mExtraYaw = angle; } void setExtraRoll(float angle) { mExtraRoll = angle; } + osg::Quat getOrient() const; + /// @param Force view mode switch, even if currently not allowed by the animation. void toggleViewMode(bool force = false); bool toggleVanityMode(bool enable); diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 9914aec7ca..a4c0181d35 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -247,7 +247,7 @@ namespace MWRender defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); stateset->setAttribute(defaultMat); - SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *stateset); + SceneUtil::ShadowManager::instance().disableShadowsForStateSet(*stateset); // assign large value to effectively turn off fog // shaders don't respect glDisable(GL_FOG) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 92be726f09..7a29f1bb07 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -95,6 +95,8 @@ namespace MWRender { // Other objects are likely cheaper and should let us skip all but a few groundcover instances cullVisitor.computeNearPlane(); + computedZNear = cullVisitor.getCalculatedNearPlane(); + computedZFar = cullVisitor.getCalculatedFarPlane(); if (dNear < computedZNear) { @@ -196,6 +198,18 @@ namespace MWRender { } + void apply(osg::Group& group) override + { + for (unsigned int i = 0; i < group.getNumChildren();) + { + if (group.getChild(i)->asDrawable() && !group.getChild(i)->asGeometry()) + group.removeChild(i); + else + ++i; + } + traverse(group); + } + void apply(osg::Geometry& geom) override { for (unsigned int i = 0; i < geom.getNumPrimitiveSets(); ++i) @@ -449,6 +463,6 @@ namespace MWRender void Groundcover::reportStats(unsigned int frameNumber, osg::Stats* stats) const { - stats->setAttribute(frameNumber, "Groundcover Chunk", mCache->getCacheSize()); + Resource::reportStats("Groundcover Chunk", frameNumber, mCache->getStats(), *stats); } } diff --git a/apps/openmw/mwrender/landmanager.cpp b/apps/openmw/mwrender/landmanager.cpp index f8a3ebd962..d17933b2b7 100644 --- a/apps/openmw/mwrender/landmanager.cpp +++ b/apps/openmw/mwrender/landmanager.cpp @@ -1,7 +1,5 @@ #include "landmanager.hpp" -#include - #include #include #include @@ -53,7 +51,7 @@ namespace MWRender void LandManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const { - stats->setAttribute(frameNumber, "Land", mCache->getCacheSize()); + Resource::reportStats("Land", frameNumber, mCache->getStats(), *stats); } } diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 892a8b5428..9e934d6f20 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -763,7 +763,7 @@ namespace MWRender lightSource->setStateSetModes(*stateset, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); - SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *stateset); + SceneUtil::ShadowManager::instance().disableShadowsForStateSet(*stateset); // override sun for local map SceneUtil::configureStateSetSunOverride(static_cast(mSceneRoot), light, stateset); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 61260e687e..721806d992 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -312,9 +312,8 @@ namespace MWRender class DepthClearCallback : public osgUtil::RenderBin::DrawCallback { public: - DepthClearCallback(Resource::ResourceSystem* resourceSystem) + DepthClearCallback() { - mPassNormals = resourceSystem->getSceneManager()->getSupportsNormalsRT(); mDepth = new SceneUtil::AutoDepth; mDepth->setWriteMask(true); @@ -335,11 +334,6 @@ namespace MWRender unsigned int frameId = state->getFrameStamp()->getFrameNumber() % 2; postProcessor->getFbo(PostProcessor::FBO_FirstPerson, frameId)->apply(*state); - if (mPassNormals) - { - state->get()->glColorMaski(1, true, true, true, true); - state->haveAppliedAttribute(osg::StateAttribute::COLORMASK); - } glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // color accumulation pass bin->drawImplementation(renderInfo, previous); @@ -360,7 +354,6 @@ namespace MWRender state->checkGLErrors("after DepthClearCallback::drawImplementation"); } - bool mPassNormals; osg::ref_ptr mDepth; osg::ref_ptr mStateSet; }; @@ -409,7 +402,7 @@ namespace MWRender if (!prototypeAdded) { osg::ref_ptr depthClearBin(new osgUtil::RenderBin); - depthClearBin->setDrawCallback(new DepthClearCallback(mResourceSystem)); + depthClearBin->setDrawCallback(new DepthClearCallback()); osgUtil::RenderBin::addRenderBinPrototype("DepthClear", depthClearBin); prototypeAdded = true; } @@ -546,8 +539,7 @@ namespace MWRender if (mesh.empty()) return std::string(); - std::string holsteredName = mesh; - holsteredName = holsteredName.replace(holsteredName.size() - 4, 4, "_sh.nif"); + const std::string holsteredName = addSuffixBeforeExtension(mesh, "_sh"); if (mResourceSystem->getVFS()->exists(holsteredName)) { osg::ref_ptr shieldTemplate = mResourceSystem->getSceneManager()->getInstance(holsteredName); diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index f1bccc13c9..f426672784 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -1013,7 +1013,7 @@ namespace MWRender void ObjectPaging::reportStats(unsigned int frameNumber, osg::Stats* stats) const { - stats->setAttribute(frameNumber, "Object Chunk", mCache->getCacheSize()); + Resource::reportStats("Object Chunk", frameNumber, mCache->getStats(), *stats); } } diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index 42b2e4e1ee..1c0702879d 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -346,7 +346,7 @@ namespace MWRender for (auto& technique : mTechniques) { - if (technique->getStatus() == fx::Technique::Status::File_Not_exists) + if (!technique || technique->getStatus() == fx::Technique::Status::File_Not_exists) continue; const auto lastWriteTime = std::filesystem::last_write_time(mTechniqueFileMap[technique->getName()]); @@ -564,7 +564,7 @@ namespace MWRender for (const auto& technique : mTechniques) { - if (!technique->isValid()) + if (!technique || !technique->isValid()) continue; if (technique->getGLSLVersion() > mGLSLVersion) @@ -718,7 +718,7 @@ namespace MWRender PostProcessor::Status PostProcessor::disableTechnique(std::shared_ptr technique, bool dirty) { - if (technique->getLocked()) + if (!technique || technique->getLocked()) return Status_Error; auto it = std::find(mTechniques.begin(), mTechniques.end(), technique); @@ -734,6 +734,9 @@ namespace MWRender bool PostProcessor::isTechniqueEnabled(const std::shared_ptr& technique) const { + if (!technique) + return false; + if (auto it = std::find(mTechniques.begin(), mTechniques.end(), technique); it == mTechniques.end()) return false; @@ -815,7 +818,7 @@ namespace MWRender void PostProcessor::disableDynamicShaders() { for (auto& technique : mTechniques) - if (technique->getDynamic()) + if (technique && technique->getDynamic()) disableTechnique(technique); } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 15faabb6df..dc71e455b2 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -59,6 +59,7 @@ #include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/groundcoverstore.hpp" +#include "../mwworld/scene.hpp" #include "../mwgui/postprocessorhud.hpp" @@ -328,13 +329,56 @@ namespace MWRender const SceneUtil::LightingMethod lightingMethod = Settings::shaders().mLightingMethod; resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem); - // Shadows and radial fog have problems with fixed-function mode. - bool forceShaders = Settings::fog().mRadialFog || Settings::fog().mExponentialFog - || Settings::shaders().mSoftParticles || Settings::shaders().mForceShaders - || Settings::shadows().mEnableShadows || lightingMethod != SceneUtil::LightingMethod::FFP || reverseZ - || mSkyBlending || Stereo::getMultiview(); - resourceSystem->getSceneManager()->setForceShaders(forceShaders); + // Figure out which pipeline must be used by default and inform the user + bool forceShaders = Settings::shaders().mForceShaders; + { + std::vector requesters; + if (!forceShaders) + { + if (Settings::fog().mRadialFog) + requesters.push_back("radial fog"); + if (Settings::fog().mExponentialFog) + requesters.push_back("exponential fog"); + if (mSkyBlending) + requesters.push_back("sky blending"); + if (Settings::shaders().mSoftParticles) + requesters.push_back("soft particles"); + if (Settings::shadows().mEnableShadows) + requesters.push_back("shadows"); + if (lightingMethod != SceneUtil::LightingMethod::FFP) + requesters.push_back("lighting method"); + if (reverseZ) + requesters.push_back("reverse-Z depth buffer"); + if (Stereo::getMultiview()) + requesters.push_back("stereo multiview"); + + if (!requesters.empty()) + forceShaders = true; + } + + if (forceShaders) + { + std::string message = "Using rendering with shaders by default"; + if (requesters.empty()) + { + message += " (forced)"; + } + else + { + message += ", requested by:"; + for (size_t i = 0; i < requesters.size(); i++) + message += "\n - " + requesters[i]; + } + Log(Debug::Info) << message; + } + else + { + Log(Debug::Info) << "Using fixed-function rendering by default"; + } + } + + resourceSystem->getSceneManager()->setForceShaders(forceShaders); // FIXME: calling dummy method because terrain needs to know whether lighting is clamped resourceSystem->getSceneManager()->setClampLighting(Settings::shaders().mClampLighting); resourceSystem->getSceneManager()->setAutoUseNormalMaps(Settings::shaders().mAutoUseObjectNormalMaps); @@ -397,7 +441,7 @@ namespace MWRender globalDefines["radialFog"] = (exponentialFog || Settings::fog().mRadialFog) ? "1" : "0"; globalDefines["exponentialFog"] = exponentialFog ? "1" : "0"; globalDefines["skyBlending"] = mSkyBlending ? "1" : "0"; - globalDefines["refraction_enabled"] = "0"; + globalDefines["waterRefraction"] = "0"; globalDefines["useGPUShader4"] = "0"; globalDefines["useOVR_multiview"] = "0"; globalDefines["numViews"] = "1"; @@ -432,8 +476,10 @@ namespace MWRender mViewer->getIncrementalCompileOperation()->setTargetFrameRate(Settings::cells().mTargetFramerate); } - mDebugDraw - = std::make_unique(mResourceSystem->getSceneManager()->getShaderManager(), mRootNode); + mDebugDraw = new Debug::DebugDrawer(mResourceSystem->getSceneManager()->getShaderManager()); + mDebugDraw->setNodeMask(Mask_Debug); + sceneRoot->addChild(mDebugDraw); + mResourceSystem->getSceneManager()->setIncrementalCompileOperation(mViewer->getIncrementalCompileOperation()); mEffectManager = std::make_unique(sceneRoot, mResourceSystem); @@ -504,6 +550,8 @@ namespace MWRender sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("specStrength", 1.f)); sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("distortionStrength", 0.f)); + resourceSystem->getSceneManager()->setUpNormalsRTForStateSet(sceneRoot->getOrCreateStateSet(), true); + mFog = std::make_unique(); mSky = std::make_unique( @@ -1014,20 +1062,17 @@ namespace MWRender return osg::Vec4f(min_x, min_y, max_x, max_y); } - RenderingManager::RayResult getIntersectionResult(osgUtil::LineSegmentIntersector* intersector) + RenderingManager::RayResult getIntersectionResult(osgUtil::LineSegmentIntersector* intersector, + const osg::ref_ptr& visitor, std::span ignoreList = {}) { RenderingManager::RayResult result; result.mHit = false; result.mRatio = 0; - if (intersector->containsIntersections()) - { - result.mHit = true; - osgUtil::LineSegmentIntersector::Intersection intersection = intersector->getFirstIntersection(); - result.mHitPointWorld = intersection.getWorldIntersectPoint(); - result.mHitNormalWorld = intersection.getWorldIntersectNormal(); - result.mRatio = intersection.ratio; + if (!intersector->containsIntersections()) + return result; + auto test = [&](const osgUtil::LineSegmentIntersector::Intersection& intersection) { PtrHolder* ptrHolder = nullptr; std::vector refnumMarkers; for (osg::NodePath::const_iterator it = intersection.nodePath.begin(); it != intersection.nodePath.end(); @@ -1039,9 +1084,16 @@ namespace MWRender for (unsigned int i = 0; i < userDataContainer->getNumUserObjects(); ++i) { if (PtrHolder* p = dynamic_cast(userDataContainer->getUserObject(i))) - ptrHolder = p; + { + if (std::find(ignoreList.begin(), ignoreList.end(), p->mPtr) == ignoreList.end()) + { + ptrHolder = p; + } + } if (RefnumMarker* r = dynamic_cast(userDataContainer->getUserObject(i))) + { refnumMarkers.push_back(r); + } } } @@ -1056,21 +1108,113 @@ namespace MWRender || (intersectionIndex >= vertexCounter && intersectionIndex < vertexCounter + refnumMarkers[i]->mNumVertices)) { - result.mHitRefnum = refnumMarkers[i]->mRefnum; + auto it = std::find_if( + ignoreList.begin(), ignoreList.end(), [target = refnumMarkers[i]->mRefnum](const auto& ptr) { + return target == ptr.getCellRef().getRefNum(); + }); + + if (it == ignoreList.end()) + { + result.mHitRefnum = refnumMarkers[i]->mRefnum; + } + break; } vertexCounter += refnumMarkers[i]->mNumVertices; } + + if (!result.mHitObject.isEmpty() || result.mHitRefnum.isSet()) + { + result.mHit = true; + result.mHitPointWorld = intersection.getWorldIntersectPoint(); + result.mHitNormalWorld = intersection.getWorldIntersectNormal(); + result.mRatio = intersection.ratio; + } + }; + + if (ignoreList.empty() || intersector->getIntersectionLimit() != osgUtil::LineSegmentIntersector::NO_LIMIT) + { + test(intersector->getFirstIntersection()); + } + else + { + for (const auto& intersection : intersector->getIntersections()) + { + test(intersection); + + if (result.mHit) + { + break; + } + } } return result; } + class IntersectionVisitorWithIgnoreList : public osgUtil::IntersectionVisitor + { + public: + bool skipTransform(osg::Transform& transform) + { + if (mContainsPagedRefs) + return false; + + osg::UserDataContainer* userDataContainer = transform.getUserDataContainer(); + if (!userDataContainer) + return false; + + for (unsigned int i = 0; i < userDataContainer->getNumUserObjects(); ++i) + { + if (PtrHolder* p = dynamic_cast(userDataContainer->getUserObject(i))) + { + if (std::find(mIgnoreList.begin(), mIgnoreList.end(), p->mPtr) != mIgnoreList.end()) + { + return true; + } + } + } + + return false; + } + + void apply(osg::Transform& transform) override + { + if (skipTransform(transform)) + { + return; + } + osgUtil::IntersectionVisitor::apply(transform); + } + + void setIgnoreList(std::span ignoreList) { mIgnoreList = ignoreList; } + void setContainsPagedRefs(bool contains) { mContainsPagedRefs = contains; } + + private: + std::span mIgnoreList; + bool mContainsPagedRefs = false; + }; + osg::ref_ptr RenderingManager::getIntersectionVisitor( - osgUtil::Intersector* intersector, bool ignorePlayer, bool ignoreActors) + osgUtil::Intersector* intersector, bool ignorePlayer, bool ignoreActors, + std::span ignoreList) { if (!mIntersectionVisitor) - mIntersectionVisitor = new osgUtil::IntersectionVisitor; + mIntersectionVisitor = new IntersectionVisitorWithIgnoreList; + + mIntersectionVisitor->setIgnoreList(ignoreList); + mIntersectionVisitor->setContainsPagedRefs(false); + + MWWorld::Scene* worldScene = MWBase::Environment::get().getWorldScene(); + for (const auto& ptr : ignoreList) + { + if (worldScene->isPagedRef(ptr)) + { + mIntersectionVisitor->setContainsPagedRefs(true); + intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT); + break; + } + } mIntersectionVisitor->setTraversalNumber(mViewer->getFrameStamp()->getFrameNumber()); mIntersectionVisitor->setFrameStamp(mViewer->getFrameStamp()); @@ -1088,16 +1232,16 @@ namespace MWRender return mIntersectionVisitor; } - RenderingManager::RayResult RenderingManager::castRay( - const osg::Vec3f& origin, const osg::Vec3f& dest, bool ignorePlayer, bool ignoreActors) + RenderingManager::RayResult RenderingManager::castRay(const osg::Vec3f& origin, const osg::Vec3f& dest, + bool ignorePlayer, bool ignoreActors, std::span ignoreList) { osg::ref_ptr intersector( new osgUtil::LineSegmentIntersector(osgUtil::LineSegmentIntersector::MODEL, origin, dest)); intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::LIMIT_NEAREST); - mRootNode->accept(*getIntersectionVisitor(intersector, ignorePlayer, ignoreActors)); + mRootNode->accept(*getIntersectionVisitor(intersector, ignorePlayer, ignoreActors, ignoreList)); - return getIntersectionResult(intersector); + return getIntersectionResult(intersector, mIntersectionVisitor, ignoreList); } RenderingManager::RayResult RenderingManager::castCameraToViewportRay( @@ -1117,7 +1261,7 @@ namespace MWRender mViewer->getCamera()->accept(*getIntersectionVisitor(intersector, ignorePlayer, ignoreActors)); - return getIntersectionResult(intersector); + return getIntersectionResult(intersector, mIntersectionVisitor); } void RenderingManager::updatePtr(const MWWorld::Ptr& old, const MWWorld::Ptr& updated) diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 22ef987c01..81adcc85be 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_MWRENDER_RENDERINGMANAGER_H #define OPENMW_MWRENDER_RENDERINGMANAGER_H +#include + #include #include #include @@ -87,6 +89,7 @@ namespace MWRender class StateUpdater; class SharedUniformStateUpdater; class PerViewUniformStateUpdater; + class IntersectionVisitorWithIgnoreList; class EffectManager; class ScreenshotManager; @@ -177,8 +180,8 @@ namespace MWRender float mRatio; }; - RayResult castRay( - const osg::Vec3f& origin, const osg::Vec3f& dest, bool ignorePlayer, bool ignoreActors = false); + RayResult castRay(const osg::Vec3f& origin, const osg::Vec3f& dest, bool ignorePlayer, + bool ignoreActors = false, std::span ignoreList = {}); /// Return the object under the mouse cursor / crosshair position, given by nX and nY normalized screen /// coordinates, where (0,0) is the top left corner. @@ -299,10 +302,10 @@ namespace MWRender const bool mSkyBlending; - osg::ref_ptr getIntersectionVisitor( - osgUtil::Intersector* intersector, bool ignorePlayer, bool ignoreActors); + osg::ref_ptr getIntersectionVisitor(osgUtil::Intersector* intersector, + bool ignorePlayer, bool ignoreActors, std::span ignoreList = {}); - osg::ref_ptr mIntersectionVisitor; + osg::ref_ptr mIntersectionVisitor; osg::ref_ptr mViewer; osg::ref_ptr mRootNode; @@ -335,7 +338,7 @@ namespace MWRender osg::ref_ptr mPlayerAnimation; osg::ref_ptr mPlayerNode; std::unique_ptr mCamera; - std::unique_ptr mDebugDraw; + osg::ref_ptr mDebugDraw; osg::ref_ptr mStateUpdater; osg::ref_ptr mSharedUniformStateUpdater; diff --git a/apps/openmw/mwrender/ripples.cpp b/apps/openmw/mwrender/ripples.cpp index dea372666e..d0069d8d92 100644 --- a/apps/openmw/mwrender/ripples.cpp +++ b/apps/openmw/mwrender/ripples.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include "../mwworld/ptr.hpp" @@ -43,9 +44,9 @@ namespace MWRender mUseCompute = false; #else constexpr float minimumGLVersionRequiredForCompute = 4.4; - osg::GLExtensions* exts = osg::GLExtensions::Get(0, false); - mUseCompute = exts->glVersion >= minimumGLVersionRequiredForCompute - && exts->glslLanguageVersion >= minimumGLVersionRequiredForCompute; + osg::GLExtensions& exts = SceneUtil::getGLExtensions(); + mUseCompute = exts.glVersion >= minimumGLVersionRequiredForCompute + && exts.glslLanguageVersion >= minimumGLVersionRequiredForCompute; #endif if (mUseCompute) @@ -63,7 +64,7 @@ namespace MWRender stateset->addUniform(new osg::Uniform("offset", osg::Vec2f())); stateset->addUniform(new osg::Uniform("positionCount", 0)); stateset->addUniform(new osg::Uniform(osg::Uniform::Type::FLOAT_VEC3, "positions", 100)); - stateset->setAttributeAndModes(new osg::Viewport(0, 0, RipplesSurface::mRTTSize, RipplesSurface::mRTTSize)); + stateset->setAttributeAndModes(new osg::Viewport(0, 0, RipplesSurface::sRTTSize, RipplesSurface::sRTTSize)); mState[i].mStateset = stateset; } @@ -78,7 +79,7 @@ namespace MWRender texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_BORDER); texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_BORDER); texture->setBorderColor(osg::Vec4(0, 0, 0, 0)); - texture->setTextureSize(mRTTSize, mRTTSize); + texture->setTextureSize(sRTTSize, sRTTSize); mTextures[i] = texture; @@ -99,7 +100,7 @@ namespace MWRender { auto& shaderManager = mResourceSystem->getSceneManager()->getShaderManager(); - Shader::ShaderManager::DefineMap defineMap = { { "ripple_map_size", std::to_string(mRTTSize) + ".0" } }; + Shader::ShaderManager::DefineMap defineMap = { { "rippleMapSize", std::to_string(sRTTSize) + ".0" } }; osg::ref_ptr vertex = shaderManager.getShader("fullscreen_tri.vert", {}, osg::Shader::VERTEX); @@ -119,59 +120,83 @@ namespace MWRender nullptr, shaderManager.getShader("core/ripples_simulate.comp", {}, osg::Shader::COMPUTE)); } - void RipplesSurface::traverse(osg::NodeVisitor& nv) + void RipplesSurface::updateState(const osg::FrameStamp& frameStamp, State& state) { - if (!nv.getFrameStamp()) + state.mPaused = mPaused; + + if (mPaused) return; - if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR) + constexpr double updateFrequency = 60.0; + constexpr double updatePeriod = 1.0 / updateFrequency; + + const double simulationTime = frameStamp.getSimulationTime(); + const double frameDuration = simulationTime - mLastSimulationTime; + mLastSimulationTime = simulationTime; + + mRemainingWaveTime += frameDuration; + const double ticks = std::floor(mRemainingWaveTime * updateFrequency); + mRemainingWaveTime -= ticks * updatePeriod; + + if (ticks == 0) { - size_t frameId = nv.getFrameStamp()->getFrameNumber() % 2; + state.mPaused = true; + return; + } - const auto& player = MWMechanics::getPlayer(); - const ESM::Position& playerPos = player.getRefData().getPosition(); + const MWWorld::Ptr player = MWMechanics::getPlayer(); + const ESM::Position& playerPos = player.getRefData().getPosition(); - mCurrentPlayerPos = osg::Vec2f( - std::floor(playerPos.pos[0] / mWorldScaleFactor), std::floor(playerPos.pos[1] / mWorldScaleFactor)); - osg::Vec2f offset = mCurrentPlayerPos - mLastPlayerPos; - mLastPlayerPos = mCurrentPlayerPos; - mState[frameId].mPaused = mPaused; - mState[frameId].mOffset = offset; - mState[frameId].mStateset->getUniform("positionCount")->set(static_cast(mPositionCount)); - mState[frameId].mStateset->getUniform("offset")->set(offset); + mCurrentPlayerPos = osg::Vec2f( + std::floor(playerPos.pos[0] / sWorldScaleFactor), std::floor(playerPos.pos[1] / sWorldScaleFactor)); + const osg::Vec2f offset = mCurrentPlayerPos - mLastPlayerPos; + mLastPlayerPos = mCurrentPlayerPos; - auto* positions = mState[frameId].mStateset->getUniform("positions"); + state.mStateset->getUniform("positionCount")->set(static_cast(mPositionCount)); + state.mStateset->getUniform("offset")->set(offset); - for (size_t i = 0; i < mPositionCount; ++i) - { - osg::Vec3f pos = mPositions[i] - - osg::Vec3f( - mCurrentPlayerPos.x() * mWorldScaleFactor, mCurrentPlayerPos.y() * mWorldScaleFactor, 0.0) - + osg::Vec3f(mRTTSize * mWorldScaleFactor / 2, mRTTSize * mWorldScaleFactor / 2, 0.0); - pos /= mWorldScaleFactor; - positions->setElement(i, pos); - } - positions->dirty(); + osg::Uniform* const positions = state.mStateset->getUniform("positions"); - mPositionCount = 0; + for (std::size_t i = 0; i < mPositionCount; ++i) + { + osg::Vec3f pos = mPositions[i] + - osg::Vec3f(mCurrentPlayerPos.x() * sWorldScaleFactor, mCurrentPlayerPos.y() * sWorldScaleFactor, 0.0) + + osg::Vec3f(sRTTSize * sWorldScaleFactor / 2, sRTTSize * sWorldScaleFactor / 2, 0.0); + pos /= sWorldScaleFactor; + positions->setElement(i, pos); } + positions->dirty(); + + mPositionCount = 0; + } + + void RipplesSurface::traverse(osg::NodeVisitor& nv) + { + const osg::FrameStamp* const frameStamp = nv.getFrameStamp(); + + if (frameStamp == nullptr) + return; + + if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR) + updateState(*frameStamp, mState[frameStamp->getFrameNumber() % 2]); + osg::Geometry::traverse(nv); } void RipplesSurface::drawImplementation(osg::RenderInfo& renderInfo) const { osg::State& state = *renderInfo.getState(); - osg::GLExtensions& ext = *state.get(); - size_t contextID = state.getContextID(); - - size_t currentFrame = state.getFrameStamp()->getFrameNumber() % 2; + const std::size_t currentFrame = state.getFrameStamp()->getFrameNumber() % 2; const State& frameState = mState[currentFrame]; if (frameState.mPaused) { return; } - auto bindImage = [contextID, &state, &ext](osg::Texture2D* texture, GLuint index, GLenum access) { + osg::GLExtensions& ext = *state.get(); + const std::size_t contextID = state.getContextID(); + + const auto bindImage = [&](osg::Texture2D* texture, GLuint index, GLenum access) { osg::Texture::TextureObject* to = texture->getTextureObject(contextID); if (!to || texture->isDirty(contextID)) { @@ -181,52 +206,42 @@ namespace MWRender ext.glBindImageTexture(index, to->id(), 0, GL_FALSE, 0, access, GL_RGBA16F); }; - // Run simulation at a fixed rate independent on current FPS - // FIXME: when we skip frames we need to preserve positions. this doesn't work now - size_t ticks = 1; - // PASS: Blot in all ripple spawners mProgramBlobber->apply(state); state.apply(frameState.mStateset); - for (size_t i = 0; i < ticks; i++) + if (mUseCompute) { - if (mUseCompute) - { - bindImage(mTextures[1], 0, GL_WRITE_ONLY_ARB); - bindImage(mTextures[0], 1, GL_READ_ONLY_ARB); + bindImage(mTextures[1], 0, GL_WRITE_ONLY_ARB); + bindImage(mTextures[0], 1, GL_READ_ONLY_ARB); - ext.glDispatchCompute(mRTTSize / 16, mRTTSize / 16, 1); - ext.glMemoryBarrier(GL_ALL_BARRIER_BITS); - } - else - { - mFBOs[1]->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); - state.applyTextureAttribute(0, mTextures[0]); - osg::Geometry::drawImplementation(renderInfo); - } + ext.glDispatchCompute(sRTTSize / 16, sRTTSize / 16, 1); + ext.glMemoryBarrier(GL_ALL_BARRIER_BITS); + } + else + { + mFBOs[1]->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + state.applyTextureAttribute(0, mTextures[0]); + osg::Geometry::drawImplementation(renderInfo); } // PASS: Wave simulation mProgramSimulation->apply(state); state.apply(frameState.mStateset); - for (size_t i = 0; i < ticks; i++) + if (mUseCompute) { - if (mUseCompute) - { - bindImage(mTextures[0], 0, GL_WRITE_ONLY_ARB); - bindImage(mTextures[1], 1, GL_READ_ONLY_ARB); + bindImage(mTextures[0], 0, GL_WRITE_ONLY_ARB); + bindImage(mTextures[1], 1, GL_READ_ONLY_ARB); - ext.glDispatchCompute(mRTTSize / 16, mRTTSize / 16, 1); - ext.glMemoryBarrier(GL_ALL_BARRIER_BITS); - } - else - { - mFBOs[0]->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); - state.applyTextureAttribute(0, mTextures[1]); - osg::Geometry::drawImplementation(renderInfo); - } + ext.glDispatchCompute(sRTTSize / 16, sRTTSize / 16, 1); + ext.glMemoryBarrier(GL_ALL_BARRIER_BITS); + } + else + { + mFBOs[0]->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + state.applyTextureAttribute(0, mTextures[1]); + osg::Geometry::drawImplementation(renderInfo); } } @@ -271,7 +286,7 @@ namespace MWRender setReferenceFrame(osg::Camera::ABSOLUTE_RF); setNodeMask(Mask_RenderToTexture); setClearMask(GL_NONE); - setViewport(0, 0, RipplesSurface::mRTTSize, RipplesSurface::mRTTSize); + setViewport(0, 0, RipplesSurface::sRTTSize, RipplesSurface::sRTTSize); addChild(mRipples); setCullingActive(false); setImplicitBufferAttachmentMask(0, 0); diff --git a/apps/openmw/mwrender/ripples.hpp b/apps/openmw/mwrender/ripples.hpp index 0d5b055eb5..e355b16ecd 100644 --- a/apps/openmw/mwrender/ripples.hpp +++ b/apps/openmw/mwrender/ripples.hpp @@ -46,28 +46,30 @@ namespace MWRender void releaseGLObjects(osg::State* state) const override; - static constexpr size_t mRTTSize = 1024; + static constexpr size_t sRTTSize = 1024; // e.g. texel to cell unit ratio - static constexpr float mWorldScaleFactor = 2.5; - - Resource::ResourceSystem* mResourceSystem; + static constexpr float sWorldScaleFactor = 2.5; + private: struct State { - osg::Vec2f mOffset; - osg::ref_ptr mStateset; bool mPaused = true; + osg::ref_ptr mStateset; }; + void setupFragmentPipeline(); + + void setupComputePipeline(); + + inline void updateState(const osg::FrameStamp& frameStamp, State& state); + + Resource::ResourceSystem* mResourceSystem; + size_t mPositionCount = 0; std::array mPositions; std::array mState; - private: - void setupFragmentPipeline(); - void setupComputePipeline(); - osg::Vec2f mCurrentPlayerPos; osg::Vec2f mLastPlayerPos; @@ -79,6 +81,9 @@ namespace MWRender bool mPaused = false; bool mUseCompute = false; + + double mLastSimulationTime = 0; + double mRemainingWaveTime = 0; }; class Ripples : public osg::Camera diff --git a/apps/openmw/mwrender/rotatecontroller.cpp b/apps/openmw/mwrender/rotatecontroller.cpp index d7f8bb902c..61a2a2628a 100644 --- a/apps/openmw/mwrender/rotatecontroller.cpp +++ b/apps/openmw/mwrender/rotatecontroller.cpp @@ -1,6 +1,7 @@ #include "rotatecontroller.hpp" #include +#include namespace MWRender { @@ -43,6 +44,17 @@ namespace MWRender node->setMatrix(matrix); + // If we are linked to a bone we must call setMatrixInSkeletonSpace + osgAnimation::Bone* b = dynamic_cast(node); + if (b) + { + osgAnimation::Bone* parent = b->getBoneParent(); + if (parent) + matrix *= parent->getMatrixInSkeletonSpace(); + + b->setMatrixInSkeletonSpace(matrix); + } + traverse(node, nv); } diff --git a/apps/openmw/mwrender/screenshotmanager.cpp b/apps/openmw/mwrender/screenshotmanager.cpp index a23d242a15..f478229daa 100644 --- a/apps/openmw/mwrender/screenshotmanager.cpp +++ b/apps/openmw/mwrender/screenshotmanager.cpp @@ -243,7 +243,7 @@ namespace MWRender osg::ref_ptr stateset = quad->getOrCreateStateSet(); Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager(); - stateset->setAttributeAndModes(shaderMgr.getProgram("360"), osg::StateAttribute::ON); + stateset->setAttributeAndModes(shaderMgr.getProgram("s360"), osg::StateAttribute::ON); stateset->addUniform(new osg::Uniform("cubeMap", 0)); stateset->addUniform(new osg::Uniform("mapping", static_cast(screenshotMapping))); diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index af41d2c590..c75849d532 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -220,7 +220,7 @@ namespace camera->setNodeMask(MWRender::Mask_RenderToTexture); camera->setCullMask(MWRender::Mask_Sky); camera->addChild(mEarlyRenderBinRoot); - SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *camera->getOrCreateStateSet()); + SceneUtil::ShadowManager::instance().disableShadowsForStateSet(*camera->getOrCreateStateSet()); } private: @@ -274,7 +274,8 @@ namespace MWRender if (!mSceneManager->getForceShaders()) skyroot->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(), osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED | osg::StateAttribute::ON); - SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *skyroot->getOrCreateStateSet()); + mSceneManager->setUpNormalsRTForStateSet(skyroot->getOrCreateStateSet(), false); + SceneUtil::ShadowManager::instance().disableShadowsForStateSet(*skyroot->getOrCreateStateSet()); parentNode->addChild(skyroot); mEarlyRenderBinRoot = new osg::Group; @@ -528,7 +529,7 @@ namespace MWRender if (hasRain()) return mRainRipplesEnabled; - if (mParticleNode && mCurrentParticleEffect == "meshes\\snow.nif") + if (mParticleNode && mCurrentParticleEffect == Settings::models().mWeathersnow.get()) return mSnowRipplesEnabled; return false; @@ -554,7 +555,7 @@ namespace MWRender osg::Quat quat; quat.makeRotate(MWWorld::Weather::defaultDirection(), mStormParticleDirection); // Morrowind deliberately rotates the blizzard mesh, so so should we. - if (mCurrentParticleEffect == "meshes\\blizzard.nif") + if (mCurrentParticleEffect == Settings::models().mWeatherblizzard.get()) quat.makeRotate(osg::Vec3f(-1, 0, 0), mStormParticleDirection); mParticleNode->setAttitude(quat); } @@ -726,7 +727,7 @@ namespace MWRender const osg::Vec3 defaultWrapRange = osg::Vec3(1024, 1024, 800); const bool occlusionEnabledForEffect - = !mRainEffect.empty() || mCurrentParticleEffect == "meshes\\snow.nif"; + = !mRainEffect.empty() || mCurrentParticleEffect == Settings::models().mWeathersnow.get(); for (unsigned int i = 0; i < findPSVisitor.mFoundNodes.size(); ++i) { diff --git a/apps/openmw/mwrender/skyutil.cpp b/apps/openmw/mwrender/skyutil.cpp index 3274b8c6b0..53baf36416 100644 --- a/apps/openmw/mwrender/skyutil.cpp +++ b/apps/openmw/mwrender/skyutil.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include @@ -794,8 +793,7 @@ namespace MWRender // Disable writing to the color buffer. We are using this geometry for visibility tests only. osg::ref_ptr colormask = new osg::ColorMask(0, 0, 0, 0); stateset->setAttributeAndModes(colormask); - if (sceneManager.getSupportsNormalsRT()) - stateset->setAttributeAndModes(new osg::ColorMaski(1, false, false, false, false)); + sceneManager.setUpNormalsRTForStateSet(stateset, false); mTransform->addChild(queryNode); mOcclusionQueryVisiblePixels = createOcclusionQueryNode(queryNode, true); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index d5fb01242f..cbc560d5ac 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -114,7 +114,10 @@ namespace MWRender } // move the plane back along its normal a little bit to prevent bleeding at the water shore - const float clipFudge = -5; + float fov = Settings::camera().mFieldOfView; + const float clipFudgeMin = 2.5; // minimum offset of clip plane + const float clipFudgeScale = -15000.0; + float clipFudge = abs(abs((*mCullPlane)[3]) - eyePoint.z()) * fov / clipFudgeScale - clipFudgeMin; modelViewMatrix->preMultTranslate(mCullPlane->getNormal() * clipFudge); cv->pushModelViewMatrix(modelViewMatrix, osg::Transform::RELATIVE_RF); @@ -273,8 +276,7 @@ namespace MWRender camera->setNodeMask(Mask_RenderToTexture); if (Settings::water().mRefractionScale != 1) // TODO: to be removed with issue #5709 - SceneUtil::ShadowManager::disableShadowsForStateSet( - Settings::shadows(), *camera->getOrCreateStateSet()); + SceneUtil::ShadowManager::instance().disableShadowsForStateSet(*camera->getOrCreateStateSet()); } void apply(osg::Camera* camera) override @@ -350,7 +352,7 @@ namespace MWRender camera->addChild(mClipCullNode); camera->setNodeMask(Mask_RenderToTexture); - SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *camera->getOrCreateStateSet()); + SceneUtil::ShadowManager::instance().disableShadowsForStateSet(*camera->getOrCreateStateSet()); } void apply(osg::Camera* camera) override @@ -698,11 +700,13 @@ namespace MWRender { // use a define map to conditionally compile the shader std::map defineMap; - defineMap["refraction_enabled"] = std::string(mRefraction ? "1" : "0"); + defineMap["waterRefraction"] = std::string(mRefraction ? "1" : "0"); const int rippleDetail = Settings::water().mRainRippleDetail; - defineMap["rain_ripple_detail"] = std::to_string(rippleDetail); - defineMap["ripple_map_world_scale"] = std::to_string(RipplesSurface::mWorldScaleFactor); - defineMap["ripple_map_size"] = std::to_string(RipplesSurface::mRTTSize) + ".0"; + defineMap["rainRippleDetail"] = std::to_string(rippleDetail); + defineMap["rippleMapWorldScale"] = std::to_string(RipplesSurface::sWorldScaleFactor); + defineMap["rippleMapSize"] = std::to_string(RipplesSurface::sRTTSize) + ".0"; + defineMap["sunlightScattering"] = Settings::water().mSunlightScattering ? "1" : "0"; + defineMap["wobblyShores"] = Settings::water().mWobblyShores ? "1" : "0"; Stereo::shaderStereoDefines(defineMap); @@ -711,8 +715,6 @@ namespace MWRender osg::ref_ptr normalMap( new osg::Texture2D(mResourceSystem->getImageManager()->getImage("textures/omw/water_nm.png"))); - if (normalMap->getImage()) - normalMap->getImage()->flipVertical(); normalMap->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); normalMap->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); normalMap->setMaxAnisotropy(16); diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 481a0e2ec1..cfdeb8c658 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -631,7 +631,7 @@ namespace MWScript ESM::RefId gem = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); - if (!ptr.getClass().hasInventoryStore(ptr)) + if (!ptr.getClass().isActor()) return; const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); @@ -664,10 +664,10 @@ namespace MWScript for (unsigned int i = 0; i < arg0; ++i) runtime.pop(); - if (!ptr.getClass().hasInventoryStore(ptr)) + if (!ptr.getClass().isActor()) return; - MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { if (it->getCellRef().getSoul() == soul) @@ -780,10 +780,10 @@ namespace MWScript ESM::RefId soul = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); - if (!ptr.getClass().hasInventoryStore(ptr)) + if (!ptr.getClass().isActor()) return; - MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); for (MWWorld::ContainerStoreIterator iter(store.begin()); iter != store.end(); ++iter) { diff --git a/apps/openmw/mwscript/soundextensions.cpp b/apps/openmw/mwscript/soundextensions.cpp index 44cdc25064..79bdf20160 100644 --- a/apps/openmw/mwscript/soundextensions.cpp +++ b/apps/openmw/mwscript/soundextensions.cpp @@ -33,7 +33,7 @@ namespace MWScript MWScript::InterpreterContext& context = static_cast(runtime.getContext()); - std::string file{ runtime.getStringLiteral(runtime[0].mInteger) }; + VFS::Path::Normalized file{ runtime.getStringLiteral(runtime[0].mInteger) }; runtime.pop(); std::string_view text = runtime.getStringLiteral(runtime[0].mInteger); @@ -63,7 +63,7 @@ namespace MWScript public: void execute(Interpreter::Runtime& runtime) override { - std::string music{ runtime.getStringLiteral(runtime[0].mInteger) }; + const VFS::Path::Normalized music(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); MWBase::Environment::get().getSoundManager()->streamMusic( diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 4bc59e1524..064e90f114 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -560,7 +560,7 @@ namespace MWScript runtime.pop(); if (ptr.getClass().isActor()) - ptr.getClass().getCreatureStats(ptr).getActiveSpells().removeEffects(ptr, spellid); + ptr.getClass().getCreatureStats(ptr).getActiveSpells().removeEffectsBySourceSpellId(ptr, spellid); } }; diff --git a/apps/openmw/mwsound/constants.hpp b/apps/openmw/mwsound/constants.hpp new file mode 100644 index 0000000000..217dd1935e --- /dev/null +++ b/apps/openmw/mwsound/constants.hpp @@ -0,0 +1,15 @@ +#ifndef OPENMW_APPS_OPENMW_MWSOUND_CONSTANTS_H +#define OPENMW_APPS_OPENMW_MWSOUND_CONSTANTS_H + +#include + +namespace MWSound +{ + constexpr VFS::Path::NormalizedView battlePlaylist("battle"); + constexpr VFS::Path::NormalizedView explorePlaylist("explore"); + constexpr VFS::Path::NormalizedView titleMusic("music/special/morrowind title.mp3"); + constexpr VFS::Path::NormalizedView triumphMusic("music/special/mw_triumph.mp3"); + constexpr VFS::Path::NormalizedView deathMusic("music/special/mw_death.mp3"); +} + +#endif diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index bd63d3de40..e9a5a1eb94 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -1,15 +1,41 @@ #include "ffmpeg_decoder.hpp" -#include - #include +#include #include +#include #include #include namespace MWSound { + void AVIOContextDeleter::operator()(AVIOContext* ptr) const + { + if (ptr->buffer != nullptr) + av_freep(&ptr->buffer); + +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 80, 100) + avio_context_free(&ptr); +#else + av_free(ptr); +#endif + } + + void AVFormatContextDeleter::operator()(AVFormatContext* ptr) const + { + avformat_close_input(&ptr); + } + + void AVCodecContextDeleter::operator()(AVCodecContext* ptr) const + { + avcodec_free_context(&ptr); + } + + void AVFrameDeleter::operator()(AVFrame* ptr) const + { + av_frame_free(&ptr); + } int FFmpeg_Decoder::readPacket(void* user_data, uint8_t* buf, int buf_size) { @@ -21,7 +47,9 @@ namespace MWSound std::streamsize count = stream.gcount(); if (count == 0) return AVERROR_EOF; - return count; + if (count > std::numeric_limits::max()) + return AVERROR_BUG; + return static_cast(count); } catch (std::exception&) { @@ -72,8 +100,8 @@ namespace MWSound if (!mStream) return false; - int stream_idx = mStream - mFormatCtx->streams; - while (av_read_frame(mFormatCtx, &mPacket) >= 0) + std::ptrdiff_t stream_idx = mStream - mFormatCtx->streams; + while (av_read_frame(mFormatCtx.get(), &mPacket) >= 0) { /* Check if the packet belongs to this stream */ if (stream_idx == mPacket.stream_index) @@ -100,12 +128,12 @@ namespace MWSound do { /* Decode some data, and check for errors */ - int ret = avcodec_receive_frame(mCodecCtx, mFrame); + int ret = avcodec_receive_frame(mCodecCtx.get(), mFrame.get()); if (ret == AVERROR(EAGAIN)) { if (mPacket.size == 0 && !getNextPacket()) return false; - ret = avcodec_send_packet(mCodecCtx, &mPacket); + ret = avcodec_send_packet(mCodecCtx.get(), &mPacket); av_packet_unref(&mPacket); if (ret == 0) continue; @@ -180,142 +208,100 @@ namespace MWSound return dec; } - void FFmpeg_Decoder::open(const std::string& fname) + void FFmpeg_Decoder::open(VFS::Path::NormalizedView fname) { close(); mDataStream = mResourceMgr->get(fname); - if ((mFormatCtx = avformat_alloc_context()) == nullptr) + AVIOContextPtr ioCtx(avio_alloc_context(nullptr, 0, 0, this, readPacket, writePacket, seek)); + if (ioCtx == nullptr) + throw std::runtime_error("Failed to allocate AVIO context"); + + AVFormatContext* formatCtx = avformat_alloc_context(); + if (formatCtx == nullptr) throw std::runtime_error("Failed to allocate context"); - try + formatCtx->pb = ioCtx.get(); + + // avformat_open_input frees user supplied AVFormatContext on failure + if (avformat_open_input(&formatCtx, fname.value().data(), nullptr, nullptr) != 0) + throw std::runtime_error("Failed to open input"); + + AVFormatContextPtr formatCtxPtr(std::exchange(formatCtx, nullptr)); + + if (avformat_find_stream_info(formatCtxPtr.get(), nullptr) < 0) + throw std::runtime_error("Failed to find stream info"); + + AVStream** stream = nullptr; + for (size_t j = 0; j < formatCtxPtr->nb_streams; j++) { - mFormatCtx->pb = avio_alloc_context(nullptr, 0, 0, this, readPacket, writePacket, seek); - if (!mFormatCtx->pb || avformat_open_input(&mFormatCtx, fname.c_str(), nullptr, nullptr) != 0) + if (formatCtxPtr->streams[j]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { - // "Note that a user-supplied AVFormatContext will be freed on failure". - if (mFormatCtx) - { - if (mFormatCtx->pb != nullptr) - { - if (mFormatCtx->pb->buffer != nullptr) - { - av_free(mFormatCtx->pb->buffer); - mFormatCtx->pb->buffer = nullptr; - } - av_free(mFormatCtx->pb); - mFormatCtx->pb = nullptr; - } - avformat_free_context(mFormatCtx); - } - mFormatCtx = nullptr; - throw std::runtime_error("Failed to allocate input stream"); + stream = &formatCtxPtr->streams[j]; + break; } + } - if (avformat_find_stream_info(mFormatCtx, nullptr) < 0) - throw std::runtime_error("Failed to find stream info in " + fname); + if (stream == nullptr) + throw std::runtime_error("No audio streams"); - for (size_t j = 0; j < mFormatCtx->nb_streams; j++) - { - if (mFormatCtx->streams[j]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) - { - mStream = &mFormatCtx->streams[j]; - break; - } - } - if (!mStream) - throw std::runtime_error("No audio streams in " + fname); + const AVCodec* codec = avcodec_find_decoder((*stream)->codecpar->codec_id); + if (codec == nullptr) + throw std::runtime_error("No codec found for id " + std::to_string((*stream)->codecpar->codec_id)); - const AVCodec* codec = avcodec_find_decoder((*mStream)->codecpar->codec_id); - if (!codec) - { - std::string ss = "No codec found for id " + std::to_string((*mStream)->codecpar->codec_id); - throw std::runtime_error(ss); - } + AVCodecContext* codecCtx = avcodec_alloc_context3(codec); + if (codecCtx == nullptr) + throw std::runtime_error("Failed to allocate codec context"); - AVCodecContext* avctx = avcodec_alloc_context3(codec); - avcodec_parameters_to_context(avctx, (*mStream)->codecpar); + avcodec_parameters_to_context(codecCtx, (*stream)->codecpar); // This is not needed anymore above FFMpeg version 4.0 #if LIBAVCODEC_VERSION_INT < 3805796 - av_codec_set_pkt_timebase(avctx, (*mStream)->time_base); + av_codec_set_pkt_timebase(avctx, (*stream)->time_base); #endif - mCodecCtx = avctx; - - if (avcodec_open2(mCodecCtx, codec, nullptr) < 0) - throw std::runtime_error(std::string("Failed to open audio codec ") + codec->long_name); + AVCodecContextPtr codecCtxPtr(std::exchange(codecCtx, nullptr)); - mFrame = av_frame_alloc(); + if (avcodec_open2(codecCtxPtr.get(), codec, nullptr) < 0) + throw std::runtime_error(std::string("Failed to open audio codec ") + codec->long_name); - if (mCodecCtx->sample_fmt == AV_SAMPLE_FMT_U8P) - mOutputSampleFormat = AV_SAMPLE_FMT_U8; - // FIXME: Check for AL_EXT_FLOAT32 support - // else if (mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLT || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLTP) - // mOutputSampleFormat = AV_SAMPLE_FMT_S16; - else - mOutputSampleFormat = AV_SAMPLE_FMT_S16; + AVFramePtr frame(av_frame_alloc()); + if (frame == nullptr) + throw std::runtime_error("Failed to allocate frame"); - mOutputChannelLayout = (*mStream)->codecpar->channel_layout; - if (mOutputChannelLayout == 0) - mOutputChannelLayout = av_get_default_channel_layout(mCodecCtx->channels); + if (codecCtxPtr->sample_fmt == AV_SAMPLE_FMT_U8P) + mOutputSampleFormat = AV_SAMPLE_FMT_U8; + // FIXME: Check for AL_EXT_FLOAT32 support + // else if (codecCtxPtr->sample_fmt == AV_SAMPLE_FMT_FLT || codecCtxPtr->sample_fmt == AV_SAMPLE_FMT_FLTP) + // mOutputSampleFormat = AV_SAMPLE_FMT_S16; + else + mOutputSampleFormat = AV_SAMPLE_FMT_S16; - mCodecCtx->channel_layout = mOutputChannelLayout; - } - catch (...) - { - if (mStream) - avcodec_free_context(&mCodecCtx); - mStream = nullptr; + mOutputChannelLayout = (*stream)->codecpar->channel_layout; + if (mOutputChannelLayout == 0) + mOutputChannelLayout = av_get_default_channel_layout(codecCtxPtr->channels); - if (mFormatCtx != nullptr) - { - if (mFormatCtx->pb->buffer != nullptr) - { - av_free(mFormatCtx->pb->buffer); - mFormatCtx->pb->buffer = nullptr; - } - av_free(mFormatCtx->pb); - mFormatCtx->pb = nullptr; + codecCtxPtr->channel_layout = mOutputChannelLayout; - avformat_close_input(&mFormatCtx); - } - } + mIoCtx = std::move(ioCtx); + mFrame = std::move(frame); + mFormatCtx = std::move(formatCtxPtr); + mCodecCtx = std::move(codecCtxPtr); + mStream = stream; } void FFmpeg_Decoder::close() { - if (mStream) - avcodec_free_context(&mCodecCtx); mStream = nullptr; + mCodecCtx.reset(); av_packet_unref(&mPacket); av_freep(&mDataBuf); - av_frame_free(&mFrame); + mFrame.reset(); swr_free(&mSwr); - if (mFormatCtx) - { - if (mFormatCtx->pb != nullptr) - { - // mFormatCtx->pb->buffer must be freed by hand, - // if not, valgrind will show memleak, see: - // - // https://trac.ffmpeg.org/ticket/1357 - // - if (mFormatCtx->pb->buffer != nullptr) - { - av_freep(&mFormatCtx->pb->buffer); - } -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 80, 100) - avio_context_free(&mFormatCtx->pb); -#else - av_freep(&mFormatCtx->pb); -#endif - } - avformat_close_input(&mFormatCtx); - } - + mFormatCtx.reset(); + mIoCtx.reset(); mDataStream.reset(); } @@ -427,17 +413,14 @@ namespace MWSound size_t FFmpeg_Decoder::getSampleOffset() { - int delay = (mFrameSize - mFramePos) / av_get_channel_layout_nb_channels(mOutputChannelLayout) + std::size_t delay = (mFrameSize - mFramePos) / av_get_channel_layout_nb_channels(mOutputChannelLayout) / av_get_bytes_per_sample(mOutputSampleFormat); - return (int)(mNextPts * mCodecCtx->sample_rate) - delay; + return static_cast(mNextPts * mCodecCtx->sample_rate) - delay; } FFmpeg_Decoder::FFmpeg_Decoder(const VFS::Manager* vfs) : Sound_Decoder(vfs) - , mFormatCtx(nullptr) - , mCodecCtx(nullptr) , mStream(nullptr) - , mFrame(nullptr) , mFrameSize(0) , mFramePos(0) , mNextPts(0.0) @@ -468,5 +451,4 @@ namespace MWSound { close(); } - } diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp index 88dd3316f5..264ff8fab7 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.hpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp @@ -32,17 +32,46 @@ extern "C" namespace MWSound { + struct AVIOContextDeleter + { + void operator()(AVIOContext* ptr) const; + }; + + using AVIOContextPtr = std::unique_ptr; + + struct AVFormatContextDeleter + { + void operator()(AVFormatContext* ptr) const; + }; + + using AVFormatContextPtr = std::unique_ptr; + + struct AVCodecContextDeleter + { + void operator()(AVCodecContext* ptr) const; + }; + + using AVCodecContextPtr = std::unique_ptr; + + struct AVFrameDeleter + { + void operator()(AVFrame* ptr) const; + }; + + using AVFramePtr = std::unique_ptr; + class FFmpeg_Decoder final : public Sound_Decoder { - AVFormatContext* mFormatCtx; - AVCodecContext* mCodecCtx; + AVIOContextPtr mIoCtx; + AVFormatContextPtr mFormatCtx; + AVCodecContextPtr mCodecCtx; AVStream** mStream; AVPacket mPacket; - AVFrame* mFrame; + AVFramePtr mFrame; - int mFrameSize; - int mFramePos; + std::size_t mFrameSize; + std::size_t mFramePos; double mNextPts; @@ -64,7 +93,7 @@ namespace MWSound bool getAVAudioData(); size_t readAVAudioData(void* data, size_t length); - void open(const std::string& fname) override; + void open(VFS::Path::NormalizedView fname) override; void close() override; std::string getName() override; diff --git a/apps/openmw/mwsound/loudness.cpp b/apps/openmw/mwsound/loudness.cpp index b1c1a3f2af..2a6ac5ac8e 100644 --- a/apps/openmw/mwsound/loudness.cpp +++ b/apps/openmw/mwsound/loudness.cpp @@ -15,11 +15,11 @@ namespace MWSound return; int samplesPerSegment = static_cast(mSampleRate / mSamplesPerSec); - int numSamples = bytesToFrames(mQueue.size(), mChannelConfig, mSampleType); - int advance = framesToBytes(1, mChannelConfig, mSampleType); + std::size_t numSamples = bytesToFrames(mQueue.size(), mChannelConfig, mSampleType); + std::size_t advance = framesToBytes(1, mChannelConfig, mSampleType); - int segment = 0; - int sample = 0; + std::size_t segment = 0; + std::size_t sample = 0; while (segment < numSamples / samplesPerSegment) { float sum = 0; @@ -61,7 +61,7 @@ namespace MWSound if (mSamplesPerSec <= 0.0f || mSamples.empty() || sec < 0.0f) return 0.0f; - size_t index = std::clamp(sec * mSamplesPerSec, 0, mSamples.size() - 1); + size_t index = std::min(static_cast(sec * mSamplesPerSec), mSamples.size() - 1); return mSamples[index]; } diff --git a/apps/openmw/mwsound/movieaudiofactory.cpp b/apps/openmw/mwsound/movieaudiofactory.cpp index 1bb5275c45..68ea221321 100644 --- a/apps/openmw/mwsound/movieaudiofactory.cpp +++ b/apps/openmw/mwsound/movieaudiofactory.cpp @@ -24,8 +24,10 @@ namespace MWSound private: MWSound::MovieAudioDecoder* mDecoder; - void open(const std::string& fname) override; - void close() override; + void open(VFS::Path::NormalizedView fname) override { throw std::runtime_error("Method not implemented"); } + + void close() override {} + std::string getName() override; void getInfo(int* samplerate, ChannelConfig* chans, SampleType* type) override; size_t read(char* buffer, size_t bytes) override; @@ -92,12 +94,6 @@ namespace MWSound std::shared_ptr mDecoderBridge; }; - void MWSoundDecoderBridge::open(const std::string& fname) - { - throw std::runtime_error("Method not implemented"); - } - void MWSoundDecoderBridge::close() {} - std::string MWSoundDecoderBridge::getName() { return mDecoder->getStreamName(); diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 99003d5ce3..0261649fa9 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -1034,7 +1034,7 @@ namespace MWSound return ret; } - std::pair OpenAL_Output::loadSound(const std::string& fname) + std::pair OpenAL_Output::loadSound(VFS::Path::NormalizedView fname) { getALError(); @@ -1045,7 +1045,7 @@ namespace MWSound try { DecoderPtr decoder = mManager.getDecoder(); - decoder->open(Misc::ResourceHelpers::correctSoundPath(fname, decoder->mResourceMgr)); + decoder->open(Misc::ResourceHelpers::correctSoundPath(fname, *decoder->mResourceMgr)); ChannelConfig chans; SampleType type; diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index 7636f7bda9..b419038eab 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -7,6 +7,8 @@ #include #include +#include + #include "al.h" #include "alc.h" #include "alext.h" @@ -85,7 +87,7 @@ namespace MWSound std::vector enumerateHrtf() override; - std::pair loadSound(const std::string& fname) override; + std::pair loadSound(VFS::Path::NormalizedView fname) override; size_t unloadSound(Sound_Handle data) override; bool playSound(Sound* sound, Sound_Handle data, float offset) override; diff --git a/apps/openmw/mwsound/regionsoundselector.cpp b/apps/openmw/mwsound/regionsoundselector.cpp index 8fda57596a..cb2ece7f8f 100644 --- a/apps/openmw/mwsound/regionsoundselector.cpp +++ b/apps/openmw/mwsound/regionsoundselector.cpp @@ -4,29 +4,18 @@ #include #include -#include -#include - #include "../mwbase/environment.hpp" #include "../mwworld/esmstore.hpp" namespace MWSound { - namespace - { - int addChance(int result, const ESM::Region::SoundRef& v) - { - return result + v.mChance; - } - } - RegionSoundSelector::RegionSoundSelector() : mMinTimeBetweenSounds(Fallback::Map::getFloat("Weather_Minimum_Time_Between_Environmental_Sounds")) , mMaxTimeBetweenSounds(Fallback::Map::getFloat("Weather_Maximum_Time_Between_Environmental_Sounds")) { } - std::optional RegionSoundSelector::getNextRandom(float duration, const ESM::RefId& regionName) + ESM::RefId RegionSoundSelector::getNextRandom(float duration, const ESM::RefId& regionName) { mTimePassed += duration; @@ -37,40 +26,17 @@ namespace MWSound mTimeToNextEnvSound = mMinTimeBetweenSounds + (mMaxTimeBetweenSounds - mMinTimeBetweenSounds) * a; mTimePassed = 0; - if (mLastRegionName != regionName) - { - mLastRegionName = regionName; - mSumChance = 0; - } - const ESM::Region* const region - = MWBase::Environment::get().getESMStore()->get().search(mLastRegionName); + = MWBase::Environment::get().getESMStore()->get().search(regionName); if (region == nullptr) return {}; - if (mSumChance == 0) + for (const ESM::Region::SoundRef& sound : region->mSoundList) { - mSumChance = std::accumulate(region->mSoundList.begin(), region->mSoundList.end(), 0, addChance); - if (mSumChance == 0) - return {}; + if (Misc::Rng::roll0to99() < sound.mChance) + return sound.mSound; } - - const int r = Misc::Rng::rollDice(std::max(mSumChance, 100)); - int pos = 0; - - const auto isSelected = [&](const ESM::Region::SoundRef& sound) { - if (r - pos < sound.mChance) - return true; - pos += sound.mChance; - return false; - }; - - const auto it = std::find_if(region->mSoundList.begin(), region->mSoundList.end(), isSelected); - - if (it == region->mSoundList.end()) - return {}; - - return it->mSound; + return {}; } } diff --git a/apps/openmw/mwsound/regionsoundselector.hpp b/apps/openmw/mwsound/regionsoundselector.hpp index 1a9e6e450b..474e1afa06 100644 --- a/apps/openmw/mwsound/regionsoundselector.hpp +++ b/apps/openmw/mwsound/regionsoundselector.hpp @@ -2,27 +2,18 @@ #define GAME_SOUND_REGIONSOUNDSELECTOR_H #include -#include -#include - -namespace MWBase -{ - class World; -} namespace MWSound { class RegionSoundSelector { public: - std::optional getNextRandom(float duration, const ESM::RefId& regionName); + ESM::RefId getNextRandom(float duration, const ESM::RefId& regionName); RegionSoundSelector(); private: float mTimeToNextEnvSound = 0.0f; - int mSumChance = 0; - ESM::RefId mLastRegionName; float mTimePassed = 0.0; float mMinTimeBetweenSounds; float mMaxTimeBetweenSounds; diff --git a/apps/openmw/mwsound/sound_buffer.cpp b/apps/openmw/mwsound/sound_buffer.cpp index a3fdcb8b5c..f28b268df2 100644 --- a/apps/openmw/mwsound/sound_buffer.cpp +++ b/apps/openmw/mwsound/sound_buffer.cpp @@ -183,9 +183,8 @@ namespace MWSound min = std::max(min, 1.0f); max = std::max(min, max); - Sound_Buffer& sfx - = mSoundBuffers.emplace_back(Misc::ResourceHelpers::correctSoundPath(sound.mSound), volume, min, max); - VFS::Path::normalizeFilenameInPlace(sfx.mResourceName); + Sound_Buffer& sfx = mSoundBuffers.emplace_back( + Misc::ResourceHelpers::correctSoundPath(VFS::Path::Normalized(sound.mSound)), volume, min, max); mBufferNameMap.emplace(soundId, &sfx); return &sfx; diff --git a/apps/openmw/mwsound/sound_buffer.hpp b/apps/openmw/mwsound/sound_buffer.hpp index 3bf734a4b6..7de6dab9ae 100644 --- a/apps/openmw/mwsound/sound_buffer.hpp +++ b/apps/openmw/mwsound/sound_buffer.hpp @@ -35,7 +35,7 @@ namespace MWSound { } - const std::string& getResourceName() const noexcept { return mResourceName; } + const VFS::Path::Normalized& getResourceName() const noexcept { return mResourceName; } Sound_Handle getHandle() const noexcept { return mHandle; } @@ -46,7 +46,7 @@ namespace MWSound float getMaxDist() const noexcept { return mMaxDist; } private: - std::string mResourceName; + VFS::Path::Normalized mResourceName; float mVolume; float mMinDist; float mMaxDist; diff --git a/apps/openmw/mwsound/sound_decoder.hpp b/apps/openmw/mwsound/sound_decoder.hpp index f6dcdb7032..17f9d28909 100644 --- a/apps/openmw/mwsound/sound_decoder.hpp +++ b/apps/openmw/mwsound/sound_decoder.hpp @@ -1,6 +1,8 @@ #ifndef GAME_SOUND_SOUND_DECODER_H #define GAME_SOUND_SOUND_DECODER_H +#include + #include #include @@ -36,7 +38,7 @@ namespace MWSound { const VFS::Manager* mResourceMgr; - virtual void open(const std::string& fname) = 0; + virtual void open(VFS::Path::NormalizedView fname) = 0; virtual void close() = 0; virtual std::string getName() = 0; diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index df95f0909e..5a77124985 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -6,6 +6,7 @@ #include #include +#include #include "../mwbase/soundmanager.hpp" @@ -39,7 +40,7 @@ namespace MWSound virtual std::vector enumerateHrtf() = 0; - virtual std::pair loadSound(const std::string& fname) = 0; + virtual std::pair loadSound(VFS::Path::NormalizedView fname) = 0; virtual size_t unloadSound(Sound_Handle data) = 0; virtual bool playSound(Sound* sound, Sound_Handle data, float offset) = 0; diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 0cc276807f..bcaec8ddfd 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -26,14 +26,14 @@ #include "../mwmechanics/actorutil.hpp" +#include "constants.hpp" +#include "ffmpeg_decoder.hpp" +#include "openal_output.hpp" #include "sound.hpp" #include "sound_buffer.hpp" #include "sound_decoder.hpp" #include "sound_output.hpp" -#include "ffmpeg_decoder.hpp" -#include "openal_output.hpp" - namespace MWSound { namespace @@ -172,12 +172,12 @@ namespace MWSound return std::make_shared(mVFS); } - DecoderPtr SoundManager::loadVoice(const std::string& voicefile) + DecoderPtr SoundManager::loadVoice(VFS::Path::NormalizedView voicefile) { try { DecoderPtr decoder = getDecoder(); - decoder->open(Misc::ResourceHelpers::correctSoundPath(voicefile, decoder->mResourceMgr)); + decoder->open(Misc::ResourceHelpers::correctSoundPath(voicefile, *decoder->mResourceMgr)); return decoder; } catch (std::exception& e) @@ -252,13 +252,13 @@ namespace MWSound } } - void SoundManager::streamMusicFull(const std::string& filename) + void SoundManager::streamMusicFull(VFS::Path::NormalizedView filename) { if (!mOutput->isInitialized()) return; stopMusic(); - if (filename.empty()) + if (filename.value().empty()) return; Log(Debug::Info) << "Playing \"" << filename << "\""; @@ -269,7 +269,7 @@ namespace MWSound { decoder->open(filename); } - catch (std::exception& e) + catch (const std::exception& e) { Log(Debug::Error) << "Failed to load audio from \"" << filename << "\": " << e.what(); return; @@ -285,7 +285,7 @@ namespace MWSound mOutput->streamSound(std::move(decoder), mMusic.get()); } - void SoundManager::advanceMusic(const std::string& filename, float fadeOut) + void SoundManager::advanceMusic(VFS::Path::NormalizedView filename, float fadeOut) { if (!isMusicPlaying()) { @@ -300,13 +300,16 @@ namespace MWSound void SoundManager::startRandomTitle() { - const std::vector& filelist = mMusicFiles[mCurrentPlaylist]; - if (filelist.empty()) + const auto playlist = mMusicFiles.find(mCurrentPlaylist); + + if (playlist == mMusicFiles.end() || playlist->second.empty()) { - advanceMusic(std::string()); + advanceMusic(VFS::Path::NormalizedView()); return; } + const std::vector& filelist = playlist->second; + auto& tracklist = mMusicToPlay[mCurrentPlaylist]; // Do a Fisher-Yates shuffle @@ -335,7 +338,7 @@ namespace MWSound return mMusic && mOutput->isStreamPlaying(mMusic.get()); } - void SoundManager::streamMusic(const std::string& filename, MusicType type, float fade) + void SoundManager::streamMusic(VFS::Path::NormalizedView filename, MusicType type, float fade) { const auto mechanicsManager = MWBase::Environment::get().getMechanicsManager(); @@ -344,35 +347,36 @@ namespace MWSound && type != MusicType::Special) return; - std::string normalizedName = VFS::Path::normalizeFilename(filename); - mechanicsManager->setMusicType(type); - advanceMusic(normalizedName, fade); + advanceMusic(filename, fade); if (type == MWSound::MusicType::Battle) - mCurrentPlaylist = "Battle"; + mCurrentPlaylist = battlePlaylist; else if (type == MWSound::MusicType::Explore) - mCurrentPlaylist = "Explore"; + mCurrentPlaylist = explorePlaylist; } - void SoundManager::playPlaylist(const std::string& playlist) + void SoundManager::playPlaylist(VFS::Path::NormalizedView playlist) { if (mCurrentPlaylist == playlist) return; - if (mMusicFiles.find(playlist) == mMusicFiles.end()) + auto it = mMusicFiles.find(playlist); + + if (it == mMusicFiles.end()) { - std::vector filelist; - auto playlistPath = Misc::ResourceHelpers::correctMusicPath(playlist) + '/'; - for (const auto& name : mVFS->getRecursiveDirectoryIterator(playlistPath)) + std::vector filelist; + const VFS::Path::Normalized playlistPath + = Misc::ResourceHelpers::correctMusicPath(playlist) / VFS::Path::NormalizedView(); + for (const auto& name : mVFS->getRecursiveDirectoryIterator(VFS::Path::NormalizedView(playlistPath))) filelist.push_back(name); - mMusicFiles[playlist] = std::move(filelist); + it = mMusicFiles.emplace_hint(it, playlist, std::move(filelist)); } // No Battle music? Use Explore playlist - if (playlist == "Battle" && mMusicFiles[playlist].empty()) + if (playlist == battlePlaylist && it->second.empty()) { - playPlaylist("Explore"); + playPlaylist(explorePlaylist); return; } @@ -380,7 +384,7 @@ namespace MWSound startRandomTitle(); } - void SoundManager::say(const MWWorld::ConstPtr& ptr, const std::string& filename) + void SoundManager::say(const MWWorld::ConstPtr& ptr, VFS::Path::NormalizedView filename) { if (!mOutput->isInitialized()) return; @@ -412,7 +416,7 @@ namespace MWSound return 0.0f; } - void SoundManager::say(const std::string& filename) + void SoundManager::say(VFS::Path::NormalizedView filename) { if (!mOutput->isInitialized()) return; @@ -900,8 +904,9 @@ namespace MWSound if (mCurrentRegionSound && mOutput->isSoundPlaying(mCurrentRegionSound)) return; - if (const auto next = mRegionSoundSelector.getNextRandom(duration, cell->getRegion())) - mCurrentRegionSound = playSound(*next, 1.0f, 1.0f); + ESM::RefId next = mRegionSoundSelector.getNextRandom(duration, cell->getRegion()); + if (!next.empty()) + mCurrentRegionSound = playSound(next, 1.0f, 1.0f); } void SoundManager::updateWaterSound() @@ -1018,7 +1023,7 @@ namespace MWSound mTimePassed = 0.0f; // Make sure music is still playing - if (!isMusicPlaying() && !mCurrentPlaylist.empty()) + if (!isMusicPlaying() && !mCurrentPlaylist.value().empty()) startRandomTitle(); Environment env = Env_Normal; @@ -1136,10 +1141,10 @@ namespace MWSound if (!mMusic || !mMusic->updateFade(duration) || !mOutput->isStreamPlaying(mMusic.get())) { stopMusic(); - if (!mNextMusic.empty()) + if (!mNextMusic.value().empty()) { streamMusicFull(mNextMusic); - mNextMusic.clear(); + mNextMusic = VFS::Path::Normalized(); } } else @@ -1159,9 +1164,8 @@ namespace MWSound if (isMainMenu && !isMusicPlaying()) { - std::string titlefile = "music/special/morrowind title.mp3"; - if (mVFS->exists(titlefile)) - streamMusic(titlefile, MWSound::MusicType::Special); + if (mVFS->exists(MWSound::titleMusic)) + streamMusic(MWSound::titleMusic, MWSound::MusicType::Special); } updateSounds(duration); diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index 6154d202cd..1ba80f1d73 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -9,6 +9,7 @@ #include #include +#include #include #include "../mwbase/soundmanager.hpp" @@ -52,9 +53,10 @@ namespace MWSound std::unique_ptr mOutput; // Caches available music tracks by - std::unordered_map> mMusicFiles; + std::unordered_map, VFS::Path::Hash, std::equal_to<>> + mMusicFiles; std::unordered_map> mMusicToPlay; // A list with music files not yet played - std::string mLastPlayedMusic; // The music file that was last played + VFS::Path::Normalized mLastPlayedMusic; // The music file that was last played WaterSoundUpdater mWaterSoundUpdater; @@ -90,7 +92,7 @@ namespace MWSound TrackList mActiveTracks; StreamPtr mMusic; - std::string mCurrentPlaylist; + VFS::Path::Normalized mCurrentPlaylist; bool mListenerUnderwater; osg::Vec3f mListenerPos; @@ -102,7 +104,7 @@ namespace MWSound Sound* mUnderwaterSound; Sound* mNearWaterSound; - std::string mNextMusic; + VFS::Path::Normalized mNextMusic; bool mPlaybackPaused; RegionSoundSelector mRegionSoundSelector; @@ -116,15 +118,15 @@ namespace MWSound Sound_Buffer* insertSound(const std::string& soundId, const ESM::Sound* sound); // returns a decoder to start streaming, or nullptr if the sound was not found - DecoderPtr loadVoice(const std::string& voicefile); + DecoderPtr loadVoice(VFS::Path::NormalizedView voicefile); SoundPtr getSoundRef(); StreamPtr getStreamRef(); StreamPtr playVoice(DecoderPtr decoder, const osg::Vec3f& pos, bool playlocal); - void streamMusicFull(const std::string& filename); - void advanceMusic(const std::string& filename, float fadeOut = 1.f); + void streamMusicFull(VFS::Path::NormalizedView filename); + void advanceMusic(VFS::Path::NormalizedView filename, float fadeOut = 1.f); void startRandomTitle(); void cull3DSound(SoundBase* sound); @@ -174,7 +176,7 @@ namespace MWSound void stopMusic() override; ///< Stops music if it's playing - void streamMusic(const std::string& filename, MWSound::MusicType type, float fade = 1.f) override; + void streamMusic(VFS::Path::NormalizedView filename, MWSound::MusicType type, float fade = 1.f) override; ///< Play a soundifle /// \param filename name of a sound file in the data directory. /// \param type music type. @@ -183,16 +185,16 @@ namespace MWSound bool isMusicPlaying() override; ///< Returns true if music is playing - void playPlaylist(const std::string& playlist) override; + void playPlaylist(VFS::Path::NormalizedView playlist) override; ///< Start playing music from the selected folder /// \param name of the folder that contains the playlist /// Title music playlist is predefined - void say(const MWWorld::ConstPtr& reference, const std::string& filename) override; + void say(const MWWorld::ConstPtr& reference, VFS::Path::NormalizedView filename) override; ///< Make an actor say some text. /// \param filename name of a sound file in the VFS - void say(const std::string& filename) override; + void say(VFS::Path::NormalizedView filename) override; ///< Say some text, without an actor ref /// \param filename name of a sound file in the VFS diff --git a/apps/openmw/mwstate/character.cpp b/apps/openmw/mwstate/character.cpp index 9a3bc46742..a486ff4bec 100644 --- a/apps/openmw/mwstate/character.cpp +++ b/apps/openmw/mwstate/character.cpp @@ -132,9 +132,9 @@ const MWState::Slot* MWState::Character::createSlot(const ESM::SavedGame& profil void MWState::Character::deleteSlot(const Slot* slot) { - int index = slot - mSlots.data(); + std::ptrdiff_t index = slot - mSlots.data(); - if (index < 0 || index >= static_cast(mSlots.size())) + if (index < 0 || static_cast(index) >= mSlots.size()) { // sanity check; not entirely reliable throw std::logic_error("slot not found"); @@ -147,9 +147,9 @@ void MWState::Character::deleteSlot(const Slot* slot) const MWState::Slot* MWState::Character::updateSlot(const Slot* slot, const ESM::SavedGame& profile) { - int index = slot - mSlots.data(); + std::ptrdiff_t index = slot - mSlots.data(); - if (index < 0 || index >= static_cast(mSlots.size())) + if (index < 0 || static_cast(index) >= mSlots.size()) { // sanity check; not entirely reliable throw std::logic_error("slot not found"); diff --git a/apps/openmw/mwworld/actioneat.cpp b/apps/openmw/mwworld/actioneat.cpp index b77fe146ef..c67502bdee 100644 --- a/apps/openmw/mwworld/actioneat.cpp +++ b/apps/openmw/mwworld/actioneat.cpp @@ -11,7 +11,7 @@ namespace MWWorld void ActionEat::executeImp(const Ptr& actor) { if (actor.getClass().consume(getTarget(), actor) && actor == MWMechanics::getPlayer()) - actor.getClass().skillUsageSucceeded(actor, ESM::Skill::Alchemy, 1); + actor.getClass().skillUsageSucceeded(actor, ESM::Skill::Alchemy, ESM::Skill::Alchemy_UseIngredient); } ActionEat::ActionEat(const MWWorld::Ptr& object) diff --git a/apps/openmw/mwworld/actionread.cpp b/apps/openmw/mwworld/actionread.cpp index 3f48920dc0..477c92d2dd 100644 --- a/apps/openmw/mwworld/actionread.cpp +++ b/apps/openmw/mwworld/actionread.cpp @@ -5,6 +5,7 @@ #include #include "../mwbase/environment.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/actorutil.hpp" @@ -49,12 +50,7 @@ namespace MWWorld ESM::RefId skill = ESM::Skill::indexToRefId(ref->mBase->mData.mSkillId); if (!skill.empty() && !npcStats.hasBeenUsed(ref->mBase->mId)) { - MWWorld::LiveCellRef* playerRef = actor.get(); - - const ESM::Class* class_ - = MWBase::Environment::get().getESMStore()->get().find(playerRef->mBase->mClass); - - npcStats.increaseSkill(skill, *class_, true, true); + MWBase::Environment::get().getLuaManager()->skillLevelUp(actor, skill, "book"); npcStats.flagAsUsed(ref->mBase->mId); } diff --git a/apps/openmw/mwworld/cell.cpp b/apps/openmw/mwworld/cell.cpp index 56afc104cf..1bd9761f72 100644 --- a/apps/openmw/mwworld/cell.cpp +++ b/apps/openmw/mwworld/cell.cpp @@ -100,6 +100,8 @@ namespace MWWorld mWaterHeight = -1.f; mHasWater = true; } + else + mGridPos = {}; } ESM::RefId Cell::getWorldSpace() const diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 364f3e169e..7157e67d82 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -4,6 +4,8 @@ #include #include +#include + #include #include #include @@ -276,6 +278,7 @@ namespace MWWorld { oldestCell->second.mWorkItem->abort(); mPreloadCells.erase(oldestCell); + ++mEvicted; } else return; @@ -285,7 +288,8 @@ namespace MWWorld mResourceSystem->getKeyframeManager(), mTerrain, mLandManager, mPreloadInstances)); mWorkQueue->addWorkItem(item); - mPreloadCells[&cell] = PreloadEntry(timestamp, item); + mPreloadCells.emplace(&cell, PreloadEntry(timestamp, item)); + ++mAdded; } void CellPreloader::notifyLoaded(CellStore* cell) @@ -300,6 +304,7 @@ namespace MWWorld } mPreloadCells.erase(found); + ++mLoaded; } } @@ -329,6 +334,7 @@ namespace MWWorld it->second.mWorkItem = nullptr; } mPreloadCells.erase(it++); + ++mExpired; } else ++it; @@ -467,4 +473,12 @@ namespace MWWorld mPreloadCells.clear(); } + void CellPreloader::reportStats(unsigned int frameNumber, osg::Stats& stats) const + { + stats.setAttribute(frameNumber, "CellPreloader Count", mPreloadCells.size()); + stats.setAttribute(frameNumber, "CellPreloader Added", mAdded); + stats.setAttribute(frameNumber, "CellPreloader Evicted", mEvicted); + stats.setAttribute(frameNumber, "CellPreloader Loaded", mLoaded); + stats.setAttribute(frameNumber, "CellPreloader Expired", mExpired); + } } diff --git a/apps/openmw/mwworld/cellpreloader.hpp b/apps/openmw/mwworld/cellpreloader.hpp index ddf13cab83..ce5d5e7a0f 100644 --- a/apps/openmw/mwworld/cellpreloader.hpp +++ b/apps/openmw/mwworld/cellpreloader.hpp @@ -2,11 +2,20 @@ #define OPENMW_MWWORLD_CELLPRELOADER_H #include + #include + #include #include #include +#include + +namespace osg +{ + class Stats; +} + namespace Resource { class ResourceSystem; @@ -76,6 +85,8 @@ namespace MWWorld bool isTerrainLoaded(const CellPreloader::PositionCellGrid& position, double referenceTime) const; void setTerrain(Terrain::World* terrain); + void reportStats(unsigned int frameNumber, osg::Stats& stats) const; + private: void clearAllTasks(); @@ -118,6 +129,10 @@ namespace MWWorld std::vector mLoadedTerrainPositions; double mLoadedTerrainTimestamp; + std::size_t mEvicted = 0; + std::size_t mAdded = 0; + std::size_t mExpired = 0; + std::size_t mLoaded = 0; }; } diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 932c290aaa..a1eec196eb 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -532,7 +532,7 @@ namespace MWWorld return result; const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getESMStore()->get().search( - enchantment->mEffects.mList.front().mEffectID); + enchantment->mEffects.mList.front().mData.mEffectID); if (!magicEffect) return result; diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 7ecaaa217d..ff3c73311e 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -171,31 +171,31 @@ namespace auto iter = spell.mEffects.mList.begin(); while (iter != spell.mEffects.mList.end()) { - const ESM::MagicEffect* mgef = magicEffects.search(iter->mEffectID); + const ESM::MagicEffect* mgef = magicEffects.search(iter->mData.mEffectID); if (!mgef) { Log(Debug::Verbose) << RecordType::getRecordType() << " " << spell.mId - << ": dropping invalid effect (index " << iter->mEffectID << ")"; + << ": dropping invalid effect (index " << iter->mData.mEffectID << ")"; iter = spell.mEffects.mList.erase(iter); changed = true; continue; } - if (!(mgef->mData.mFlags & ESM::MagicEffect::TargetAttribute) && iter->mAttribute != -1) + if (!(mgef->mData.mFlags & ESM::MagicEffect::TargetAttribute) && iter->mData.mAttribute != -1) { - iter->mAttribute = -1; + iter->mData.mAttribute = -1; Log(Debug::Verbose) << RecordType::getRecordType() << " " << spell.mId << ": dropping unexpected attribute argument of " - << ESM::MagicEffect::indexToGmstString(iter->mEffectID) << " effect"; + << ESM::MagicEffect::indexToGmstString(iter->mData.mEffectID) << " effect"; changed = true; } - if (!(mgef->mData.mFlags & ESM::MagicEffect::TargetSkill) && iter->mSkill != -1) + if (!(mgef->mData.mFlags & ESM::MagicEffect::TargetSkill) && iter->mData.mSkill != -1) { - iter->mSkill = -1; + iter->mData.mSkill = -1; Log(Debug::Verbose) << RecordType::getRecordType() << " " << spell.mId << ": dropping unexpected skill argument of " - << ESM::MagicEffect::indexToGmstString(iter->mEffectID) << " effect"; + << ESM::MagicEffect::indexToGmstString(iter->mData.mEffectID) << " effect"; changed = true; } @@ -742,7 +742,16 @@ namespace MWWorld case ESM::REC_DYNA: reader.getSubNameIs("COUN"); - reader.getHT(mDynamicCount); + if (reader.getFormatVersion() <= ESM::MaxActiveSpellTypeVersion) + { + uint32_t dynamicCount32 = 0; + reader.getHT(dynamicCount32); + mDynamicCount = dynamicCount32; + } + else + { + reader.getHT(mDynamicCount); + } return true; default: diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 16062c97db..c6271a4428 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -162,7 +162,7 @@ namespace MWWorld std::vector mStores; std::vector mDynamicStores; - unsigned int mDynamicCount; + uint64_t mDynamicCount; mutable std::unordered_map> mSpellListCache; @@ -209,6 +209,7 @@ namespace MWWorld void clearDynamic(); void rebuildIdsIndex(); + ESM::RefId generateId() { return ESM::RefId::generated(mDynamicCount++); } void movePlayerRecord(); @@ -229,7 +230,7 @@ namespace MWWorld template const T* insert(const T& x) { - const ESM::RefId id = ESM::RefId::generated(mDynamicCount++); + const ESM::RefId id = generateId(); Store& store = getWritable(); if (store.search(id) != nullptr) diff --git a/apps/openmw/mwworld/magiceffects.cpp b/apps/openmw/mwworld/magiceffects.cpp index 4ff0e60c46..ce83b3e18f 100644 --- a/apps/openmw/mwworld/magiceffects.cpp +++ b/apps/openmw/mwworld/magiceffects.cpp @@ -46,8 +46,8 @@ namespace MWWorld for (auto& effect : spell->mEffects.mList) { - if (effect.mEffectID == ESM::MagicEffect::DrainAttribute) - stats.mWorsenings[effect.mAttribute] = oldStats.mWorsenings; + if (effect.mData.mEffectID == ESM::MagicEffect::DrainAttribute) + stats.mWorsenings[effect.mData.mAttribute] = oldStats.mWorsenings; } creatureStats.mCorprusSpells[id] = stats; } @@ -58,30 +58,30 @@ namespace MWWorld if (!spell || spell->mData.mType == ESM::Spell::ST_Spell || spell->mData.mType == ESM::Spell::ST_Power) continue; ESM::ActiveSpells::ActiveSpellParams params; - params.mId = id; + params.mSourceSpellId = id; params.mDisplayName = spell->mName; params.mCasterActorId = creatureStats.mActorId; if (spell->mData.mType == ESM::Spell::ST_Ability) - params.mType = ESM::ActiveSpells::Type_Ability; + params.mFlags = ESM::Compatibility::ActiveSpells::Type_Ability_Flags; else - params.mType = ESM::ActiveSpells::Type_Permanent; + params.mFlags = ESM::Compatibility::ActiveSpells::Type_Permanent_Flags; params.mWorsenings = -1; params.mNextWorsening = ESM::TimeStamp(); - int effectIndex = 0; for (const auto& enam : spell->mEffects.mList) { - if (oldParams.mPurgedEffects.find(effectIndex) == oldParams.mPurgedEffects.end()) + if (oldParams.mPurgedEffects.find(enam.mIndex) == oldParams.mPurgedEffects.end()) { ESM::ActiveEffect effect; - effect.mEffectId = enam.mEffectID; - effect.mArg = MWMechanics::EffectKey(enam).mArg; + effect.mEffectId = enam.mData.mEffectID; + effect.mArg = MWMechanics::EffectKey(enam.mData).mArg; effect.mDuration = -1; effect.mTimeLeft = -1; - effect.mEffectIndex = effectIndex; - auto rand = oldParams.mEffectRands.find(effectIndex); + effect.mEffectIndex = enam.mIndex; + auto rand = oldParams.mEffectRands.find(enam.mIndex); if (rand != oldParams.mEffectRands.end()) { - float magnitude = (enam.mMagnMax - enam.mMagnMin) * rand->second + enam.mMagnMin; + float magnitude + = (enam.mData.mMagnMax - enam.mData.mMagnMin) * rand->second + enam.mData.mMagnMin; effect.mMagnitude = magnitude; effect.mMinMagnitude = magnitude; effect.mMaxMagnitude = magnitude; @@ -92,13 +92,12 @@ namespace MWWorld else { effect.mMagnitude = 0.f; - effect.mMinMagnitude = enam.mMagnMin; - effect.mMaxMagnitude = enam.mMagnMax; + effect.mMinMagnitude = enam.mData.mMagnMin; + effect.mMaxMagnitude = enam.mData.mMagnMax; effect.mFlags = ESM::ActiveEffect::Flag_None; } params.mEffects.emplace_back(effect); } - effectIndex++; } creatureStats.mActiveSpells.mSpells.emplace_back(params); } @@ -132,30 +131,28 @@ namespace MWWorld if (!enchantment) continue; ESM::ActiveSpells::ActiveSpellParams params; - params.mId = id; + params.mSourceSpellId = id; params.mDisplayName = std::move(name); params.mCasterActorId = creatureStats.mActorId; - params.mType = ESM::ActiveSpells::Type_Enchantment; + params.mFlags = ESM::Compatibility::ActiveSpells::Type_Enchantment_Flags; params.mWorsenings = -1; params.mNextWorsening = ESM::TimeStamp(); - for (std::size_t effectIndex = 0; - effectIndex < oldMagnitudes.size() && effectIndex < enchantment->mEffects.mList.size(); ++effectIndex) + for (const auto& enam : enchantment->mEffects.mList) { - const auto& enam = enchantment->mEffects.mList[effectIndex]; - auto [random, multiplier] = oldMagnitudes[effectIndex]; - float magnitude = (enam.mMagnMax - enam.mMagnMin) * random + enam.mMagnMin; + auto [random, multiplier] = oldMagnitudes[enam.mIndex]; + float magnitude = (enam.mData.mMagnMax - enam.mData.mMagnMin) * random + enam.mData.mMagnMin; magnitude *= multiplier; if (magnitude <= 0) continue; ESM::ActiveEffect effect; - effect.mEffectId = enam.mEffectID; + effect.mEffectId = enam.mData.mEffectID; effect.mMagnitude = magnitude; effect.mMinMagnitude = magnitude; effect.mMaxMagnitude = magnitude; - effect.mArg = MWMechanics::EffectKey(enam).mArg; + effect.mArg = MWMechanics::EffectKey(enam.mData).mArg; effect.mDuration = -1; effect.mTimeLeft = -1; - effect.mEffectIndex = static_cast(effectIndex); + effect.mEffectIndex = enam.mIndex; // Prevent recalculation of resistances and don't reflect or absorb the effect effect.mFlags = ESM::ActiveEffect::Flag_Ignore_Resistances | ESM::ActiveEffect::Flag_Ignore_Reflect | ESM::ActiveEffect::Flag_Ignore_SpellAbsorption; @@ -172,7 +169,7 @@ namespace MWWorld { auto it = std::find_if(creatureStats.mActiveSpells.mSpells.begin(), creatureStats.mActiveSpells.mSpells.end(), - [&](const auto& params) { return params.mId == spell.first; }); + [&](const auto& params) { return params.mSourceSpellId == spell.first; }); if (it != creatureStats.mActiveSpells.mSpells.end()) { it->mNextWorsening = spell.second.mNextWorsening; @@ -188,7 +185,7 @@ namespace MWWorld continue; for (auto& params : creatureStats.mActiveSpells.mSpells) { - if (params.mId == key.mSourceId) + if (params.mSourceSpellId == key.mSourceId) { bool found = false; for (auto& effect : params.mEffects) diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index b498bb488b..7849ba1458 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -325,6 +325,7 @@ namespace MWWorld player.mObject.mEnabled = true; } + MWBase::Environment::get().getWorldModel()->deregisterLiveCellRef(mPlayer); mPlayer.load(player.mObject); for (size_t i = 0; i < mSaveAttributes.size(); ++i) diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 6fc515981b..d6715c5ad2 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -79,18 +79,17 @@ namespace int count = 0; speed = 0.0f; ESM::EffectList projectileEffects; - for (std::vector::const_iterator iter(effects->mList.begin()); iter != effects->mList.end(); - ++iter) + for (const ESM::IndexedENAMstruct& effect : effects->mList) { const ESM::MagicEffect* magicEffect - = MWBase::Environment::get().getESMStore()->get().find(iter->mEffectID); + = MWBase::Environment::get().getESMStore()->get().find(effect.mData.mEffectID); // Speed of multi-effect projectiles should be the average of the constituent effects, // based on observation of the original engine. speed += magicEffect->mData.mSpeed; count++; - if (iter->mRange != ESM::RT_Target) + if (effect.mData.mRange != ESM::RT_Target) continue; if (magicEffect->mBolt.empty()) @@ -106,7 +105,7 @@ namespace ->get() .find(magicEffect->mData.mSchool) ->mSchool->mBoltSound); - projectileEffects.mList.push_back(*iter); + projectileEffects.mList.push_back(effect); } if (count != 0) @@ -117,7 +116,7 @@ namespace { const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getESMStore()->get().find( - effects->mList.begin()->mEffectID); + effects->mList.begin()->mData.mEffectID); texture = magicEffect->mParticle; } @@ -136,10 +135,10 @@ namespace { // Calculate combined light diffuse color from magical effects osg::Vec4 lightDiffuseColor; - for (const ESM::ENAMstruct& enam : effects.mList) + for (const ESM::IndexedENAMstruct& enam : effects.mList) { const ESM::MagicEffect* magicEffect - = MWBase::Environment::get().getESMStore()->get().find(enam.mEffectID); + = MWBase::Environment::get().getESMStore()->get().find(enam.mData.mEffectID); lightDiffuseColor += magicEffect->getColor(); } int numberOfEffects = effects.mList.size(); diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp index f7ba76da21..dc49ff0a4e 100644 --- a/apps/openmw/mwworld/refdata.cpp +++ b/apps/openmw/mwworld/refdata.cpp @@ -58,12 +58,12 @@ namespace MWWorld RefData::RefData() : mBaseNode(nullptr) + , mCustomData(nullptr) + , mFlags(0) , mDeletedByContentFile(false) , mEnabled(true) , mPhysicsPostponed(false) - , mCustomData(nullptr) , mChanged(false) - , mFlags(0) { for (int i = 0; i < 3; ++i) { @@ -74,50 +74,50 @@ namespace MWWorld RefData::RefData(const ESM::CellRef& cellRef) : mBaseNode(nullptr) + , mPosition(cellRef.mPos) + , mCustomData(nullptr) + , mFlags(0) // Loading from ESM/ESP files -> assume unchanged , mDeletedByContentFile(false) , mEnabled(true) , mPhysicsPostponed(false) - , mPosition(cellRef.mPos) - , mCustomData(nullptr) , mChanged(false) - , mFlags(0) // Loading from ESM/ESP files -> assume unchanged { } RefData::RefData(const ESM4::Reference& ref) : mBaseNode(nullptr) + , mPosition(ref.mPos) + , mCustomData(nullptr) + , mFlags(0) , mDeletedByContentFile(ref.mFlags & ESM4::Rec_Deleted) , mEnabled(!(ref.mFlags & ESM4::Rec_Disabled)) , mPhysicsPostponed(false) - , mPosition(ref.mPos) - , mCustomData(nullptr) , mChanged(false) - , mFlags(0) { } RefData::RefData(const ESM4::ActorCharacter& ref) : mBaseNode(nullptr) + , mPosition(ref.mPos) + , mCustomData(nullptr) + , mFlags(0) , mDeletedByContentFile(ref.mFlags & ESM4::Rec_Deleted) , mEnabled(!(ref.mFlags & ESM4::Rec_Disabled)) , mPhysicsPostponed(false) - , mPosition(ref.mPos) - , mCustomData(nullptr) , mChanged(false) - , mFlags(0) { } RefData::RefData(const ESM::ObjectState& objectState, bool deletedByContentFile) : mBaseNode(nullptr) - , mDeletedByContentFile(deletedByContentFile) - , mEnabled(objectState.mEnabled != 0) - , mPhysicsPostponed(false) , mPosition(objectState.mPosition) , mAnimationState(objectState.mAnimationState) , mCustomData(nullptr) - , mChanged(true) , mFlags(objectState.mFlags) // Loading from a savegame -> assume changed + , mDeletedByContentFile(deletedByContentFile) + , mEnabled(objectState.mEnabled != 0) + , mPhysicsPostponed(false) + , mChanged(true) { // "Note that the ActivationFlag_UseEnabled is saved to the reference, // which will result in permanently suppressed activation if the reference script is removed. diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index ae80a0d64e..e0b62c94b6 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -1,7 +1,7 @@ #ifndef GAME_MWWORLD_REFDATA_H #define GAME_MWWORLD_REFDATA_H -#include +#include #include #include @@ -47,6 +47,10 @@ namespace MWWorld MWScript::Locals mLocals; std::shared_ptr mLuaScripts; + ESM::Position mPosition; + ESM::AnimationState mAnimationState; + std::unique_ptr mCustomData; + unsigned int mFlags; /// separate delete flag used for deletion by a content file /// @note not stored in the save game file. @@ -58,20 +62,12 @@ namespace MWWorld bool mPhysicsPostponed : 1; private: - ESM::Position mPosition; - - ESM::AnimationState mAnimationState; - - std::unique_ptr mCustomData; + bool mChanged : 1; void copy(const RefData& refData); void cleanup(); - bool mChanged; - - unsigned int mFlags; - public: RefData(); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 8424076758..beb519b9e6 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -99,6 +99,10 @@ namespace return ptr.getClass().getCorrectedModel(ptr); } + // Null node meant to distinguish objects that aren't in the scene from paged objects + // TODO: find a more clever way to make paging exclusion more reliable? + static osg::ref_ptr pagedNode = new SceneUtil::PositionAttitudeTransform; + void addObject(const MWWorld::Ptr& ptr, const MWWorld::World& world, const std::vector& pagedRefs, MWPhysics::PhysicsSystem& physics, MWRender::RenderingManager& rendering) { @@ -111,11 +115,6 @@ namespace std::string model = getModel(ptr); const auto rotation = makeDirectNodeRotation(ptr); - // Null node meant to distinguish objects that aren't in the scene from paged objects - // TODO: find a more clever way to make paging exclusion more reliable? - static const osg::ref_ptr pagedNode( - new SceneUtil::PositionAttitudeTransform); - ESM::RefNum refnum = ptr.getCellRef().getRefNum(); if (!refnum.hasContentFile() || !std::binary_search(pagedRefs.begin(), pagedRefs.end(), refnum)) ptr.getClass().insertObjectRendering(ptr, model, rendering); @@ -164,13 +163,13 @@ namespace Misc::Convert::makeBulletQuaternion(ptr.getCellRef().getPosition()), transform.getOrigin()); const auto start = Misc::Convert::toOsg(closedDoorTransform(center + toPoint)); - const auto startPoint = physics.castRay(start, start - osg::Vec3f(0, 0, 1000), ptr, {}, + const auto startPoint = physics.castRay(start, start - osg::Vec3f(0, 0, 1000), { ptr }, {}, MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | MWPhysics::CollisionType_Water); const auto connectionStart = startPoint.mHit ? startPoint.mHitPos : start; const auto end = Misc::Convert::toOsg(closedDoorTransform(center - toPoint)); - const auto endPoint = physics.castRay(end, end - osg::Vec3f(0, 0, 1000), ptr, {}, + const auto endPoint = physics.castRay(end, end - osg::Vec3f(0, 0, 1000), { ptr }, {}, MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | MWPhysics::CollisionType_Water); const auto connectionEnd = endPoint.mHit ? endPoint.mHitPos : end; @@ -191,7 +190,8 @@ namespace { const DetourNavigator::AgentBounds agentBounds = world.getPathfindingAgentBounds(ptr); if (!navigator.addAgent(agentBounds)) - Log(Debug::Warning) << "Agent bounds are not supported by navigator: " << agentBounds; + Log(Debug::Warning) << "Agent bounds are not supported by navigator for " << ptr.toString() << ": " + << agentBounds; } } @@ -274,7 +274,6 @@ namespace namespace MWWorld { - void Scene::removeFromPagedRefs(const Ptr& ptr) { ESM::RefNum refnum = ptr.getCellRef().getRefNum(); @@ -288,6 +287,11 @@ namespace MWWorld } } + bool Scene::isPagedRef(const Ptr& ptr) const + { + return ptr.getRefData().getBaseNode() == pagedNode.get(); + } + void Scene::updateObjectRotation(const Ptr& ptr, RotationOrder order) { const auto rot = makeNodeRotation(ptr, order); @@ -1282,4 +1286,9 @@ namespace MWWorld } } } + + void Scene::reportStats(unsigned int frameNumber, osg::Stats& stats) const + { + mPreloader->reportStats(frameNumber, stats); + } } diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index f3dd377845..6c915d4f92 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -19,6 +19,7 @@ namespace osg { class Vec3f; + class Stats; } namespace ESM @@ -190,6 +191,8 @@ namespace MWWorld void removeFromPagedRefs(const Ptr& ptr); + bool isPagedRef(const Ptr& ptr) const; + void updateObjectRotation(const Ptr& ptr, RotationOrder order); void updateObjectScale(const Ptr& ptr); @@ -201,6 +204,8 @@ namespace MWWorld void testExteriorCells(); void testInteriorCells(); + + void reportStats(unsigned int frameNumber, osg::Stats& stats) const; }; } diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 36b5958dc3..4f6f52a81a 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -1,5 +1,7 @@ #include "weather.hpp" +#include + #include #include @@ -45,7 +47,8 @@ namespace MWWorld osg::Vec3f calculateStormDirection(const std::string& particleEffect) { osg::Vec3f stormDirection = MWWorld::Weather::defaultDirection(); - if (particleEffect == "meshes\\ashcloud.nif" || particleEffect == "meshes\\blightcloud.nif") + if (particleEffect == Settings::models().mWeatherashcloud.get() + || particleEffect == Settings::models().mWeatherblightcloud.get()) { osg::Vec3f playerPos = MWMechanics::getPlayer().getRefData().getPosition().asVec3(); playerPos.z() = 0; @@ -581,10 +584,10 @@ namespace MWWorld addWeather("Overcast", 0.7f, 0.0f); // 3 addWeather("Rain", 0.5f, 10.0f); // 4 addWeather("Thunderstorm", 0.5f, 20.0f); // 5 - addWeather("Ashstorm", 0.2f, 50.0f, "meshes\\ashcloud.nif"); // 6 - addWeather("Blight", 0.2f, 60.0f, "meshes\\blightcloud.nif"); // 7 - addWeather("Snow", 0.5f, 40.0f, "meshes\\snow.nif"); // 8 - addWeather("Blizzard", 0.16f, 70.0f, "meshes\\blizzard.nif"); // 9 + addWeather("Ashstorm", 0.2f, 50.0f, Settings::models().mWeatherashcloud.get()); // 6 + addWeather("Blight", 0.2f, 60.0f, Settings::models().mWeatherblightcloud.get()); // 7 + addWeather("Snow", 0.5f, 40.0f, Settings::models().mWeathersnow.get()); // 8 + addWeather("Blizzard", 0.16f, 70.0f, Settings::models().mWeatherblizzard.get()); // 9 Store::iterator it = store.get().begin(); for (; it != store.get().end(); ++it) @@ -720,7 +723,7 @@ namespace MWWorld // For some reason Ash Storm is not considered as a precipitation weather in game mPrecipitation = !(mResult.mParticleEffect.empty() && mResult.mRainEffect.empty()) - && mResult.mParticleEffect != "meshes\\ashcloud.nif"; + && mResult.mParticleEffect != Settings::models().mWeatherashcloud.get(); mStormDirection = calculateStormDirection(mResult.mParticleEffect); mRendering.getSkyManager()->setStormParticleDirection(mStormDirection); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 689edaf62e..c72a9993d6 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -89,6 +90,8 @@ #include "../mwphysics/object.hpp" #include "../mwphysics/physicssystem.hpp" +#include "../mwsound/constants.hpp" + #include "actionteleport.hpp" #include "cellstore.hpp" #include "containerstore.hpp" @@ -393,7 +396,7 @@ namespace MWWorld { // Make sure that we do not continue to play a Title music after a new game video. MWBase::Environment::get().getSoundManager()->stopMusic(); - MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore")); + MWBase::Environment::get().getSoundManager()->playPlaylist(MWSound::explorePlaylist); MWBase::Environment::get().getWindowManager()->playVideo(video, true); } } @@ -590,6 +593,12 @@ namespace MWWorld // Must be cleared before mRendering is destroyed if (mProjectileManager) mProjectileManager->clear(); + + if (Settings::navigator().mWaitForAllJobsOnExit) + { + Log(Debug::Verbose) << "Waiting for all navmesh jobs to be done..."; + mNavigator->wait(DetourNavigator::WaitConditionType::allJobsDone, nullptr); + } } void World::setRandomSeed(uint32_t seed) @@ -662,25 +671,29 @@ namespace MWWorld if (!cell.isExterior() || !cell.getDisplayName().empty()) return cell.getDisplayName(); - return ESM::visit(ESM::VisitOverload{ - [&](const ESM::Cell& cellIn) -> std::string_view { return getCellName(&cellIn); }, - [&](const ESM4::Cell& cellIn) -> std::string_view { - return mStore.get().find("sDefaultCellname")->mValue.getString(); - }, - }, - cell); - } - - std::string_view World::getCellName(const ESM::Cell* cell) const - { - if (cell) + if (!cell.getRegion().empty()) { - if (!cell->isExterior() || !cell->mName.empty()) - return cell->mName; + std::string_view regionName + = ESM::visit(ESM::VisitOverload{ + [&](const ESM::Cell& cellIn) -> std::string_view { + if (const ESM::Region* region = mStore.get().search(cell.getRegion())) + return !region->mName.empty() ? region->mName : region->mId.getRefIdString(); + return {}; + }, + [&](const ESM4::Cell& cellIn) -> std::string_view { return {}; }, + }, + cell); + if (!regionName.empty()) + return regionName; + } - if (const ESM::Region* region = mStore.get().search(cell->mRegion)) - return region->mName; + if (!cell.getWorldSpace().empty() && ESM::isEsm4Ext(cell.getWorldSpace())) + { + if (const ESM4::World* worldspace = mStore.get().search(cell.getWorldSpace())) + if (!worldspace->mFullName.empty()) + return worldspace->mFullName; } + return mStore.get().find("sDefaultCellname")->mValue.getString(); } @@ -1738,23 +1751,27 @@ namespace MWWorld void World::updateSoundListener() { - osg::Vec3f cameraPosition = mRendering->getCamera()->getPosition(); + const MWRender::Camera* camera = mRendering->getCamera(); const auto& player = getPlayerPtr(); const ESM::Position& refpos = player.getRefData().getPosition(); - osg::Vec3f listenerPos; + osg::Vec3f listenerPos, up, forward; + osg::Quat listenerOrient; - if (isFirstPerson()) - listenerPos = cameraPosition; + if (isFirstPerson() || Settings::sound().mCameraListener) + listenerPos = camera->getPosition(); else listenerPos = refpos.asVec3() + osg::Vec3f(0, 0, 1.85f * mPhysics->getHalfExtents(player).z()); - osg::Quat listenerOrient = osg::Quat(refpos.rot[1], osg::Vec3f(0, -1, 0)) - * osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1)); + if (isFirstPerson() || Settings::sound().mCameraListener) + listenerOrient = camera->getOrient(); + else + listenerOrient = osg::Quat(refpos.rot[1], osg::Vec3f(0, -1, 0)) + * osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1)); - osg::Vec3f forward = listenerOrient * osg::Vec3f(0, 1, 0); - osg::Vec3f up = listenerOrient * osg::Vec3f(0, 0, 1); + forward = listenerOrient * osg::Vec3f(0, 1, 0); + up = listenerOrient * osg::Vec3f(0, 0, 1); - bool underwater = isUnderwater(player.getCell(), cameraPosition); + bool underwater = isUnderwater(player.getCell(), camera->getPosition()); MWBase::Environment::get().getSoundManager()->setListenerPosDir(listenerPos, forward, up, underwater); } @@ -1817,9 +1834,10 @@ namespace MWWorld } bool World::castRenderingRay(MWPhysics::RayCastingResult& res, const osg::Vec3f& from, const osg::Vec3f& to, - bool ignorePlayer, bool ignoreActors) + bool ignorePlayer, bool ignoreActors, std::span ignoreList) { - MWRender::RenderingManager::RayResult rayRes = mRendering->castRay(from, to, ignorePlayer, ignoreActors); + MWRender::RenderingManager::RayResult rayRes + = mRendering->castRay(from, to, ignorePlayer, ignoreActors, ignoreList); res.mHit = rayRes.mHit; res.mHitPos = rayRes.mHitPointWorld; res.mHitNormal = rayRes.mHitNormalWorld; @@ -2598,7 +2616,7 @@ namespace MWWorld collisionTypes |= MWPhysics::CollisionType_Water; } MWPhysics::RayCastingResult result - = mPhysics->castRay(from, to, MWWorld::Ptr(), std::vector(), collisionTypes); + = mPhysics->castRay(from, to, { MWWorld::Ptr() }, std::vector(), collisionTypes); if (!result.mHit) return maxDist; @@ -2930,96 +2948,82 @@ namespace MWWorld return result; } - void World::castSpell(const Ptr& actor, bool manualSpell) + void World::castSpell(const Ptr& actor, bool scriptedSpell) { MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); - // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit - // result. - std::vector targetActors; - if (!actor.isEmpty() && actor != MWMechanics::getPlayer() && !manualSpell) - stats.getAiSequence().getCombatTargets(targetActors); - - const float fCombatDistance = mStore.get().find("fCombatDistance")->mValue.getFloat(); - - osg::Vec3f hitPosition = actor.getRefData().getPosition().asVec3(); - - // for player we can take faced object first + const bool casterIsPlayer = actor == MWMechanics::getPlayer(); MWWorld::Ptr target; - if (actor == MWMechanics::getPlayer()) - target = getFacedObject(); - - // if the faced object can not be activated, do not use it - if (!target.isEmpty() && !target.getClass().hasToolTip(target)) - target = nullptr; - - if (target.isEmpty()) + // For scripted spells we should not use hit contact + if (scriptedSpell) { - // For scripted spells we should not use hit contact - if (manualSpell) + if (!casterIsPlayer) { - if (actor != MWMechanics::getPlayer()) + for (const auto& package : stats.getAiSequence()) { - for (const auto& package : stats.getAiSequence()) + if (package->getTypeId() == MWMechanics::AiPackageTypeId::Cast) { - if (package->getTypeId() == MWMechanics::AiPackageTypeId::Cast) - { - target = package->getTarget(); - break; - } + target = package->getTarget(); + break; } } } - else + } + else + { + if (casterIsPlayer) + target = getFacedObject(); + + if (target.isEmpty() || !target.getClass().hasToolTip(target)) { // For actor targets, we want to use melee hit contact. // This is to give a slight tolerance for errors, especially with creatures like the Skeleton that would - // be very hard to aim at otherwise. For object targets, we want the detailed shapes (rendering - // raycast). If we used the bounding boxes for static objects, then we would not be able to target e.g. + // be very hard to aim at otherwise. + // For object targets, we want the detailed shapes (rendering raycast). + // If we used the bounding boxes for static objects, then we would not be able to target e.g. // objects lying on a shelf. - const std::pair result1 = MWMechanics::getHitContact(actor, fCombatDistance); - - // Get the target to use for "on touch" effects, using the facing direction from Head node - osg::Vec3f origin = getActorHeadTransform(actor).getTrans(); - - osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1, 0, 0)) - * osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0, 0, -1)); - - osg::Vec3f direction = orient * osg::Vec3f(0, 1, 0); - float distance = getMaxActivationDistance(); - osg::Vec3f dest = origin + direction * distance; - - MWRender::RenderingManager::RayResult result2 = mRendering->castRay(origin, dest, true, true); + const float fCombatDistance = mStore.get().find("fCombatDistance")->mValue.getFloat(); + target = MWMechanics::getHitContact(actor, fCombatDistance).first; - float dist1 = std::numeric_limits::max(); - float dist2 = std::numeric_limits::max(); - - if (!result1.first.isEmpty() && result1.first.getClass().isActor()) - dist1 = (origin - result1.second).length(); - if (result2.mHit) - dist2 = (origin - result2.mHitPointWorld).length(); - - if (!result1.first.isEmpty() && result1.first.getClass().isActor()) + if (target.isEmpty()) { - target = result1.first; - hitPosition = result1.second; - if (dist1 > getMaxActivationDistance()) - target = nullptr; - } - else if (result2.mHit) - { - target = result2.mHitObject; - hitPosition = result2.mHitPointWorld; - if (dist2 > getMaxActivationDistance() && !target.isEmpty() - && !target.getClass().hasToolTip(target)) - target = nullptr; + // Get the target using the facing direction from Head node + const osg::Vec3f origin = getActorHeadTransform(actor).getTrans(); + const osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1, 0, 0)) + * osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0, 0, -1)); + const osg::Vec3f direction = orient * osg::Vec3f(0, 1, 0); + const osg::Vec3f dest = origin + direction * getMaxActivationDistance(); + const MWRender::RenderingManager::RayResult result = mRendering->castRay(origin, dest, true, true); + if (result.mHit) + target = result.mHitObject; } } } + osg::Vec3f hitPosition = actor.getRefData().getPosition().asVec3(); + if (!target.isEmpty()) + { + // Touch explosion placement doesn't depend on where the target was "touched". + // In Morrowind, it's at 0.7 of the actor's AABB height for actors + // or at 0.7 of the player's height for non-actors if the player is the caster + // This is probably meant to prevent the explosion from being too far above on large objects + // but it often puts the explosions way above small objects, so we'll deviate here + // and use the object's bounds when reasonable (it's $CURRENT_YEAR, we can afford that) + // Note collision object origin is intentionally not used + hitPosition = target.getRefData().getPosition().asVec3(); + constexpr float explosionHeight = 0.7f; + float targetHeight = getHalfExtents(target).z() * 2.f; + if (!target.getClass().isActor() && casterIsPlayer) + { + const float playerHeight = getHalfExtents(actor).z() * 2.f; + targetHeight = std::min(targetHeight, playerHeight); + } + hitPosition.z() += targetHeight * explosionHeight; + } + const ESM::RefId& selectedSpell = stats.getSpells().getSelectedSpell(); - MWMechanics::CastSpell cast(actor, target, false, manualSpell); + MWMechanics::CastSpell cast(actor, target, false, scriptedSpell); cast.mHitPosition = hitPosition; if (!selectedSpell.empty()) @@ -3064,8 +3068,8 @@ namespace MWWorld actor.getClass().getCreatureStats(actor).getAiSequence().getCombatTargets(targetActors); // Check for impact, if yes, handle hit, if not, launch projectile - MWPhysics::RayCastingResult result - = mPhysics->castRay(sourcePos, worldPos, actor, targetActors, 0xff, MWPhysics::CollisionType_Projectile); + MWPhysics::RayCastingResult result = mPhysics->castRay( + sourcePos, worldPos, { actor }, targetActors, 0xff, MWPhysics::CollisionType_Projectile); if (result.mHit) MWMechanics::projectileHit(actor, result.mHitObject, bow, projectile, result.mHitPos, attackStrength); else @@ -3707,22 +3711,22 @@ namespace MWWorld void World::preloadEffects(const ESM::EffectList* effectList) { - for (const ESM::ENAMstruct& effectInfo : effectList->mList) + for (const ESM::IndexedENAMstruct& effectInfo : effectList->mList) { - const ESM::MagicEffect* effect = mStore.get().find(effectInfo.mEffectID); + const ESM::MagicEffect* effect = mStore.get().find(effectInfo.mData.mEffectID); - if (MWMechanics::isSummoningEffect(effectInfo.mEffectID)) + if (MWMechanics::isSummoningEffect(effectInfo.mData.mEffectID)) { preload(mWorldScene.get(), mStore, ESM::RefId::stringRefId("VFX_Summon_Start")); - preload(mWorldScene.get(), mStore, MWMechanics::getSummonedCreature(effectInfo.mEffectID)); + preload(mWorldScene.get(), mStore, MWMechanics::getSummonedCreature(effectInfo.mData.mEffectID)); } preload(mWorldScene.get(), mStore, effect->mCasting); preload(mWorldScene.get(), mStore, effect->mHit); - if (effectInfo.mArea > 0) + if (effectInfo.mData.mArea > 0) preload(mWorldScene.get(), mStore, effect->mArea); - if (effectInfo.mRange == ESM::RT_Target) + if (effectInfo.mData.mRange == ESM::RT_Target) preload(mWorldScene.get(), mStore, effect->mBolt); } } @@ -3790,6 +3794,7 @@ namespace MWWorld { DetourNavigator::reportStats(mNavigator->getStats(), frameNumber, stats); mPhysics->reportStats(frameNumber, stats); + mWorldScene->reportStats(frameNumber, stats); } void World::updateSkyDate() diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index b5d56753b0..b7db68214d 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -273,7 +273,6 @@ namespace MWWorld /// \note If cell==0, the cell the player is currently in will be used instead to /// generate a name. std::string_view getCellName(const MWWorld::Cell& cell) const override; - std::string_view getCellName(const ESM::Cell* cell) const override; void removeRefScript(const MWWorld::CellRef* ref) override; //< Remove the script attached to ref from mLocalScripts @@ -392,7 +391,7 @@ namespace MWWorld const MWPhysics::RayCastingInterface* getRayCasting() const override; bool castRenderingRay(MWPhysics::RayCastingResult& res, const osg::Vec3f& from, const osg::Vec3f& to, - bool ignorePlayer, bool ignoreActors) override; + bool ignorePlayer, bool ignoreActors, std::span ignoreList) override; void setActorCollisionMode(const Ptr& ptr, bool internal, bool external) override; bool isActorCollisionEnabled(const Ptr& ptr) override; diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 967511953d..db6ecb816a 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -29,6 +29,7 @@ file(GLOB UNITTEST_SRC_FILES lua/test_storage.cpp lua/test_async.cpp lua/test_inputactions.cpp + lua/test_yaml.cpp lua/test_ui_content.cpp @@ -97,6 +98,10 @@ file(GLOB UNITTEST_SRC_FILES esmterrain/testgridsampling.cpp resource/testobjectcache.cpp + + vfs/testpathutil.cpp + + sceneutil/osgacontroller.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) @@ -124,7 +129,7 @@ target_compile_definitions(openmw_test_suite PRIVATE OPENMW_DATA_DIR=u8"${CMAKE_CURRENT_BINARY_DIR}/data" OPENMW_PROJECT_SOURCE_DIR=u8"${PROJECT_SOURCE_DIR}") -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw_test_suite PRIVATE diff --git a/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp b/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp index bc1288f5f6..51ab37b123 100644 --- a/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp +++ b/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp @@ -267,7 +267,6 @@ namespace updater.wait(WaitConditionType::allJobsDone, &mListener); updater.stop(); const std::set present{ - TilePosition(-2, 0), TilePosition(-1, -1), TilePosition(-1, 0), TilePosition(-1, 1), @@ -278,6 +277,7 @@ namespace TilePosition(0, 2), TilePosition(1, -1), TilePosition(1, 0), + TilePosition(1, 1), }; for (int x = -5; x <= 5; ++x) for (int y = -5; y <= 5; ++y) @@ -336,4 +336,273 @@ namespace EXPECT_EQ(tile->mTileId, 2); EXPECT_EQ(tile->mVersion, navMeshFormatVersion); } + + TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, repeated_tile_updates_should_be_delayed) + { + mRecastMeshManager.setWorldspace(mWorldspace, nullptr); + + mSettings.mMaxTilesNumber = 9; + mSettings.mMinUpdateInterval = std::chrono::milliseconds(250); + + AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr); + const auto navMeshCacheItem = std::make_shared(1, mSettings); + + std::map changedTiles; + + for (int x = -3; x <= 3; ++x) + for (int y = -3; y <= 3; ++y) + changedTiles.emplace(TilePosition{ x, y }, ChangeType::update); + + updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); + + updater.wait(WaitConditionType::allJobsDone, &mListener); + + { + const AsyncNavMeshUpdaterStats stats = updater.getStats(); + EXPECT_EQ(stats.mJobs, 0); + EXPECT_EQ(stats.mWaiting.mDelayed, 0); + } + + updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); + + { + const AsyncNavMeshUpdaterStats stats = updater.getStats(); + EXPECT_EQ(stats.mJobs, 49); + EXPECT_EQ(stats.mWaiting.mDelayed, 49); + } + + updater.wait(WaitConditionType::allJobsDone, &mListener); + + { + const AsyncNavMeshUpdaterStats stats = updater.getStats(); + EXPECT_EQ(stats.mJobs, 0); + EXPECT_EQ(stats.mWaiting.mDelayed, 0); + } + } + + struct DetourNavigatorSpatialJobQueueTest : Test + { + const AgentBounds mAgentBounds{ CollisionShapeType::Aabb, osg::Vec3f(1, 1, 1) }; + const std::shared_ptr mNavMeshCacheItemPtr; + const std::weak_ptr mNavMeshCacheItem = mNavMeshCacheItemPtr; + const std::string_view mWorldspace = "worldspace"; + const TilePosition mChangedTile{ 0, 0 }; + const std::chrono::steady_clock::time_point mProcessTime{}; + const TilePosition mPlayerTile{ 0, 0 }; + const int mMaxTiles = 9; + }; + + TEST_F(DetourNavigatorSpatialJobQueueTest, should_store_multiple_jobs_per_tile) + { + std::list jobs; + SpatialJobQueue queue; + + queue.push(jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, "worldspace1", mChangedTile, + ChangeType::remove, mProcessTime)); + queue.push(jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, "worldspace2", mChangedTile, + ChangeType::update, mProcessTime)); + + ASSERT_EQ(queue.size(), 2); + + const auto job1 = queue.pop(mChangedTile); + ASSERT_TRUE(job1.has_value()); + EXPECT_EQ((*job1)->mWorldspace, "worldspace1"); + + const auto job2 = queue.pop(mChangedTile); + ASSERT_TRUE(job2.has_value()); + EXPECT_EQ((*job2)->mWorldspace, "worldspace2"); + + EXPECT_EQ(queue.size(), 0); + } + + struct DetourNavigatorJobQueueTest : DetourNavigatorSpatialJobQueueTest + { + }; + + TEST_F(DetourNavigatorJobQueueTest, pop_should_return_nullptr_from_empty) + { + JobQueue queue; + ASSERT_FALSE(queue.hasJob()); + ASSERT_FALSE(queue.pop(mPlayerTile).has_value()); + } + + TEST_F(DetourNavigatorJobQueueTest, push_on_change_type_remove_should_add_to_removing) + { + const std::chrono::steady_clock::time_point processTime{}; + + std::list jobs; + const JobIt job = jobs.emplace( + jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, mChangedTile, ChangeType::remove, processTime); + + JobQueue queue; + queue.push(job); + + EXPECT_EQ(queue.getStats().mRemoving, 1); + } + + TEST_F(DetourNavigatorJobQueueTest, pop_should_return_last_removing) + { + std::list jobs; + JobQueue queue; + + queue.push(jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, TilePosition(0, 0), + ChangeType::remove, mProcessTime)); + queue.push(jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, TilePosition(1, 0), + ChangeType::remove, mProcessTime)); + + ASSERT_TRUE(queue.hasJob()); + const auto job = queue.pop(mPlayerTile); + ASSERT_TRUE(job.has_value()); + EXPECT_EQ((*job)->mChangedTile, TilePosition(1, 0)); + } + + TEST_F(DetourNavigatorJobQueueTest, push_on_change_type_not_remove_should_add_to_updating) + { + std::list jobs; + const JobIt job = jobs.emplace( + jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, mChangedTile, ChangeType::update, mProcessTime); + + JobQueue queue; + queue.push(job); + + EXPECT_EQ(queue.getStats().mUpdating, 1); + } + + TEST_F(DetourNavigatorJobQueueTest, pop_should_return_nearest_to_player_tile) + { + std::list jobs; + + JobQueue queue; + queue.push(jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, TilePosition(0, 0), + ChangeType::update, mProcessTime)); + queue.push(jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, TilePosition(1, 0), + ChangeType::update, mProcessTime)); + + ASSERT_TRUE(queue.hasJob()); + const auto job = queue.pop(TilePosition(1, 0)); + ASSERT_TRUE(job.has_value()); + EXPECT_EQ((*job)->mChangedTile, TilePosition(1, 0)); + } + + TEST_F(DetourNavigatorJobQueueTest, push_on_processing_time_more_than_now_should_add_to_delayed) + { + const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + const std::chrono::steady_clock::time_point processTime = now + std::chrono::seconds(1); + + std::list jobs; + const JobIt job = jobs.emplace( + jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, mChangedTile, ChangeType::update, processTime); + + JobQueue queue; + queue.push(job, now); + + EXPECT_EQ(queue.getStats().mDelayed, 1); + } + + TEST_F(DetourNavigatorJobQueueTest, pop_should_return_when_delayed_job_is_ready) + { + const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + const std::chrono::steady_clock::time_point processTime = now + std::chrono::seconds(1); + + std::list jobs; + const JobIt job = jobs.emplace( + jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, mChangedTile, ChangeType::update, processTime); + + JobQueue queue; + queue.push(job, now); + + ASSERT_FALSE(queue.hasJob(now)); + ASSERT_FALSE(queue.pop(mPlayerTile, now).has_value()); + + ASSERT_TRUE(queue.hasJob(processTime)); + EXPECT_TRUE(queue.pop(mPlayerTile, processTime).has_value()); + } + + TEST_F(DetourNavigatorJobQueueTest, update_should_move_ready_delayed_to_updating) + { + const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + const std::chrono::steady_clock::time_point processTime = now + std::chrono::seconds(1); + + std::list jobs; + const JobIt job = jobs.emplace( + jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, mChangedTile, ChangeType::update, processTime); + + JobQueue queue; + queue.push(job, now); + + ASSERT_EQ(queue.getStats().mDelayed, 1); + + queue.update(mPlayerTile, mMaxTiles, processTime); + + EXPECT_EQ(queue.getStats().mDelayed, 0); + EXPECT_EQ(queue.getStats().mUpdating, 1); + } + + TEST_F(DetourNavigatorJobQueueTest, update_should_move_ready_delayed_to_removing_when_out_of_range) + { + const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + const std::chrono::steady_clock::time_point processTime = now + std::chrono::seconds(1); + + std::list jobs; + const JobIt job = jobs.emplace( + jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, mChangedTile, ChangeType::update, processTime); + + JobQueue queue; + queue.push(job, now); + + ASSERT_EQ(queue.getStats().mDelayed, 1); + + queue.update(TilePosition(10, 10), mMaxTiles, processTime); + + EXPECT_EQ(queue.getStats().mDelayed, 0); + EXPECT_EQ(queue.getStats().mRemoving, 1); + EXPECT_EQ(job->mChangeType, ChangeType::remove); + } + + TEST_F(DetourNavigatorJobQueueTest, update_should_move_updating_to_removing_when_out_of_range) + { + std::list jobs; + + JobQueue queue; + queue.push(jobs.emplace( + jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, mChangedTile, ChangeType::update, mProcessTime)); + queue.push(jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, TilePosition(10, 10), + ChangeType::update, mProcessTime)); + + ASSERT_EQ(queue.getStats().mUpdating, 2); + + queue.update(TilePosition(10, 10), mMaxTiles); + + EXPECT_EQ(queue.getStats().mUpdating, 1); + EXPECT_EQ(queue.getStats().mRemoving, 1); + } + + TEST_F(DetourNavigatorJobQueueTest, clear_should_remove_all) + { + const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + const std::chrono::steady_clock::time_point processTime = now + std::chrono::seconds(1); + + std::list jobs; + const JobIt removing = jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, + TilePosition(0, 0), ChangeType::remove, mProcessTime); + const JobIt updating = jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, + TilePosition(1, 0), ChangeType::update, mProcessTime); + const JobIt delayed = jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, TilePosition(2, 0), + ChangeType::update, processTime); + + JobQueue queue; + queue.push(removing); + queue.push(updating); + queue.push(delayed, now); + + ASSERT_EQ(queue.getStats().mRemoving, 1); + ASSERT_EQ(queue.getStats().mUpdating, 1); + ASSERT_EQ(queue.getStats().mDelayed, 1); + + queue.clear(); + + EXPECT_EQ(queue.getStats().mRemoving, 0); + EXPECT_EQ(queue.getStats().mUpdating, 0); + EXPECT_EQ(queue.getStats().mDelayed, 0); + } } diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index b61a88662b..6dcade083a 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -1055,6 +1055,96 @@ namespace } } + TEST_P(DetourNavigatorUpdateTest, update_should_change_covered_area_when_player_moves_without_waiting_for_all) + { + Loading::Listener listener; + Settings settings = makeSettings(); + settings.mMaxTilesNumber = 1; + settings.mWaitUntilMinDistanceToPlayer = 1; + NavigatorImpl navigator(settings, nullptr); + const AgentBounds agentBounds{ CollisionShapeType::Aabb, { 29, 29, 66 } }; + ASSERT_TRUE(navigator.addAgent(agentBounds)); + + GetParam()(navigator); + + { + auto updateGuard = navigator.makeUpdateGuard(); + navigator.update(osg::Vec3f(3000, 3000, 0), updateGuard.get()); + } + + navigator.wait(WaitConditionType::requiredTilesPresent, &listener); + + { + const auto navMesh = navigator.getNavMesh(agentBounds); + ASSERT_NE(navMesh, nullptr); + + const TilePosition expectedTile(4, 4); + const auto usedTiles = getUsedTiles(*navMesh->lockConst()); + EXPECT_THAT(usedTiles, Contains(expectedTile)) << usedTiles; + } + + { + auto updateGuard = navigator.makeUpdateGuard(); + navigator.update(osg::Vec3f(6000, 3000, 0), updateGuard.get()); + } + + navigator.wait(WaitConditionType::requiredTilesPresent, &listener); + + { + const auto navMesh = navigator.getNavMesh(agentBounds); + ASSERT_NE(navMesh, nullptr); + + const TilePosition expectedTile(8, 4); + const auto usedTiles = getUsedTiles(*navMesh->lockConst()); + EXPECT_THAT(usedTiles, Contains(expectedTile)) << usedTiles; + } + } + + TEST_P(DetourNavigatorUpdateTest, update_should_change_covered_area_when_player_moves_with_db) + { + Loading::Listener listener; + Settings settings = makeSettings(); + settings.mMaxTilesNumber = 1; + settings.mWaitUntilMinDistanceToPlayer = 1; + NavigatorImpl navigator(settings, std::make_unique(":memory:", settings.mMaxDbFileSize)); + const AgentBounds agentBounds{ CollisionShapeType::Aabb, { 29, 29, 66 } }; + ASSERT_TRUE(navigator.addAgent(agentBounds)); + + GetParam()(navigator); + + { + auto updateGuard = navigator.makeUpdateGuard(); + navigator.update(osg::Vec3f(3000, 3000, 0), updateGuard.get()); + } + + navigator.wait(WaitConditionType::requiredTilesPresent, &listener); + + { + const auto navMesh = navigator.getNavMesh(agentBounds); + ASSERT_NE(navMesh, nullptr); + + const TilePosition expectedTile(4, 4); + const auto usedTiles = getUsedTiles(*navMesh->lockConst()); + EXPECT_THAT(usedTiles, Contains(expectedTile)) << usedTiles; + } + + { + auto updateGuard = navigator.makeUpdateGuard(); + navigator.update(osg::Vec3f(6000, 3000, 0), updateGuard.get()); + } + + navigator.wait(WaitConditionType::requiredTilesPresent, &listener); + + { + const auto navMesh = navigator.getNavMesh(agentBounds); + ASSERT_NE(navMesh, nullptr); + + const TilePosition expectedTile(8, 4); + const auto usedTiles = getUsedTiles(*navMesh->lockConst()); + EXPECT_THAT(usedTiles, Contains(expectedTile)) << usedTiles; + } + } + struct AddHeightfieldSurface { static constexpr std::size_t sSize = 65; diff --git a/apps/openmw_test_suite/esm3/testsaveload.cpp b/apps/openmw_test_suite/esm3/testsaveload.cpp index f8ef23e887..afffbbb3d6 100644 --- a/apps/openmw_test_suite/esm3/testsaveload.cpp +++ b/apps/openmw_test_suite/esm3/testsaveload.cpp @@ -1,11 +1,15 @@ #include +#include #include +#include #include #include #include #include +#include #include #include +#include #include #include @@ -90,7 +94,16 @@ namespace ESM constexpr std::uint32_t fakeRecordId = fourCC("FAKE"); template - void save(const T& record, ESMWriter& writer) + concept HasSave = requires(T v, ESMWriter& w) + { + v.save(w); + }; + + template + concept NotHasSave = !HasSave; + + template + auto save(const T& record, ESMWriter& writer) { record.save(writer); } @@ -100,6 +113,12 @@ namespace ESM record.save(writer, true); } + template + auto save(const T& record, ESMWriter& writer) + { + writer.writeComposite(record); + } + template std::unique_ptr makeEsmStream(const T& record, FormatVersion formatVersion) { @@ -113,36 +132,29 @@ namespace ESM return stream; } - template > - struct HasLoad : std::false_type + template + concept HasLoad = requires(T v, ESMReader& r) { + v.load(r); }; template - struct HasLoad().load(std::declval()))>> : std::true_type + concept HasLoadWithDelete = requires(T v, ESMReader& r, bool& d) { + v.load(r, d); }; template - auto load(ESMReader& reader, T& record) -> std::enable_if_t>::value> + concept NotHasLoad = !HasLoad && !HasLoadWithDelete; + + template + void load(ESMReader& reader, T& record) { record.load(reader); } - template > - struct HasLoadWithDelete : std::false_type - { - }; - - template - struct HasLoadWithDelete().load(std::declval(), std::declval()))>> - : std::true_type - { - }; - - template - auto load(ESMReader& reader, T& record) -> std::enable_if_t>::value> + template + void load(ESMReader& reader, T& record) { bool deleted = false; record.load(reader, deleted); @@ -154,6 +166,12 @@ namespace ESM record.load(reader, deleted, true); } + template + void load(ESMReader& reader, T& record) + { + reader.getComposite(record); + } + template void saveAndLoadRecord(const T& record, FormatVersion formatVersion, T& result) { @@ -490,6 +508,179 @@ namespace ESM EXPECT_EQ(result.mRepeat, record.mRepeat); } + TEST_P(Esm3SaveLoadRecordTest, aiDataShouldNotChange) + { + AIData record = { + .mHello = 1, + .mFight = 2, + .mFlee = 3, + .mAlarm = 4, + .mServices = 5, + }; + + AIData result; + saveAndLoadRecord(record, GetParam(), result); + + EXPECT_EQ(result.mHello, record.mHello); + EXPECT_EQ(result.mFight, record.mFight); + EXPECT_EQ(result.mFlee, record.mFlee); + EXPECT_EQ(result.mAlarm, record.mAlarm); + EXPECT_EQ(result.mServices, record.mServices); + } + + TEST_P(Esm3SaveLoadRecordTest, enamShouldNotChange) + { + EffectList record; + record.mList.emplace_back(IndexedENAMstruct{ { + .mEffectID = 1, + .mSkill = 2, + .mAttribute = 3, + .mRange = 4, + .mArea = 5, + .mDuration = 6, + .mMagnMin = 7, + .mMagnMax = 8, + }, + 0 }); + + EffectList result; + saveAndLoadRecord(record, GetParam(), result); + + EXPECT_EQ(result.mList.size(), record.mList.size()); + EXPECT_EQ(result.mList[0].mData.mEffectID, record.mList[0].mData.mEffectID); + EXPECT_EQ(result.mList[0].mData.mSkill, record.mList[0].mData.mSkill); + EXPECT_EQ(result.mList[0].mData.mAttribute, record.mList[0].mData.mAttribute); + EXPECT_EQ(result.mList[0].mData.mRange, record.mList[0].mData.mRange); + EXPECT_EQ(result.mList[0].mData.mArea, record.mList[0].mData.mArea); + EXPECT_EQ(result.mList[0].mData.mDuration, record.mList[0].mData.mDuration); + EXPECT_EQ(result.mList[0].mData.mMagnMin, record.mList[0].mData.mMagnMin); + EXPECT_EQ(result.mList[0].mData.mMagnMax, record.mList[0].mData.mMagnMax); + } + + TEST_P(Esm3SaveLoadRecordTest, weaponShouldNotChange) + { + Weapon record = { + .mData = { + .mWeight = 0, + .mValue = 1, + .mType = 2, + .mHealth = 3, + .mSpeed = 4, + .mReach = 5, + .mEnchant = 6, + .mChop = { 7, 8 }, + .mSlash = { 9, 10 }, + .mThrust = { 11, 12 }, + .mFlags = 13, + }, + .mRecordFlags = 0, + .mId = generateRandomRefId(32), + .mEnchant = generateRandomRefId(32), + .mScript = generateRandomRefId(32), + .mName = generateRandomString(32), + .mModel = generateRandomString(32), + .mIcon = generateRandomString(32), + }; + + Weapon result; + saveAndLoadRecord(record, GetParam(), result); + + EXPECT_EQ(result.mData.mWeight, record.mData.mWeight); + EXPECT_EQ(result.mData.mValue, record.mData.mValue); + EXPECT_EQ(result.mData.mType, record.mData.mType); + EXPECT_EQ(result.mData.mHealth, record.mData.mHealth); + EXPECT_EQ(result.mData.mSpeed, record.mData.mSpeed); + EXPECT_EQ(result.mData.mReach, record.mData.mReach); + EXPECT_EQ(result.mData.mEnchant, record.mData.mEnchant); + EXPECT_EQ(result.mData.mChop, record.mData.mChop); + EXPECT_EQ(result.mData.mSlash, record.mData.mSlash); + EXPECT_EQ(result.mData.mThrust, record.mData.mThrust); + EXPECT_EQ(result.mData.mFlags, record.mData.mFlags); + EXPECT_EQ(result.mId, record.mId); + EXPECT_EQ(result.mEnchant, record.mEnchant); + EXPECT_EQ(result.mScript, record.mScript); + EXPECT_EQ(result.mName, record.mName); + EXPECT_EQ(result.mModel, record.mModel); + EXPECT_EQ(result.mIcon, record.mIcon); + } + + TEST_P(Esm3SaveLoadRecordTest, infoShouldNotChange) + { + DialInfo record = { + .mData = { + .mType = ESM::Dialogue::Topic, + .mDisposition = 1, + .mRank = 2, + .mGender = ESM::DialInfo::NA, + .mPCrank = 3, + }, + .mSelects = { + ESM::DialogueCondition{ + .mVariable = {}, + .mValue = 42, + .mIndex = 0, + .mFunction = ESM::DialogueCondition::Function_Level, + .mComparison = ESM::DialogueCondition::Comp_Eq + }, + ESM::DialogueCondition{ + .mVariable = generateRandomString(32), + .mValue = 0, + .mIndex = 1, + .mFunction = ESM::DialogueCondition::Function_NotLocal, + .mComparison = ESM::DialogueCondition::Comp_Eq + }, + }, + .mId = generateRandomRefId(32), + .mPrev = generateRandomRefId(32), + .mNext = generateRandomRefId(32), + .mActor = generateRandomRefId(32), + .mRace = generateRandomRefId(32), + .mClass = generateRandomRefId(32), + .mFaction = generateRandomRefId(32), + .mPcFaction = generateRandomRefId(32), + .mCell = generateRandomRefId(32), + .mSound = generateRandomString(32), + .mResponse = generateRandomString(32), + .mResultScript = generateRandomString(32), + .mFactionLess = false, + .mQuestStatus = ESM::DialInfo::QS_None, + }; + + DialInfo result; + saveAndLoadRecord(record, GetParam(), result); + + EXPECT_EQ(result.mData.mType, record.mData.mType); + EXPECT_EQ(result.mData.mDisposition, record.mData.mDisposition); + EXPECT_EQ(result.mData.mRank, record.mData.mRank); + EXPECT_EQ(result.mData.mGender, record.mData.mGender); + EXPECT_EQ(result.mData.mPCrank, record.mData.mPCrank); + EXPECT_EQ(result.mId, record.mId); + EXPECT_EQ(result.mPrev, record.mPrev); + EXPECT_EQ(result.mNext, record.mNext); + EXPECT_EQ(result.mActor, record.mActor); + EXPECT_EQ(result.mRace, record.mRace); + EXPECT_EQ(result.mClass, record.mClass); + EXPECT_EQ(result.mFaction, record.mFaction); + EXPECT_EQ(result.mPcFaction, record.mPcFaction); + EXPECT_EQ(result.mCell, record.mCell); + EXPECT_EQ(result.mSound, record.mSound); + EXPECT_EQ(result.mResponse, record.mResponse); + EXPECT_EQ(result.mResultScript, record.mResultScript); + EXPECT_EQ(result.mFactionLess, record.mFactionLess); + EXPECT_EQ(result.mQuestStatus, record.mQuestStatus); + EXPECT_EQ(result.mSelects.size(), record.mSelects.size()); + for (size_t i = 0; i < result.mSelects.size(); ++i) + { + const auto& resultS = result.mSelects[i]; + const auto& recordS = record.mSelects[i]; + EXPECT_EQ(resultS.mVariable, recordS.mVariable); + EXPECT_EQ(resultS.mValue, recordS.mValue); + EXPECT_EQ(resultS.mIndex, recordS.mIndex); + EXPECT_EQ(resultS.mFunction, recordS.mFunction); + EXPECT_EQ(resultS.mComparison, recordS.mComparison); + } + } + INSTANTIATE_TEST_SUITE_P(FormatVersions, Esm3SaveLoadRecordTest, ValuesIn(getFormats())); } } diff --git a/apps/openmw_test_suite/files/hash.cpp b/apps/openmw_test_suite/files/hash.cpp index 6ad19713dc..32c8380422 100644 --- a/apps/openmw_test_suite/files/hash.cpp +++ b/apps/openmw_test_suite/files/hash.cpp @@ -10,6 +10,8 @@ #include #include +#include + #include "../testing_util.hpp" namespace @@ -35,7 +37,8 @@ namespace std::fill_n(std::back_inserter(content), 1, 'a'); std::istringstream stream(content); stream.exceptions(std::ios::failbit | std::ios::badbit); - EXPECT_THAT(getHash(fileName, stream), ElementsAre(9607679276477937801ull, 16624257681780017498ull)); + EXPECT_THAT(getHash(Files::pathToUnicodeString(fileName), stream), + ElementsAre(9607679276477937801ull, 16624257681780017498ull)); } TEST_P(FilesGetHash, shouldReturnHashForStringStream) @@ -44,7 +47,7 @@ namespace std::string content; std::fill_n(std::back_inserter(content), GetParam().mSize, 'a'); std::istringstream stream(content); - EXPECT_EQ(getHash(fileName, stream), GetParam().mHash); + EXPECT_EQ(getHash(Files::pathToUnicodeString(fileName), stream), GetParam().mHash); } TEST_P(FilesGetHash, shouldReturnHashForConstrainedFileStream) @@ -57,7 +60,7 @@ namespace std::fstream(file, std::ios_base::out | std::ios_base::binary) .write(content.data(), static_cast(content.size())); const auto stream = Files::openConstrainedFileStream(file, 0, content.size()); - EXPECT_EQ(getHash(file, *stream), GetParam().mHash); + EXPECT_EQ(getHash(Files::pathToUnicodeString(file), *stream), GetParam().mHash); } INSTANTIATE_TEST_SUITE_P(Params, FilesGetHash, diff --git a/apps/openmw_test_suite/lua/test_lua.cpp b/apps/openmw_test_suite/lua/test_lua.cpp index 90c987522d..a669a3c670 100644 --- a/apps/openmw_test_suite/lua/test_lua.cpp +++ b/apps/openmw_test_suite/lua/test_lua.cpp @@ -51,6 +51,9 @@ return { } )X"); + TestingOpenMW::VFSTestFile metaIndexErrorFile( + "return setmetatable({}, { __index = function(t, key) error('meta index error') end })"); + std::string genBigScript() { std::stringstream buf; @@ -70,7 +73,7 @@ return { { std::unique_ptr mVFS = TestingOpenMW::createTestVFS({ { "aaa/counter.lua", &counterFile }, { "bbb/tests.lua", &testsFile }, { "invalid.lua", &invalidScriptFile }, { "big.lua", &bigScriptFile }, - { "requireBig.lua", &requireBigScriptFile } }); + { "requireBig.lua", &requireBigScriptFile }, { "metaIndexError.lua", &metaIndexErrorFile } }); LuaUtil::ScriptsConfiguration mCfg; LuaUtil::LuaState mLua{ mVFS.get(), &mCfg }; @@ -223,4 +226,11 @@ return { // At this moment all instances of the script should be garbage-collected. EXPECT_LT(memWithoutScript, memWithScript); } + + TEST_F(LuaStateTest, SafeIndexMetamethod) + { + sol::table t = mLua.runInNewSandbox("metaIndexError.lua"); + // without safe get we crash here + EXPECT_ERROR(LuaUtil::safeGet(t, "any key"), "meta index error"); + } } diff --git a/apps/openmw_test_suite/lua/test_storage.cpp b/apps/openmw_test_suite/lua/test_storage.cpp index a36a527e0c..165737d1ed 100644 --- a/apps/openmw_test_suite/lua/test_storage.cpp +++ b/apps/openmw_test_suite/lua/test_storage.cpp @@ -15,7 +15,7 @@ namespace return lua.safe_script("return " + luaCode).get(); } - TEST(LuaUtilStorageTest, Basic) + TEST(LuaUtilStorageTest, Subscribe) { // Note: LuaUtil::Callback can be used only if Lua is initialized via LuaUtil::LuaState LuaUtil::LuaState luaState{ nullptr, nullptr }; @@ -24,21 +24,21 @@ namespace LuaUtil::LuaStorage storage(mLua); storage.setActive(true); - std::vector callbackCalls; sol::table callbackHiddenData(mLua, sol::create); callbackHiddenData[LuaUtil::ScriptsContainer::sScriptIdKey] = LuaUtil::ScriptId{}; - sol::table callback(mLua, sol::create); - callback[1] = [&](const std::string& section, const sol::optional& key) { - if (key) - callbackCalls.push_back(section + "_" + *key); - else - callbackCalls.push_back(section + "_*"); - }; - callback[2] = LuaUtil::AsyncPackageId{ nullptr, 0, callbackHiddenData }; + LuaUtil::getAsyncPackageInitializer( + mLua.lua_state(), []() { return 0.0; }, []() { return 0.0; })(callbackHiddenData); + mLua["async"] = LuaUtil::AsyncPackageId{ nullptr, 0, callbackHiddenData }; mLua["mutable"] = storage.getMutableSection("test"); mLua["ro"] = storage.getReadOnlySection("test"); - mLua["ro"]["subscribe"](mLua["ro"], callback); + + mLua.safe_script(R"( + callbackCalls = {} + ro:subscribe(async:callback(function(section, key) + table.insert(callbackCalls, section .. '_' .. (key or '*')) + end)) + )"); mLua.safe_script("mutable:set('x', 5)"); EXPECT_EQ(get(mLua, "mutable:get('x')"), 5); @@ -58,7 +58,7 @@ namespace EXPECT_EQ(get(mLua, "ro:get('x')"), 4); EXPECT_EQ(get(mLua, "ro:get('y')"), 7); - EXPECT_THAT(callbackCalls, ::testing::ElementsAre("test_x", "test_*", "test_*")); + EXPECT_THAT(get(mLua, "table.concat(callbackCalls, ', ')"), "test_x, test_*, test_*"); } TEST(LuaUtilStorageTest, Table) diff --git a/apps/openmw_test_suite/lua/test_yaml.cpp b/apps/openmw_test_suite/lua/test_yaml.cpp new file mode 100644 index 0000000000..fa28889440 --- /dev/null +++ b/apps/openmw_test_suite/lua/test_yaml.cpp @@ -0,0 +1,357 @@ +#include + +#include +#include +#include + +#include + +#include + +namespace +{ + template + bool checkNumber(sol::state_view& lua, const std::string& inputData, T requiredValue) + { + sol::object result = LuaUtil::loadYaml(inputData, lua); + if (result.get_type() != sol::type::number) + return false; + + return result.as() == requiredValue; + } + + bool checkBool(sol::state_view& lua, const std::string& inputData, bool requiredValue) + { + sol::object result = LuaUtil::loadYaml(inputData, lua); + if (result.get_type() != sol::type::boolean) + return false; + + return result.as() == requiredValue; + } + + bool checkNil(sol::state_view& lua, const std::string& inputData) + { + sol::object result = LuaUtil::loadYaml(inputData, lua); + return result == sol::nil; + } + + bool checkNan(sol::state_view& lua, const std::string& inputData) + { + sol::object result = LuaUtil::loadYaml(inputData, lua); + if (result.get_type() != sol::type::number) + return false; + + return std::isnan(result.as()); + } + + bool checkString(sol::state_view& lua, const std::string& inputData, const std::string& requiredValue) + { + sol::object result = LuaUtil::loadYaml(inputData, lua); + if (result.get_type() != sol::type::string) + return false; + + return result.as() == requiredValue; + } + + bool checkString(sol::state_view& lua, const std::string& inputData) + { + sol::object result = LuaUtil::loadYaml(inputData, lua); + if (result.get_type() != sol::type::string) + return false; + + return result.as() == inputData; + } + + TEST(LuaUtilYamlLoader, ScalarTypeDeduction) + { + sol::state lua; + + ASSERT_TRUE(checkNil(lua, "null")); + ASSERT_TRUE(checkNil(lua, "Null")); + ASSERT_TRUE(checkNil(lua, "NULL")); + ASSERT_TRUE(checkNil(lua, "~")); + ASSERT_TRUE(checkNil(lua, "")); + ASSERT_FALSE(checkNil(lua, "NUll")); + ASSERT_TRUE(checkString(lua, "NUll")); + ASSERT_TRUE(checkString(lua, "'null'", "null")); + + ASSERT_TRUE(checkNumber(lua, "017", 17)); + ASSERT_TRUE(checkNumber(lua, "-017", -17)); + ASSERT_TRUE(checkNumber(lua, "+017", 17)); + ASSERT_TRUE(checkNumber(lua, "17", 17)); + ASSERT_TRUE(checkNumber(lua, "-17", -17)); + ASSERT_TRUE(checkNumber(lua, "+17", 17)); + ASSERT_TRUE(checkNumber(lua, "0o17", 15)); + ASSERT_TRUE(checkString(lua, "-0o17")); + ASSERT_TRUE(checkString(lua, "+0o17")); + ASSERT_TRUE(checkString(lua, "0b1")); + ASSERT_TRUE(checkString(lua, "1:00")); + ASSERT_TRUE(checkString(lua, "'17'", "17")); + ASSERT_TRUE(checkNumber(lua, "0x17", 23)); + ASSERT_TRUE(checkString(lua, "'-0x17'", "-0x17")); + ASSERT_TRUE(checkString(lua, "'+0x17'", "+0x17")); + + ASSERT_TRUE(checkNumber(lua, "2.1e-05", 2.1e-5)); + ASSERT_TRUE(checkNumber(lua, "-2.1e-05", -2.1e-5)); + ASSERT_TRUE(checkNumber(lua, "+2.1e-05", 2.1e-5)); + ASSERT_TRUE(checkNumber(lua, "2.1e+5", 210000)); + ASSERT_TRUE(checkNumber(lua, "-2.1e+5", -210000)); + ASSERT_TRUE(checkNumber(lua, "+2.1e+5", 210000)); + ASSERT_TRUE(checkNumber(lua, "0.27", 0.27)); + ASSERT_TRUE(checkNumber(lua, "-0.27", -0.27)); + ASSERT_TRUE(checkNumber(lua, "+0.27", 0.27)); + ASSERT_TRUE(checkNumber(lua, "2.7", 2.7)); + ASSERT_TRUE(checkNumber(lua, "-2.7", -2.7)); + ASSERT_TRUE(checkNumber(lua, "+2.7", 2.7)); + ASSERT_TRUE(checkNumber(lua, ".27", 0.27)); + ASSERT_TRUE(checkNumber(lua, "-.27", -0.27)); + ASSERT_TRUE(checkNumber(lua, "+.27", 0.27)); + ASSERT_TRUE(checkNumber(lua, "27.", 27.0)); + ASSERT_TRUE(checkNumber(lua, "-27.", -27.0)); + ASSERT_TRUE(checkNumber(lua, "+27.", 27.0)); + + ASSERT_TRUE(checkNan(lua, ".nan")); + ASSERT_TRUE(checkNan(lua, ".NaN")); + ASSERT_TRUE(checkNan(lua, ".NAN")); + ASSERT_FALSE(checkNan(lua, "nan")); + ASSERT_FALSE(checkNan(lua, ".nAn")); + ASSERT_TRUE(checkString(lua, "'.nan'", ".nan")); + ASSERT_TRUE(checkString(lua, ".nAn")); + + ASSERT_TRUE(checkNumber(lua, "1.7976931348623157E+308", std::numeric_limits::max())); + ASSERT_TRUE(checkNumber(lua, "-1.7976931348623157E+308", std::numeric_limits::lowest())); + ASSERT_TRUE(checkNumber(lua, "2.2250738585072014e-308", std::numeric_limits::min())); + ASSERT_TRUE(checkNumber(lua, ".inf", std::numeric_limits::infinity())); + ASSERT_TRUE(checkNumber(lua, "+.inf", std::numeric_limits::infinity())); + ASSERT_TRUE(checkNumber(lua, "-.inf", -std::numeric_limits::infinity())); + ASSERT_TRUE(checkNumber(lua, ".Inf", std::numeric_limits::infinity())); + ASSERT_TRUE(checkNumber(lua, "+.Inf", std::numeric_limits::infinity())); + ASSERT_TRUE(checkNumber(lua, "-.Inf", -std::numeric_limits::infinity())); + ASSERT_TRUE(checkNumber(lua, ".INF", std::numeric_limits::infinity())); + ASSERT_TRUE(checkNumber(lua, "+.INF", std::numeric_limits::infinity())); + ASSERT_TRUE(checkNumber(lua, "-.INF", -std::numeric_limits::infinity())); + ASSERT_TRUE(checkString(lua, ".INf")); + ASSERT_TRUE(checkString(lua, "-.INf")); + ASSERT_TRUE(checkString(lua, "+.INf")); + + ASSERT_TRUE(checkBool(lua, "true", true)); + ASSERT_TRUE(checkBool(lua, "false", false)); + ASSERT_TRUE(checkBool(lua, "True", true)); + ASSERT_TRUE(checkBool(lua, "False", false)); + ASSERT_TRUE(checkBool(lua, "TRUE", true)); + ASSERT_TRUE(checkBool(lua, "FALSE", false)); + ASSERT_TRUE(checkString(lua, "y")); + ASSERT_TRUE(checkString(lua, "n")); + ASSERT_TRUE(checkString(lua, "On")); + ASSERT_TRUE(checkString(lua, "Off")); + ASSERT_TRUE(checkString(lua, "YES")); + ASSERT_TRUE(checkString(lua, "NO")); + ASSERT_TRUE(checkString(lua, "TrUe")); + ASSERT_TRUE(checkString(lua, "FaLsE")); + ASSERT_TRUE(checkString(lua, "'true'", "true")); + } + + TEST(LuaUtilYamlLoader, DepthLimit) + { + sol::state lua; + + const std::string input = R"( + array1: &array1_alias + [ + <: *array1_alias, + foo + ] + )"; + + bool depthExceptionThrown = false; + try + { + YAML::Node root = YAML::Load(input); + sol::object result = LuaUtil::loadYaml(input, lua); + } + catch (const std::runtime_error& e) + { + ASSERT_EQ(std::string(e.what()), "Maximum layers depth exceeded, probably caused by a circular reference"); + depthExceptionThrown = true; + } + + ASSERT_TRUE(depthExceptionThrown); + } + + TEST(LuaUtilYamlLoader, Collections) + { + sol::state lua; + + sol::object map = LuaUtil::loadYaml("{ x: , y: 2, 4: 5 }", lua); + ASSERT_EQ(map.as()["x"], sol::nil); + ASSERT_EQ(map.as()["y"], 2); + ASSERT_EQ(map.as()[4], 5); + + sol::object array = LuaUtil::loadYaml("[ 3, 4 ]", lua); + ASSERT_EQ(array.as()[1], 3); + + sol::object emptyTable = LuaUtil::loadYaml("{}", lua); + ASSERT_TRUE(emptyTable.as().empty()); + + sol::object emptyArray = LuaUtil::loadYaml("[]", lua); + ASSERT_TRUE(emptyArray.as().empty()); + + ASSERT_THROW(LuaUtil::loadYaml("{ null: 1 }", lua), std::runtime_error); + ASSERT_THROW(LuaUtil::loadYaml("{ .nan: 1 }", lua), std::runtime_error); + + const std::string scalarArrayInput = R"( + - First Scalar + - 1 + - true)"; + + sol::object scalarArray = LuaUtil::loadYaml(scalarArrayInput, lua); + ASSERT_EQ(scalarArray.as()[1], std::string("First Scalar")); + ASSERT_EQ(scalarArray.as()[2], 1); + ASSERT_EQ(scalarArray.as()[3], true); + + const std::string scalarMapWithCommentsInput = R"( + string: 'str' # String value + integer: 65 # Integer value + float: 0.278 # Float value + bool: false # Boolean value)"; + + sol::object scalarMapWithComments = LuaUtil::loadYaml(scalarMapWithCommentsInput, lua); + ASSERT_EQ(scalarMapWithComments.as()["string"], std::string("str")); + ASSERT_EQ(scalarMapWithComments.as()["integer"], 65); + ASSERT_EQ(scalarMapWithComments.as()["float"], 0.278); + ASSERT_EQ(scalarMapWithComments.as()["bool"], false); + + const std::string mapOfArraysInput = R"( + x: + - 2 + - 7 + - true + y: + - aaa + - false + - 1)"; + + sol::object mapOfArrays = LuaUtil::loadYaml(mapOfArraysInput, lua); + ASSERT_EQ(mapOfArrays.as()["x"][3], true); + ASSERT_EQ(mapOfArrays.as()["y"][1], std::string("aaa")); + + const std::string arrayOfMapsInput = R"( + - + name: Name1 + hr: 65 + avg: 0.278 + - + name: Name2 + hr: 63 + avg: 0.288)"; + + sol::object arrayOfMaps = LuaUtil::loadYaml(arrayOfMapsInput, lua); + ASSERT_EQ(arrayOfMaps.as()[1]["avg"], 0.278); + ASSERT_EQ(arrayOfMaps.as()[2]["name"], std::string("Name2")); + + const std::string arrayOfArraysInput = R"( + - [Name1, 65, 0.278] + - [Name2 , 63, 0.288])"; + + sol::object arrayOfArrays = LuaUtil::loadYaml(arrayOfArraysInput, lua); + ASSERT_EQ(arrayOfArrays.as()[1][2], 65); + ASSERT_EQ(arrayOfArrays.as()[2][1], std::string("Name2")); + + const std::string mapOfMapsInput = R"( + Name1: {hr: 65, avg: 0.278} + Name2 : { + hr: 63, + avg: 0.288, + })"; + + sol::object mapOfMaps = LuaUtil::loadYaml(mapOfMapsInput, lua); + ASSERT_EQ(mapOfMaps.as()["Name1"]["hr"], 65); + ASSERT_EQ(mapOfMaps.as()["Name2"]["avg"], 0.288); + } + + TEST(LuaUtilYamlLoader, Structures) + { + sol::state lua; + + const std::string twoDocumentsInput + = "---\n" + " - First Scalar\n" + " - 2\n" + " - true\n" + "\n" + "---\n" + " - Second Scalar\n" + " - 3\n" + " - false"; + + sol::object twoDocuments = LuaUtil::loadYaml(twoDocumentsInput, lua); + ASSERT_EQ(twoDocuments.as()[1][1], std::string("First Scalar")); + ASSERT_EQ(twoDocuments.as()[2][3], false); + + const std::string anchorInput = R"(--- + x: + - Name1 + # Following node labeled as "a" + - &a Value1 + y: + - *a # Subsequent occurrence + - Name2)"; + + sol::object anchor = LuaUtil::loadYaml(anchorInput, lua); + ASSERT_EQ(anchor.as()["y"][1], std::string("Value1")); + + const std::string compoundKeyInput = R"( + ? - String1 + - String2 + : - 1 + + ? [ String3, + String4 ] + : [ 2, 3, 4 ])"; + + ASSERT_THROW(LuaUtil::loadYaml(compoundKeyInput, lua), std::runtime_error); + + const std::string compactNestedMappingInput = R"( + - item : Item1 + quantity: 2 + - item : Item2 + quantity: 4 + - item : Item3 + quantity: 11)"; + + sol::object compactNestedMapping = LuaUtil::loadYaml(compactNestedMappingInput, lua); + ASSERT_EQ(compactNestedMapping.as()[2]["quantity"], 4); + } + + TEST(LuaUtilYamlLoader, Scalars) + { + sol::state lua; + + const std::string literalScalarInput = R"(--- | + a + b + c)"; + + ASSERT_TRUE(checkString(lua, literalScalarInput, "a\nb\nc")); + + const std::string foldedScalarInput = R"(--- > + a + b + c)"; + + ASSERT_TRUE(checkString(lua, foldedScalarInput, "a b c")); + + const std::string multiLinePlanarScalarsInput = R"( + plain: + This unquoted scalar + spans many lines. + + quoted: "So does this + quoted scalar.\n")"; + + sol::object multiLinePlanarScalars = LuaUtil::loadYaml(multiLinePlanarScalarsInput, lua); + ASSERT_TRUE( + multiLinePlanarScalars.as()["plain"] == std::string("This unquoted scalar spans many lines.")); + ASSERT_TRUE(multiLinePlanarScalars.as()["quoted"] == std::string("So does this quoted scalar.\n")); + } +} diff --git a/apps/openmw_test_suite/misc/test_resourcehelpers.cpp b/apps/openmw_test_suite/misc/test_resourcehelpers.cpp index 0db147d8a3..48edb72578 100644 --- a/apps/openmw_test_suite/misc/test_resourcehelpers.cpp +++ b/apps/openmw_test_suite/misc/test_resourcehelpers.cpp @@ -8,26 +8,22 @@ namespace TEST(CorrectSoundPath, wav_files_not_overridden_with_mp3_in_vfs_are_not_corrected) { std::unique_ptr mVFS = TestingOpenMW::createTestVFS({ { "sound/bar.wav", nullptr } }); - EXPECT_EQ(correctSoundPath("sound/bar.wav", mVFS.get()), "sound/bar.wav"); + constexpr VFS::Path::NormalizedView path("sound/bar.wav"); + EXPECT_EQ(correctSoundPath(path, *mVFS), "sound/bar.wav"); } TEST(CorrectSoundPath, wav_files_overridden_with_mp3_in_vfs_are_corrected) { std::unique_ptr mVFS = TestingOpenMW::createTestVFS({ { "sound/foo.mp3", nullptr } }); - EXPECT_EQ(correctSoundPath("sound/foo.wav", mVFS.get()), "sound/foo.mp3"); + constexpr VFS::Path::NormalizedView path("sound/foo.wav"); + EXPECT_EQ(correctSoundPath(path, *mVFS), "sound/foo.mp3"); } TEST(CorrectSoundPath, corrected_path_does_not_check_existence_in_vfs) { std::unique_ptr mVFS = TestingOpenMW::createTestVFS({}); - EXPECT_EQ(correctSoundPath("sound/foo.wav", mVFS.get()), "sound/foo.mp3"); - } - - TEST(CorrectSoundPath, correct_path_normalize_paths) - { - std::unique_ptr mVFS = TestingOpenMW::createTestVFS({}); - EXPECT_EQ(correctSoundPath("sound\\foo.wav", mVFS.get()), "sound/foo.mp3"); - EXPECT_EQ(correctSoundPath("SOUND\\foo.WAV", mVFS.get()), "sound/foo.mp3"); + constexpr VFS::Path::NormalizedView path("sound/foo.wav"); + EXPECT_EQ(correctSoundPath(path, *mVFS), "sound/foo.mp3"); } namespace diff --git a/apps/openmw_test_suite/nifosg/testnifloader.cpp b/apps/openmw_test_suite/nifosg/testnifloader.cpp index f05d651301..fa023fff0d 100644 --- a/apps/openmw_test_suite/nifosg/testnifloader.cpp +++ b/apps/openmw_test_suite/nifosg/testnifloader.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -29,6 +30,7 @@ namespace { VFS::Manager mVfs; Resource::ImageManager mImageManager{ &mVfs, 0 }; + Resource::BgsmFileManager mMaterialManager{ &mVfs, 0 }; const osgDB::ReaderWriter* mReaderWriter = osgDB::Registry::instance()->getReaderWriterForExtension("osgt"); osg::ref_ptr mOptions = new osgDB::Options; @@ -70,7 +72,7 @@ namespace init(node); Nif::NIFFile file("test.nif"); file.mRoots.push_back(&node); - auto result = Loader::load(file, &mImageManager); + auto result = Loader::load(file, &mImageManager, &mMaterialManager); EXPECT_EQ(serialize(*result), R"( osg::Group { UniqueID 1 @@ -259,7 +261,7 @@ osg::Group { node.mProperties.push_back(Nif::RecordPtrT(&property)); Nif::NIFFile file("test.nif"); file.mRoots.push_back(&node); - auto result = Loader::load(file, &mImageManager); + auto result = Loader::load(file, &mImageManager, &mMaterialManager); EXPECT_EQ(serialize(*result), formatOsgNodeForBSShaderProperty(GetParam().mExpectedShaderPrefix)); } @@ -289,7 +291,7 @@ osg::Group { node.mProperties.push_back(Nif::RecordPtrT(&property)); Nif::NIFFile file("test.nif"); file.mRoots.push_back(&node); - auto result = Loader::load(file, &mImageManager); + auto result = Loader::load(file, &mImageManager, &mMaterialManager); EXPECT_EQ(serialize(*result), formatOsgNodeForBSLightingShaderProperty(GetParam().mExpectedShaderPrefix)); } diff --git a/apps/openmw_test_suite/openmw/options.cpp b/apps/openmw_test_suite/openmw/options.cpp index fc89264f8c..fe319f64fa 100644 --- a/apps/openmw_test_suite/openmw/options.cpp +++ b/apps/openmw_test_suite/openmw/options.cpp @@ -36,7 +36,7 @@ namespace (result.emplace_back(makeString(args)), ...); for (int i = 1; i <= std::numeric_limits::max(); ++i) if (i != '&' && i != '"' && i != ' ' && i != '\n') - result.push_back(std::string(1, i)); + result.push_back(std::string(1, static_cast(i))); return result; } diff --git a/apps/openmw_test_suite/resource/testobjectcache.cpp b/apps/openmw_test_suite/resource/testobjectcache.cpp index 1c8cff6af0..e2f5799edb 100644 --- a/apps/openmw_test_suite/resource/testobjectcache.cpp +++ b/apps/openmw_test_suite/resource/testobjectcache.cpp @@ -114,9 +114,11 @@ namespace Resource cache->update(referenceTime, expiryDelay); ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + ASSERT_EQ(cache->getStats().mExpired, 0); cache->update(referenceTime + expiryDelay, expiryDelay); EXPECT_EQ(cache->getRefFromObjectCacheOrNone(key), std::nullopt); + ASSERT_EQ(cache->getStats().mExpired, 1); } TEST(ResourceGenericObjectCacheTest, updateShouldKeepExternallyReferencedItems) @@ -249,7 +251,7 @@ namespace Resource EXPECT_THAT(actual, ElementsAre(Pair(1, value1.get()), Pair(2, value2.get()), Pair(3, value3.get()))); } - TEST(ResourceGenericObjectCacheTest, getCacheSizeShouldReturnNumberOrAddedItems) + TEST(ResourceGenericObjectCacheTest, getStatsShouldReturnNumberOrAddedItems) { osg::ref_ptr> cache(new GenericObjectCache); @@ -258,7 +260,33 @@ namespace Resource cache->addEntryToObjectCache(13, value1); cache->addEntryToObjectCache(42, value2); - EXPECT_EQ(cache->getCacheSize(), 2); + const CacheStats stats = cache->getStats(); + + EXPECT_EQ(stats.mSize, 2); + } + + TEST(ResourceGenericObjectCacheTest, getStatsShouldReturnNumberOrGetsAndHits) + { + osg::ref_ptr> cache(new GenericObjectCache); + + { + const CacheStats stats = cache->getStats(); + + EXPECT_EQ(stats.mGet, 0); + EXPECT_EQ(stats.mHit, 0); + } + + osg::ref_ptr value(new Object); + cache->addEntryToObjectCache(13, value); + cache->getRefFromObjectCache(13); + cache->getRefFromObjectCache(42); + + { + const CacheStats stats = cache->getStats(); + + EXPECT_EQ(stats.mGet, 2); + EXPECT_EQ(stats.mHit, 1); + } } TEST(ResourceGenericObjectCacheTest, lowerBoundShouldReturnFirstNotLessThatGivenKey) diff --git a/apps/openmw_test_suite/sceneutil/osgacontroller.cpp b/apps/openmw_test_suite/sceneutil/osgacontroller.cpp new file mode 100644 index 0000000000..309de4a878 --- /dev/null +++ b/apps/openmw_test_suite/sceneutil/osgacontroller.cpp @@ -0,0 +1,131 @@ +#include + +#include +#include + +#include +#include + +namespace +{ + using namespace SceneUtil; + + static const std::string ROOT_BONE_NAME = "bip01"; + + // Creates a merged anim track with a single root channel with two start/end matrix transforms + osg::ref_ptr createMergedAnimationTrack(std::string name, osg::Matrixf startTransform, + osg::Matrixf endTransform, float startTime = 0.0f, float endTime = 1.0f) + { + osg::ref_ptr mergedAnimationTrack = new Resource::Animation; + mergedAnimationTrack->setName(name); + + osgAnimation::MatrixKeyframeContainer* cbCntr = new osgAnimation::MatrixKeyframeContainer; + cbCntr->push_back(osgAnimation::MatrixKeyframe(startTime, startTransform)); + cbCntr->push_back(osgAnimation::MatrixKeyframe(endTime, endTransform)); + + osg::ref_ptr rootChannel = new osgAnimation::MatrixLinearChannel; + rootChannel->setName("transform"); + rootChannel->setTargetName(ROOT_BONE_NAME); + rootChannel->getOrCreateSampler()->setKeyframeContainer(cbCntr); + mergedAnimationTrack->addChannel(rootChannel); + return mergedAnimationTrack; + } + + TEST(OsgAnimationControllerTest, getTranslationShouldReturnSampledChannelTranslationForBip01) + { + std::vector emulatedAnimations; + emulatedAnimations.push_back({ 0.0f, 1.0f, "test1" }); // should sample this + emulatedAnimations.push_back({ 1.1f, 2.0f, "test2" }); // should ignore this + + OsgAnimationController controller; + controller.setEmulatedAnimations(emulatedAnimations); + + osg::Matrixf startTransform = osg::Matrixf::identity(); + osg::Matrixf endTransform = osg::Matrixf::identity(); + osg::Matrixf endTransform2 = osg::Matrixf::identity(); + endTransform.setTrans(1.0f, 1.0f, 1.0f); + controller.addMergedAnimationTrack(createMergedAnimationTrack("test1", startTransform, endTransform)); + endTransform2.setTrans(2.0f, 2.0f, 2.0f); + controller.addMergedAnimationTrack( + createMergedAnimationTrack("test2", endTransform, endTransform2, 0.1f, 0.9f)); + + // should be halfway between 0,0,0 and 1,1,1 + osg::Vec3f translation = controller.getTranslation(0.5f); + EXPECT_EQ(translation, osg::Vec3f(0.5f, 0.5f, 0.5f)); + } + + TEST(OsgAnimationControllerTest, getTranslationShouldReturnZeroVectorIfNotFound) + { + std::vector emulatedAnimations; + emulatedAnimations.push_back({ 0.0f, 1.0f, "test1" }); + + OsgAnimationController controller; + controller.setEmulatedAnimations(emulatedAnimations); + + osg::Matrixf startTransform = osg::Matrixf::identity(); + osg::Matrixf endTransform = osg::Matrixf::identity(); + endTransform.setTrans(1.0f, 1.0f, 1.0f); + controller.addMergedAnimationTrack(createMergedAnimationTrack("test1", startTransform, endTransform)); + + // Has no emulated animation at time so will return 0,0,0 + osg::Vec3f translation = controller.getTranslation(100.0f); + EXPECT_EQ(translation, osg::Vec3f(0.0f, 0.0f, 0.0f)); + } + + TEST(OsgAnimationControllerTest, getTranslationShouldReturnZeroVectorIfNoMergedTracks) + { + std::vector emulatedAnimations; + emulatedAnimations.push_back({ 0.0f, 1.0f, "test1" }); + + OsgAnimationController controller; + controller.setEmulatedAnimations(emulatedAnimations); + + // Has no merged tracks so will return 0,0,0 + osg::Vec3f translation = controller.getTranslation(0.5); + EXPECT_EQ(translation, osg::Vec3f(0.0f, 0.0f, 0.0f)); + } + + TEST(OsgAnimationControllerTest, getTransformShouldReturnIdentityIfNotFound) + { + std::vector emulatedAnimations; + emulatedAnimations.push_back({ 0.0f, 1.0f, "test1" }); + + OsgAnimationController controller; + controller.setEmulatedAnimations(emulatedAnimations); + + osg::Matrixf startTransform = osg::Matrixf::identity(); + osg::Matrixf endTransform = osg::Matrixf::identity(); + endTransform.setTrans(1.0f, 1.0f, 1.0f); + controller.addMergedAnimationTrack(createMergedAnimationTrack("test1", startTransform, endTransform)); + + // Has no emulated animation at time so will return identity + EXPECT_EQ(controller.getTransformForNode(100.0f, ROOT_BONE_NAME), osg::Matrixf::identity()); + + // Has no bone animation at time so will return identity + EXPECT_EQ(controller.getTransformForNode(0.5f, "wrongbone"), osg::Matrixf::identity()); + } + + TEST(OsgAnimationControllerTest, getTransformShouldReturnSampledAnimMatrixAtTime) + { + std::vector emulatedAnimations; + emulatedAnimations.push_back({ 0.0f, 1.0f, "test1" }); // should sample this + emulatedAnimations.push_back({ 1.1f, 2.0f, "test2" }); // should ignore this + + OsgAnimationController controller; + controller.setEmulatedAnimations(emulatedAnimations); + + osg::Matrixf startTransform = osg::Matrixf::identity(); + osg::Matrixf endTransform = osg::Matrixf::identity(); + endTransform.setTrans(1.0f, 1.0f, 1.0f); + controller.addMergedAnimationTrack(createMergedAnimationTrack("test1", startTransform, endTransform)); + osg::Matrixf endTransform2 = osg::Matrixf::identity(); + endTransform2.setTrans(2.0f, 2.0f, 2.0f); + controller.addMergedAnimationTrack( + createMergedAnimationTrack("test2", endTransform, endTransform2, 0.1f, 0.9f)); + + EXPECT_EQ(controller.getTransformForNode(0.0f, ROOT_BONE_NAME), startTransform); // start of test1 + EXPECT_EQ(controller.getTransformForNode(1.0f, ROOT_BONE_NAME), endTransform); // end of test1 + EXPECT_EQ(controller.getTransformForNode(1.1f, ROOT_BONE_NAME), endTransform); // start of test2 + EXPECT_EQ(controller.getTransformForNode(2.0f, ROOT_BONE_NAME), endTransform2); // end of test2 + } +} diff --git a/apps/openmw_test_suite/sqlite3/request.cpp b/apps/openmw_test_suite/sqlite3/request.cpp index 23efe9dc2e..c299493952 100644 --- a/apps/openmw_test_suite/sqlite3/request.cpp +++ b/apps/openmw_test_suite/sqlite3/request.cpp @@ -151,7 +151,7 @@ namespace const std::int64_t value = 1099511627776; EXPECT_EQ(execute(*mDb, insert, value), 1); Statement select(*mDb, GetAll("ints")); - std::vector> result; + std::vector> result; request(*mDb, select, std::back_inserter(result), std::numeric_limits::max()); EXPECT_THAT(result, ElementsAre(std::tuple(value))); } @@ -205,7 +205,7 @@ namespace const std::int64_t value = 1099511627776; EXPECT_EQ(execute(*mDb, insert, value), 1); Statement select(*mDb, GetExact("ints")); - std::vector> result; + std::vector> result; request(*mDb, select, std::back_inserter(result), std::numeric_limits::max(), value); EXPECT_THAT(result, ElementsAre(std::tuple(value))); } diff --git a/apps/openmw_test_suite/testing_util.hpp b/apps/openmw_test_suite/testing_util.hpp index b819848a8f..0afd04e639 100644 --- a/apps/openmw_test_suite/testing_util.hpp +++ b/apps/openmw_test_suite/testing_util.hpp @@ -2,6 +2,7 @@ #define TESTING_UTIL_H #include +#include #include #include @@ -51,25 +52,21 @@ namespace TestingOpenMW struct VFSTestData : public VFS::Archive { - std::map mFiles; + VFS::FileMap mFiles; - VFSTestData(std::map files) + explicit VFSTestData(VFS::FileMap&& files) : mFiles(std::move(files)) { } - void listResources(VFS::FileMap& out) override - { - for (const auto& [key, value] : mFiles) - out.emplace(key, value); - } + void listResources(VFS::FileMap& out) override { out = mFiles; } - bool contains(std::string_view file) const override { return mFiles.contains(file); } + bool contains(VFS::Path::NormalizedView file) const override { return mFiles.contains(file); } std::string getDescription() const override { return "TestData"; } }; - inline std::unique_ptr createTestVFS(std::map files) + inline std::unique_ptr createTestVFS(VFS::FileMap&& files) { auto vfs = std::make_unique(); vfs->addArchive(std::make_unique(std::move(files))); @@ -77,6 +74,12 @@ namespace TestingOpenMW return vfs; } + inline std::unique_ptr createTestVFS( + std::initializer_list> files) + { + return createTestVFS(VFS::FileMap(files.begin(), files.end())); + } + #define EXPECT_ERROR(X, ERR_SUBSTR) \ try \ { \ diff --git a/apps/openmw_test_suite/toutf8/toutf8.cpp b/apps/openmw_test_suite/toutf8/toutf8.cpp index f189294cf2..9a259c69ab 100644 --- a/apps/openmw_test_suite/toutf8/toutf8.cpp +++ b/apps/openmw_test_suite/toutf8/toutf8.cpp @@ -47,7 +47,7 @@ namespace { std::string input; for (int c = 1; c <= std::numeric_limits::max(); ++c) - input.push_back(c); + input.push_back(static_cast(c)); Utf8Encoder encoder(FromType::CP437); const std::string_view result = encoder.getUtf8(input); EXPECT_EQ(result.data(), input.data()); @@ -99,7 +99,7 @@ namespace { std::string input; for (int c = 1; c <= std::numeric_limits::max(); ++c) - input.push_back(c); + input.push_back(static_cast(c)); Utf8Encoder encoder(FromType::CP437); const std::string_view result = encoder.getLegacyEnc(input); EXPECT_EQ(result.data(), input.data()); diff --git a/apps/openmw_test_suite/vfs/testpathutil.cpp b/apps/openmw_test_suite/vfs/testpathutil.cpp new file mode 100644 index 0000000000..3819f9905a --- /dev/null +++ b/apps/openmw_test_suite/vfs/testpathutil.cpp @@ -0,0 +1,201 @@ +#include + +#include + +#include + +namespace VFS::Path +{ + namespace + { + using namespace testing; + + TEST(NormalizedTest, shouldSupportDefaultConstructor) + { + const Normalized value; + EXPECT_EQ(value.value(), ""); + } + + TEST(NormalizedTest, shouldSupportConstructorFromString) + { + const std::string string("Foo\\Bar/baz"); + const Normalized value(string); + EXPECT_EQ(value.value(), "foo/bar/baz"); + } + + TEST(NormalizedTest, shouldSupportConstructorFromConstCharPtr) + { + const char* const ptr = "Foo\\Bar/baz"; + const Normalized value(ptr); + EXPECT_EQ(value.value(), "foo/bar/baz"); + } + + TEST(NormalizedTest, shouldSupportConstructorFromStringView) + { + const std::string_view view = "Foo\\Bar/baz"; + const Normalized value(view); + EXPECT_EQ(value.view(), "foo/bar/baz"); + } + + TEST(NormalizedTest, shouldSupportConstructorFromNormalizedView) + { + const NormalizedView view("foo/bar/baz"); + const Normalized value(view); + EXPECT_EQ(value.view(), "foo/bar/baz"); + } + + TEST(NormalizedTest, supportMovingValueOut) + { + Normalized value("Foo\\Bar/baz"); + EXPECT_EQ(std::move(value).value(), "foo/bar/baz"); + EXPECT_EQ(value.value(), ""); + } + + TEST(NormalizedTest, isNotEqualToNotNormalized) + { + const Normalized value("Foo\\Bar/baz"); + EXPECT_NE(value.value(), "Foo\\Bar/baz"); + } + + TEST(NormalizedTest, shouldSupportOperatorLeftShiftToOStream) + { + const Normalized value("Foo\\Bar/baz"); + std::stringstream stream; + stream << value; + EXPECT_EQ(stream.str(), "foo/bar/baz"); + } + + TEST(NormalizedTest, shouldSupportOperatorDivEqual) + { + Normalized value("foo/bar"); + value /= NormalizedView("baz"); + EXPECT_EQ(value.value(), "foo/bar/baz"); + } + + TEST(NormalizedTest, shouldSupportOperatorDivEqualWithStringView) + { + Normalized value("foo/bar"); + value /= std::string_view("BAZ"); + EXPECT_EQ(value.value(), "foo/bar/baz"); + } + + TEST(NormalizedTest, changeExtensionShouldReplaceAfterLastDot) + { + Normalized value("foo/bar.a"); + ASSERT_TRUE(value.changeExtension("so")); + EXPECT_EQ(value.value(), "foo/bar.so"); + } + + TEST(NormalizedTest, changeExtensionShouldNormalizeExtension) + { + Normalized value("foo/bar.a"); + ASSERT_TRUE(value.changeExtension("SO")); + EXPECT_EQ(value.value(), "foo/bar.so"); + } + + TEST(NormalizedTest, changeExtensionShouldIgnorePathWithoutADot) + { + Normalized value("foo/bar"); + ASSERT_FALSE(value.changeExtension("so")); + EXPECT_EQ(value.value(), "foo/bar"); + } + + TEST(NormalizedTest, changeExtensionShouldIgnorePathWithDotBeforeSeparator) + { + Normalized value("foo.bar/baz"); + ASSERT_FALSE(value.changeExtension("so")); + EXPECT_EQ(value.value(), "foo.bar/baz"); + } + + TEST(NormalizedTest, changeExtensionShouldThrowExceptionOnExtensionWithDot) + { + Normalized value("foo.a"); + EXPECT_THROW(value.changeExtension(".so"), std::invalid_argument); + } + + TEST(NormalizedTest, changeExtensionShouldThrowExceptionOnExtensionWithSeparator) + { + Normalized value("foo.a"); + EXPECT_THROW(value.changeExtension("/so"), std::invalid_argument); + } + + template + struct NormalizedOperatorsTest : Test + { + }; + + TYPED_TEST_SUITE_P(NormalizedOperatorsTest); + + TYPED_TEST_P(NormalizedOperatorsTest, supportsEqual) + { + using Type0 = typename TypeParam::Type0; + using Type1 = typename TypeParam::Type1; + const Type0 normalized{ "a/foo/bar/baz" }; + const Type1 otherEqual{ "a/foo/bar/baz" }; + const Type1 otherNotEqual{ "b/foo/bar/baz" }; + EXPECT_EQ(normalized, otherEqual); + EXPECT_EQ(otherEqual, normalized); + EXPECT_NE(normalized, otherNotEqual); + EXPECT_NE(otherNotEqual, normalized); + } + + TYPED_TEST_P(NormalizedOperatorsTest, supportsLess) + { + using Type0 = typename TypeParam::Type0; + using Type1 = typename TypeParam::Type1; + const Type0 normalized{ "b/foo/bar/baz" }; + const Type1 otherEqual{ "b/foo/bar/baz" }; + const Type1 otherLess{ "a/foo/bar/baz" }; + const Type1 otherGreater{ "c/foo/bar/baz" }; + EXPECT_FALSE(normalized < otherEqual); + EXPECT_FALSE(otherEqual < normalized); + EXPECT_LT(otherLess, normalized); + EXPECT_FALSE(normalized < otherLess); + EXPECT_LT(normalized, otherGreater); + EXPECT_FALSE(otherGreater < normalized); + } + + REGISTER_TYPED_TEST_SUITE_P(NormalizedOperatorsTest, supportsEqual, supportsLess); + + template + struct TypePair + { + using Type0 = T0; + using Type1 = T1; + }; + + using TypePairs = Types, TypePair, + TypePair, TypePair, + TypePair, TypePair, + TypePair, TypePair, + TypePair, TypePair>; + + INSTANTIATE_TYPED_TEST_SUITE_P(Typed, NormalizedOperatorsTest, TypePairs); + + TEST(NormalizedViewTest, shouldSupportConstructorFromNormalized) + { + const Normalized value("Foo\\Bar/baz"); + const NormalizedView view(value); + EXPECT_EQ(view.value(), "foo/bar/baz"); + } + + TEST(NormalizedViewTest, shouldSupportConstexprConstructorFromNormalizedStringLiteral) + { + constexpr NormalizedView view("foo/bar/baz"); + EXPECT_EQ(view.value(), "foo/bar/baz"); + } + + TEST(NormalizedViewTest, constructorShouldThrowExceptionOnNotNormalized) + { + EXPECT_THROW([] { NormalizedView("Foo\\Bar/baz"); }(), std::invalid_argument); + } + + TEST(NormalizedView, shouldSupportOperatorDiv) + { + const NormalizedView a("foo/bar"); + const NormalizedView b("baz"); + const Normalized result = a / b; + EXPECT_EQ(result.value(), "foo/bar/baz"); + } + } +} diff --git a/apps/wizard/CMakeLists.txt b/apps/wizard/CMakeLists.txt index 3a17210cd3..444290f3a6 100644 --- a/apps/wizard/CMakeLists.txt +++ b/apps/wizard/CMakeLists.txt @@ -85,7 +85,7 @@ target_link_libraries(openmw-wizard components_qt ) -target_link_libraries(openmw-wizard Qt::Widgets Qt::Core) +target_link_libraries(openmw-wizard Qt::Widgets Qt::Core Qt::Svg) if (OPENMW_USE_UNSHIELD) target_link_libraries(openmw-wizard ${LIBUNSHIELD_LIBRARIES}) diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index 60e9f3ccf9..aec64e275d 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -18,7 +18,7 @@ Wizard::InstallationPage::InstallationPage(QWidget* parent, Config::GameSettings mFinished = false; mThread = std::make_unique(); - mUnshield = std::make_unique(mGameSettings.value("morrowind-bsa-filesize").toLongLong()); + mUnshield = std::make_unique(mGameSettings.value("morrowind-bsa-filesize").value.toLongLong()); mUnshield->moveToThread(mThread.get()); connect(mThread.get(), &QThread::started, mUnshield.get(), &UnshieldWorker::extract); diff --git a/apps/wizard/installationtargetpage.cpp b/apps/wizard/installationtargetpage.cpp index dc94d2d002..1da28b1237 100644 --- a/apps/wizard/installationtargetpage.cpp +++ b/apps/wizard/installationtargetpage.cpp @@ -19,6 +19,8 @@ Wizard::InstallationTargetPage::InstallationTargetPage(QWidget* parent, const Fi setupUi(this); + folderIconLabel->setPixmap(QIcon(":folder").pixmap(QSize(48, 48))); + registerField(QLatin1String("installation.path*"), targetLineEdit); } diff --git a/apps/wizard/languageselectionpage.cpp b/apps/wizard/languageselectionpage.cpp index 7dcf642dd6..7d4c2184ee 100644 --- a/apps/wizard/languageselectionpage.cpp +++ b/apps/wizard/languageselectionpage.cpp @@ -9,6 +9,8 @@ Wizard::LanguageSelectionPage::LanguageSelectionPage(QWidget* parent) setupUi(this); + flagIconLabel->setPixmap(QIcon(":preferences-desktop-locale").pixmap(QSize(48, 48))); + registerField(QLatin1String("installation.language"), languageComboBox, "currentData", "currentDataChanged"); } diff --git a/apps/wizard/main.cpp b/apps/wizard/main.cpp index a911ac7350..e740f06015 100644 --- a/apps/wizard/main.cpp +++ b/apps/wizard/main.cpp @@ -1,11 +1,11 @@ #include #include -#include #include #include #include +#include #include "mainwizard.hpp" @@ -45,18 +45,9 @@ int main(int argc, char* argv[]) resourcesPath = Files::pathToQString(variables["resources"].as().u8string()); } - // Internationalization - QString locale = QLocale::system().name().section('_', 0, 0); + l10n::installQtTranslations(app, "wizard", resourcesPath); - QTranslator appTranslator; - appTranslator.load(resourcesPath + "/translations/wizard_" + locale + ".qm"); - app.installTranslator(&appTranslator); - - QTranslator componentsAppTranslator; - componentsAppTranslator.load(resourcesPath + "/translations/components_" + locale + ".qm"); - app.installTranslator(&componentsAppTranslator); - - Wizard::MainWizard wizard; + Wizard::MainWizard wizard(std::move(configurationManager)); wizard.show(); return app.exec(); diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index 2f1f373cfd..0b5cadd979 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -24,11 +24,14 @@ #include "installationpage.hpp" #endif +#include + using namespace Process; -Wizard::MainWizard::MainWizard(QWidget* parent) +Wizard::MainWizard::MainWizard(Files::ConfigurationManager&& cfgMgr, QWidget* parent) : QWizard(parent) , mInstallations() + , mCfgMgr(cfgMgr) , mError(false) , mGameSettings(mCfgMgr) { @@ -166,16 +169,13 @@ void Wizard::MainWizard::setupGameSettings() QTextStream stream(&file); Misc::ensureUtf8Encoding(stream); - mGameSettings.readUserFile(stream); + mGameSettings.readUserFile(stream, QFileInfo(path).dir().path()); } file.close(); // Now the rest - QStringList paths; - paths.append(Files::getUserConfigPathQString(mCfgMgr)); - paths.append(QLatin1String("openmw.cfg")); - paths.append(Files::getGlobalConfigPathQString(mCfgMgr)); + QStringList paths = Files::getActiveConfigPathsQString(mCfgMgr); for (const QString& path2 : paths) { @@ -198,7 +198,7 @@ void Wizard::MainWizard::setupGameSettings() QTextStream stream(&file); Misc::ensureUtf8Encoding(stream); - mGameSettings.readFile(stream); + mGameSettings.readFile(stream, QFileInfo(path2).dir().path()); } file.close(); } @@ -243,11 +243,11 @@ void Wizard::MainWizard::setupLauncherSettings() void Wizard::MainWizard::setupInstallations() { // Check if the paths actually contain a Morrowind installation - for (const QString& path : mGameSettings.getDataDirs()) + for (const auto& path : mGameSettings.getDataDirs()) { - if (findFiles(QLatin1String("Morrowind"), path)) - addInstallation(path); + if (findFiles(QLatin1String("Morrowind"), path.value)) + addInstallation(path.value); } } @@ -334,10 +334,12 @@ void Wizard::MainWizard::addInstallation(const QString& path) mInstallations.insert(QDir::toNativeSeparators(path), install); // Add it to the openmw.cfg too - if (!mGameSettings.getDataDirs().contains(path)) + const auto& dataDirs = mGameSettings.getDataDirs(); + if (std::none_of( + dataDirs.begin(), dataDirs.end(), [&](const Config::SettingValue& dir) { return dir.value == path; })) { - mGameSettings.setMultiValue(QLatin1String("data"), path); - mGameSettings.addDataDir(path); + mGameSettings.setMultiValue(QLatin1String("data"), { path }); + mGameSettings.addDataDir({ path }); } } @@ -396,15 +398,15 @@ void Wizard::MainWizard::writeSettings() if (language == QLatin1String("Polish")) { - mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1250")); + mGameSettings.setValue(QLatin1String("encoding"), { "win1250" }); } else if (language == QLatin1String("Russian")) { - mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1251")); + mGameSettings.setValue(QLatin1String("encoding"), { "win1251" }); } else { - mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1252")); + mGameSettings.setValue(QLatin1String("encoding"), { "win1252" }); } // Write the installation path so that openmw can find them @@ -412,7 +414,7 @@ void Wizard::MainWizard::writeSettings() // Make sure the installation path is the last data= entry mGameSettings.removeDataDir(path); - mGameSettings.addDataDir(path); + mGameSettings.addDataDir({ path }); QString userPath(Files::pathToQString(mCfgMgr.getUserConfigPath())); QDir dir(userPath); diff --git a/apps/wizard/mainwizard.hpp b/apps/wizard/mainwizard.hpp index 60f46fa1a5..2f120f1955 100644 --- a/apps/wizard/mainwizard.hpp +++ b/apps/wizard/mainwizard.hpp @@ -45,7 +45,7 @@ namespace Wizard Page_Conclusion }; - MainWizard(QWidget* parent = nullptr); + MainWizard(Files::ConfigurationManager&& cfgMgr, QWidget* parent = nullptr); ~MainWizard() override; bool findFiles(const QString& name, const QString& path); diff --git a/apps/wizard/methodselectionpage.cpp b/apps/wizard/methodselectionpage.cpp index b3a1c73635..2ff7db5487 100644 --- a/apps/wizard/methodselectionpage.cpp +++ b/apps/wizard/methodselectionpage.cpp @@ -11,6 +11,10 @@ Wizard::MethodSelectionPage::MethodSelectionPage(QWidget* parent) setupUi(this); + installerIconLabel->setPixmap(QIcon(":system-installer").pixmap(QSize(48, 48))); + folderIconLabel->setPixmap(QIcon(":folder").pixmap(QSize(48, 48))); + buyIconLabel->setPixmap(QIcon(":dollar").pixmap(QSize(48, 48))); + #ifndef OPENMW_USE_UNSHIELD retailDiscRadioButton->setEnabled(false); existingLocationRadioButton->setChecked(true); diff --git a/apps/wizard/ui/importpage.ui b/apps/wizard/ui/importpage.ui index acc0d268ec..669920c5f3 100644 --- a/apps/wizard/ui/importpage.ui +++ b/apps/wizard/ui/importpage.ui @@ -33,7 +33,7 @@ - Import settings from Morrowind.ini + Import Settings From Morrowind.ini true @@ -43,7 +43,7 @@ - Import add-on and plugin selection + Import Add-on and Plugin Selection true @@ -53,7 +53,7 @@ - Import bitmap fonts setup from Morrowind.ini + Import Bitmap Fonts Setup From Morrowind.ini Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, diff --git a/apps/wizard/ui/installationtargetpage.ui b/apps/wizard/ui/installationtargetpage.ui index c87214d8d9..4b1f4238d4 100644 --- a/apps/wizard/ui/installationtargetpage.ui +++ b/apps/wizard/ui/installationtargetpage.ui @@ -30,9 +30,6 @@ 0 - - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - Qt::RichText diff --git a/apps/wizard/ui/languageselectionpage.ui b/apps/wizard/ui/languageselectionpage.ui index fccd2aa424..3cff2c5b44 100644 --- a/apps/wizard/ui/languageselectionpage.ui +++ b/apps/wizard/ui/languageselectionpage.ui @@ -30,9 +30,6 @@ 0 - - <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> - Qt::RichText diff --git a/apps/wizard/ui/methodselectionpage.ui b/apps/wizard/ui/methodselectionpage.ui index c2dd260527..28755ad438 100644 --- a/apps/wizard/ui/methodselectionpage.ui +++ b/apps/wizard/ui/methodselectionpage.ui @@ -59,9 +59,6 @@ 0 - - <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> - Qt::RichText @@ -124,9 +121,6 @@ 0 - - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - Qt::RichText @@ -191,9 +185,6 @@ 0 - - <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> - diff --git a/cmake/GitVersion.cmake b/cmake/GitVersion.cmake index a77b0d5b0a..44b57b17d4 100644 --- a/cmake/GitVersion.cmake +++ b/cmake/GitVersion.cmake @@ -29,4 +29,4 @@ endif () include(${MACROSFILE}) configure_resource_file(${VERSION_RESOURCE_FILE_IN} ${OpenMW_BINARY_DIR} ${VERSION_RESOURCE_FILE_RELATIVE}) -configure_file("${OpenMW_SOURCE_DIR}/${VERSION_CPP_FILE}.in" "${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}") +configure_file("${VERSION_CPP_FILE_IN}" "${VERSION_CPP_FILE_OUT}") diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index bb6fd1dd37..1235072de5 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -12,8 +12,8 @@ set (VERSION_CPP_FILE "components/version/version.cpp") if (GIT_CHECKOUT) get_generator_is_multi_config(multi_config) - add_custom_command ( - OUTPUT "${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}" + add_custom_target (get-version + BYPRODUCTS "${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}" DEPENDS "${OpenMW_SOURCE_DIR}/${VERSION_CPP_FILE}.in" COMMAND ${CMAKE_COMMAND} -DGIT_EXECUTABLE=${GIT_EXECUTABLE} @@ -21,18 +21,22 @@ if (GIT_CHECKOUT) -DOpenMW_BINARY_DIR=${OpenMW_BINARY_DIR} -DVERSION_RESOURCE_FILE_IN=${VERSION_RESOURCE_FILE_IN} -DVERSION_RESOURCE_FILE_RELATIVE=${VERSION_RESOURCE_FILE_RELATIVE} - -DVERSION_CPP_FILE=${VERSION_CPP_FILE} + -DVERSION_CPP_FILE_IN=${OpenMW_SOURCE_DIR}/${VERSION_CPP_FILE}.in + -DVERSION_CPP_FILE_OUT=${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}.out -DOPENMW_VERSION_MAJOR=${OPENMW_VERSION_MAJOR} -DOPENMW_VERSION_MINOR=${OPENMW_VERSION_MINOR} -DOPENMW_VERSION_RELEASE=${OPENMW_VERSION_RELEASE} -DOPENMW_LUA_API_REVISION=${OPENMW_LUA_API_REVISION} -DOPENMW_POSTPROCESSING_API_REVISION=${OPENMW_POSTPROCESSING_API_REVISION} -DOPENMW_VERSION=${OPENMW_VERSION} + -DOPENMW_DOC_BASEURL=${OPENMW_DOC_BASEURL} -DMACROSFILE=${CMAKE_SOURCE_DIR}/cmake/OpenMWMacros.cmake "-DCMAKE_CONFIGURATION_TYPES=${CMAKE_CONFIGURATION_TYPES}" -Dgenerator_is_multi_config_var=${multi_config} -P ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/GitVersion.cmake - VERBATIM) + COMMAND ${CMAKE_COMMAND} + -E copy_if_different ${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}.out ${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE} + VERBATIM) else (GIT_CHECKOUT) configure_resource_file(${VERSION_RESOURCE_FILE_IN} ${OpenMW_BINARY_DIR} ${VERSION_RESOURCE_FILE_RELATIVE}) configure_file("${OpenMW_SOURCE_DIR}/${VERSION_CPP_FILE}.in" "${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}") @@ -40,11 +44,22 @@ endif (GIT_CHECKOUT) list (APPEND COMPONENT_FILES "${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}") +# OSG plugin checker +# Helpfully, OSG doesn't export this to its CMake config as it doesn't have one +list(TRANSFORM USED_OSG_PLUGINS REPLACE "^osgdb_" "" OUTPUT_VARIABLE USED_OSG_PLUGIN_NAMES) +list(TRANSFORM USED_OSG_PLUGIN_NAMES PREPEND "\"" OUTPUT_VARIABLE USED_OSG_PLUGIN_NAMES_FORMATTED) +list(TRANSFORM USED_OSG_PLUGIN_NAMES_FORMATTED APPEND "\"") +list(JOIN USED_OSG_PLUGIN_NAMES_FORMATTED ", " USED_OSG_PLUGIN_NAMES_FORMATTED) + +set(OSG_PLUGIN_CHECKER_CPP_FILE "components/misc/osgpluginchecker.cpp") +configure_file("${OpenMW_SOURCE_DIR}/${OSG_PLUGIN_CHECKER_CPP_FILE}.in" "${OpenMW_BINARY_DIR}/${OSG_PLUGIN_CHECKER_CPP_FILE}") +list(APPEND COMPONENT_FILES "${OpenMW_BINARY_DIR}/${OSG_PLUGIN_CHECKER_CPP_FILE}") + # source files add_component_dir (lua luastate scriptscontainer asyncpackage utilpackage serialization configuration l10n storage utf8 - shapes/box inputactions + shapes/box inputactions yamlloader ) add_component_dir (l10n @@ -92,6 +107,10 @@ add_component_dir (settings windowmode ) +add_component_dir (bgsm + stream file + ) + add_component_dir (bsa bsa_file compressedbsafile ba2gnrlfile ba2dx10file ba2file memorystream ) @@ -110,7 +129,7 @@ add_component_dir (vfs add_component_dir (resource scenemanager keyframemanager imagemanager bulletshapemanager bulletshape niffilemanager objectcache multiobjectcache resourcesystem - resourcemanager stats animation foreachbulletobject errormarker + resourcemanager stats animation foreachbulletobject errormarker cachestats bgsmfilemanager ) add_component_dir (shader @@ -122,7 +141,7 @@ add_component_dir (sceneutil lightmanager lightutil positionattitudetransform workqueue pathgridutil waterutil writescene serialize optimizer detourdebugdraw navmesh agentpath shadow mwshadowtechnique recastmesh shadowsbin osgacontroller rtt screencapture depth color riggeometryosgaextension extradata unrefqueue lightcommon lightingmethod clearcolor - cullsafeboundsvisitor keyframe nodecallback textkeymap + cullsafeboundsvisitor keyframe nodecallback textkeymap glextensions ) add_component_dir (nif @@ -166,7 +185,7 @@ add_component_dir (esm3 inventorystate containerstate npcstate creaturestate dialoguestate statstate npcstats creaturestats weatherstate quickkeys fogstate spellstate activespells creaturelevliststate doorstate projectilestate debugprofile aisequence magiceffects custommarkerstate stolenitems transport animationstate controlsstate mappings readerscache - infoorder timestamp formatversion landrecorddata selectiongroup + infoorder timestamp formatversion landrecorddata selectiongroup dialoguecondition ) add_component_dir (esmterrain @@ -274,8 +293,8 @@ add_component_dir (esm4 add_component_dir (misc barrier budgetmeasurement color compression constants convert coordinateconverter display endianness float16 frameratelimiter - guarded math mathutil messageformatparser notnullptr objectpool osguservalues progressreporter resourcehelpers rng - strongtypedef thread timeconvert timer tuplehelpers tuplemeta utf8stream weakcache windows + guarded math mathutil messageformatparser notnullptr objectpool osgpluginchecker osguservalues progressreporter resourcehelpers + rng strongtypedef thread timeconvert timer tuplehelpers tuplemeta utf8stream weakcache windows ) add_component_dir (misc/strings @@ -496,15 +515,20 @@ set (ESM_UI ${CMAKE_CURRENT_SOURCE_DIR}/contentselector/contentselector.ui if (USE_QT) add_component_qt_dir (contentselector model/modelitem model/esmfile - model/naturalsort model/contentmodel + model/contentmodel model/loadordererror view/combobox view/contentselector ) + add_component_qt_dir (config gamesettings launchersettings ) + add_component_qt_dir (l10n + qttranslations + ) + add_component_qt_dir (process processinvoker ) @@ -521,18 +545,16 @@ if (USE_QT) QT_WRAP_UI(ESM_UI_HDR ${ESM_UI}) endif() -if (CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") - if("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "x86_64" AND NOT APPLE) - add_definitions(-fPIC) - endif() -endif () - include_directories(${BULLET_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) find_package(SQLite3 REQUIRED) add_library(components STATIC ${COMPONENT_FILES}) +if (ANDROID) + set_property(TARGET components PROPERTY POSITION_INDEPENDENT_CODE ON) +endif() + target_link_libraries(components ${COLLADA_DOM_LIBRARIES} @@ -593,7 +615,6 @@ endif() if (USE_QT) add_library(components_qt STATIC ${COMPONENT_QT_FILES} ${ESM_UI_HDR}) target_link_libraries(components_qt components Qt::Widgets Qt::Core) - target_compile_definitions(components_qt PRIVATE OPENMW_DOC_BASEURL="${OPENMW_DOC_BASEURL}") if (BUILD_LAUNCHER OR BUILD_WIZARD) add_dependencies(components_qt qm-files) @@ -633,6 +654,7 @@ endif() set(COMPONENT_FILES ${COMPONENT_FILES} PARENT_SCOPE) target_compile_definitions(components PUBLIC BT_USE_DOUBLE_PRECISION) +target_compile_definitions(components PRIVATE OPENMW_DOC_BASEURL="${OPENMW_DOC_BASEURL}") if(OSG_STATIC) unset(_osg_plugins_static_files) @@ -666,7 +688,7 @@ if(USE_QT) set_property(TARGET components_qt PROPERTY AUTOMOC ON) endif(USE_QT) -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(components PUBLIC diff --git a/components/bgsm/file.cpp b/components/bgsm/file.cpp new file mode 100644 index 0000000000..2aaacaf25c --- /dev/null +++ b/components/bgsm/file.cpp @@ -0,0 +1,236 @@ +#include "file.hpp" + +#include +#include + +#include "stream.hpp" + +namespace Bgsm +{ + MaterialFilePtr parse(Files::IStreamPtr&& inputStream) + { + std::shared_ptr file; + BGSMStream stream(std::move(inputStream)); + + std::array signature; + stream.readArray(signature); + std::string shaderType(signature.data(), 4); + if (shaderType == "BGEM") + { + file = std::make_shared(); + file->mShaderType = Bgsm::ShaderType::Effect; + } + else if (shaderType == "BGSM") + { + file = std::make_shared(); + file->mShaderType = Bgsm::ShaderType::Lighting; + } + else + throw std::runtime_error("Invalid material file"); + + file->read(stream); + return file; + } + + void MaterialFile::read(BGSMStream& stream) + { + stream.read(mVersion); + stream.read(mClamp); + stream.read(mUVOffset); + stream.read(mUVScale); + stream.read(mTransparency); + stream.read(mAlphaBlend); + stream.read(mSourceBlendMode); + stream.read(mDestinationBlendMode); + stream.read(mAlphaTestThreshold); + stream.read(mAlphaTest); + stream.read(mDepthWrite); + stream.read(mDepthTest); + stream.read(mSSR); + stream.read(mWetnessControlSSR); + stream.read(mDecal); + stream.read(mTwoSided); + stream.read(mDecalNoFade); + stream.read(mNonOccluder); + stream.read(mRefraction); + stream.read(mRefractionFalloff); + stream.read(mRefractionPower); + if (mVersion < 10) + { + stream.read(mEnvMapEnabled); + stream.read(mEnvMapMaskScale); + } + else + { + stream.read(mDepthBias); + } + stream.read(mGrayscaleToPaletteColor); + if (mVersion >= 6) + stream.read(mMaskWrites); + } + + void BGSMFile::read(BGSMStream& stream) + { + MaterialFile::read(stream); + + stream.read(mDiffuseMap); + stream.read(mNormalMap); + stream.read(mSmoothSpecMap); + stream.read(mGrayscaleMap); + if (mVersion >= 3) + { + stream.read(mGlowMap); + stream.read(mWrinkleMap); + stream.read(mSpecularMap); + stream.read(mLightingMap); + stream.read(mFlowMap); + if (mVersion >= 17) + stream.read(mDistanceFieldAlphaMap); + } + else + { + stream.read(mEnvMap); + stream.read(mGlowMap); + stream.read(mInnerLayerMap); + stream.read(mWrinkleMap); + stream.read(mDisplacementMap); + } + stream.read(mEnableEditorAlphaThreshold); + if (mVersion >= 8) + { + stream.read(mTranslucency); + stream.read(mTranslucencyThickObject); + stream.read(mTranslucencyMixAlbedoWithSubsurfaceColor); + stream.read(mTranslucencySubsurfaceColor); + stream.read(mTranslucencyTransmissiveScale); + stream.read(mTranslucencyTurbulence); + } + else + { + stream.read(mRimLighting); + stream.read(mRimPower); + stream.read(mBackLightPower); + stream.read(mSubsurfaceLighting); + stream.read(mSubsurfaceLightingRolloff); + } + stream.read(mSpecularEnabled); + stream.read(mSpecularColor); + stream.read(mSpecularMult); + stream.read(mSmoothness); + stream.read(mFresnelPower); + stream.read(mWetnessControlSpecScale); + stream.read(mWetnessControlSpecPowerScale); + stream.read(mWetnessControlSpecMinvar); + if (mVersion < 10) + stream.read(mWetnessControlEnvMapScale); + stream.read(mWetnessControlFresnelPower); + stream.read(mWetnessControlMetalness); + if (mVersion >= 3) + { + stream.read(mPBR); + if (mVersion >= 9) + { + stream.read(mCustomPorosity); + stream.read(mPorosityValue); + } + } + stream.read(mRootMaterialPath); + stream.read(mAnisoLighting); + stream.read(mEmitEnabled); + if (mEmitEnabled) + stream.read(mEmittanceColor); + stream.read(mEmittanceMult); + stream.read(mModelSpaceNormals); + stream.read(mExternalEmittance); + if (mVersion >= 12) + { + stream.read(mLumEmittance); + if (mVersion >= 13) + { + stream.read(mUseAdaptiveEmissive); + stream.read(mAdaptiveEmissiveExposureParams); + } + } + else if (mVersion < 8) + { + stream.read(mBackLighting); + } + stream.read(mReceiveShadows); + stream.read(mHideSecret); + stream.read(mCastShadows); + stream.read(mDissolveFade); + stream.read(mAssumeShadowmask); + stream.read(mGlowMapEnabled); + if (mVersion < 7) + { + stream.read(mEnvMapWindow); + stream.read(mEnvMapEye); + } + stream.read(mHair); + stream.read(mHairTintColor); + stream.read(mTree); + stream.read(mFacegen); + stream.read(mSkinTint); + stream.read(mTessellate); + if (mVersion < 3) + { + stream.read(mDisplacementMapParams); + stream.read(mTessellationParams); + } + stream.read(mGrayscaleToPaletteScale); + if (mVersion >= 1) + { + stream.read(mSkewSpecularAlpha); + stream.read(mTerrain); + if (mTerrain) + { + if (mVersion == 3) + stream.skip(4); // Unknown + + stream.read(mTerrainParams); + } + } + } + + void BGEMFile::read(BGSMStream& stream) + { + MaterialFile::read(stream); + + stream.read(mBaseMap); + stream.read(mGrayscaleMap); + stream.read(mEnvMap); + stream.read(mNormalMap); + stream.read(mEnvMapMask); + if (mVersion >= 11) + { + stream.read(mSpecularMap); + stream.read(mLightingMap); + stream.read(mGlowMap); + } + if (mVersion >= 10) + { + stream.read(mEnvMapEnabled); + stream.read(mEnvMapMaskScale); + } + stream.read(mBlood); + stream.read(mEffectLighting); + stream.read(mFalloff); + stream.read(mFalloffColor); + stream.read(mGrayscaleToPaletteAlpha); + stream.read(mSoft); + stream.read(mBaseColor); + stream.read(mBaseColorScale); + stream.read(mFalloffParams); + stream.read(mLightingInfluence); + stream.read(mEnvmapMinLOD); + stream.read(mSoftDepth); + if (mVersion >= 11) + stream.read(mEmittanceColor); + if (mVersion >= 15) + stream.read(mAdaptiveEmissiveExposureParams); + if (mVersion >= 16) + stream.read(mGlowMapEnabled); + if (mVersion >= 20) + stream.read(mEffectPbrSpecular); + } +} diff --git a/components/bgsm/file.hpp b/components/bgsm/file.hpp new file mode 100644 index 0000000000..b409752d74 --- /dev/null +++ b/components/bgsm/file.hpp @@ -0,0 +1,167 @@ +#ifndef OPENMW_COMPONENTS_BGSM_FILE_HPP +#define OPENMW_COMPONENTS_BGSM_FILE_HPP + +#include +#include +#include +#include + +#include +#include +#include + +#include + +namespace Bgsm +{ + class BGSMStream; + + enum class ShaderType + { + Lighting, + Effect, + }; + + struct MaterialFile + { + ShaderType mShaderType; + std::uint32_t mVersion; + std::uint32_t mClamp; + osg::Vec2f mUVOffset, mUVScale; + float mTransparency; + bool mAlphaBlend; + std::uint32_t mSourceBlendMode; + std::uint32_t mDestinationBlendMode; + std::uint8_t mAlphaTestThreshold; + bool mAlphaTest; + bool mDepthWrite, mDepthTest; + bool mSSR; + bool mWetnessControlSSR; + bool mDecal; + bool mTwoSided; + bool mDecalNoFade; + bool mNonOccluder; + bool mRefraction; + bool mRefractionFalloff; + float mRefractionPower; + bool mEnvMapEnabled; + float mEnvMapMaskScale; + bool mDepthBias; + bool mGrayscaleToPaletteColor; + std::uint8_t mMaskWrites; + + MaterialFile() = default; + virtual void read(BGSMStream& stream); + virtual ~MaterialFile() = default; + }; + + struct BGSMFile : MaterialFile + { + std::string mDiffuseMap; + std::string mNormalMap; + std::string mSmoothSpecMap; + std::string mGrayscaleMap; + std::string mGlowMap; + std::string mWrinkleMap; + std::string mSpecularMap; + std::string mLightingMap; + std::string mFlowMap; + std::string mDistanceFieldAlphaMap; + std::string mEnvMap; + std::string mInnerLayerMap; + std::string mDisplacementMap; + bool mEnableEditorAlphaThreshold; + bool mTranslucency; + bool mTranslucencyThickObject; + bool mTranslucencyMixAlbedoWithSubsurfaceColor; + osg::Vec3f mTranslucencySubsurfaceColor; + float mTranslucencyTransmissiveScale; + float mTranslucencyTurbulence; + bool mRimLighting; + float mRimPower; + float mBackLightPower; + bool mSubsurfaceLighting; + float mSubsurfaceLightingRolloff; + bool mSpecularEnabled; + osg::Vec3f mSpecularColor; + float mSpecularMult; + float mSmoothness; + float mFresnelPower; + float mWetnessControlSpecScale; + float mWetnessControlSpecPowerScale; + float mWetnessControlSpecMinvar; + float mWetnessControlEnvMapScale; + float mWetnessControlFresnelPower; + float mWetnessControlMetalness; + bool mPBR; + bool mCustomPorosity; + float mPorosityValue; + std::string mRootMaterialPath; + bool mAnisoLighting; + bool mEmitEnabled; + osg::Vec3f mEmittanceColor; + float mEmittanceMult; + bool mModelSpaceNormals; + bool mExternalEmittance; + float mLumEmittance; + bool mUseAdaptiveEmissive; + osg::Vec3f mAdaptiveEmissiveExposureParams; + bool mBackLighting; + bool mReceiveShadows; + bool mHideSecret; + bool mCastShadows; + bool mDissolveFade; + bool mAssumeShadowmask; + bool mGlowMapEnabled; + bool mEnvMapWindow; + bool mEnvMapEye; + bool mHair; + osg::Vec3f mHairTintColor; + bool mTree; + bool mFacegen; + bool mSkinTint; + bool mTessellate; + osg::Vec2f mDisplacementMapParams; + osg::Vec3f mTessellationParams; + float mGrayscaleToPaletteScale; + bool mSkewSpecularAlpha; + bool mTerrain; + osg::Vec3f mTerrainParams; + + void read(BGSMStream& stream) override; + }; + + struct BGEMFile : MaterialFile + { + std::string mBaseMap; + std::string mGrayscaleMap; + std::string mEnvMap; + std::string mNormalMap; + std::string mEnvMapMask; + std::string mSpecularMap; + std::string mLightingMap; + std::string mGlowMap; + bool mBlood; + bool mEffectLighting; + bool mFalloff; + bool mFalloffColor; + bool mGrayscaleToPaletteAlpha; + bool mSoft; + osg::Vec3f mBaseColor; + float mBaseColorScale; + osg::Vec4f mFalloffParams; + float mLightingInfluence; + std::uint8_t mEnvmapMinLOD; + float mSoftDepth; + osg::Vec3f mEmittanceColor; + osg::Vec3f mAdaptiveEmissiveExposureParams; + bool mGlowMapEnabled; + bool mEffectPbrSpecular; + + void read(BGSMStream& stream) override; + }; + + using MaterialFilePtr = std::shared_ptr; + MaterialFilePtr parse(Files::IStreamPtr&& stream); +} +#endif diff --git a/components/bgsm/stream.cpp b/components/bgsm/stream.cpp new file mode 100644 index 0000000000..c4fa9c1d8c --- /dev/null +++ b/components/bgsm/stream.cpp @@ -0,0 +1,39 @@ +#include "stream.hpp" + +namespace Bgsm +{ + template <> + void BGSMStream::read(osg::Vec2f& vec) + { + readBufferOfType(mStream, vec._v); + } + + template <> + void BGSMStream::read(osg::Vec3f& vec) + { + readBufferOfType(mStream, vec._v); + } + + template <> + void BGSMStream::read(osg::Vec4f& vec) + { + readBufferOfType(mStream, vec._v); + } + + template <> + void BGSMStream::read(std::string& str) + { + std::uint32_t length; + read(length); + // Prevent potential memory allocation freezes; strings this long are not expected in BGSM + if (length > 1024) + throw std::runtime_error("Requested string length is too large: " + std::to_string(length)); + str = std::string(length, '\0'); + mStream->read(str.data(), length); + if (mStream->bad()) + throw std::runtime_error("Failed to read sized string of " + std::to_string(length) + " chars"); + std::size_t end = str.find('\0'); + if (end != std::string::npos) + str.erase(end); + } +} diff --git a/components/bgsm/stream.hpp b/components/bgsm/stream.hpp new file mode 100644 index 0000000000..a355523367 --- /dev/null +++ b/components/bgsm/stream.hpp @@ -0,0 +1,77 @@ +#ifndef OPENMW_COMPONENTS_BGSM_STREAM_HPP +#define OPENMW_COMPONENTS_BGSM_STREAM_HPP + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace Bgsm +{ + template + inline void readBufferOfType(Files::IStreamPtr& pIStream, T* dest) + { + static_assert(std::is_arithmetic_v, "Buffer element type is not arithmetic"); + pIStream->read(reinterpret_cast(dest), numInstances * sizeof(T)); + if (pIStream->bad()) + throw std::runtime_error("Failed to read typed (" + std::string(typeid(T).name()) + ") buffer of " + + std::to_string(numInstances) + " instances"); + if constexpr (Misc::IS_BIG_ENDIAN) + for (std::size_t i = 0; i < numInstances; i++) + Misc::swapEndiannessInplace(dest[i]); + } + + template + inline void readBufferOfType(Files::IStreamPtr& pIStream, T (&dest)[numInstances]) + { + readBufferOfType(pIStream, static_cast(dest)); + } + + class BGSMStream + { + Files::IStreamPtr mStream; + + public: + explicit BGSMStream(Files::IStreamPtr&& stream) + : mStream(std::move(stream)) + { + } + + void skip(size_t size) { mStream->ignore(size); } + + /// Read into a single instance of type + template + void read(T& data) + { + readBufferOfType<1>(mStream, &data); + } + + /// Read multiple instances of type into an array + template + void readArray(std::array& arr) + { + readBufferOfType(mStream, arr.data()); + } + }; + + template <> + void BGSMStream::read(osg::Vec2f& vec); + template <> + void BGSMStream::read(osg::Vec3f& vec); + template <> + void BGSMStream::read(osg::Vec4f& vec); + template <> + void BGSMStream::read(std::string& str); +} + +#endif diff --git a/components/bsa/ba2dx10file.cpp b/components/bsa/ba2dx10file.cpp index aa3f8d0581..82a5ee8473 100644 --- a/components/bsa/ba2dx10file.cpp +++ b/components/bsa/ba2dx10file.cpp @@ -6,17 +6,21 @@ #include -#include -#include -#include - #if defined(_MSC_VER) +// why is this necessary? These are included with /external:I #pragma warning(push) #pragma warning(disable : 4706) +#pragma warning(disable : 4702) +#include #include +#include +#include #pragma warning(pop) #else +#include #include +#include +#include #endif #include diff --git a/components/bsa/ba2gnrlfile.cpp b/components/bsa/ba2gnrlfile.cpp index 02df12593c..da5ad47029 100644 --- a/components/bsa/ba2gnrlfile.cpp +++ b/components/bsa/ba2gnrlfile.cpp @@ -6,16 +6,19 @@ #include -#include -#include - #if defined(_MSC_VER) +// why is this necessary? These are included with /external:I #pragma warning(push) #pragma warning(disable : 4706) +#pragma warning(disable : 4702) +#include #include +#include #pragma warning(pop) #else +#include #include +#include #endif #include diff --git a/components/bsa/bsa_file.cpp b/components/bsa/bsa_file.cpp index b3e24c75ab..4704e6e7e0 100644 --- a/components/bsa/bsa_file.cpp +++ b/components/bsa/bsa_file.cpp @@ -325,43 +325,33 @@ BsaVersion Bsa::BSAFile::detectVersion(const std::filesystem::path& filePath) { std::ifstream input(filePath, std::ios_base::binary); - // Total archive size - std::streamoff fsize = 0; - if (input.seekg(0, std::ios_base::end)) - { - fsize = input.tellg(); - input.seekg(0); - } - - if (fsize < 12) - { - return BSAVER_UNKNOWN; - } - // Get essential header numbers // First 12 bytes uint32_t head[3]; - input.read(reinterpret_cast(head), 12); + input.read(reinterpret_cast(head), sizeof(head)); + + if (input.gcount() != sizeof(head)) + return BsaVersion::Unknown; - if (head[0] == static_cast(BSAVER_UNCOMPRESSED)) + if (head[0] == static_cast(BsaVersion::Uncompressed)) { - return BSAVER_UNCOMPRESSED; + return BsaVersion::Uncompressed; } - if (head[0] == static_cast(BSAVER_COMPRESSED) || head[0] == ESM::fourCC("BTDX")) + if (head[0] == static_cast(BsaVersion::Compressed) || head[0] == ESM::fourCC("BTDX")) { if (head[1] == static_cast(0x01)) { if (head[2] == ESM::fourCC("GNRL")) - return BSAVER_BA2_GNRL; + return BsaVersion::BA2GNRL; if (head[2] == ESM::fourCC("DX10")) - return BSAVER_BA2_DX10; - return BSAVER_UNKNOWN; + return BsaVersion::BA2DX10; + return BsaVersion::Unknown; } - return BSAVER_COMPRESSED; + return BsaVersion::Compressed; } - return BSAVER_UNKNOWN; + return BsaVersion::Unknown; } diff --git a/components/bsa/bsa_file.hpp b/components/bsa/bsa_file.hpp index 8a953245d2..03a0703885 100644 --- a/components/bsa/bsa_file.hpp +++ b/components/bsa/bsa_file.hpp @@ -35,13 +35,13 @@ namespace Bsa { - enum BsaVersion + enum class BsaVersion : std::uint32_t { - BSAVER_UNKNOWN = 0x0, - BSAVER_UNCOMPRESSED = 0x100, - BSAVER_COMPRESSED = 0x415342, // B, S, A, - BSAVER_BA2_GNRL, // used by FO4, BSA which contains files - BSAVER_BA2_DX10 // used by FO4, BSA which contains textures + Unknown = 0x0, + Uncompressed = 0x100, + Compressed = 0x415342, // B, S, A, + BA2GNRL, // used by FO4, BSA which contains files + BA2DX10 // used by FO4, BSA which contains textures }; /** diff --git a/components/bsa/compressedbsafile.cpp b/components/bsa/compressedbsafile.cpp index b916de7919..14d90f5d91 100644 --- a/components/bsa/compressedbsafile.cpp +++ b/components/bsa/compressedbsafile.cpp @@ -30,16 +30,18 @@ #include -#include -#include - #if defined(_MSC_VER) #pragma warning(push) #pragma warning(disable : 4706) +#pragma warning(disable : 4702) +#include #include +#include #pragma warning(pop) #else +#include #include +#include #endif #include @@ -70,7 +72,7 @@ namespace Bsa input.read(reinterpret_cast(&mHeader), sizeof(mHeader)); - if (mHeader.mFormat != BSAVER_COMPRESSED) // BSA + if (mHeader.mFormat != static_cast(BsaVersion::Compressed)) // BSA fail("Unrecognized compressed BSA format"); if (mHeader.mVersion != Version_TES4 && mHeader.mVersion != Version_FO3 && mHeader.mVersion != Version_SSE) fail("Unrecognized compressed BSA version"); @@ -168,7 +170,7 @@ namespace Bsa name.resize(input.gcount()); if (name.back() != '\0') fail("Failed to read a filename: filename is too long"); - mHeader.mFileNamesLength -= input.gcount(); + mHeader.mFileNamesLength -= static_cast(input.gcount()); file.mName.insert(file.mName.begin(), folder.mName.begin(), folder.mName.end()); file.mName.insert(file.mName.begin() + folder.mName.size(), '\\'); } diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index ad7c73d3d9..a7da8fa150 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -13,7 +13,8 @@ const char Config::GameSettings::sDirectoryKey[] = "data"; namespace { - QStringList reverse(QStringList values) + template + QList reverse(QList values) { std::reverse(values.begin(), values.end()); return values; @@ -23,83 +24,111 @@ namespace Config::GameSettings::GameSettings(const Files::ConfigurationManager& cfg) : mCfgMgr(cfg) { +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + // this needs calling once so Qt can see its stream operators, which it needs when dragging and dropping + // it's automatic with Qt 6 + qRegisterMetaTypeStreamOperators("Config::SettingValue"); +#endif } void Config::GameSettings::validatePaths() { - QStringList paths = mSettings.values(QString("data")); - Files::PathContainer dataDirs; + QList paths = mSettings.values(QString("data")); - for (const QString& path : paths) - { - dataDirs.emplace_back(Files::pathFromQString(path)); - } - - // Parse the data dirs to convert the tokenized paths - mCfgMgr.processPaths(dataDirs, /*basePath=*/""); mDataDirs.clear(); - for (const auto& dataDir : dataDirs) + for (const auto& dataDir : paths) { - if (is_directory(dataDir)) - mDataDirs.append(Files::pathToQString(dataDir)); + if (QDir(dataDir.value).exists()) + { + SettingValue copy = dataDir; + copy.value = QDir(dataDir.value).canonicalPath(); + mDataDirs.append(copy); + } } // Do the same for data-local - QString local = mSettings.value(QString("data-local")); - if (local.length() && local.at(0) == QChar('\"')) - { - local.remove(0, 1); - local.chop(1); - } - - if (local.isEmpty()) - return; - - dataDirs.clear(); - dataDirs.emplace_back(Files::pathFromQString(local)); + const QString& local = mSettings.value(QString("data-local")).value; - mCfgMgr.processPaths(dataDirs, /*basePath=*/""); - - if (!dataDirs.empty()) + if (!local.isEmpty() && QDir(local).exists()) { - const auto& path = dataDirs.front(); - if (is_directory(path)) - mDataLocal = Files::pathToQString(path); + mDataLocal = QDir(local).canonicalPath(); } } -std::filesystem::path Config::GameSettings::getGlobalDataDir() const +QString Config::GameSettings::getResourcesVfs() const { - // global data dir may not exists if OpenMW is not installed (ie if run from build directory) - const auto& path = mCfgMgr.getGlobalDataPath(); - if (std::filesystem::exists(path)) - return std::filesystem::canonical(path); - return {}; + QString resources = mSettings.value(QString("resources"), { "./resources", "", "" }).value; + resources += "/vfs"; + return QFileInfo(resources).canonicalFilePath(); } -QStringList Config::GameSettings::values(const QString& key, const QStringList& defaultValues) const +QList Config::GameSettings::values( + const QString& key, const QList& defaultValues) const { if (!mSettings.values(key).isEmpty()) return mSettings.values(key); return defaultValues; } -bool Config::GameSettings::readFile(QTextStream& stream, bool ignoreContent) +bool Config::GameSettings::containsValue(const QString& key, const QString& value) const +{ + auto [itr, end] = mSettings.equal_range(key); + while (itr != end) + { + if (itr->value == value) + return true; + ++itr; + } + return false; +} + +bool Config::GameSettings::readFile(QTextStream& stream, const QString& context, bool ignoreContent) { - return readFile(stream, mSettings, ignoreContent); + if (readFile(stream, mSettings, context, ignoreContent)) + { + mContexts.push_back(context); + return true; + } + return false; } -bool Config::GameSettings::readUserFile(QTextStream& stream, bool ignoreContent) +bool Config::GameSettings::readUserFile(QTextStream& stream, const QString& context, bool ignoreContent) { - return readFile(stream, mUserSettings, ignoreContent); + return readFile(stream, mUserSettings, context, ignoreContent); } -bool Config::GameSettings::readFile(QTextStream& stream, QMultiMap& settings, bool ignoreContent) +bool Config::GameSettings::readFile( + QTextStream& stream, QMultiMap& settings, const QString& context, bool ignoreContent) { - QMultiMap cache; + QMultiMap cache; + QRegularExpression replaceRe("^\\s*replace\\s*=\\s*(.+)$"); QRegularExpression keyRe("^([^=]+)\\s*=\\s*(.+)$"); + auto initialPos = stream.pos(); + + while (!stream.atEnd()) + { + QString line = stream.readLine(); + + if (line.isEmpty() || line.startsWith("#")) + continue; + + QRegularExpressionMatch match = replaceRe.match(line); + if (match.hasMatch()) + { + QString key = match.captured(1).trimmed(); + // Replace composing entries with a replace= line + if (key == QLatin1String("config") || key == QLatin1String("replace") || key == QLatin1String("data") + || key == QLatin1String("fallback-archive") || key == QLatin1String("content") + || key == QLatin1String("groundcover") || key == QLatin1String("script-blacklist") + || key == QLatin1String("fallback")) + settings.remove(key); + } + } + + stream.seek(initialPos); + while (!stream.atEnd()) { QString line = stream.readLine(); @@ -111,27 +140,32 @@ bool Config::GameSettings::readFile(QTextStream& stream, QMultiMap values = cache.values(key); values.append(settings.values(key)); - if (!values.contains(value)) + bool exists = false; + for (const auto& existingValue : values) + { + if (existingValue.value == value.value) + { + exists = true; + break; + } + } + if (!exists) { cache.insert(key, value); } @@ -184,15 +232,16 @@ bool Config::GameSettings::writeFile(QTextStream& stream) // Boost-like quoting rules but internally stores as a std::filesystem::path. // Caution: This is intentional behaviour to duplicate how Boost and what we replaced it with worked, and we // rely on that. - if (i.key() == QLatin1String("data") || i.key() == QLatin1String("data-local") - || i.key() == QLatin1String("resources") || i.key() == QLatin1String("load-savegame")) + if (i.key() == QLatin1String("config") || i.key() == QLatin1String("user-data") + || i.key() == QLatin1String("resources") || i.key() == QLatin1String("data") + || i.key() == QLatin1String("data-local") || i.key() == QLatin1String("load-savegame")) { stream << i.key() << "="; // Equivalent to stream << std::quoted(i.value(), '"', '&'), which won't work on QStrings. QChar delim = '\"'; QChar escape = '&'; - QString string = i.value(); + QString string = i.value().originalRepresentation; stream << delim; for (auto& it : string) @@ -207,7 +256,7 @@ bool Config::GameSettings::writeFile(QTextStream& stream) continue; } - stream << i.key() << "=" << i.value() << "\n"; + stream << i.key() << "=" << i.value().originalRepresentation << "\n"; } return true; @@ -362,10 +411,11 @@ bool Config::GameSettings::writeFileWithComments(QFile& file) *iter = QString(); // assume no match QString key = match.captured(1); QString keyVal = match.captured(1) + "=" + match.captured(2); - QMultiMap::const_iterator i = mUserSettings.find(key); + QMultiMap::const_iterator i = mUserSettings.find(key); while (i != mUserSettings.end() && i.key() == key) { - QString settingLine = i.key() + "=" + i.value(); + // todo: does this need to handle paths? + QString settingLine = i.key() + "=" + i.value().originalRepresentation; QRegularExpressionMatch keyMatch = settingRegex.match(settingLine); if (keyMatch.hasMatch()) { @@ -408,15 +458,16 @@ bool Config::GameSettings::writeFileWithComments(QFile& file) // Boost-like quoting rules but internally stores as a std::filesystem::path. // Caution: This is intentional behaviour to duplicate how Boost and what we replaced it with worked, and we // rely on that. - if (it.key() == QLatin1String("data") || it.key() == QLatin1String("data-local") - || it.key() == QLatin1String("resources") || it.key() == QLatin1String("load-savegame")) + if (it.key() == QLatin1String("config") || it.key() == QLatin1String("user-data") + || it.key() == QLatin1String("resources") || it.key() == QLatin1String("data") + || it.key() == QLatin1String("data-local") || it.key() == QLatin1String("load-savegame")) { settingLine = it.key() + "="; // Equivalent to settingLine += std::quoted(it.value(), '"', '&'), which won't work on QStrings. QChar delim = '\"'; QChar escape = '&'; - QString string = it.value(); + QString string = it.value().originalRepresentation; settingLine += delim; for (auto& iter : string) @@ -428,7 +479,7 @@ bool Config::GameSettings::writeFileWithComments(QFile& file) settingLine += delim; } else - settingLine = it.key() + "=" + it.value(); + settingLine = it.key() + "=" + it.value().originalRepresentation; QRegularExpressionMatch match = settingRegex.match(settingLine); if (match.hasMatch()) @@ -487,11 +538,11 @@ bool Config::GameSettings::writeFileWithComments(QFile& file) bool Config::GameSettings::hasMaster() { bool result = false; - QStringList content = mSettings.values(QString(Config::GameSettings::sContentKey)); + QList content = mSettings.values(QString(Config::GameSettings::sContentKey)); for (int i = 0; i < content.count(); ++i) { - if (content.at(i).endsWith(QLatin1String(".omwgame"), Qt::CaseInsensitive) - || content.at(i).endsWith(QLatin1String(".esm"), Qt::CaseInsensitive)) + if (content.at(i).value.endsWith(QLatin1String(".omwgame"), Qt::CaseInsensitive) + || content.at(i).value.endsWith(QLatin1String(".esm"), Qt::CaseInsensitive)) { result = true; break; @@ -502,40 +553,62 @@ bool Config::GameSettings::hasMaster() } void Config::GameSettings::setContentList( - const QStringList& dirNames, const QStringList& archiveNames, const QStringList& fileNames) + const QList& dirNames, const QList& archiveNames, const QStringList& fileNames) { - auto const reset = [this](const char* key, const QStringList& list) { - remove(key); - for (auto const& item : list) - setMultiValue(key, item); - }; - - reset(sDirectoryKey, dirNames); - reset(sArchiveKey, archiveNames); - reset(sContentKey, fileNames); + remove(sDirectoryKey); + for (auto const& item : dirNames) + setMultiValue(sDirectoryKey, item); + remove(sArchiveKey); + for (auto const& item : archiveNames) + setMultiValue(sArchiveKey, item); + remove(sContentKey); + for (auto const& item : fileNames) + setMultiValue(sContentKey, { item }); } -QStringList Config::GameSettings::getDataDirs() const +QList Config::GameSettings::getDataDirs() const { return reverse(mDataDirs); } -QStringList Config::GameSettings::getArchiveList() const +QList Config::GameSettings::getArchiveList() const { // QMap returns multiple rows in LIFO order, so need to reverse return reverse(values(sArchiveKey)); } -QStringList Config::GameSettings::getContentList() const +QList Config::GameSettings::getContentList() const { // QMap returns multiple rows in LIFO order, so need to reverse return reverse(values(sContentKey)); } +bool Config::GameSettings::isUserSetting(const SettingValue& settingValue) const +{ + return settingValue.context.isEmpty() || settingValue.context == getUserContext(); +} + void Config::GameSettings::clear() { mSettings.clear(); + mContexts.clear(); mUserSettings.clear(); mDataDirs.clear(); mDataLocal.clear(); } + +QDataStream& Config::operator<<(QDataStream& out, const SettingValue& settingValue) +{ + out << settingValue.value; + out << settingValue.originalRepresentation; + out << settingValue.context; + return out; +} + +QDataStream& Config::operator>>(QDataStream& in, SettingValue& settingValue) +{ + in >> settingValue.value; + in >> settingValue.originalRepresentation; + in >> settingValue.context; + return in; +} diff --git a/components/config/gamesettings.hpp b/components/config/gamesettings.hpp index 34e3dc73ea..7627d5153a 100644 --- a/components/config/gamesettings.hpp +++ b/components/config/gamesettings.hpp @@ -17,33 +17,48 @@ namespace Files namespace Config { + struct SettingValue + { + QString value = ""; + // value as found in openmw.cfg, e.g. relative path with ?slug? + QString originalRepresentation = value; + // path of openmw.cfg, e.g. to resolve relative paths + QString context = ""; + + friend std::strong_ordering operator<=>(const SettingValue&, const SettingValue&) = default; + }; + class GameSettings { public: explicit GameSettings(const Files::ConfigurationManager& cfg); - inline QString value(const QString& key, const QString& defaultValue = QString()) + inline SettingValue value(const QString& key, const SettingValue& defaultValue = {}) { - return mSettings.value(key).isEmpty() ? defaultValue : mSettings.value(key); + return mSettings.contains(key) ? mSettings.value(key) : defaultValue; } - inline void setValue(const QString& key, const QString& value) + inline void setValue(const QString& key, const SettingValue& value) { mSettings.remove(key); mSettings.insert(key, value); mUserSettings.remove(key); - mUserSettings.insert(key, value); + if (isUserSetting(value)) + mUserSettings.insert(key, value); } - inline void setMultiValue(const QString& key, const QString& value) + inline void setMultiValue(const QString& key, const SettingValue& value) { - QStringList values = mSettings.values(key); + QList values = mSettings.values(key); if (!values.contains(value)) mSettings.insert(key, value); - values = mUserSettings.values(key); - if (!values.contains(value)) - mUserSettings.insert(key, value); + if (isUserSetting(value)) + { + values = mUserSettings.values(key); + if (!values.contains(value)) + mUserSettings.insert(key, value); + } } inline void remove(const QString& key) @@ -52,35 +67,50 @@ namespace Config mUserSettings.remove(key); } - QStringList getDataDirs() const; - std::filesystem::path getGlobalDataDir() const; + QList getDataDirs() const; + + QString getResourcesVfs() const; - inline void removeDataDir(const QString& dir) + inline void removeDataDir(const QString& existingDir) { - if (!dir.isEmpty()) - mDataDirs.removeAll(dir); + if (!existingDir.isEmpty()) + { + // non-user settings can't be removed as we can't edit the openmw.cfg they're in + mDataDirs.erase( + std::remove_if(mDataDirs.begin(), mDataDirs.end(), + [&](const SettingValue& dir) { return isUserSetting(dir) && dir.value == existingDir; }), + mDataDirs.end()); + } } - inline void addDataDir(const QString& dir) + + inline void addDataDir(const SettingValue& dir) { - if (!dir.isEmpty()) + if (!dir.value.isEmpty()) mDataDirs.append(dir); } + inline QString getDataLocal() const { return mDataLocal; } bool hasMaster(); - QStringList values(const QString& key, const QStringList& defaultValues = QStringList()) const; + QList values(const QString& key, const QList& defaultValues = {}) const; + bool containsValue(const QString& key, const QString& value) const; - bool readFile(QTextStream& stream, bool ignoreContent = false); - bool readFile(QTextStream& stream, QMultiMap& settings, bool ignoreContent = false); - bool readUserFile(QTextStream& stream, bool ignoreContent = false); + bool readFile(QTextStream& stream, const QString& context, bool ignoreContent = false); + bool readFile(QTextStream& stream, QMultiMap& settings, const QString& context, + bool ignoreContent = false); + bool readUserFile(QTextStream& stream, const QString& context, bool ignoreContent = false); bool writeFile(QTextStream& stream); bool writeFileWithComments(QFile& file); - QStringList getArchiveList() const; - void setContentList(const QStringList& dirNames, const QStringList& archiveNames, const QStringList& fileNames); - QStringList getContentList() const; + QList getArchiveList() const; + void setContentList( + const QList& dirNames, const QList& archiveNames, const QStringList& fileNames); + QList getContentList() const; + + const QString& getUserContext() const { return mContexts.back(); } + bool isUserSetting(const SettingValue& settingValue) const; void clear(); @@ -88,10 +118,12 @@ namespace Config const Files::ConfigurationManager& mCfgMgr; void validatePaths(); - QMultiMap mSettings; - QMultiMap mUserSettings; + QMultiMap mSettings; + QMultiMap mUserSettings; + + QStringList mContexts; - QStringList mDataDirs; + QList mDataDirs; QString mDataLocal; static const char sArchiveKey[]; @@ -100,5 +132,11 @@ namespace Config static bool isOrderedLine(const QString& line); }; + + QDataStream& operator<<(QDataStream& out, const SettingValue& settingValue); + QDataStream& operator>>(QDataStream& in, SettingValue& settingValue); } + +Q_DECLARE_METATYPE(Config::SettingValue) + #endif // GAMESETTINGS_HPP diff --git a/components/config/launchersettings.cpp b/components/config/launchersettings.cpp index 9d12535619..f9f067e58a 100644 --- a/components/config/launchersettings.cpp +++ b/components/config/launchersettings.cpp @@ -223,9 +223,25 @@ QStringList Config::LauncherSettings::getContentLists() void Config::LauncherSettings::setContentList(const GameSettings& gameSettings) { // obtain content list from game settings (if present) - QStringList dirs(gameSettings.getDataDirs()); - const QStringList archives(gameSettings.getArchiveList()); - const QStringList files(gameSettings.getContentList()); + QList dirs(gameSettings.getDataDirs()); + dirs.erase(std::remove_if( + dirs.begin(), dirs.end(), [&](const SettingValue& dir) { return !gameSettings.isUserSetting(dir); }), + dirs.end()); + // archives and content files aren't preprocessed, so we don't need to track their original form + const QList archivesOriginal(gameSettings.getArchiveList()); + QStringList archives; + for (const auto& archive : archivesOriginal) + { + if (gameSettings.isUserSetting(archive)) + archives.push_back(archive.value); + } + const QList filesOriginal(gameSettings.getContentList()); + QStringList files; + for (const auto& file : filesOriginal) + { + if (gameSettings.isUserSetting(file)) + files.push_back(file.value); + } // if openmw.cfg has no content, exit so we don't create an empty content list. if (dirs.isEmpty() || files.isEmpty()) @@ -233,17 +249,28 @@ void Config::LauncherSettings::setContentList(const GameSettings& gameSettings) return; } - // global and local data directories are not part of any profile - const auto globalDataDir = Files::pathToQString(gameSettings.getGlobalDataDir()); + // local data directory and resources/vfs are not part of any profile + const auto resourcesVfs = gameSettings.getResourcesVfs(); const auto dataLocal = gameSettings.getDataLocal(); - dirs.removeAll(globalDataDir); - dirs.removeAll(dataLocal); + dirs.erase( + std::remove_if(dirs.begin(), dirs.end(), [&](const SettingValue& dir) { return dir.value == resourcesVfs; }), + dirs.end()); + dirs.erase( + std::remove_if(dirs.begin(), dirs.end(), [&](const SettingValue& dir) { return dir.value == dataLocal; }), + dirs.end()); // if any existing profile in launcher matches the content list, make that profile the default for (const QString& listName : getContentLists()) { - if (files == getContentListFiles(listName) && archives == getArchiveList(listName) - && dirs == getDataDirectoryList(listName)) + const auto& listDirs = getDataDirectoryList(listName); + if (dirs.length() != listDirs.length()) + continue; + for (int i = 0; i < dirs.length(); ++i) + { + if (dirs[i].value != listDirs[i]) + continue; + } + if (files == getContentListFiles(listName) && archives == getArchiveList(listName)) { setCurrentContentListName(listName); return; @@ -253,7 +280,10 @@ void Config::LauncherSettings::setContentList(const GameSettings& gameSettings) // otherwise, add content list QString newContentListName(makeNewContentListName()); setCurrentContentListName(newContentListName); - setContentList(newContentListName, dirs, archives, files); + QStringList newListDirs; + for (const auto& dir : dirs) + newListDirs.push_back(dir.value); + setContentList(newContentListName, newListDirs, archives, files); } void Config::LauncherSettings::setContentList(const QString& contentListName, const QStringList& dirNames, diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index d800112712..8e7b77d308 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -109,6 +109,9 @@ Qt::ItemFlags ContentSelectorModel::ContentModel::flags(const QModelIndex& index if (!file) return Qt::NoItemFlags; + if (file->builtIn() || file->fromAnotherConfigFile()) + return Qt::ItemIsEnabled; + // game files can always be checked if (file == mGameFile) return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable; @@ -130,7 +133,7 @@ Qt::ItemFlags ContentSelectorModel::ContentModel::flags(const QModelIndex& index continue; noGameFiles = false; - if (mCheckedFiles.contains(depFile)) + if (depFile->builtIn() || depFile->fromAnotherConfigFile() || mCheckedFiles.contains(depFile)) { gamefileChecked = true; break; @@ -217,7 +220,8 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex& index, int if (file == mGameFile) return QVariant(); - return mCheckedFiles.contains(file) ? Qt::Checked : Qt::Unchecked; + return (file->builtIn() || file->fromAnotherConfigFile() || mCheckedFiles.contains(file)) ? Qt::Checked + : Qt::Unchecked; } case Qt::UserRole: @@ -279,7 +283,12 @@ bool ContentSelectorModel::ContentModel::setData(const QModelIndex& index, const { int checkValue = value.toInt(); bool setState = false; - if (checkValue == Qt::Checked && !mCheckedFiles.contains(file)) + if (file->builtIn() || file->fromAnotherConfigFile()) + { + setState = false; + success = false; + } + else if (checkValue == Qt::Checked && !mCheckedFiles.contains(file)) { setState = true; success = true; @@ -374,6 +383,13 @@ bool ContentSelectorModel::ContentModel::dropMimeData( else if (parent.isValid()) beginRow = parent.row(); + int firstModifiable = 0; + while (item(firstModifiable)->builtIn() || item(firstModifiable)->fromAnotherConfigFile()) + ++firstModifiable; + + if (beginRow < firstModifiable) + return false; + QByteArray encodedData = data->data(mMimeType); QDataStream stream(&encodedData, QIODevice::ReadOnly); @@ -434,10 +450,6 @@ void ContentSelectorModel::ContentModel::addFiles(const QString& path, bool newf { QFileInfo info(dir.absoluteFilePath(path2)); - // Enabled by default in system openmw.cfg; shouldn't be shown in content list. - if (info.fileName().compare("builtin.omwscripts", Qt::CaseInsensitive) == 0) - continue; - EsmFile* file = const_cast(item(info.fileName())); bool add = file == nullptr; std::unique_ptr newFile; @@ -453,6 +465,11 @@ void ContentSelectorModel::ContentModel::addFiles(const QString& path, bool newf file->setGameFiles({}); } + if (info.fileName().compare("builtin.omwscripts", Qt::CaseInsensitive) == 0) + file->setBuiltIn(true); + + file->setFromAnotherConfigFile(mNonUserContent.contains(info.fileName().toLower())); + if (info.fileName().endsWith(".omwscripts", Qt::CaseInsensitive)) { file->setDate(info.lastModified()); @@ -583,15 +600,20 @@ void ContentSelectorModel::ContentModel::setCurrentGameFile(const EsmFile* file) void ContentSelectorModel::ContentModel::sortFiles() { emit layoutAboutToBeChanged(); + + int firstModifiable = 0; + while (mFiles.at(firstModifiable)->builtIn() || mFiles.at(firstModifiable)->fromAnotherConfigFile()) + ++firstModifiable; + // Dependency sort std::unordered_set moved; - for (int i = mFiles.size() - 1; i > 0;) + for (int i = mFiles.size() - 1; i > firstModifiable;) { const auto file = mFiles.at(i); if (moved.find(file) == moved.end()) { int index = -1; - for (int j = 0; j < i; ++j) + for (int j = firstModifiable; j < i; ++j) { const QStringList& gameFiles = mFiles.at(j)->gameFiles(); // All addon files are implicitly dependent on the game file @@ -641,6 +663,28 @@ void ContentSelectorModel::ContentModel::setNew(const QString& filepath, bool is mNewFiles[filepath] = isNew; } +void ContentSelectorModel::ContentModel::setNonUserContent(const QStringList& fileList) +{ + mNonUserContent.clear(); + for (const auto& file : fileList) + mNonUserContent.insert(file.toLower()); + for (auto* file : mFiles) + file->setFromAnotherConfigFile(mNonUserContent.contains(file->fileName().toLower())); + + int insertPosition = 0; + while (mFiles.at(insertPosition)->builtIn()) + ++insertPosition; + + for (const auto& filepath : fileList) + { + const EsmFile* file = item(filepath); + int filePosition = indexFromItem(file).row(); + mFiles.move(filePosition, insertPosition++); + } + + sortFiles(); +} + bool ContentSelectorModel::ContentModel::isLoadOrderError(const EsmFile* file) const { return mPluginsWithLoadOrderError.contains(file->filePath()); @@ -654,6 +698,7 @@ void ContentSelectorModel::ContentModel::setContentList(const QStringList& fileL { if (setCheckState(filepath, true)) { + // setCheckState already gracefully handles builtIn and fromAnotherConfigFile // as necessary, move plug-ins in visible list to match sequence of supplied filelist const EsmFile* file = item(filepath); int filePosition = indexFromItem(file).row(); @@ -751,7 +796,7 @@ bool ContentSelectorModel::ContentModel::setCheckState(const QString& filepath, const EsmFile* file = item(filepath); - if (!file) + if (!file || file->builtIn() || file->fromAnotherConfigFile()) return false; if (checkState) diff --git a/components/contentselector/model/contentmodel.hpp b/components/contentselector/model/contentmodel.hpp index f754b9ea30..3cc05fd3cb 100644 --- a/components/contentselector/model/contentmodel.hpp +++ b/components/contentselector/model/contentmodel.hpp @@ -62,6 +62,7 @@ namespace ContentSelectorModel bool setCheckState(const QString& filepath, bool isChecked); bool isNew(const QString& filepath) const; void setNew(const QString& filepath, bool isChecked); + void setNonUserContent(const QStringList& fileList); void setContentList(const QStringList& fileList); ContentFileList checkedItems() const; void uncheckAll(); @@ -85,7 +86,7 @@ namespace ContentSelectorModel const EsmFile* mGameFile; ContentFileList mFiles; - QStringList mArchives; + QSet mNonUserContent; std::set mCheckedFiles; QHash mNewFiles; QSet mPluginsWithLoadOrderError; diff --git a/components/contentselector/model/esmfile.cpp b/components/contentselector/model/esmfile.cpp index e4280baef7..02ac0efa70 100644 --- a/components/contentselector/model/esmfile.cpp +++ b/components/contentselector/model/esmfile.cpp @@ -41,6 +41,16 @@ void ContentSelectorModel::EsmFile::setDescription(const QString& description) mDescription = description; } +void ContentSelectorModel::EsmFile::setBuiltIn(bool builtIn) +{ + mBuiltIn = builtIn; +} + +void ContentSelectorModel::EsmFile::setFromAnotherConfigFile(bool fromAnotherConfigFile) +{ + mFromAnotherConfigFile = fromAnotherConfigFile; +} + bool ContentSelectorModel::EsmFile::isGameFile() const { return (mGameFiles.size() == 0) @@ -76,6 +86,14 @@ QVariant ContentSelectorModel::EsmFile::fileProperty(const FileProperty prop) co return mDescription; break; + case FileProperty_BuiltIn: + return mBuiltIn; + break; + + case FileProperty_FromAnotherConfigFile: + return mFromAnotherConfigFile; + break; + case FileProperty_GameFile: return mGameFiles; break; @@ -113,6 +131,15 @@ void ContentSelectorModel::EsmFile::setFileProperty(const FileProperty prop, con mDescription = value; break; + // todo: check these work + case FileProperty_BuiltIn: + mBuiltIn = value == "true"; + break; + + case FileProperty_FromAnotherConfigFile: + mFromAnotherConfigFile = value == "true"; + break; + case FileProperty_GameFile: mGameFiles << value; break; diff --git a/components/contentselector/model/esmfile.hpp b/components/contentselector/model/esmfile.hpp index 606cc3d319..4703df562c 100644 --- a/components/contentselector/model/esmfile.hpp +++ b/components/contentselector/model/esmfile.hpp @@ -26,7 +26,9 @@ namespace ContentSelectorModel FileProperty_DateModified = 3, FileProperty_FilePath = 4, FileProperty_Description = 5, - FileProperty_GameFile = 6 + FileProperty_BuiltIn = 6, + FileProperty_FromAnotherConfigFile = 7, + FileProperty_GameFile = 8, }; EsmFile(const QString& fileName = QString(), ModelItem* parent = nullptr); @@ -40,6 +42,8 @@ namespace ContentSelectorModel void setFilePath(const QString& path); void setGameFiles(const QStringList& gameFiles); void setDescription(const QString& description); + void setBuiltIn(bool builtIn); + void setFromAnotherConfigFile(bool fromAnotherConfigFile); void addGameFile(const QString& name) { mGameFiles.append(name); } QVariant fileProperty(const FileProperty prop) const; @@ -49,18 +53,29 @@ namespace ContentSelectorModel QDateTime modified() const { return mModified; } QString formatVersion() const { return mVersion; } QString filePath() const { return mPath; } + bool builtIn() const { return mBuiltIn; } + bool fromAnotherConfigFile() const { return mFromAnotherConfigFile; } /// @note Contains file names, not paths. const QStringList& gameFiles() const { return mGameFiles; } QString description() const { return mDescription; } QString toolTip() const { - return mTooltipTemlate.arg(mAuthor) - .arg(mVersion) - .arg(mModified.toString(Qt::ISODate)) - .arg(mPath) - .arg(mDescription) - .arg(mGameFiles.join(", ")); + QString tooltip = mTooltipTemlate.arg(mAuthor) + .arg(mVersion) + .arg(mModified.toString(Qt::ISODate)) + .arg(mPath) + .arg(mDescription) + .arg(mGameFiles.join(", ")); + + if (mBuiltIn) + tooltip += tr("
    This content file cannot be disabled because it is part of OpenMW.
    "); + else if (mFromAnotherConfigFile) + tooltip += tr( + "
    This content file cannot be disabled because it is enabled in a config file other than " + "the user one.
    "); + + return tooltip; } bool isGameFile() const; @@ -82,6 +97,8 @@ namespace ContentSelectorModel QStringList mGameFiles; QString mDescription; QString mToolTip; + bool mBuiltIn = false; + bool mFromAnotherConfigFile = false; }; } diff --git a/components/contentselector/model/naturalsort.cpp b/components/contentselector/model/naturalsort.cpp deleted file mode 100644 index b090137d97..0000000000 --- a/components/contentselector/model/naturalsort.cpp +++ /dev/null @@ -1,112 +0,0 @@ -/* - * This file contains code found in the QtGui module of the Qt Toolkit. - * See Qt's qfilesystemmodel source files for more information - */ - -#include "naturalsort.hpp" - -static inline QChar getNextChar(const QString& s, int location) -{ - return (location < s.length()) ? s.at(location) : QChar(); -} - -/*! - * Natural number sort, skips spaces. - * - * Examples: - * 1, 2, 10, 55, 100 - * 01.jpg, 2.jpg, 10.jpg - * - * Note on the algorithm: - * Only as many characters as necessary are looked at and at most they all - * are looked at once. - * - * Slower then QString::compare() (of course) - */ -int naturalCompare(const QString& s1, const QString& s2, Qt::CaseSensitivity cs) -{ - for (int l1 = 0, l2 = 0; l1 <= s1.count() && l2 <= s2.count(); ++l1, ++l2) - { - // skip spaces, tabs and 0's - QChar c1 = getNextChar(s1, l1); - while (c1.isSpace()) - c1 = getNextChar(s1, ++l1); - QChar c2 = getNextChar(s2, l2); - while (c2.isSpace()) - c2 = getNextChar(s2, ++l2); - - if (c1.isDigit() && c2.isDigit()) - { - while (c1.digitValue() == 0) - c1 = getNextChar(s1, ++l1); - while (c2.digitValue() == 0) - c2 = getNextChar(s2, ++l2); - - int lookAheadLocation1 = l1; - int lookAheadLocation2 = l2; - int currentReturnValue = 0; - // find the last digit, setting currentReturnValue as we go if it isn't equal - for (QChar lookAhead1 = c1, lookAhead2 = c2; - (lookAheadLocation1 <= s1.length() && lookAheadLocation2 <= s2.length()); - lookAhead1 = getNextChar(s1, ++lookAheadLocation1), lookAhead2 = getNextChar(s2, ++lookAheadLocation2)) - { - bool is1ADigit = !lookAhead1.isNull() && lookAhead1.isDigit(); - bool is2ADigit = !lookAhead2.isNull() && lookAhead2.isDigit(); - if (!is1ADigit && !is2ADigit) - break; - if (!is1ADigit) - return -1; - if (!is2ADigit) - return 1; - if (currentReturnValue == 0) - { - if (lookAhead1 < lookAhead2) - { - currentReturnValue = -1; - } - else if (lookAhead1 > lookAhead2) - { - currentReturnValue = 1; - } - } - } - if (currentReturnValue != 0) - return currentReturnValue; - } - - if (cs == Qt::CaseInsensitive) - { - if (!c1.isLower()) - c1 = c1.toLower(); - if (!c2.isLower()) - c2 = c2.toLower(); - } - int r = QString::localeAwareCompare(c1, c2); - if (r < 0) - return -1; - if (r > 0) - return 1; - } - // The two strings are the same (02 == 2) so fall back to the normal sort - return QString::compare(s1, s2, cs); -} - -bool naturalSortLessThanCS(const QString& left, const QString& right) -{ - return (naturalCompare(left, right, Qt::CaseSensitive) < 0); -} - -bool naturalSortLessThanCI(const QString& left, const QString& right) -{ - return (naturalCompare(left, right, Qt::CaseInsensitive) < 0); -} - -bool naturalSortGreaterThanCS(const QString& left, const QString& right) -{ - return (naturalCompare(left, right, Qt::CaseSensitive) > 0); -} - -bool naturalSortGreaterThanCI(const QString& left, const QString& right) -{ - return (naturalCompare(left, right, Qt::CaseInsensitive) > 0); -} diff --git a/components/contentselector/model/naturalsort.hpp b/components/contentselector/model/naturalsort.hpp deleted file mode 100644 index 6973c55c2e..0000000000 --- a/components/contentselector/model/naturalsort.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef NATURALSORT_H -#define NATURALSORT_H - -#include - -bool naturalSortLessThanCS(const QString& left, const QString& right); -bool naturalSortLessThanCI(const QString& left, const QString& right); -bool naturalSortGreaterThanCS(const QString& left, const QString& right); -bool naturalSortGreaterThanCI(const QString& left, const QString& right); - -#endif diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index 00c32e272d..ab12f45fd7 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -31,7 +31,7 @@ ContentSelectorView::ContentSelector::~ContentSelector() = default; void ContentSelectorView::ContentSelector::buildContentModel(bool showOMWScripts) { - QIcon warningIcon(ui->addonView->style()->standardIcon(QStyle::SP_MessageBoxWarning).pixmap(QSize(16, 15))); + QIcon warningIcon(ui->addonView->style()->standardIcon(QStyle::SP_MessageBoxWarning)); mContentModel = new ContentSelectorModel::ContentModel(this, warningIcon, showOMWScripts); } @@ -123,6 +123,11 @@ void ContentSelectorView::ContentSelector::buildContextMenu() mContextMenu->addAction(tr("&Copy Path(s) to Clipboard"), this, SLOT(slotCopySelectedItemsPaths())); } +void ContentSelectorView::ContentSelector::setNonUserContent(const QStringList& fileList) +{ + mContentModel->setNonUserContent(fileList); +} + void ContentSelectorView::ContentSelector::setProfileContent(const QStringList& fileList) { clearCheckStates(); @@ -336,4 +341,4 @@ void ContentSelectorView::ContentSelector::slotSearchFilterTextChanged(const QSt void ContentSelectorView::ContentSelector::slotRowsMoved() { ui->addonView->selectionModel()->clearSelection(); -} \ No newline at end of file +} diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index 2b739645ba..2fdd38c799 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -40,6 +40,7 @@ namespace ContentSelectorView void sortFiles(); bool containsDataFiles(const QString& path); void clearFiles(); + void setNonUserContent(const QStringList& fileList); void setProfileContent(const QStringList& fileList); void clearCheckStates(); diff --git a/components/crashcatcher/crashcatcher.cpp b/components/crashcatcher/crashcatcher.cpp index 9a662c4a92..009029ef19 100644 --- a/components/crashcatcher/crashcatcher.cpp +++ b/components/crashcatcher/crashcatcher.cpp @@ -583,8 +583,6 @@ static bool isDebuggerPresent() void crashCatcherInstall(int argc, char** argv, const std::filesystem::path& crashLogPath) { -#if (defined(__APPLE__) || (defined(__linux) && !defined(ANDROID)) || (defined(__unix) && !defined(ANDROID)) \ - || defined(__posix)) if (argc == 2 && strcmp(argv[1], crash_switch) == 0) handleCrash(Files::pathToUnicodeString(crashLogPath).c_str()); @@ -595,5 +593,4 @@ void crashCatcherInstall(int argc, char** argv, const std::filesystem::path& cra Log(Debug::Info) << "Crash handler installed"; else Log(Debug::Warning) << "Installing crash handler failed"; -#endif } diff --git a/components/crashcatcher/crashcatcher.hpp b/components/crashcatcher/crashcatcher.hpp index 9dd1000385..16b416cf98 100644 --- a/components/crashcatcher/crashcatcher.hpp +++ b/components/crashcatcher/crashcatcher.hpp @@ -3,6 +3,11 @@ #include +#if (defined(__APPLE__) || (defined(__linux) && !defined(ANDROID)) || (defined(__unix) && !defined(ANDROID)) \ + || defined(__posix)) void crashCatcherInstall(int argc, char** argv, const std::filesystem::path& crashLogPath); +#else +inline void crashCatcherInstall(int /*argc*/, char** /*argv*/, const std::filesystem::path& /*crashLogPath*/) {} +#endif #endif diff --git a/components/debug/debugdraw.cpp b/components/debug/debugdraw.cpp index 32f71580a8..cd98fe2b6d 100644 --- a/components/debug/debugdraw.cpp +++ b/components/debug/debugdraw.cpp @@ -124,7 +124,7 @@ static void generateCylinder(osg::Geometry& geom, float radius, float height, in for (int i = 0; i < subdiv; i++) { float theta = (float(i) / float(subdiv)) * osg::PI * 2.; - osg::Vec3 pos = sphereCoordToCartesian(theta, osg::PI_2, 1.); + osg::Vec3 pos = sphereCoordToCartesian(theta, osg::PI_2f, 1.); pos *= radius; pos.z() = height / 2.; vertices->push_back(pos); @@ -150,7 +150,7 @@ static void generateCylinder(osg::Geometry& geom, float radius, float height, in for (int i = 0; i < subdiv; i++) { float theta = float(i) / float(subdiv) * osg::PI * 2.; - osg::Vec3 pos = sphereCoordToCartesian(theta, osg::PI_2, 1.); + osg::Vec3 pos = sphereCoordToCartesian(theta, osg::PI_2f, 1.); pos *= radius; pos.z() = -height / 2.; vertices->push_back(pos); @@ -162,7 +162,7 @@ static void generateCylinder(osg::Geometry& geom, float radius, float height, in for (int i = 0; i < subdiv; i++) { float theta = float(i) / float(subdiv) * osg::PI * 2.; - osg::Vec3 normal = sphereCoordToCartesian(theta, osg::PI_2, 1.); + osg::Vec3 normal = sphereCoordToCartesian(theta, osg::PI_2f, 1.); auto posTop = normal; posTop *= radius; auto posBot = posTop; @@ -215,12 +215,12 @@ static void generateCylinder(osg::Geometry& geom, float radius, float height, in geom.addPrimitiveSet(indices); } -static int getIdexBufferReadFromFrame(const long long int& nFrame) +static int getIndexBufferReadFromFrame(const unsigned int& nFrame) { return nFrame % 2; } -static int getIdexBufferWriteFromFrame(const long long int& nFrame) +static int getIndexBufferWriteFromFrame(const unsigned int& nFrame) { return (nFrame + 1) % 2; } @@ -232,7 +232,7 @@ namespace Debug auto vertices = new osg::Vec3Array; auto color = new osg::Vec3Array; lines.setDataVariance(osg::Object::STATIC); - lines.setUseVertexArrayObject(true); + lines.setUseVertexBufferObjects(true); lines.setUseDisplayList(false); lines.setCullingActive(false); @@ -248,6 +248,16 @@ namespace Debug makeLineInstance(*mLinesToDraw); } + DebugCustomDraw::DebugCustomDraw(const DebugCustomDraw& copy, const osg::CopyOp& copyop) + : Drawable(copy, copyop) + , mShapesToDraw(copy.mShapesToDraw) + , mLinesToDraw(copy.mLinesToDraw) + , mCubeGeometry(copy.mCubeGeometry) + , mCylinderGeometry(copy.mCylinderGeometry) + , mWireCubeGeometry(copy.mWireCubeGeometry) + { + } + void DebugCustomDraw::drawImplementation(osg::RenderInfo& renderInfo) const { auto state = renderInfo.getState(); @@ -255,96 +265,61 @@ namespace Debug const osg::StateSet* stateSet = getStateSet(); - auto program = static_cast(stateSet->getAttribute(osg::StateAttribute::PROGRAM)); - const osg::Program::PerContextProgram* pcp = program->getPCP(*state); - if (!pcp) - { - return; - } - - const osg::Uniform* uTrans = stateSet->getUniform("trans"); - const osg::Uniform* uCol = stateSet->getUniform("color"); - const osg::Uniform* uScale = stateSet->getUniform("scale"); - const osg::Uniform* uUseNormalAsColor = stateSet->getUniform("useNormalAsColor"); - - auto transLocation = pcp->getUniformLocation(uTrans->getNameID()); - auto colLocation = pcp->getUniformLocation(uCol->getNameID()); - auto scaleLocation = pcp->getUniformLocation(uScale->getNameID()); - auto normalAsColorLocation = pcp->getUniformLocation(uUseNormalAsColor->getNameID()); + const osg::Program::PerContextProgram& pcp = *state->getLastAppliedProgramObject(); + auto transLocation = pcp.getUniformLocation(stateSet->getUniform("trans")->getNameID()); + auto colLocation = pcp.getUniformLocation(stateSet->getUniform("color")->getNameID()); + auto scaleLocation = pcp.getUniformLocation(stateSet->getUniform("scale")->getNameID()); + auto normalAsColorLocation = pcp.getUniformLocation(stateSet->getUniform("useNormalAsColor")->getNameID()); - ext->glUniform3f(transLocation, 0., 0., 0.); - ext->glUniform3f(colLocation, 1., 1., 1.); - ext->glUniform3f(scaleLocation, 1., 1., 1.); - ext->glUniform1i(normalAsColorLocation, true); - - mLinesToDraw->drawImplementation(renderInfo); + auto drawPrimitive = [&](const osg::Drawable* primitive, const osg::Vec3f& pos, const osg::Vec3f& color, + const osg::Vec3f& scale, const bool normalAsColor) { + ext->glUniform3f(transLocation, pos.x(), pos.y(), pos.z()); + ext->glUniform3f(colLocation, color.x(), color.y(), color.z()); + ext->glUniform3f(scaleLocation, scale.x(), scale.y(), scale.z()); + ext->glUniform1i(normalAsColorLocation, normalAsColor); + primitive->drawImplementation(renderInfo); + }; - ext->glUniform1i(normalAsColorLocation, false); + drawPrimitive(mLinesToDraw, { 0.f, 0.f, 0.f }, { 1.f, 1.f, 1.f }, { 1.f, 1.f, 1.f }, true); for (const auto& shapeToDraw : mShapesToDraw) { - osg::Vec3f translation = shapeToDraw.mPosition; - osg::Vec3f color = shapeToDraw.mColor; - osg::Vec3f scale = shapeToDraw.mDims; - - ext->glUniform3f(transLocation, translation.x(), translation.y(), translation.z()); - ext->glUniform3f(colLocation, color.x(), color.y(), color.z()); - ext->glUniform3f(scaleLocation, scale.x(), scale.y(), scale.z()); - + const osg::Geometry* geometry = nullptr; switch (shapeToDraw.mDrawShape) { case DrawShape::Cube: - mCubeGeometry->drawImplementation(renderInfo); + geometry = mCubeGeometry; break; case DrawShape::Cylinder: - mCylinderGeometry->drawImplementation(renderInfo); + geometry = mCylinderGeometry; break; case DrawShape::WireCube: - mWireCubeGeometry->drawImplementation(renderInfo); + geometry = mWireCubeGeometry; break; } + drawPrimitive(geometry, shapeToDraw.mPosition, shapeToDraw.mColor, shapeToDraw.mDims, false); } mShapesToDraw.clear(); static_cast(mLinesToDraw->getVertexArray())->clear(); static_cast(mLinesToDraw->getNormalArray())->clear(); + static_cast(mLinesToDraw->getPrimitiveSet(0))->setCount(0); + pcp.resetAppliedUniforms(); } - - class DebugDrawCallback : public SceneUtil::NodeCallback - { - public: - DebugDrawCallback(Debug::DebugDrawer& debugDrawer) - : mDebugDrawer(debugDrawer) - { - } - - void operator()(osg::Node* node, osg::NodeVisitor* nv) - { - mDebugDrawer.mCurrentFrame = nv->getTraversalNumber(); - int indexRead = getIdexBufferReadFromFrame(mDebugDrawer.mCurrentFrame); - auto& lines = mDebugDrawer.mCustomDebugDrawer[indexRead]->mLinesToDraw; - lines->removePrimitiveSet(0, 1); - lines->addPrimitiveSet(new osg::DrawArrays( - osg::PrimitiveSet::LINES, 0, static_cast(lines->getVertexArray())->size())); - - nv->pushOntoNodePath(mDebugDrawer.mCustomDebugDrawer[indexRead]); - nv->apply(*mDebugDrawer.mCustomDebugDrawer[indexRead]); - nv->popFromNodePath(); - } - - Debug::DebugDrawer& mDebugDrawer; - }; } -Debug::DebugDrawer::DebugDrawer(Shader::ShaderManager& shaderManager, osg::ref_ptr parentNode) - : mParentNode(std::move(parentNode)) +Debug::DebugDrawer::DebugDrawer(const DebugDrawer& copy, const osg::CopyOp& copyop) + : Node(copy, copyop) + , mCurrentFrame(copy.mCurrentFrame) + , mCustomDebugDrawer(copy.mCustomDebugDrawer) { - mCurrentFrame = 0; +} +Debug::DebugDrawer::DebugDrawer(Shader::ShaderManager& shaderManager) +{ auto program = shaderManager.getProgram("debug"); - mDebugDrawSceneObjects = new osg::Group; - mDebugDrawSceneObjects->setCullingActive(false); - osg::StateSet* stateset = mDebugDrawSceneObjects->getOrCreateStateSet(); + setCullingActive(false); + osg::StateSet* stateset = getOrCreateStateSet(); stateset->addUniform(new osg::Uniform("color", osg::Vec3f(1., 1., 1.))); stateset->addUniform(new osg::Uniform("trans", osg::Vec3f(0., 0., 0.))); stateset->addUniform(new osg::Uniform("scale", osg::Vec3f(1., 1., 1.))); @@ -378,19 +353,17 @@ Debug::DebugDrawer::DebugDrawer(Shader::ShaderManager& shaderManager, osg::ref_p mCustomDebugDrawer[i]->mCubeGeometry = cubeGeometry; mCustomDebugDrawer[i]->mCylinderGeometry = cylinderGeom; } - mDebugDrawSceneObjects->addCullCallback(new DebugDrawCallback(*this)); - - mParentNode->addChild(mDebugDrawSceneObjects); } -Debug::DebugDrawer::~DebugDrawer() +void Debug::DebugDrawer::traverse(osg::NodeVisitor& nv) { - mParentNode->removeChild(mDebugDrawSceneObjects); + mCurrentFrame = nv.getTraversalNumber(); + mCustomDebugDrawer[getIndexBufferReadFromFrame(mCurrentFrame)]->accept(nv); } void Debug::DebugDrawer::drawCube(osg::Vec3f mPosition, osg::Vec3f mDims, osg::Vec3f mColor) { - mCustomDebugDrawer[getIdexBufferWriteFromFrame(mCurrentFrame)]->mShapesToDraw.push_back( + mCustomDebugDrawer[getIndexBufferWriteFromFrame(mCurrentFrame)]->mShapesToDraw.push_back( { mPosition, mDims, mColor, DrawShape::Cube }); } @@ -403,15 +376,16 @@ void Debug::DebugDrawer::drawCubeMinMax(osg::Vec3f min, osg::Vec3f max, osg::Vec void Debug::DebugDrawer::addDrawCall(const DrawCall& draw) { - mCustomDebugDrawer[getIdexBufferWriteFromFrame(mCurrentFrame)]->mShapesToDraw.push_back(draw); + mCustomDebugDrawer[getIndexBufferWriteFromFrame(mCurrentFrame)]->mShapesToDraw.push_back(draw); } void Debug::DebugDrawer::addLine(const osg::Vec3& start, const osg::Vec3& end, const osg::Vec3 color) { - const int indexWrite = getIdexBufferWriteFromFrame(mCurrentFrame); + const int indexWrite = getIndexBufferWriteFromFrame(mCurrentFrame); const auto& lines = mCustomDebugDrawer[indexWrite]->mLinesToDraw; auto vertices = static_cast(lines->getVertexArray()); auto colors = static_cast(lines->getNormalArray()); + auto primitive = static_cast(lines->getPrimitiveSet(0)); vertices->push_back(start); vertices->push_back(end); @@ -420,4 +394,6 @@ void Debug::DebugDrawer::addLine(const osg::Vec3& start, const osg::Vec3& end, c colors->push_back(color); colors->push_back(color); colors->dirty(); + + primitive->setCount(vertices->size()); } diff --git a/components/debug/debugdraw.hpp b/components/debug/debugdraw.hpp index 659968d35a..eb4219e06b 100644 --- a/components/debug/debugdraw.hpp +++ b/components/debug/debugdraw.hpp @@ -70,6 +70,9 @@ namespace Debug { public: DebugCustomDraw(); + DebugCustomDraw(const DebugCustomDraw& copy, const osg::CopyOp& copyop); + + META_Node(Debug, DebugCustomDraw) mutable std::vector mShapesToDraw; osg::ref_ptr mLinesToDraw; @@ -81,12 +84,15 @@ namespace Debug virtual void drawImplementation(osg::RenderInfo&) const override; }; - struct DebugDrawer + struct DebugDrawer : public osg::Node { - friend DebugDrawCallback; + DebugDrawer() = default; + DebugDrawer(const DebugDrawer& copy, const osg::CopyOp& copyop); + DebugDrawer(Shader::ShaderManager& shaderManager); + + META_Node(Debug, DebugDrawer) - DebugDrawer(Shader::ShaderManager& shaderManager, osg::ref_ptr parentNode); - ~DebugDrawer(); + void traverse(osg::NodeVisitor& nv) override; void drawCube( osg::Vec3f mPosition, osg::Vec3f mDims = osg::Vec3(50., 50., 50.), osg::Vec3f mColor = colorWhite); @@ -95,11 +101,9 @@ namespace Debug void addLine(const osg::Vec3& start, const osg::Vec3& end, const osg::Vec3 color = colorWhite); private: - long long int mCurrentFrame; + unsigned int mCurrentFrame = 0; std::array, 2> mCustomDebugDrawer; - osg::ref_ptr mDebugDrawSceneObjects; - osg::ref_ptr mParentNode; }; } #endif // ! diff --git a/components/debug/debugging.cpp b/components/debug/debugging.cpp index d170cf1929..67e7ecaaf3 100644 --- a/components/debug/debugging.cpp +++ b/components/debug/debugging.cpp @@ -6,7 +6,15 @@ #include #include +#ifdef _MSC_VER +// TODO: why is this necessary? this has /external:I +#pragma warning(push) +#pragma warning(disable : 4702) +#endif #include +#ifdef _MSC_VER +#pragma warning(pop) +#endif #include #include @@ -53,6 +61,11 @@ namespace Debug bool outRedirected = isRedirected(STD_OUTPUT_HANDLE); bool errRedirected = isRedirected(STD_ERROR_HANDLE); + // Note: Do not spend three days reinvestigating this PowerShell bug thinking its our bug. + // https://gitlab.com/OpenMW/openmw/-/merge_requests/408#note_447467393 + // The handles look valid, but GetFinalPathNameByHandleA can't tell what files they go to and writing to them + // doesn't work. + if (AttachConsole(ATTACH_PARENT_PROCESS)) { fflush(stdout); @@ -65,16 +78,19 @@ namespace Debug { _wfreopen(L"CON", L"r", stdin); freopen("CON", "r", stdin); + std::cin.clear(); } if (!outRedirected) { _wfreopen(L"CON", L"w", stdout); freopen("CON", "w", stdout); + std::cout.clear(); } if (!errRedirected) { _wfreopen(L"CON", L"w", stderr); freopen("CON", "w", stderr); + std::cerr.clear(); } return true; @@ -111,7 +127,7 @@ namespace Debug msg = msg.substr(1); char prefix[32]; - int prefixSize; + std::size_t prefixSize; { prefix[0] = '['; const auto now = std::chrono::system_clock::now(); @@ -248,9 +264,17 @@ namespace Debug private: static bool useColoredOutput() { - // Note: cmd.exe in Win10 should support ANSI colors, but in its own way. #if defined(_WIN32) - return 0; + if (getenv("NO_COLOR")) + return false; + + DWORD mode; + if (GetConsoleMode(GetStdHandle(STD_ERROR_HANDLE), &mode) && mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) + return true; + + // some console emulators may not use the Win32 API, so try the Unixy approach + char* term = getenv("TERM"); + return term && GetFileType(GetStdHandle(STD_ERROR_HANDLE)) == FILE_TYPE_CHAR; #else char* term = getenv("TERM"); bool useColor = term && !getenv("NO_COLOR") && isatty(fileno(stderr)); diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 03f6062788..fe6d0625f5 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -16,8 +16,9 @@ #include +#include + #include -#include #include #include #include @@ -49,40 +50,6 @@ namespace DetourNavigator return false; } - auto getPriority(const Job& job) noexcept - { - return std::make_tuple(-static_cast>(job.mState), job.mProcessTime, - job.mChangeType, job.mTryNumber, job.mDistanceToPlayer, job.mDistanceToOrigin); - } - - struct LessByJobPriority - { - bool operator()(JobIt lhs, JobIt rhs) const noexcept { return getPriority(*lhs) < getPriority(*rhs); } - }; - - void insertPrioritizedJob(JobIt job, std::deque& queue) - { - const auto it = std::upper_bound(queue.begin(), queue.end(), job, LessByJobPriority{}); - queue.insert(it, job); - } - - auto getDbPriority(const Job& job) noexcept - { - return std::make_tuple(static_cast>(job.mState), job.mChangeType, - job.mDistanceToPlayer, job.mDistanceToOrigin); - } - - struct LessByJobDbPriority - { - bool operator()(JobIt lhs, JobIt rhs) const noexcept { return getDbPriority(*lhs) < getDbPriority(*rhs); } - }; - - void insertPrioritizedDbJob(JobIt job, std::deque& queue) - { - const auto it = std::upper_bound(queue.begin(), queue.end(), job, LessByJobDbPriority{}); - queue.insert(it, job); - } - auto getAgentAndTile(const Job& job) noexcept { return std::make_tuple(job.mAgentBounds, job.mChangedTile); @@ -97,16 +64,6 @@ namespace DetourNavigator settings.mRecast, settings.mWriteToNavMeshDb); } - void updateJobs(std::deque& jobs, TilePosition playerTile, int maxTiles) - { - for (JobIt job : jobs) - { - job->mDistanceToPlayer = getManhattanDistance(job->mChangedTile, playerTile); - if (!shouldAddTile(job->mChangedTile, playerTile, maxTiles)) - job->mChangeType = ChangeType::remove; - } - } - std::size_t getNextJobId() { static std::atomic_size_t nextJobId{ 1 }; @@ -134,7 +91,7 @@ namespace DetourNavigator } Job::Job(const AgentBounds& agentBounds, std::weak_ptr navMeshCacheItem, - std::string_view worldspace, const TilePosition& changedTile, ChangeType changeType, int distanceToPlayer, + std::string_view worldspace, const TilePosition& changedTile, ChangeType changeType, std::chrono::steady_clock::time_point processTime) : mId(getNextJobId()) , mAgentBounds(agentBounds) @@ -143,11 +100,148 @@ namespace DetourNavigator , mChangedTile(changedTile) , mProcessTime(processTime) , mChangeType(changeType) - , mDistanceToPlayer(distanceToPlayer) - , mDistanceToOrigin(getManhattanDistance(changedTile, TilePosition{ 0, 0 })) { } + void SpatialJobQueue::clear() + { + mValues.clear(); + mIndex.clear(); + mSize = 0; + } + + void SpatialJobQueue::push(JobIt job) + { + auto it = mValues.find(job->mChangedTile); + + if (it == mValues.end()) + { + it = mValues.emplace_hint(it, job->mChangedTile, std::deque()); + mIndex.insert(IndexValue(IndexPoint(job->mChangedTile.x(), job->mChangedTile.y()), it)); + } + + it->second.push_back(job); + + ++mSize; + } + + std::optional SpatialJobQueue::pop(TilePosition playerTile) + { + const IndexPoint point(playerTile.x(), playerTile.y()); + const auto it = mIndex.qbegin(boost::geometry::index::nearest(point, 1)); + + if (it == mIndex.qend()) + return std::nullopt; + + const UpdatingMap::iterator mapIt = it->second; + std::deque& tileJobs = mapIt->second; + JobIt result = tileJobs.front(); + tileJobs.pop_front(); + + --mSize; + + if (tileJobs.empty()) + { + mValues.erase(mapIt); + mIndex.remove(*it); + } + + return result; + } + + void SpatialJobQueue::update(TilePosition playerTile, int maxTiles, std::vector& removing) + { + for (auto it = mValues.begin(); it != mValues.end();) + { + if (shouldAddTile(it->first, playerTile, maxTiles)) + { + ++it; + continue; + } + + for (JobIt job : it->second) + { + job->mChangeType = ChangeType::remove; + removing.push_back(job); + } + + mSize -= it->second.size(); + mIndex.remove(IndexValue(IndexPoint(it->first.x(), it->first.y()), it)); + it = mValues.erase(it); + } + } + + bool JobQueue::hasJob(std::chrono::steady_clock::time_point now) const + { + return !mRemoving.empty() || mUpdating.size() > 0 + || (!mDelayed.empty() && mDelayed.front()->mProcessTime <= now); + } + + void JobQueue::clear() + { + mRemoving.clear(); + mDelayed.clear(); + mUpdating.clear(); + } + + void JobQueue::push(JobIt job, std::chrono::steady_clock::time_point now) + { + if (job->mProcessTime > now) + { + mDelayed.push_back(job); + return; + } + + if (job->mChangeType == ChangeType::remove) + { + mRemoving.push_back(job); + return; + } + + mUpdating.push(job); + } + + std::optional JobQueue::pop(TilePosition playerTile, std::chrono::steady_clock::time_point now) + { + if (!mRemoving.empty()) + { + const JobIt result = mRemoving.back(); + mRemoving.pop_back(); + return result; + } + + if (const std::optional result = mUpdating.pop(playerTile)) + return result; + + if (mDelayed.empty() || mDelayed.front()->mProcessTime > now) + return std::nullopt; + + const JobIt result = mDelayed.front(); + mDelayed.pop_front(); + return result; + } + + void JobQueue::update(TilePosition playerTile, int maxTiles, std::chrono::steady_clock::time_point now) + { + mUpdating.update(playerTile, maxTiles, mRemoving); + + while (!mDelayed.empty() && mDelayed.front()->mProcessTime <= now) + { + const JobIt job = mDelayed.front(); + mDelayed.pop_front(); + + if (shouldAddTile(job->mChangedTile, playerTile, maxTiles)) + { + mUpdating.push(job); + } + else + { + job->mChangeType = ChangeType::remove; + mRemoving.push_back(job); + } + } + } + AsyncNavMeshUpdater::AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager, OffMeshConnectionsManager& offMeshConnectionsManager, std::unique_ptr&& db) : mSettings(settings) @@ -180,48 +274,47 @@ namespace DetourNavigator if (!playerTileChanged && changedTiles.empty()) return; - const int maxTiles - = std::min(mSettings.get().mMaxTilesNumber, navMeshCacheItem->lockConst()->getImpl().getParams()->maxTiles); - std::unique_lock lock(mMutex); if (playerTileChanged) - updateJobs(mWaiting, playerTile, maxTiles); + { + Log(Debug::Debug) << "Player tile has been changed to " << playerTile; + mWaiting.update(playerTile, mSettings.get().mMaxTilesNumber); + } for (const auto& [changedTile, changeType] : changedTiles) { if (mPushed.emplace(agentBounds, changedTile).second) { - const auto processTime = changeType == ChangeType::update - ? mLastUpdates[std::tie(agentBounds, changedTile)] + mSettings.get().mMinUpdateInterval - : std::chrono::steady_clock::time_point(); - - const JobIt it = mJobs.emplace(mJobs.end(), agentBounds, navMeshCacheItem, worldspace, changedTile, - changeType, getManhattanDistance(changedTile, playerTile), processTime); + const auto processTime = [&, changedTile = changedTile, changeType = changeType] { + if (changeType != ChangeType::update) + return std::chrono::steady_clock::time_point(); + const auto lastUpdate = mLastUpdates.find(std::tie(agentBounds, changedTile)); + if (lastUpdate == mLastUpdates.end()) + return std::chrono::steady_clock::time_point(); + return lastUpdate->second + mSettings.get().mMinUpdateInterval; + }(); + + const JobIt it = mJobs.emplace( + mJobs.end(), agentBounds, navMeshCacheItem, worldspace, changedTile, changeType, processTime); Log(Debug::Debug) << "Post job " << it->mId << " for agent=(" << it->mAgentBounds << ")" - << " changedTile=(" << it->mChangedTile << ") " + << " changedTile=(" << it->mChangedTile << ")" << " changeType=" << it->mChangeType; - if (playerTileChanged) - mWaiting.push_back(it); - else - insertPrioritizedJob(it, mWaiting); + mWaiting.push(it); } } - if (playerTileChanged) - std::sort(mWaiting.begin(), mWaiting.end(), LessByJobPriority{}); - Log(Debug::Debug) << "Posted " << mJobs.size() << " navigator jobs"; - if (!mWaiting.empty()) + if (mWaiting.hasJob()) mHasJob.notify_all(); lock.unlock(); if (playerTileChanged && mDbWorker != nullptr) - mDbWorker->updateJobs(playerTile, maxTiles); + mDbWorker->update(playerTile); } void AsyncNavMeshUpdater::wait(WaitConditionType waitConditionType, Loading::Listener* listener) @@ -313,7 +406,7 @@ namespace DetourNavigator { const std::lock_guard lock(mMutex); result.mJobs = mJobs.size(); - result.mWaiting = mWaiting.size(); + result.mWaiting = mWaiting.getStats(); result.mPushed = mPushed.size(); } result.mProcessing = mProcessingTiles.lockConst()->size(); @@ -335,7 +428,8 @@ namespace DetourNavigator if (JobIt job = getNextJob(); job != mJobs.end()) { const JobStatus status = processJob(*job); - Log(Debug::Debug) << "Processed job " << job->mId << " with status=" << status; + Log(Debug::Debug) << "Processed job " << job->mId << " with status=" << status + << " changeType=" << job->mChangeType; switch (status) { case JobStatus::Done: @@ -346,7 +440,8 @@ namespace DetourNavigator removeJob(job); break; case JobStatus::Fail: - repost(job); + unlockTile(job->mId, job->mAgentBounds, job->mChangedTile); + removeJob(job); break; case JobStatus::MemoryCacheMiss: { @@ -368,7 +463,9 @@ namespace DetourNavigator JobStatus AsyncNavMeshUpdater::processJob(Job& job) { - Log(Debug::Debug) << "Processing job " << job.mId << " by thread=" << std::this_thread::get_id(); + Log(Debug::Debug) << "Processing job " << job.mId << " for agent=(" << job.mAgentBounds << ")" + << " changedTile=(" << job.mChangedTile << ")" + << " changeType=" << job.mChangeType << " by thread=" << std::this_thread::get_id(); const auto navMeshCacheItem = job.mNavMeshCacheItem.lock(); @@ -376,12 +473,11 @@ namespace DetourNavigator return JobStatus::Done; const auto playerTile = *mPlayerTile.lockConst(); - const int maxTiles - = std::min(mSettings.get().mMaxTilesNumber, navMeshCacheItem->lockConst()->getImpl().getParams()->maxTiles); - if (!shouldAddTile(job.mChangedTile, playerTile, maxTiles)) + if (!shouldAddTile(job.mChangedTile, playerTile, mSettings.get().mMaxTilesNumber)) { Log(Debug::Debug) << "Ignore add tile by job " << job.mId << ": too far from player"; + job.mChangeType = ChangeType::remove; navMeshCacheItem->lock()->removeTile(job.mChangedTile); return JobStatus::Done; } @@ -549,9 +645,8 @@ namespace DetourNavigator bool shouldStop = false; const auto hasJob = [&] { - shouldStop = mShouldStop; - return shouldStop - || (!mWaiting.empty() && mWaiting.front()->mProcessTime <= std::chrono::steady_clock::now()); + shouldStop = mShouldStop.load(); + return shouldStop || mWaiting.hasJob(); }; if (!mHasJob.wait_for(lock, std::chrono::milliseconds(10), hasJob)) @@ -564,9 +659,15 @@ namespace DetourNavigator if (shouldStop) return mJobs.end(); - const JobIt job = mWaiting.front(); + const TilePosition playerTile = *mPlayerTile.lockConst(); + + JobIt job = mJobs.end(); - mWaiting.pop_front(); + if (const std::optional nextJob = mWaiting.pop(playerTile)) + job = *nextJob; + + if (job == mJobs.end()) + return job; Log(Debug::Debug) << "Pop job " << job->mId << " by thread=" << std::this_thread::get_id(); @@ -575,9 +676,9 @@ namespace DetourNavigator if (!lockTile(job->mId, job->mAgentBounds, job->mChangedTile)) { - Log(Debug::Debug) << "Failed to lock tile by job " << job->mId << " try=" << job->mTryNumber; - ++job->mTryNumber; - insertPrioritizedJob(job, mWaiting); + Log(Debug::Debug) << "Failed to lock tile by job " << job->mId; + job->mProcessTime = std::chrono::steady_clock::now() + mSettings.get().mMinUpdateInterval; + mWaiting.push(job); return mJobs.end(); } @@ -613,26 +714,6 @@ namespace DetourNavigator writeToFile(shared->lockConst()->getImpl(), mSettings.get().mNavMeshPathPrefix, navMeshRevision); } - void AsyncNavMeshUpdater::repost(JobIt job) - { - unlockTile(job->mId, job->mAgentBounds, job->mChangedTile); - - if (mShouldStop || job->mTryNumber > 2) - return; - - const std::lock_guard lock(mMutex); - - if (mPushed.emplace(job->mAgentBounds, job->mChangedTile).second) - { - ++job->mTryNumber; - insertPrioritizedJob(job, mWaiting); - mHasJob.notify_all(); - return; - } - - mJobs.erase(job); - } - bool AsyncNavMeshUpdater::lockTile( std::size_t jobId, const AgentBounds& agentBounds, const TilePosition& changedTile) { @@ -677,7 +758,7 @@ namespace DetourNavigator { Log(Debug::Debug) << "Enqueueing job " << job->mId << " by thread=" << std::this_thread::get_id(); const std::lock_guard lock(mMutex); - insertPrioritizedJob(job, mWaiting); + mWaiting.push(job); mHasJob.notify_all(); } @@ -691,40 +772,47 @@ namespace DetourNavigator void DbJobQueue::push(JobIt job) { const std::lock_guard lock(mMutex); - insertPrioritizedDbJob(job, mJobs); if (isWritingDbJob(*job)) - ++mWritingJobs; + mWriting.push_back(job); else - ++mReadingJobs; + mReading.push(job); mHasJob.notify_all(); } std::optional DbJobQueue::pop() { std::unique_lock lock(mMutex); - mHasJob.wait(lock, [&] { return mShouldStop || !mJobs.empty(); }); - if (mJobs.empty()) + + const auto hasJob = [&] { return mShouldStop || mReading.size() > 0 || mWriting.size() > 0; }; + + mHasJob.wait(lock, hasJob); + + if (mShouldStop) return std::nullopt; - const JobIt job = mJobs.front(); - mJobs.pop_front(); - if (isWritingDbJob(*job)) - --mWritingJobs; - else - --mReadingJobs; + + if (const std::optional job = mReading.pop(mPlayerTile)) + return job; + + if (mWriting.empty()) + return std::nullopt; + + const JobIt job = mWriting.front(); + mWriting.pop_front(); + return job; } - void DbJobQueue::update(TilePosition playerTile, int maxTiles) + void DbJobQueue::update(TilePosition playerTile) { const std::lock_guard lock(mMutex); - updateJobs(mJobs, playerTile, maxTiles); - std::sort(mJobs.begin(), mJobs.end(), LessByJobDbPriority{}); + mPlayerTile = playerTile; } void DbJobQueue::stop() { const std::lock_guard lock(mMutex); - mJobs.clear(); + mReading.clear(); + mWriting.clear(); mShouldStop = true; mHasJob.notify_all(); } @@ -732,7 +820,10 @@ namespace DetourNavigator DbJobQueueStats DbJobQueue::getStats() const { const std::lock_guard lock(mMutex); - return DbJobQueueStats{ .mWritingJobs = mWritingJobs, .mReadingJobs = mReadingJobs }; + return DbJobQueueStats{ + .mReadingJobs = mReading.size(), + .mWritingJobs = mWriting.size(), + }; } DbWorker::DbWorker(AsyncNavMeshUpdater& updater, std::unique_ptr&& db, TileVersion version, @@ -761,8 +852,10 @@ namespace DetourNavigator DbWorkerStats DbWorker::getStats() const { - return DbWorkerStats{ .mJobs = mQueue.getStats(), - .mGetTileCount = mGetTileCount.load(std::memory_order_relaxed) }; + return DbWorkerStats{ + .mJobs = mQueue.getStats(), + .mGetTileCount = mGetTileCount.load(std::memory_order_relaxed), + }; } void DbWorker::stop() diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index 9b95d4f4b3..f3d624a8a6 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -14,6 +14,9 @@ #include "tileposition.hpp" #include "waitconditiontype.hpp" +#include +#include + #include #include #include @@ -49,11 +52,8 @@ namespace DetourNavigator const std::weak_ptr mNavMeshCacheItem; const std::string mWorldspace; const TilePosition mChangedTile; - const std::chrono::steady_clock::time_point mProcessTime; - unsigned mTryNumber = 0; + std::chrono::steady_clock::time_point mProcessTime; ChangeType mChangeType; - int mDistanceToPlayer; - const int mDistanceToOrigin; JobState mState = JobState::Initial; std::vector mInput; std::shared_ptr mRecastMesh; @@ -61,12 +61,65 @@ namespace DetourNavigator std::unique_ptr mGeneratedNavMeshData; Job(const AgentBounds& agentBounds, std::weak_ptr navMeshCacheItem, - std::string_view worldspace, const TilePosition& changedTile, ChangeType changeType, int distanceToPlayer, + std::string_view worldspace, const TilePosition& changedTile, ChangeType changeType, std::chrono::steady_clock::time_point processTime); }; using JobIt = std::list::iterator; + class SpatialJobQueue + { + public: + std::size_t size() const { return mSize; } + + void clear(); + + void push(JobIt job); + + std::optional pop(TilePosition playerTile); + + void update(TilePosition playerTile, int maxTiles, std::vector& removing); + + private: + using IndexPoint = boost::geometry::model::point; + using UpdatingMap = std::map>; + using IndexValue = std::pair; + + std::size_t mSize = 0; + UpdatingMap mValues; + boost::geometry::index::rtree> mIndex; + }; + + class JobQueue + { + public: + JobQueueStats getStats() const + { + return JobQueueStats{ + .mRemoving = mRemoving.size(), + .mUpdating = mUpdating.size(), + .mDelayed = mDelayed.size(), + }; + } + + bool hasJob(std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()) const; + + void clear(); + + void push(JobIt job, std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()); + + std::optional pop( + TilePosition playerTile, std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()); + + void update(TilePosition playerTile, int maxTiles, + std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()); + + private: + std::vector mRemoving; + SpatialJobQueue mUpdating; + std::deque mDelayed; + }; + enum class JobStatus { Done, @@ -83,7 +136,7 @@ namespace DetourNavigator std::optional pop(); - void update(TilePosition playerTile, int maxTiles); + void update(TilePosition playerTile); void stop(); @@ -92,10 +145,10 @@ namespace DetourNavigator private: mutable std::mutex mMutex; std::condition_variable mHasJob; - std::deque mJobs; + SpatialJobQueue mReading; + std::deque mWriting; + TilePosition mPlayerTile; bool mShouldStop = false; - std::size_t mWritingJobs = 0; - std::size_t mReadingJobs = 0; }; class AsyncNavMeshUpdater; @@ -112,7 +165,7 @@ namespace DetourNavigator void enqueueJob(JobIt job); - void updateJobs(TilePosition playerTile, int maxTiles) { mQueue.update(playerTile, maxTiles); } + void update(TilePosition playerTile) { mQueue.update(playerTile); } void stop(); @@ -169,7 +222,7 @@ namespace DetourNavigator std::condition_variable mDone; std::condition_variable mProcessed; std::list mJobs; - std::deque mWaiting; + JobQueue mWaiting; std::set> mPushed; Misc::ScopeGuarded mPlayerTile; NavMeshTilesCache mNavMeshTilesCache; @@ -197,8 +250,6 @@ namespace DetourNavigator void writeDebugFiles(const Job& job, const RecastMesh* recastMesh) const; - void repost(JobIt job); - bool lockTile(std::size_t jobId, const AgentBounds& agentBounds, const TilePosition& changedTile); void unlockTile(std::size_t jobId, const AgentBounds& agentBounds, const TilePosition& changedTile); diff --git a/components/detournavigator/changetype.hpp b/components/detournavigator/changetype.hpp index e6d0bce0a9..63a43e88fb 100644 --- a/components/detournavigator/changetype.hpp +++ b/components/detournavigator/changetype.hpp @@ -6,15 +6,9 @@ namespace DetourNavigator enum class ChangeType { remove = 0, - mixed = 1, - add = 2, - update = 3, + add = 1, + update = 2, }; - - inline ChangeType addChangeType(const ChangeType current, const ChangeType add) - { - return current == add ? current : ChangeType::mixed; - } } #endif diff --git a/components/detournavigator/collisionshapetype.cpp b/components/detournavigator/collisionshapetype.cpp index b20ae6147f..b68d5cd239 100644 --- a/components/detournavigator/collisionshapetype.cpp +++ b/components/detournavigator/collisionshapetype.cpp @@ -15,7 +15,7 @@ namespace DetourNavigator return static_cast(value); } std::string error("Invalid CollisionShapeType value: \""); - error += value; + error += std::to_string(value); error += '"'; throw std::invalid_argument(error); } diff --git a/components/detournavigator/commulativeaabb.hpp b/components/detournavigator/commulativeaabb.hpp index 5d24c329ca..46cf64b348 100644 --- a/components/detournavigator/commulativeaabb.hpp +++ b/components/detournavigator/commulativeaabb.hpp @@ -3,7 +3,7 @@ #include -#include +#include namespace DetourNavigator { diff --git a/components/detournavigator/debug.cpp b/components/detournavigator/debug.cpp index 835f37f999..5ce1464bdd 100644 --- a/components/detournavigator/debug.cpp +++ b/components/detournavigator/debug.cpp @@ -79,13 +79,13 @@ namespace DetourNavigator switch (v) { case CollisionShapeType::Aabb: - return s << "AgentShapeType::Aabb"; + return s << "CollisionShapeType::Aabb"; case CollisionShapeType::RotatingBox: - return s << "AgentShapeType::RotatingBox"; + return s << "CollisionShapeType::RotatingBox"; case CollisionShapeType::Cylinder: - return s << "AgentShapeType::Cylinder"; + return s << "CollisionShapeType::Cylinder"; } - return s << "AgentShapeType::" << static_cast>(v); + return s << "CollisionShapeType::" << static_cast>(v); } std::ostream& operator<<(std::ostream& s, const AgentBounds& v) @@ -184,8 +184,6 @@ namespace DetourNavigator { case ChangeType::remove: return stream << "ChangeType::remove"; - case ChangeType::mixed: - return stream << "ChangeType::mixed"; case ChangeType::add: return stream << "ChangeType::add"; case ChangeType::update: diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index d35ecf499d..e143bf1837 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -480,15 +480,6 @@ namespace DetourNavigator return true; } - template - unsigned long getMinValuableBitsNumber(const T value) - { - unsigned long power = 0; - while (power < sizeof(T) * 8 && (static_cast(1) << power) < value) - ++power; - return power; - } - std::pair getBoundsByZ( const RecastMesh& recastMesh, float agentHalfExtentsZ, const RecastSettings& settings) { @@ -528,10 +519,7 @@ namespace DetourNavigator return { minZ, maxZ }; } } -} // namespace DetourNavigator -namespace DetourNavigator -{ std::unique_ptr prepareNavMeshTileData(const RecastMesh& recastMesh, std::string_view worldspace, const TilePosition& tilePosition, const AgentBounds& agentBounds, const RecastSettings& settings) @@ -621,22 +609,12 @@ namespace DetourNavigator void initEmptyNavMesh(const Settings& settings, dtNavMesh& navMesh) { - // Max tiles and max polys affect how the tile IDs are caculated. - // There are 22 bits available for identifying a tile and a polygon. - const int polysAndTilesBits = 22; - const auto polysBits = getMinValuableBitsNumber(settings.mDetour.mMaxPolys); - - if (polysBits >= polysAndTilesBits) - throw InvalidArgument("Too many polygons per tile"); - - const auto tilesBits = polysAndTilesBits - polysBits; - dtNavMeshParams params; std::fill_n(params.orig, 3, 0.0f); params.tileWidth = settings.mRecast.mTileSize * settings.mRecast.mCellSize; params.tileHeight = settings.mRecast.mTileSize * settings.mRecast.mCellSize; - params.maxTiles = 1 << tilesBits; - params.maxPolys = 1 << polysBits; + params.maxTiles = settings.mMaxTilesNumber; + params.maxPolys = settings.mDetour.mMaxPolys; const auto status = navMesh.init(¶ms); diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index f2acc8c9d6..378af081d0 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATOR_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATOR_H +#include #include #include "heightfieldshape.hpp" diff --git a/components/detournavigator/navmeshcacheitem.hpp b/components/detournavigator/navmeshcacheitem.hpp index ec76b56a46..120a374195 100644 --- a/components/detournavigator/navmeshcacheitem.hpp +++ b/components/detournavigator/navmeshcacheitem.hpp @@ -131,6 +131,15 @@ namespace DetourNavigator function(position, tile.mVersion, *meshTile); } + template + void forEachTilePosition(Function&& function) const + { + for (const auto& [position, tile] : mUsedTiles) + function(position); + for (const TilePosition& position : mEmptyTiles) + function(position); + } + private: struct Tile { diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index f4a82b850f..3b62866ed7 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -13,8 +13,6 @@ #include -#include - namespace { /// Safely reset shared_ptr with definite underlying object destrutor call. @@ -179,9 +177,9 @@ namespace DetourNavigator { std::map tilesToPost = changedTiles; { + const int maxTiles = mSettings.mMaxTilesNumber; const auto locked = cached->lockConst(); const auto& navMesh = locked->getImpl(); - const auto maxTiles = std::min(mSettings.mMaxTilesNumber, navMesh.getParams()->maxTiles); getTilesPositions(range, [&](const TilePosition& tile) { if (changedTiles.find(tile) != changedTiles.end()) return; @@ -190,7 +188,11 @@ namespace DetourNavigator if (shouldAdd && !presentInNavMesh) tilesToPost.emplace(tile, locked->isEmptyTile(tile) ? ChangeType::update : ChangeType::add); else if (!shouldAdd && presentInNavMesh) - tilesToPost.emplace(tile, ChangeType::mixed); + tilesToPost.emplace(tile, ChangeType::remove); + }); + locked->forEachTilePosition([&](const TilePosition& tile) { + if (!shouldAddTile(tile, playerTile, maxTiles)) + tilesToPost.emplace(tile, ChangeType::remove); }); } mAsyncNavMeshUpdater.post(agentBounds, cached, playerTile, mWorldspace, tilesToPost); diff --git a/components/detournavigator/objecttransform.hpp b/components/detournavigator/objecttransform.hpp index e56f5dd392..1d52817f52 100644 --- a/components/detournavigator/objecttransform.hpp +++ b/components/detournavigator/objecttransform.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_OBJECTTRANSFORM_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_OBJECTTRANSFORM_H -#include +#include #include diff --git a/components/detournavigator/recastmesh.hpp b/components/detournavigator/recastmesh.hpp index ac487d3b68..6d06db0799 100644 --- a/components/detournavigator/recastmesh.hpp +++ b/components/detournavigator/recastmesh.hpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include diff --git a/components/detournavigator/settings.cpp b/components/detournavigator/settings.cpp index 0470c629e5..5e555050f7 100644 --- a/components/detournavigator/settings.cpp +++ b/components/detournavigator/settings.cpp @@ -3,41 +3,81 @@ #include #include +#include +#include +#include + namespace DetourNavigator { - RecastSettings makeRecastSettingsFromSettingsManager() + namespace { - RecastSettings result; - - result.mBorderSize = ::Settings::navigator().mBorderSize; - result.mCellHeight = ::Settings::navigator().mCellHeight; - result.mCellSize = ::Settings::navigator().mCellSize; - result.mDetailSampleDist = ::Settings::navigator().mDetailSampleDist; - result.mDetailSampleMaxError = ::Settings::navigator().mDetailSampleMaxError; - result.mMaxClimb = Constants::sStepSizeUp; - result.mMaxSimplificationError = ::Settings::navigator().mMaxSimplificationError; - result.mMaxSlope = Constants::sMaxSlope; - result.mRecastScaleFactor = ::Settings::navigator().mRecastScaleFactor; - result.mSwimHeightScale = 0; - result.mMaxEdgeLen = ::Settings::navigator().mMaxEdgeLen; - result.mMaxVertsPerPoly = ::Settings::navigator().mMaxVertsPerPoly; - result.mRegionMergeArea = ::Settings::navigator().mRegionMergeArea; - result.mRegionMinArea = ::Settings::navigator().mRegionMinArea; - result.mTileSize = ::Settings::navigator().mTileSize; + struct NavMeshLimits + { + int mMaxTiles; + int mMaxPolys; + }; - return result; - } + template + unsigned long getMinValuableBitsNumber(const T value) + { + unsigned long power = 0; + while (power < sizeof(T) * 8 && (static_cast(1) << power) < value) + ++power; + return power; + } - DetourSettings makeDetourSettingsFromSettingsManager() - { - DetourSettings result; + NavMeshLimits getNavMeshTileLimits(const DetourSettings& settings) + { + // Max tiles and max polys affect how the tile IDs are caculated. + // There are 22 bits available for identifying a tile and a polygon. + constexpr int polysAndTilesBits = 22; + const unsigned long polysBits = getMinValuableBitsNumber(settings.mMaxPolys); - result.mMaxNavMeshQueryNodes = ::Settings::navigator().mMaxNavMeshQueryNodes; - result.mMaxPolys = ::Settings::navigator().mMaxPolygonsPerTile; - result.mMaxPolygonPathSize = ::Settings::navigator().mMaxPolygonPathSize; - result.mMaxSmoothPathSize = ::Settings::navigator().mMaxSmoothPathSize; + if (polysBits >= polysAndTilesBits) + throw std::invalid_argument("Too many polygons per tile: " + std::to_string(settings.mMaxPolys)); - return result; + const unsigned long tilesBits = polysAndTilesBits - polysBits; + + return NavMeshLimits{ + .mMaxTiles = static_cast(1 << tilesBits), + .mMaxPolys = static_cast(1 << polysBits), + }; + } + + RecastSettings makeRecastSettingsFromSettingsManager() + { + RecastSettings result; + + result.mBorderSize = ::Settings::navigator().mBorderSize; + result.mCellHeight = ::Settings::navigator().mCellHeight; + result.mCellSize = ::Settings::navigator().mCellSize; + result.mDetailSampleDist = ::Settings::navigator().mDetailSampleDist; + result.mDetailSampleMaxError = ::Settings::navigator().mDetailSampleMaxError; + result.mMaxClimb = Constants::sStepSizeUp; + result.mMaxSimplificationError = ::Settings::navigator().mMaxSimplificationError; + result.mMaxSlope = Constants::sMaxSlope; + result.mRecastScaleFactor = ::Settings::navigator().mRecastScaleFactor; + result.mSwimHeightScale = 0; + result.mMaxEdgeLen = ::Settings::navigator().mMaxEdgeLen; + result.mMaxVertsPerPoly = ::Settings::navigator().mMaxVertsPerPoly; + result.mRegionMergeArea = ::Settings::navigator().mRegionMergeArea; + result.mRegionMinArea = ::Settings::navigator().mRegionMinArea; + result.mTileSize = ::Settings::navigator().mTileSize; + + return result; + } + + DetourSettings makeDetourSettingsFromSettingsManager() + { + DetourSettings result; + + result.mMaxNavMeshQueryNodes = ::Settings::navigator().mMaxNavMeshQueryNodes; + result.mMaxPolys = ::Settings::navigator().mMaxPolygonsPerTile; + result.mMaxPolygonPathSize = ::Settings::navigator().mMaxPolygonPathSize; + result.mMaxSmoothPathSize = ::Settings::navigator().mMaxSmoothPathSize; + + return result; + } } Settings makeSettingsFromSettingsManager() @@ -46,7 +86,12 @@ namespace DetourNavigator result.mRecast = makeRecastSettingsFromSettingsManager(); result.mDetour = makeDetourSettingsFromSettingsManager(); - result.mMaxTilesNumber = ::Settings::navigator().mMaxTilesNumber; + + const NavMeshLimits limits = getNavMeshTileLimits(result.mDetour); + + result.mDetour.mMaxPolys = limits.mMaxPolys; + + result.mMaxTilesNumber = std::min(limits.mMaxTiles, ::Settings::navigator().mMaxTilesNumber.get()); result.mWaitUntilMinDistanceToPlayer = ::Settings::navigator().mWaitUntilMinDistanceToPlayer; result.mAsyncNavMeshUpdaterThreads = ::Settings::navigator().mAsyncNavMeshUpdaterThreads; result.mMaxNavMeshTilesCacheSize = ::Settings::navigator().mMaxNavMeshTilesCacheSize; diff --git a/components/detournavigator/settings.hpp b/components/detournavigator/settings.hpp index 45bcf15dbf..1d1f6f5847 100644 --- a/components/detournavigator/settings.hpp +++ b/components/detournavigator/settings.hpp @@ -55,10 +55,6 @@ namespace DetourNavigator inline constexpr std::int64_t navMeshFormatVersion = 2; - RecastSettings makeRecastSettingsFromSettingsManager(); - - DetourSettings makeDetourSettingsFromSettingsManager(); - Settings makeSettingsFromSettingsManager(); } diff --git a/components/detournavigator/stats.cpp b/components/detournavigator/stats.cpp index 1d7dcac553..da56f91a38 100644 --- a/components/detournavigator/stats.cpp +++ b/components/detournavigator/stats.cpp @@ -9,16 +9,18 @@ namespace DetourNavigator void reportStats(const AsyncNavMeshUpdaterStats& stats, unsigned int frameNumber, osg::Stats& out) { out.setAttribute(frameNumber, "NavMesh Jobs", static_cast(stats.mJobs)); - out.setAttribute(frameNumber, "NavMesh Waiting", static_cast(stats.mWaiting)); + out.setAttribute(frameNumber, "NavMesh Removing", static_cast(stats.mWaiting.mRemoving)); + out.setAttribute(frameNumber, "NavMesh Updating", static_cast(stats.mWaiting.mUpdating)); + out.setAttribute(frameNumber, "NavMesh Delayed", static_cast(stats.mWaiting.mDelayed)); out.setAttribute(frameNumber, "NavMesh Pushed", static_cast(stats.mPushed)); out.setAttribute(frameNumber, "NavMesh Processing", static_cast(stats.mProcessing)); if (stats.mDb.has_value()) { - out.setAttribute( - frameNumber, "NavMesh DbJobs Write", static_cast(stats.mDb->mJobs.mWritingJobs)); out.setAttribute( frameNumber, "NavMesh DbJobs Read", static_cast(stats.mDb->mJobs.mReadingJobs)); + out.setAttribute( + frameNumber, "NavMesh DbJobs Write", static_cast(stats.mDb->mJobs.mWritingJobs)); out.setAttribute(frameNumber, "NavMesh DbCache Get", static_cast(stats.mDb->mGetTileCount)); out.setAttribute(frameNumber, "NavMesh DbCache Hit", static_cast(stats.mDbGetTileHits)); diff --git a/components/detournavigator/stats.hpp b/components/detournavigator/stats.hpp index c644f1db87..0b62b9e669 100644 --- a/components/detournavigator/stats.hpp +++ b/components/detournavigator/stats.hpp @@ -11,10 +11,17 @@ namespace osg namespace DetourNavigator { + struct JobQueueStats + { + std::size_t mRemoving = 0; + std::size_t mUpdating = 0; + std::size_t mDelayed = 0; + }; + struct DbJobQueueStats { - std::size_t mWritingJobs = 0; std::size_t mReadingJobs = 0; + std::size_t mWritingJobs = 0; }; struct DbWorkerStats @@ -35,7 +42,7 @@ namespace DetourNavigator struct AsyncNavMeshUpdaterStats { std::size_t mJobs = 0; - std::size_t mWaiting = 0; + JobQueueStats mWaiting; std::size_t mPushed = 0; std::size_t mProcessing = 0; std::size_t mDbGetTileHits = 0; diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index 0bab808300..3e3927bf65 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -10,7 +10,6 @@ #include -#include #include #include @@ -416,7 +415,7 @@ namespace DetourNavigator if (tile == mChangedTiles.end()) mChangedTiles.emplace(tilePosition, changeType); else - tile->second = addChangeType(tile->second, changeType); + tile->second = changeType == ChangeType::remove ? changeType : tile->second; } std::map TileCachedRecastMeshManager::takeChangedTiles(const UpdateGuard* guard) diff --git a/components/esm/decompose.hpp b/components/esm/decompose.hpp index eb6f5070d4..f9fecec067 100644 --- a/components/esm/decompose.hpp +++ b/components/esm/decompose.hpp @@ -5,6 +5,13 @@ namespace ESM { template void decompose(T&& value, const auto& apply) = delete; + + std::size_t getCompositeSize(const auto& value) + { + std::size_t result = 0; + decompose(value, [&](const auto&... args) { result = (0 + ... + sizeof(args)); }); + return result; + } } #endif diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index cbc70582c0..52b2afb8de 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -4,12 +4,8 @@ #include #include -#include - -#include - -#include "components/esm/fourcc.hpp" #include +#include #include namespace ESM @@ -33,37 +29,6 @@ namespace ESM RT_Target = 2 }; - // Position and rotation - struct Position - { - float pos[3]{}; - - // In radians - float rot[3]{}; - - osg::Vec3f asVec3() const { return osg::Vec3f(pos[0], pos[1], pos[2]); } - - osg::Vec3f asRotationVec3() const { return osg::Vec3f(rot[0], rot[1], rot[2]); } - - friend inline bool operator<(const Position& l, const Position& r) - { - const auto tuple = [](const Position& v) { return std::tuple(v.asVec3(), v.asRotationVec3()); }; - return tuple(l) < tuple(r); - } - }; - - bool inline operator==(const Position& left, const Position& right) noexcept - { - return left.pos[0] == right.pos[0] && left.pos[1] == right.pos[1] && left.pos[2] == right.pos[2] - && left.rot[0] == right.rot[0] && left.rot[1] == right.rot[1] && left.rot[2] == right.rot[2]; - } - - bool inline operator!=(const Position& left, const Position& right) noexcept - { - return left.pos[0] != right.pos[0] || left.pos[1] != right.pos[1] || left.pos[2] != right.pos[2] - || left.rot[0] != right.rot[0] || left.rot[1] != right.rot[1] || left.rot[2] != right.rot[2]; - } - constexpr unsigned int sEsm4RecnameFlag = 0x00800000; constexpr unsigned int esm3Recname(const char (&name)[5]) diff --git a/components/esm/esm3exteriorcellrefid.hpp b/components/esm/esm3exteriorcellrefid.hpp index 5fca8dc597..fd6a9b128d 100644 --- a/components/esm/esm3exteriorcellrefid.hpp +++ b/components/esm/esm3exteriorcellrefid.hpp @@ -3,7 +3,7 @@ #include #include - +#include #include #include diff --git a/components/esm/format.cpp b/components/esm/format.cpp index aa869ab998..04edc5c7db 100644 --- a/components/esm/format.cpp +++ b/components/esm/format.cpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace ESM { diff --git a/components/esm/generatedrefid.hpp b/components/esm/generatedrefid.hpp index c5cd1bcef5..e9d07ff314 100644 --- a/components/esm/generatedrefid.hpp +++ b/components/esm/generatedrefid.hpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace ESM { diff --git a/components/esm/luascripts.cpp b/components/esm/luascripts.cpp index 8f2048d8a7..71e2ce6dc1 100644 --- a/components/esm/luascripts.cpp +++ b/components/esm/luascripts.cpp @@ -38,7 +38,7 @@ std::string ESM::loadLuaBinaryData(ESMReader& esm) { esm.getSubHeader(); data.resize(esm.getSubSize()); - esm.getExact(data.data(), static_cast(data.size())); + esm.getExact(data.data(), data.size()); } return data; } diff --git a/components/esm/position.hpp b/components/esm/position.hpp new file mode 100644 index 0000000000..d48997610e --- /dev/null +++ b/components/esm/position.hpp @@ -0,0 +1,47 @@ +#ifndef OPENMW_ESM3_POSITION_H +#define OPENMW_ESM3_POSITION_H + +#include +#include +#include + +namespace ESM +{ + // Position and rotation + struct Position + { + float pos[3]{}; + + // In radians + float rot[3]{}; + + osg::Vec3f asVec3() const { return osg::Vec3f(pos[0], pos[1], pos[2]); } + + osg::Vec3f asRotationVec3() const { return osg::Vec3f(rot[0], rot[1], rot[2]); } + + friend inline bool operator<(const Position& l, const Position& r) + { + const auto tuple = [](const Position& v) { return std::tuple(v.asVec3(), v.asRotationVec3()); }; + return tuple(l) < tuple(r); + } + }; + + bool inline operator==(const Position& left, const Position& right) noexcept + { + return left.pos[0] == right.pos[0] && left.pos[1] == right.pos[1] && left.pos[2] == right.pos[2] + && left.rot[0] == right.rot[0] && left.rot[1] == right.rot[1] && left.rot[2] == right.rot[2]; + } + + bool inline operator!=(const Position& left, const Position& right) noexcept + { + return left.pos[0] != right.pos[0] || left.pos[1] != right.pos[1] || left.pos[2] != right.pos[2] + || left.rot[0] != right.rot[0] || left.rot[1] != right.rot[1] || left.rot[2] != right.rot[2]; + } + + template T> + void decompose(T&& v, const auto& f) + { + f(v.pos, v.rot); + } +} +#endif \ No newline at end of file diff --git a/components/esm3/activespells.cpp b/components/esm3/activespells.cpp index 8ce47b7719..c3d86c3ebf 100644 --- a/components/esm3/activespells.cpp +++ b/components/esm3/activespells.cpp @@ -93,11 +93,12 @@ namespace ESM { for (const auto& params : spells) { - esm.writeHNRefId(tag, params.mId); + esm.writeHNRefId(tag, params.mSourceSpellId); + esm.writeHNRefId("SPID", params.mActiveSpellId); esm.writeHNT("CAST", params.mCasterActorId); esm.writeHNString("DISP", params.mDisplayName); - esm.writeHNT("TYPE", params.mType); + esm.writeHNT("FLAG", params.mFlags); if (params.mItem.isSet()) esm.writeFormId(params.mItem, true, "ITEM"); if (params.mWorsenings >= 0) @@ -130,14 +131,42 @@ namespace ESM while (esm.isNextSub(tag)) { ActiveSpells::ActiveSpellParams params; - params.mId = esm.getRefId(); + params.mSourceSpellId = esm.getRefId(); + if (format > MaxActiveSpellTypeVersion) + params.mActiveSpellId = esm.getHNRefId("SPID"); esm.getHNT(params.mCasterActorId, "CAST"); params.mDisplayName = esm.getHNString("DISP"); if (format <= MaxClearModifiersFormatVersion) - params.mType = ActiveSpells::Type_Temporary; + params.mFlags = Compatibility::ActiveSpells::Type_Temporary_Flags; else { - esm.getHNT(params.mType, "TYPE"); + if (format <= MaxActiveSpellTypeVersion) + { + Compatibility::ActiveSpells::EffectType type; + esm.getHNT(type, "TYPE"); + switch (type) + { + case Compatibility::ActiveSpells::Type_Ability: + params.mFlags = Compatibility::ActiveSpells::Type_Ability_Flags; + break; + case Compatibility::ActiveSpells::Type_Consumable: + params.mFlags = Compatibility::ActiveSpells::Type_Consumable_Flags; + break; + case Compatibility::ActiveSpells::Type_Enchantment: + params.mFlags = Compatibility::ActiveSpells::Type_Enchantment_Flags; + break; + case Compatibility::ActiveSpells::Type_Permanent: + params.mFlags = Compatibility::ActiveSpells::Type_Permanent_Flags; + break; + case Compatibility::ActiveSpells::Type_Temporary: + params.mFlags = Compatibility::ActiveSpells::Type_Temporary_Flags; + break; + } + } + else + { + esm.getHNT(params.mFlags, "FLAG"); + } if (esm.peekNextSub("ITEM")) { if (format <= MaxActiveSpellSlotIndexFormatVersion) diff --git a/components/esm3/activespells.hpp b/components/esm3/activespells.hpp index 0e4e01eda3..19e1f53be5 100644 --- a/components/esm3/activespells.hpp +++ b/components/esm3/activespells.hpp @@ -46,23 +46,27 @@ namespace ESM // format 0, saved games only struct ActiveSpells { - enum EffectType + enum Flags : uint32_t { - Type_Temporary, - Type_Ability, - Type_Enchantment, - Type_Permanent, - Type_Consumable + Flag_Temporary = 1 << 0, //!< Effect will end automatically once its duration ends. + Flag_Equipment = 1 << 1, //!< Effect will end automatically if item is unequipped. + Flag_SpellStore = 1 << 2, //!< Effect will end automatically if removed from the actor's spell store. + Flag_AffectsBaseValues = 1 << 3, //!< Effects will affect base values instead of current values. + Flag_Stackable + = 1 << 4, //!< Effect can stack. If this flag is not set, spells from the same caster and item cannot stack. + Flag_Lua + = 1 << 5, //!< Effect was added via Lua. Should not do any vfx/sound as this is handled by Lua scripts. }; struct ActiveSpellParams { - RefId mId; + RefId mActiveSpellId; + RefId mSourceSpellId; std::vector mEffects; std::string mDisplayName; int32_t mCasterActorId; RefNum mItem; - EffectType mType; + Flags mFlags; int32_t mWorsenings; TimeStamp mNextWorsening; }; @@ -73,6 +77,29 @@ namespace ESM void load(ESMReader& esm); void save(ESMWriter& esm) const; }; + + namespace Compatibility + { + namespace ActiveSpells + { + enum EffectType + { + Type_Temporary, + Type_Ability, + Type_Enchantment, + Type_Permanent, + Type_Consumable, + }; + + using Flags = ESM::ActiveSpells::Flags; + constexpr Flags Type_Temporary_Flags = Flags::Flag_Temporary; + constexpr Flags Type_Consumable_Flags = static_cast(Flags::Flag_Temporary | Flags::Flag_Stackable); + constexpr Flags Type_Permanent_Flags = Flags::Flag_SpellStore; + constexpr Flags Type_Ability_Flags + = static_cast(Flags::Flag_SpellStore | Flags::Flag_AffectsBaseValues); + constexpr Flags Type_Enchantment_Flags = Flags::Flag_Equipment; + } + } } #endif diff --git a/components/esm3/aipackage.cpp b/components/esm3/aipackage.cpp index 4d4c03c349..33b8a0bca2 100644 --- a/components/esm3/aipackage.cpp +++ b/components/esm3/aipackage.cpp @@ -5,9 +5,35 @@ namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mDistance, v.mDuration, v.mTimeOfDay, v.mIdle, v.mShouldRepeat); + } + + template T> + void decompose(T&& v, const auto& f) + { + char padding[3] = { 0, 0, 0 }; + f(v.mX, v.mY, v.mZ, v.mShouldRepeat, padding); + } + + template T> + void decompose(T&& v, const auto& f) + { + char padding = 0; + f(v.mX, v.mY, v.mZ, v.mDuration, v.mId.mData, v.mShouldRepeat, padding); + } + + template T> + void decompose(T&& v, const auto& f) + { + f(v.mName.mData, v.mShouldRepeat); + } + void AIData::blank() { - mHello = mFight = mFlee = mAlarm = mU1 = mU2 = mU3 = 0; + mHello = mFight = mFlee = mAlarm = 0; mServices = 0; } @@ -28,58 +54,53 @@ namespace ESM else if (esm.retSubName() == AI_Wander) { pack.mType = AI_Wander; - esm.getHExact(&pack.mWander, 14); + esm.getSubComposite(pack.mWander); mList.push_back(pack); } else if (esm.retSubName() == AI_Travel) { pack.mType = AI_Travel; - esm.getHExact(&pack.mTravel, 16); + esm.getSubComposite(pack.mTravel); mList.push_back(pack); } else if (esm.retSubName() == AI_Escort || esm.retSubName() == AI_Follow) { pack.mType = (esm.retSubName() == AI_Escort) ? AI_Escort : AI_Follow; - esm.getHExact(&pack.mTarget, 48); + esm.getSubComposite(pack.mTarget); mList.push_back(pack); } else if (esm.retSubName() == AI_Activate) { pack.mType = AI_Activate; - esm.getHExact(&pack.mActivate, 33); + esm.getSubComposite(pack.mActivate); mList.push_back(pack); } - else - { // not AI package related data, so leave - return; - } } void AIPackageList::save(ESMWriter& esm) const { - typedef std::vector::const_iterator PackageIter; - for (PackageIter it = mList.begin(); it != mList.end(); ++it) + for (const AIPackage& package : mList) { - switch (it->mType) + switch (package.mType) { case AI_Wander: - esm.writeHNT("AI_W", it->mWander, sizeof(it->mWander)); + esm.writeNamedComposite("AI_W", package.mWander); break; case AI_Travel: - esm.writeHNT("AI_T", it->mTravel, sizeof(it->mTravel)); + esm.writeNamedComposite("AI_T", package.mTravel); break; case AI_Activate: - esm.writeHNT("AI_A", it->mActivate, sizeof(it->mActivate)); + esm.writeNamedComposite("AI_A", package.mActivate); break; case AI_Escort: case AI_Follow: { - const NAME name = (it->mType == AI_Escort) ? NAME("AI_E") : NAME("AI_F"); - esm.writeHNT(name, it->mTarget, sizeof(it->mTarget)); - esm.writeHNOCString("CNDT", it->mCellName); + const NAME name = (package.mType == AI_Escort) ? NAME("AI_E") : NAME("AI_F"); + esm.writeNamedComposite(name, package.mTarget); + esm.writeHNOCString("CNDT", package.mCellName); break; } diff --git a/components/esm3/aipackage.hpp b/components/esm3/aipackage.hpp index 7346a4af36..10e7be8f00 100644 --- a/components/esm3/aipackage.hpp +++ b/components/esm3/aipackage.hpp @@ -5,20 +5,17 @@ #include #include "components/esm/esmcommon.hpp" +#include "components/misc/concepts.hpp" namespace ESM { class ESMReader; class ESMWriter; -#pragma pack(push) -#pragma pack(1) - struct AIData { uint16_t mHello; // This is the base value for greeting distance [0, 65535] unsigned char mFight, mFlee, mAlarm; // These are probabilities [0, 100] - char mU1, mU2, mU3; // Unknown values int32_t mServices; // See the Services enum void blank(); @@ -38,7 +35,6 @@ namespace ESM { float mX, mY, mZ; unsigned char mShouldRepeat; - unsigned char mPadding[3]; }; struct AITarget @@ -47,7 +43,6 @@ namespace ESM int16_t mDuration; NAME32 mId; unsigned char mShouldRepeat; - unsigned char mPadding; }; struct AIActivate @@ -56,8 +51,6 @@ namespace ESM unsigned char mShouldRepeat; }; -#pragma pack(pop) - enum AiPackageType : std::uint32_t { AI_Wander = 0x575f4941, @@ -98,6 +91,13 @@ namespace ESM void save(ESMWriter& esm) const; }; + + template T> + void decompose(T&& v, const auto& f) + { + char padding[3] = { 0, 0, 0 }; + f(v.mHello, v.mFight, v.mFlee, v.mAlarm, padding, v.mServices); + } } #endif diff --git a/components/esm3/cellid.cpp b/components/esm3/cellid.cpp index 9a5be3aada..4d08691034 100644 --- a/components/esm3/cellid.cpp +++ b/components/esm3/cellid.cpp @@ -3,9 +3,15 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include +#include namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mX, v.mY); + } void CellId::load(ESMReader& esm) { @@ -13,7 +19,7 @@ namespace ESM mIndex.mX = 0; mIndex.mY = 0; - mPaged = esm.getHNOT("CIDX", mIndex.mX, mIndex.mY); + mPaged = esm.getOptionalComposite("CIDX", mIndex); } void CellId::save(ESMWriter& esm) const @@ -21,7 +27,7 @@ namespace ESM esm.writeHNString("SPAC", mWorldspace); if (mPaged) - esm.writeHNT("CIDX", mIndex, 8); + esm.writeNamedComposite("CIDX", mIndex); } struct VisitCellRefId diff --git a/components/esm3/cellref.cpp b/components/esm3/cellref.cpp index 93a2ece669..97ccfb730a 100644 --- a/components/esm3/cellref.cpp +++ b/components/esm3/cellref.cpp @@ -112,7 +112,7 @@ namespace ESM case fourCC("DODT"): if constexpr (load) { - esm.getHT(cellRef.mDoorDest.pos, cellRef.mDoorDest.rot); + esm.getSubComposite(cellRef.mDoorDest); cellRef.mTeleport = true; } else @@ -132,7 +132,7 @@ namespace ESM break; case fourCC("DATA"): if constexpr (load) - esm.getHT(cellRef.mPos.pos, cellRef.mPos.rot); + esm.getSubComposite(cellRef.mPos); else esm.skipHSub(); break; @@ -224,18 +224,20 @@ namespace ESM if (!inInventory && mTeleport) { - esm.writeHNT("DODT", mDoorDest); + esm.writeNamedComposite("DODT", mDoorDest); esm.writeHNOCString("DNAM", mDestCell); } if (!inInventory) { - int lockLevel = mLockLevel; - if (lockLevel == 0 && mIsLocked) - lockLevel = ZeroLock; - if (lockLevel != 0) + if (mIsLocked) + { + int lockLevel = mLockLevel; + if (lockLevel == 0) + lockLevel = ZeroLock; esm.writeHNT("FLTV", lockLevel); - esm.writeHNOCRefId("KNAM", mKey); + esm.writeHNOCRefId("KNAM", mKey); + } esm.writeHNOCRefId("TNAM", mTrap); } @@ -243,7 +245,7 @@ namespace ESM esm.writeHNT("UNAM", mReferenceBlocked); if (!inInventory) - esm.writeHNT("DATA", mPos, 24); + esm.writeNamedComposite("DATA", mPos); } void CellRef::blank() diff --git a/components/esm3/cellref.hpp b/components/esm3/cellref.hpp index 84b6ae1d18..5079095889 100644 --- a/components/esm3/cellref.hpp +++ b/components/esm3/cellref.hpp @@ -4,8 +4,9 @@ #include #include -#include "components/esm/defs.hpp" -#include "components/esm/refid.hpp" +#include +#include +#include namespace ESM { diff --git a/components/esm3/dialoguecondition.cpp b/components/esm3/dialoguecondition.cpp new file mode 100644 index 0000000000..a6a28307c2 --- /dev/null +++ b/components/esm3/dialoguecondition.cpp @@ -0,0 +1,206 @@ +#include "dialoguecondition.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" +#include "variant.hpp" + +#include +#include +#include + +namespace ESM +{ + std::optional DialogueCondition::load(ESMReader& esm, ESM::RefId context) + { + std::string rule = esm.getHString(); + ESM::Variant variant; + variant.read(esm, Variant::Format_Info); + if (rule.size() < 5) + { + Log(Debug::Warning) << "Found invalid SCVR rule of size " << rule.size() << " in INFO " << context; + return {}; + } + if (rule[4] < '0' || rule[4] > '5') + { + Log(Debug::Warning) << "Found invalid SCVR comparison operator " << static_cast(rule[4]) << " in INFO " + << context; + return {}; + } + DialogueCondition condition; + if (rule[0] >= '0' && rule[0] <= '9') + condition.mIndex = rule[0] - '0'; + else + { + Log(Debug::Info) << "Found invalid SCVR index " << static_cast(rule[0]) << " in INFO " << context; + condition.mIndex = 0; + } + if (rule[1] == '1') + { + int function = Misc::StringUtils::toNumeric(std::string_view{ rule }.substr(2, 2), -1); + if (function >= Function_FacReactionLowest && function <= Function_PcWerewolfKills) + condition.mFunction = static_cast(function); + else + { + Log(Debug::Warning) << "Encountered invalid SCVR function index " << function << " in INFO " << context; + return {}; + } + } + else if ((rule[1] > '1' && rule[1] <= '9') || (rule[1] >= 'A' && rule[1] <= 'C')) + { + if (rule.size() == 5) + { + Log(Debug::Warning) << "Missing variable for SCVR of type " << rule[1] << " in INFO " << context; + return {}; + } + bool malformed = rule[3] != 'X'; + if (rule[1] == '2') + { + condition.mFunction = Function_Global; + malformed |= rule[2] != 'f' && rule[2] != 'l' && rule[2] != 's'; + } + else if (rule[1] == '3') + { + condition.mFunction = Function_Local; + malformed |= rule[2] != 'f' && rule[2] != 'l' && rule[2] != 's'; + } + else if (rule[1] == '4') + { + condition.mFunction = Function_Journal; + malformed |= rule[2] != 'J'; + } + else if (rule[1] == '5') + { + condition.mFunction = Function_Item; + malformed |= rule[2] != 'I'; + } + else if (rule[1] == '6') + { + condition.mFunction = Function_Dead; + malformed |= rule[2] != 'D'; + } + else if (rule[1] == '7') + { + condition.mFunction = Function_NotId; + malformed |= rule[2] != 'X'; + } + else if (rule[1] == '8') + { + condition.mFunction = Function_NotFaction; + malformed |= rule[2] != 'F'; + } + else if (rule[1] == '9') + { + condition.mFunction = Function_NotClass; + malformed |= rule[2] != 'C'; + } + else if (rule[1] == 'A') + { + condition.mFunction = Function_NotRace; + malformed |= rule[2] != 'R'; + } + else if (rule[1] == 'B') + { + condition.mFunction = Function_NotCell; + malformed |= rule[2] != 'L'; + } + else if (rule[1] == 'C') + { + condition.mFunction = Function_NotLocal; + malformed |= rule[2] != 'f' && rule[2] != 'l' && rule[2] != 's'; + } + if (malformed) + Log(Debug::Info) << "Found malformed SCVR rule in INFO " << context; + } + else + { + Log(Debug::Warning) << "Found invalid SCVR function " << static_cast(rule[1]) << " in INFO " + << context; + return {}; + } + condition.mComparison = static_cast(rule[4]); + condition.mVariable = rule.substr(5); + if (variant.getType() == VT_Int) + condition.mValue = variant.getInteger(); + else if (variant.getType() == VT_Float) + condition.mValue = variant.getFloat(); + else + { + Log(Debug::Warning) << "Found invalid SCVR variant " << variant.getType() << " in INFO " << context; + return {}; + } + return condition; + } + + void DialogueCondition::save(ESMWriter& esm) const + { + auto variant = std::visit([](auto value) { return ESM::Variant(value); }, mValue); + if (variant.getType() != VT_Float) + variant.setType(VT_Int); + std::string rule; + rule.reserve(5 + mVariable.size()); + rule += static_cast(mIndex + '0'); + const auto appendVariableType = [&]() { + if (variant.getType() == VT_Float) + rule += "fX"; + else + { + int32_t value = variant.getInteger(); + if (static_cast(value) == value) + rule += "sX"; + else + rule += "lX"; + } + }; + if (mFunction == Function_Global) + { + rule += '2'; + appendVariableType(); + } + else if (mFunction == Function_Local) + { + rule += '3'; + appendVariableType(); + } + else if (mFunction == Function_Journal) + rule += "4JX"; + else if (mFunction == Function_Item) + rule += "5IX"; + else if (mFunction == Function_Dead) + rule += "6DX"; + else if (mFunction == Function_NotId) + rule += "7XX"; + else if (mFunction == Function_NotFaction) + rule += "8FX"; + else if (mFunction == Function_NotClass) + rule += "9CX"; + else if (mFunction == Function_NotRace) + rule += "ARX"; + else if (mFunction == Function_NotCell) + rule += "BLX"; + else if (mFunction == Function_NotLocal) + { + rule += 'C'; + appendVariableType(); + } + else + { + rule += "100"; + char* start = rule.data() + rule.size(); + char* end = start; + if (mFunction < Function_PcStrength) + start--; + else + start -= 2; + auto result = std::to_chars(start, end, static_cast(mFunction)); + if (result.ec != std::errc()) + { + Log(Debug::Error) << "Failed to save SCVR rule"; + return; + } + } + rule += static_cast(mComparison); + rule += mVariable; + esm.writeHNString("SCVR", rule); + variant.write(esm, Variant::Format_Info); + } +} diff --git a/components/esm3/dialoguecondition.hpp b/components/esm3/dialoguecondition.hpp new file mode 100644 index 0000000000..c06d50b601 --- /dev/null +++ b/components/esm3/dialoguecondition.hpp @@ -0,0 +1,134 @@ +#ifndef OPENMW_ESM3_DIALOGUECONDITION_H +#define OPENMW_ESM3_DIALOGUECONDITION_H + +#include +#include +#include +#include + +#include + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + struct DialogueCondition + { + enum Function : std::int8_t + { + Function_FacReactionLowest = 0, + Function_FacReactionHighest, + Function_RankRequirement, + Function_Reputation, + Function_Health_Percent, + Function_PcReputation, + Function_PcLevel, + Function_PcHealthPercent, + Function_PcMagicka, + Function_PcFatigue, + Function_PcStrength, + Function_PcBlock, + Function_PcArmorer, + Function_PcMediumArmor, + Function_PcHeavyArmor, + Function_PcBluntWeapon, + Function_PcLongBlade, + Function_PcAxe, + Function_PcSpear, + Function_PcAthletics, + Function_PcEnchant, + Function_PcDestruction, + Function_PcAlteration, + Function_PcIllusion, + Function_PcConjuration, + Function_PcMysticism, + Function_PcRestoration, + Function_PcAlchemy, + Function_PcUnarmored, + Function_PcSecurity, + Function_PcSneak, + Function_PcAcrobatics, + Function_PcLightArmor, + Function_PcShortBlade, + Function_PcMarksman, + Function_PcMerchantile, + Function_PcSpeechcraft, + Function_PcHandToHand, + Function_PcGender, + Function_PcExpelled, + Function_PcCommonDisease, + Function_PcBlightDisease, + Function_PcClothingModifier, + Function_PcCrimeLevel, + Function_SameSex, + Function_SameRace, + Function_SameFaction, + Function_FactionRankDifference, + Function_Detected, + Function_Alarmed, + Function_Choice, + Function_PcIntelligence, + Function_PcWillpower, + Function_PcAgility, + Function_PcSpeed, + Function_PcEndurance, + Function_PcPersonality, + Function_PcLuck, + Function_PcCorprus, + Function_Weather, + Function_PcVampire, + Function_Level, + Function_Attacked, + Function_TalkedToPc, + Function_PcHealth, + Function_CreatureTarget, + Function_FriendHit, + Function_Fight, + Function_Hello, + Function_Alarm, + Function_Flee, + Function_ShouldAttack, + Function_Werewolf, + Function_PcWerewolfKills = 73, + + Function_Global, + Function_Local, + Function_Journal, + Function_Item, + Function_Dead, + Function_NotId, + Function_NotFaction, + Function_NotClass, + Function_NotRace, + Function_NotCell, + Function_NotLocal, + + Function_None, // Editor only + }; + + enum Comparison : char + { + Comp_Eq = '0', + Comp_Ne = '1', + Comp_Gt = '2', + Comp_Ge = '3', + Comp_Ls = '4', + Comp_Le = '5', + + Comp_None = ' ', // Editor only + }; + + std::string mVariable; + std::variant mValue = 0; + std::uint8_t mIndex = 0; + Function mFunction = Function_None; + Comparison mComparison = Comp_None; + + static std::optional load(ESMReader& esm, ESM::RefId context); + + void save(ESMWriter& esm) const; + }; +} + +#endif diff --git a/components/esm3/effectlist.cpp b/components/esm3/effectlist.cpp index 701552b312..a71eccfb84 100644 --- a/components/esm3/effectlist.cpp +++ b/components/esm3/effectlist.cpp @@ -3,8 +3,15 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mEffectID, v.mSkill, v.mAttribute, v.mRange, v.mArea, v.mDuration, v.mMagnMin, v.mMagnMax); + } void EffectList::load(ESMReader& esm) { @@ -15,19 +22,40 @@ namespace ESM } } + void EffectList::populate(const std::vector& effects) + { + mList.clear(); + for (size_t i = 0; i < effects.size(); i++) + mList.push_back({ effects[i], static_cast(i) }); + } + + void EffectList::updateIndexes() + { + for (size_t i = 0; i < mList.size(); i++) + mList[i].mIndex = i; + } + void EffectList::add(ESMReader& esm) { ENAMstruct s; - esm.getHT(s.mEffectID, s.mSkill, s.mAttribute, s.mRange, s.mArea, s.mDuration, s.mMagnMin, s.mMagnMax); - mList.push_back(s); + esm.getSubComposite(s); + mList.push_back({ s, static_cast(mList.size()) }); } void EffectList::save(ESMWriter& esm) const { - for (std::vector::const_iterator it = mList.begin(); it != mList.end(); ++it) + for (const IndexedENAMstruct& enam : mList) { - esm.writeHNT("ENAM", *it, 24); + esm.writeNamedComposite("ENAM", enam.mData); } } + bool IndexedENAMstruct::operator!=(const IndexedENAMstruct& rhs) const + { + return mData.mEffectID != rhs.mData.mEffectID || mData.mArea != rhs.mData.mArea + || mData.mRange != rhs.mData.mRange || mData.mSkill != rhs.mData.mSkill + || mData.mAttribute != rhs.mData.mAttribute || mData.mMagnMin != rhs.mData.mMagnMin + || mData.mMagnMax != rhs.mData.mMagnMax || mData.mDuration != rhs.mData.mDuration; + } + } // end namespace diff --git a/components/esm3/effectlist.hpp b/components/esm3/effectlist.hpp index 8f2cb959d6..8eb347d6c8 100644 --- a/components/esm3/effectlist.hpp +++ b/components/esm3/effectlist.hpp @@ -9,9 +9,6 @@ namespace ESM class ESMReader; class ESMWriter; -#pragma pack(push) -#pragma pack(1) - /** Defines a spell effect. Shared between SPEL (Spells), ALCH (Potions) and ENCH (Item enchantments) records */ @@ -28,12 +25,22 @@ namespace ESM int32_t mRange; // 0 - self, 1 - touch, 2 - target (RangeType enum) int32_t mArea, mDuration, mMagnMin, mMagnMax; }; -#pragma pack(pop) + + struct IndexedENAMstruct + { + bool operator!=(const IndexedENAMstruct& rhs) const; + bool operator==(const IndexedENAMstruct& rhs) const { return !(this->operator!=(rhs)); } + ENAMstruct mData; + uint32_t mIndex; + }; /// EffectList, ENAM subrecord struct EffectList { - std::vector mList; + std::vector mList; + + void populate(const std::vector& effects); + void updateIndexes(); /// Load one effect, assumes subrecord name was already read void add(ESMReader& esm); diff --git a/components/esm3/esmreader.cpp b/components/esm3/esmreader.cpp index 92a04fb487..4f69b8edef 100644 --- a/components/esm3/esmreader.cpp +++ b/components/esm3/esmreader.cpp @@ -153,7 +153,7 @@ namespace ESM { if (isNextSub(name)) return getHString(); - return ""; + return {}; } ESM::RefId ESMReader::getHNORefId(NAME name) @@ -244,21 +244,6 @@ namespace ESM skipHString(); } - void ESMReader::getHExact(void* p, int size) - { - getSubHeader(); - if (size != static_cast(mCtx.leftSub)) - reportSubSizeMismatch(size, mCtx.leftSub); - getExact(p, size); - } - - // Read the given number of bytes from a named subrecord - void ESMReader::getHNExact(void* p, int size, NAME name) - { - getSubNameIs(name); - getHExact(p, size); - } - FormId ESMReader::getFormId(bool wide, NAME tag) { FormId res; @@ -316,7 +301,7 @@ namespace ESM // reading the subrecord data anyway. const std::size_t subNameSize = decltype(mCtx.subName)::sCapacity; - getExact(mCtx.subName.mData, static_cast(subNameSize)); + getExact(mCtx.subName.mData, subNameSize); mCtx.leftRec -= static_cast(subNameSize); } @@ -326,10 +311,10 @@ namespace ESM skip(mCtx.leftSub); } - void ESMReader::skipHSubSize(int size) + void ESMReader::skipHSubSize(std::size_t size) { skipHSub(); - if (static_cast(mCtx.leftSub) != size) + if (mCtx.leftSub != size) reportSubSizeMismatch(mCtx.leftSub, size); } @@ -506,7 +491,7 @@ namespace ESM case RefIdType::Generated: { std::uint64_t generated{}; - getExact(&generated, sizeof(std::uint64_t)); + getT(generated); return RefId::generated(generated); } case RefIdType::Index: @@ -514,14 +499,14 @@ namespace ESM RecNameInts recordType{}; getExact(&recordType, sizeof(std::uint32_t)); std::uint32_t index{}; - getExact(&index, sizeof(std::uint32_t)); + getT(index); return RefId::index(recordType, index); } case RefIdType::ESM3ExteriorCell: { int32_t x, y; - getExact(&x, sizeof(std::int32_t)); - getExact(&y, sizeof(std::int32_t)); + getT(x); + getT(y); return RefId::esm3ExteriorCell(x, y); } } @@ -529,7 +514,7 @@ namespace ESM fail("Unsupported RefIdType: " + std::to_string(static_cast(refIdType))); } - [[noreturn]] void ESMReader::fail(const std::string& msg) + [[noreturn]] void ESMReader::fail(std::string_view msg) { std::stringstream ss; diff --git a/components/esm3/esmreader.hpp b/components/esm3/esmreader.hpp index 276adf749c..5af5e75573 100644 --- a/components/esm3/esmreader.hpp +++ b/components/esm3/esmreader.hpp @@ -184,11 +184,26 @@ namespace ESM decompose(value, [&](auto&... args) { getHNT(name, args...); }); } + bool getOptionalComposite(NAME name, auto& value) + { + if (isNextSub(name)) + { + getSubComposite(value); + return true; + } + return false; + } + void getComposite(auto& value) { decompose(value, [&](auto&... args) { (getT(args), ...); }); } + void getSubComposite(auto& value) + { + decompose(value, [&](auto&... args) { getHT(args...); }); + } + template >> void skipHT() { @@ -222,12 +237,6 @@ namespace ESM void skipHRefId(); - // Read the given number of bytes from a subrecord - void getHExact(void* p, int size); - - // Read the given number of bytes from a named subrecord - void getHNExact(void* p, int size, NAME name); - ESM::FormId getFormId(bool wide = false, NAME tag = "FRMR"); /************************************************************************* @@ -260,7 +269,7 @@ namespace ESM void skipHSub(); // Skip sub record and check its size - void skipHSubSize(int size); + void skipHSubSize(std::size_t size); // Skip all subrecords until the given subrecord or no more subrecords remaining void skipHSubUntil(NAME name); @@ -339,7 +348,7 @@ namespace ESM } /// Used for error handling - [[noreturn]] void fail(const std::string& msg); + [[noreturn]] void fail(std::string_view msg); /// Sets font encoder for ESM strings void setEncoder(ToUTF8::Utf8Encoder* encoder) { mEncoder = encoder; } diff --git a/components/esm3/esmwriter.cpp b/components/esm3/esmwriter.cpp index ad64ced0a4..47c861e3ca 100644 --- a/components/esm3/esmwriter.cpp +++ b/components/esm3/esmwriter.cpp @@ -97,14 +97,14 @@ namespace ESM mHeader.mData.type = type; } - void ESMWriter::setAuthor(const std::string& auth) + void ESMWriter::setAuthor(std::string_view auth) { - mHeader.mData.author.assign(auth); + mHeader.mData.author = auth; } - void ESMWriter::setDescription(const std::string& desc) + void ESMWriter::setDescription(std::string_view desc) { - mHeader.mData.desc.assign(desc); + mHeader.mData.desc = desc; } void ESMWriter::setRecordCount(int count) @@ -122,7 +122,7 @@ namespace ESM mHeader.mMaster.clear(); } - void ESMWriter::addMaster(const std::string& name, uint64_t size) + void ESMWriter::addMaster(std::string_view name, uint64_t size) { Header::MasterData d; d.name = name; @@ -208,14 +208,14 @@ namespace ESM endRecord(NAME(name)); } - void ESMWriter::writeHNString(NAME name, const std::string& data) + void ESMWriter::writeHNString(NAME name, std::string_view data) { startSubRecord(name); writeHString(data); endRecord(name); } - void ESMWriter::writeHNString(NAME name, const std::string& data, size_t size) + void ESMWriter::writeHNString(NAME name, std::string_view data, size_t size) { assert(data.size() <= size); startSubRecord(name); @@ -278,9 +278,9 @@ namespace ESM write(string.c_str(), string.size()); } - void ESMWriter::writeHString(const std::string& data) + void ESMWriter::writeHString(std::string_view data) { - if (data.size() == 0) + if (data.empty()) write("\0", 1); else { @@ -291,7 +291,7 @@ namespace ESM } } - void ESMWriter::writeHCString(const std::string& data) + void ESMWriter::writeHCString(std::string_view data) { writeHString(data); if (data.size() > 0 && data[data.size() - 1] != '\0') diff --git a/components/esm3/esmwriter.hpp b/components/esm3/esmwriter.hpp index 101246fe43..96445bcdae 100644 --- a/components/esm3/esmwriter.hpp +++ b/components/esm3/esmwriter.hpp @@ -38,8 +38,8 @@ namespace ESM void setVersion(unsigned int ver = 0x3fa66666); void setType(int type); void setEncoder(ToUTF8::Utf8Encoder* encoding); - void setAuthor(const std::string& author); - void setDescription(const std::string& desc); + void setAuthor(std::string_view author); + void setDescription(std::string_view desc); void setHeader(const Header& value) { mHeader = value; } // Set the record count for writing it in the file header @@ -54,7 +54,7 @@ namespace ESM void clearMaster(); - void addMaster(const std::string& name, uint64_t size); + void addMaster(std::string_view name, uint64_t size); void save(std::ostream& file); ///< Start saving a file by writing the TES3 header. @@ -62,20 +62,20 @@ namespace ESM void close(); ///< \note Does not close the stream. - void writeHNString(NAME name, const std::string& data); - void writeHNString(NAME name, const std::string& data, size_t size); - void writeHNCString(NAME name, const std::string& data) + void writeHNString(NAME name, std::string_view data); + void writeHNString(NAME name, std::string_view data, size_t size); + void writeHNCString(NAME name, std::string_view data) { startSubRecord(name); writeHCString(data); endRecord(name); } - void writeHNOString(NAME name, const std::string& data) + void writeHNOString(NAME name, std::string_view data) { if (!data.empty()) writeHNString(name, data); } - void writeHNOCString(NAME name, const std::string& data) + void writeHNOCString(NAME name, std::string_view data) { if (!data.empty()) writeHNCString(name, data); @@ -140,6 +140,7 @@ namespace ESM // state being discarded without any error on writing or reading it. :( // writeHNString and friends must be used instead. void writeHNT(NAME name, const std::string& data) = delete; + void writeHNT(NAME name, std::string_view data) = delete; void writeT(NAME data) = delete; @@ -181,8 +182,8 @@ namespace ESM void endRecord(NAME name); void endRecord(uint32_t name); void writeMaybeFixedSizeString(const std::string& data, std::size_t size); - void writeHString(const std::string& data); - void writeHCString(const std::string& data); + void writeHString(std::string_view data); + void writeHCString(std::string_view data); void writeMaybeFixedSizeRefId(RefId value, std::size_t size); diff --git a/components/esm3/formatversion.hpp b/components/esm3/formatversion.hpp index 9f499a7231..36e43728e2 100644 --- a/components/esm3/formatversion.hpp +++ b/components/esm3/formatversion.hpp @@ -9,6 +9,7 @@ namespace ESM inline constexpr FormatVersion DefaultFormatVersion = 0; inline constexpr FormatVersion CurrentContentFormatVersion = 1; + inline constexpr FormatVersion MaxOldGoldValueFormatVersion = 5; inline constexpr FormatVersion MaxOldFogOfWarFormatVersion = 6; inline constexpr FormatVersion MaxUnoptimizedCharacterDataFormatVersion = 7; inline constexpr FormatVersion MaxOldTimeLeftFormatVersion = 8; @@ -25,7 +26,8 @@ namespace ESM inline constexpr FormatVersion MaxUseEsmCellIdFormatVersion = 26; inline constexpr FormatVersion MaxActiveSpellSlotIndexFormatVersion = 27; inline constexpr FormatVersion MaxOldCountFormatVersion = 30; - inline constexpr FormatVersion CurrentSaveGameFormatVersion = 31; + inline constexpr FormatVersion MaxActiveSpellTypeVersion = 31; + inline constexpr FormatVersion CurrentSaveGameFormatVersion = 32; inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 5; inline constexpr FormatVersion OpenMW0_48SaveGameFormatVersion = 21; diff --git a/components/esm3/landrecorddata.hpp b/components/esm3/landrecorddata.hpp index e7db0d9f3a..ca2a2b74ad 100644 --- a/components/esm3/landrecorddata.hpp +++ b/components/esm3/landrecorddata.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_COMPONENTS_ESM3_LANDRECORDDATA_H #define OPENMW_COMPONENTS_ESM3_LANDRECORDDATA_H +#include #include namespace ESM @@ -22,24 +23,20 @@ namespace ESM // Initial reference height for the first vertex, only needed for filling mHeights float mHeightOffset = 0; // Height in world space for each vertex - float mHeights[sLandNumVerts]; + std::array mHeights; float mMinHeight = 0; float mMaxHeight = 0; // 24-bit normals, these aren't always correct though. Edge and corner normals may be garbage. - std::int8_t mNormals[sLandNumVerts * 3]; + std::array mNormals; // 2D array of texture indices. An index can be used to look up an LandTexture, // but to do so you must subtract 1 from the index first! // An index of 0 indicates the default texture. - std::uint16_t mTextures[sLandNumTextures]; + std::array mTextures; // 24-bit RGB color for each vertex - std::uint8_t mColours[3 * sLandNumVerts]; - - // ??? - std::uint16_t mUnk1 = 0; - std::uint8_t mUnk2 = 0; + std::array mColours; int mDataLoaded = 0; }; diff --git a/components/esm3/loadalch.cpp b/components/esm3/loadalch.cpp index 2b01dd9b09..4e6c2ad1e2 100644 --- a/components/esm3/loadalch.cpp +++ b/components/esm3/loadalch.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mWeight, v.mValue, v.mFlags); + } + void Potion::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -36,7 +44,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("ALDT"): - esm.getHT(mData.mWeight, mData.mValue, mData.mAutoCalc); + esm.getSubComposite(mData); hasData = true; break; case fourCC("ENAM"): @@ -71,7 +79,7 @@ namespace ESM esm.writeHNOCString("TEXT", mIcon); esm.writeHNOCRefId("SCRI", mScript); esm.writeHNOCString("FNAM", mName); - esm.writeHNT("ALDT", mData, 12); + esm.writeNamedComposite("ALDT", mData); mEffects.save(esm); } @@ -80,7 +88,7 @@ namespace ESM mRecordFlags = 0; mData.mWeight = 0; mData.mValue = 0; - mData.mAutoCalc = 0; + mData.mFlags = 0; mName.clear(); mModel.clear(); mIcon.clear(); diff --git a/components/esm3/loadalch.hpp b/components/esm3/loadalch.hpp index ddecd7e3c7..814d21937b 100644 --- a/components/esm3/loadalch.hpp +++ b/components/esm3/loadalch.hpp @@ -25,11 +25,16 @@ namespace ESM /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Potion"; } + enum Flags + { + Autocalc = 1 // Determines value + }; + struct ALDTstruct { float mWeight; int32_t mValue; - int32_t mAutoCalc; + int32_t mFlags; }; ALDTstruct mData; diff --git a/components/esm3/loadappa.cpp b/components/esm3/loadappa.cpp index ecc00222b8..40d9fc3f72 100644 --- a/components/esm3/loadappa.cpp +++ b/components/esm3/loadappa.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mType, v.mQuality, v.mWeight, v.mValue); + } + void Apparatus::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -28,7 +36,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("AADT"): - esm.getHT(mData.mType, mData.mQuality, mData.mWeight, mData.mValue); + esm.getSubComposite(mData); hasData = true; break; case fourCC("SCRI"): @@ -65,7 +73,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNCString("FNAM", mName); - esm.writeHNT("AADT", mData, 16); + esm.writeNamedComposite("AADT", mData); esm.writeHNOCRefId("SCRI", mScript); esm.writeHNCString("ITEX", mIcon); } diff --git a/components/esm3/loadarmo.cpp b/components/esm3/loadarmo.cpp index 1832014173..37290ae39a 100644 --- a/components/esm3/loadarmo.cpp +++ b/components/esm3/loadarmo.cpp @@ -3,8 +3,15 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mType, v.mWeight, v.mValue, v.mHealth, v.mEnchant, v.mArmor); + } void PartReferenceList::add(ESMReader& esm) { @@ -59,7 +66,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("AODT"): - esm.getHT(mData.mType, mData.mWeight, mData.mValue, mData.mHealth, mData.mEnchant, mData.mArmor); + esm.getSubComposite(mData); hasData = true; break; case fourCC("SCRI"): @@ -103,7 +110,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNOCRefId("SCRI", mScript); - esm.writeHNT("AODT", mData, 24); + esm.writeNamedComposite("AODT", mData); esm.writeHNOCString("ITEX", mIcon); mParts.save(esm); esm.writeHNOCRefId("ENAM", mEnchant); diff --git a/components/esm3/loadbody.cpp b/components/esm3/loadbody.cpp index 066e5ec949..8c944c6da0 100644 --- a/components/esm3/loadbody.cpp +++ b/components/esm3/loadbody.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mPart, v.mVampire, v.mFlags, v.mType); + } + void BodyPart::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -28,7 +36,7 @@ namespace ESM mRace = esm.getRefId(); break; case fourCC("BYDT"): - esm.getHT(mData.mPart, mData.mVampire, mData.mFlags, mData.mType); + esm.getSubComposite(mData); hasData = true; break; case SREC_DELE: @@ -59,7 +67,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCRefId("FNAM", mRace); - esm.writeHNT("BYDT", mData, 4); + esm.writeNamedComposite("BYDT", mData); } void BodyPart::blank() diff --git a/components/esm3/loadbook.cpp b/components/esm3/loadbook.cpp index 8083c59828..bece59d31b 100644 --- a/components/esm3/loadbook.cpp +++ b/components/esm3/loadbook.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mWeight, v.mValue, v.mIsScroll, v.mSkillId, v.mEnchant); + } + void Book::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -28,7 +36,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("BKDT"): - esm.getHT(mData.mWeight, mData.mValue, mData.mIsScroll, mData.mSkillId, mData.mEnchant); + esm.getSubComposite(mData); hasData = true; break; case fourCC("SCRI"): @@ -70,7 +78,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); - esm.writeHNT("BKDT", mData, 20); + esm.writeNamedComposite("BKDT", mData); esm.writeHNOCRefId("SCRI", mScript); esm.writeHNOCString("ITEX", mIcon); esm.writeHNOString("TEXT", mText); diff --git a/components/esm3/loadcell.cpp b/components/esm3/loadcell.cpp index 829cf9e916..b1efea1aec 100644 --- a/components/esm3/loadcell.cpp +++ b/components/esm3/loadcell.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include "esmreader.hpp" @@ -41,6 +42,18 @@ namespace ESM { const StringRefId Cell::sDefaultWorldspaceId = StringRefId("sys::default"); + template T> + void decompose(T&& v, const auto& f) + { + f(v.mFlags, v.mX, v.mY); + } + + template T> + void decompose(T&& v, const auto& f) + { + f(v.mAmbient, v.mSunlight, v.mFog, v.mFogDensity); + } + // Some overloaded compare operators. bool operator==(const MovedCellRef& ref, const RefNum& refNum) { @@ -93,7 +106,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("DATA"): - esm.getHT(mData.mFlags, mData.mX, mData.mY); + esm.getSubComposite(mData); hasData = true; break; case SREC_DELE: @@ -118,6 +131,7 @@ namespace ESM bool overriding = !mName.empty(); bool isLoaded = false; mHasAmbi = false; + mHasWaterHeightSub = false; while (!isLoaded && esm.hasMoreSubs()) { esm.getSubName(); @@ -126,13 +140,13 @@ namespace ESM case fourCC("INTV"): int32_t waterl; esm.getHT(waterl); + mHasWaterHeightSub = true; mWater = static_cast(waterl); - mWaterInt = true; break; case fourCC("WHGT"): float waterLevel; esm.getHT(waterLevel); - mWaterInt = false; + mHasWaterHeightSub = true; if (!std::isfinite(waterLevel)) { if (!overriding) @@ -144,7 +158,7 @@ namespace ESM mWater = waterLevel; break; case fourCC("AMBI"): - esm.getHT(mAmbi.mAmbient, mAmbi.mSunlight, mAmbi.mFog, mAmbi.mFogDensity); + esm.getSubComposite(mAmbi); mHasAmbi = true; break; case fourCC("RGNN"): @@ -180,7 +194,7 @@ namespace ESM void Cell::save(ESMWriter& esm, bool isDeleted) const { esm.writeHNCString("NAME", mName); - esm.writeHNT("DATA", mData, 12); + esm.writeNamedComposite("DATA", mData); if (isDeleted) { @@ -190,25 +204,15 @@ namespace ESM if (mData.mFlags & Interior) { - if (mWaterInt) - { - int32_t water = (mWater >= 0) ? static_cast(mWater + 0.5) : static_cast(mWater - 0.5); - esm.writeHNT("INTV", water); - } - else - { + // Try to avoid saving ambient information when it's unnecessary. + // This is to fix black lighting and flooded water + // in resaved cell records that lack this information. + if (mHasWaterHeightSub) esm.writeHNT("WHGT", mWater); - } - if (mData.mFlags & QuasiEx) esm.writeHNOCRefId("RGNN", mRegion); - else - { - // Try to avoid saving ambient lighting information when it's unnecessary. - // This is to fix black lighting in resaved cell records that lack this information. - if (mHasAmbi) - esm.writeHNT("AMBI", mAmbi, 16); - } + else if (mHasAmbi) + esm.writeNamedComposite("AMBI", mAmbi); } else { @@ -324,7 +328,6 @@ namespace ESM mName.clear(); mRegion = ESM::RefId(); mWater = 0; - mWaterInt = false; mMapColor = 0; mRefNumCounter = 0; @@ -333,6 +336,7 @@ namespace ESM mData.mY = 0; mHasAmbi = true; + mHasWaterHeightSub = true; mAmbi.mAmbient = 0; mAmbi.mSunlight = 0; mAmbi.mFog = 0; diff --git a/components/esm3/loadcell.hpp b/components/esm3/loadcell.hpp index bfabdd58f9..3f16bcca31 100644 --- a/components/esm3/loadcell.hpp +++ b/components/esm3/loadcell.hpp @@ -111,7 +111,7 @@ namespace ESM , mRegion(ESM::RefId()) , mHasAmbi(true) , mWater(0) - , mWaterInt(false) + , mHasWaterHeightSub(false) , mMapColor(0) , mRefNumCounter(0) { @@ -131,7 +131,7 @@ namespace ESM bool mHasAmbi; float mWater; // Water level - bool mWaterInt; + bool mHasWaterHeightSub; int32_t mMapColor; // Counter for RefNums. This is only used during content file editing and has no impact on gameplay. // It prevents overwriting previous refNums, even if they were deleted. @@ -163,6 +163,8 @@ namespace ESM bool hasWater() const { return ((mData.mFlags & HasWater) != 0) || isExterior(); } + void setHasWaterHeightSub(bool hasWater) { mHasWaterHeightSub = hasWater; } + bool hasAmbient() const { return mHasAmbi; } void setHasAmbient(bool hasAmbi) { mHasAmbi = hasAmbi; } diff --git a/components/esm3/loadclas.cpp b/components/esm3/loadclas.cpp index ec4ff680fa..1fd22e2a49 100644 --- a/components/esm3/loadclas.cpp +++ b/components/esm3/loadclas.cpp @@ -2,7 +2,9 @@ #include -#include "components/esm/defs.hpp" +#include +#include + #include "esmreader.hpp" #include "esmwriter.hpp" @@ -12,6 +14,12 @@ namespace ESM = { "sSpecializationCombat", "sSpecializationMagic", "sSpecializationStealth" }; const std::array Class::specializationIndexToLuaId = { "combat", "magic", "stealth" }; + template T> + void decompose(T&& v, const auto& f) + { + f(v.mAttribute, v.mSpecialization, v.mSkills, v.mIsPlayable, v.mServices); + } + int32_t& Class::CLDTstruct::getSkill(int index, bool major) { return mSkills.at(index)[major ? 1 : 0]; @@ -42,8 +50,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("CLDT"): - esm.getHT( - mData.mAttribute, mData.mSpecialization, mData.mSkills, mData.mIsPlayable, mData.mServices); + esm.getSubComposite(mData); if (mData.mIsPlayable > 1) esm.fail("Unknown bool value"); hasData = true; @@ -77,7 +84,7 @@ namespace ESM } esm.writeHNOCString("FNAM", mName); - esm.writeHNT("CLDT", mData, 60); + esm.writeNamedComposite("CLDT", mData); esm.writeHNOString("DESC", mDescription); } diff --git a/components/esm3/loadclot.cpp b/components/esm3/loadclot.cpp index 7d60c82197..8e778243fc 100644 --- a/components/esm3/loadclot.cpp +++ b/components/esm3/loadclot.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mType, v.mWeight, v.mValue, v.mEnchant); + } + void Clothing::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -30,7 +38,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("CTDT"): - esm.getHT(mData.mType, mData.mWeight, mData.mValue, mData.mEnchant); + esm.getSubComposite(mData); hasData = true; break; case fourCC("SCRI"): @@ -73,7 +81,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); - esm.writeHNT("CTDT", mData, 12); + esm.writeNamedComposite("CTDT", mData); esm.writeHNOCRefId("SCRI", mScript); esm.writeHNOCString("ITEX", mIcon); diff --git a/components/esm3/loadcont.cpp b/components/esm3/loadcont.cpp index d016654fea..9d90491448 100644 --- a/components/esm3/loadcont.cpp +++ b/components/esm3/loadcont.cpp @@ -100,8 +100,8 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); - esm.writeHNT("CNDT", mWeight, 4); - esm.writeHNT("FLAG", mFlags, 4); + esm.writeHNT("CNDT", mWeight); + esm.writeHNT("FLAG", mFlags); esm.writeHNOCRefId("SCRI", mScript); diff --git a/components/esm3/loadcrea.cpp b/components/esm3/loadcrea.cpp index 0c0bad2e7c..83bdbd06ad 100644 --- a/components/esm3/loadcrea.cpp +++ b/components/esm3/loadcrea.cpp @@ -1,12 +1,19 @@ #include "loadcrea.hpp" #include +#include #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mType, v.mLevel, v.mAttributes, v.mHealth, v.mMana, v.mFatigue, v.mSoul, v.mCombat, v.mMagic, v.mStealth, + v.mAttack, v.mGold); + } void Creature::load(ESMReader& esm, bool& isDeleted) { @@ -48,8 +55,7 @@ namespace ESM mScript = esm.getRefId(); break; case fourCC("NPDT"): - esm.getHT(mData.mType, mData.mLevel, mData.mAttributes, mData.mHealth, mData.mMana, mData.mFatigue, - mData.mSoul, mData.mCombat, mData.mMagic, mData.mStealth, mData.mAttack, mData.mGold); + esm.getSubComposite(mData); hasNpdt = true; break; case fourCC("FLAG"): @@ -69,7 +75,7 @@ namespace ESM mSpells.add(esm); break; case fourCC("AIDT"): - esm.getHExact(&mAiData, sizeof(mAiData)); + esm.getSubComposite(mAiData); break; case fourCC("DODT"): case fourCC("DNAM"): @@ -121,7 +127,7 @@ namespace ESM esm.writeHNOCRefId("CNAM", mOriginal); esm.writeHNOCString("FNAM", mName); esm.writeHNOCRefId("SCRI", mScript); - esm.writeHNT("NPDT", mData, 96); + esm.writeNamedComposite("NPDT", mData); esm.writeHNT("FLAG", ((mBloodType << 10) + mFlags)); if (mScale != 1.0) { @@ -130,7 +136,7 @@ namespace ESM mInventory.save(esm); mSpells.save(esm); - esm.writeHNT("AIDT", mAiData, sizeof(mAiData)); + esm.writeNamedComposite("AIDT", mAiData); mTransport.save(esm); mAiPackage.save(esm); } diff --git a/components/esm3/loadench.cpp b/components/esm3/loadench.cpp index 1d19b690f0..9eb4fae301 100644 --- a/components/esm3/loadench.cpp +++ b/components/esm3/loadench.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mType, v.mCost, v.mCharge, v.mFlags); + } + void Enchantment::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -23,7 +31,7 @@ namespace ESM hasName = true; break; case fourCC("ENDT"): - esm.getHT(mData.mType, mData.mCost, mData.mCharge, mData.mFlags); + esm.getSubComposite(mData); hasData = true; break; case fourCC("ENAM"): @@ -55,7 +63,7 @@ namespace ESM return; } - esm.writeHNT("ENDT", mData, 16); + esm.writeNamedComposite("ENDT", mData); mEffects.save(esm); } diff --git a/components/esm3/loadinfo.cpp b/components/esm3/loadinfo.cpp index 9cff21da3e..8b1147ed45 100644 --- a/components/esm3/loadinfo.cpp +++ b/components/esm3/loadinfo.cpp @@ -3,8 +3,17 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + char padding = 0; + f(v.mType, v.mDisposition, v.mRank, v.mGender, v.mPCrank, padding); + } + void DialInfo::load(ESMReader& esm, bool& isDeleted) { mId = esm.getHNRefId("INAM"); @@ -23,8 +32,7 @@ namespace ESM switch (esm.retSubName().toInt()) { case fourCC("DATA"): - esm.getHT(mData.mUnknown1, mData.mDisposition, mData.mRank, mData.mGender, mData.mPCrank, - mData.mUnknown2); + esm.getSubComposite(mData); break; case fourCC("ONAM"): mActor = esm.getRefId(); @@ -58,10 +66,9 @@ namespace ESM break; case fourCC("SCVR"): { - SelectStruct ss; - ss.mSelectRule = esm.getHString(); - ss.mValue.read(esm, Variant::Format_Info); - mSelects.push_back(ss); + auto filter = DialogueCondition::load(esm, mId); + if (filter) + mSelects.emplace_back(std::move(*filter)); break; } case fourCC("BNAM"): @@ -102,7 +109,7 @@ namespace ESM return; } - esm.writeHNT("DATA", mData, 12); + esm.writeNamedComposite("DATA", mData); esm.writeHNOCRefId("ONAM", mActor); esm.writeHNOCRefId("RNAM", mRace); esm.writeHNOCRefId("CNAM", mClass); @@ -112,11 +119,8 @@ namespace ESM esm.writeHNOCString("SNAM", mSound); esm.writeHNOString("NAME", mResponse); - for (std::vector::const_iterator it = mSelects.begin(); it != mSelects.end(); ++it) - { - esm.writeHNString("SCVR", it->mSelectRule); - it->mValue.write(esm, Variant::Format_Info); - } + for (const auto& rule : mSelects) + rule.save(esm); esm.writeHNOString("BNAM", mResultScript); diff --git a/components/esm3/loadinfo.hpp b/components/esm3/loadinfo.hpp index 518e2eaa54..a3fb4abffa 100644 --- a/components/esm3/loadinfo.hpp +++ b/components/esm3/loadinfo.hpp @@ -4,8 +4,10 @@ #include #include -#include "components/esm/defs.hpp" -#include "components/esm/refid.hpp" +#include +#include + +#include "dialoguecondition.hpp" #include "variant.hpp" namespace ESM @@ -35,7 +37,7 @@ namespace ESM struct DATAstruct { - int32_t mUnknown1 = 0; + int32_t mType = 0; // See Dialogue::Type union { int32_t mDisposition = 0; // Used for dialogue responses @@ -44,17 +46,9 @@ namespace ESM signed char mRank = -1; // Rank of NPC signed char mGender = Gender::NA; // See Gender enum signed char mPCrank = -1; // Player rank - signed char mUnknown2 = 0; }; // 12 bytes DATAstruct mData; - // The rules for whether or not we will select this dialog item. - struct SelectStruct - { - std::string mSelectRule; // This has a complicated format - Variant mValue; - }; - // Journal quest indices (introduced with the quest system in Tribunal) enum QuestStatus { @@ -66,7 +60,7 @@ namespace ESM // Rules for when to include this item in the final list of options // visible to the player. - std::vector mSelects; + std::vector mSelects; // Id of this, previous and next INFO items RefId mId, mPrev, mNext; diff --git a/components/esm3/loadingr.cpp b/components/esm3/loadingr.cpp index 4e409ab63d..6a4753d8e4 100644 --- a/components/esm3/loadingr.cpp +++ b/components/esm3/loadingr.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mWeight, v.mValue, v.mEffectID, v.mSkills, v.mAttributes); + } + void Ingredient::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -28,7 +36,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("IRDT"): - esm.getHT(mData.mWeight, mData.mValue, mData.mEffectID, mData.mSkills, mData.mAttributes); + esm.getSubComposite(mData); hasData = true; break; case fourCC("SCRI"): @@ -82,7 +90,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); - esm.writeHNT("IRDT", mData, 56); + esm.writeNamedComposite("IRDT", mData); esm.writeHNOCRefId("SCRI", mScript); esm.writeHNOCString("ITEX", mIcon); } diff --git a/components/esm3/loadland.cpp b/components/esm3/loadland.cpp index 98e07b530f..74edf30498 100644 --- a/components/esm3/loadland.cpp +++ b/components/esm3/loadland.cpp @@ -5,7 +5,9 @@ #include #include -#include "components/esm/defs.hpp" +#include +#include + #include "esmreader.hpp" #include "esmwriter.hpp" @@ -13,27 +15,43 @@ namespace ESM { namespace { + struct VHGT + { + float mHeightOffset; + std::int8_t mHeightData[LandRecordData::sLandNumVerts]; + }; + + template T> + void decompose(T&& v, const auto& f) + { + char padding[3] = { 0, 0, 0 }; + f(v.mHeightOffset, v.mHeightData, padding); + } + void transposeTextureData(const std::uint16_t* in, std::uint16_t* out) { - int readPos = 0; // bit ugly, but it works - for (int y1 = 0; y1 < 4; y1++) - for (int x1 = 0; x1 < 4; x1++) - for (int y2 = 0; y2 < 4; y2++) - for (int x2 = 0; x2 < 4; x2++) + size_t readPos = 0; // bit ugly, but it works + for (size_t y1 = 0; y1 < 4; y1++) + for (size_t x1 = 0; x1 < 4; x1++) + for (size_t y2 = 0; y2 < 4; y2++) + for (size_t x2 = 0; x2 < 4; x2++) out[(y1 * 4 + y2) * 16 + (x1 * 4 + x2)] = in[readPos++]; } // Loads data and marks it as loaded. Return true if data is actually loaded from reader, false otherwise // including the case when data is already loaded. - bool condLoad(ESMReader& reader, int flags, int& targetFlags, int dataFlag, void* ptr, unsigned int size) + bool condLoad(ESMReader& reader, int flags, int& targetFlags, int dataFlag, auto& in) { if ((targetFlags & dataFlag) == 0 && (flags & dataFlag) != 0) { - reader.getHExact(ptr, size); + if constexpr (std::is_same_v, VHGT>) + reader.getSubComposite(in); + else + reader.getHT(in); targetFlags |= dataFlag; return true; } - reader.skipHSubSize(size); + reader.skipHSub(); return false; } } @@ -50,11 +68,7 @@ namespace ESM switch (esm.retSubName().toInt()) { case fourCC("INTV"): - esm.getSubHeader(); - if (esm.getSubSize() != 8) - esm.fail("Subrecord size is not equal to 8"); - esm.getT(mX); - esm.getT(mY); + esm.getHT(mX, mY); hasLocation = true; break; case fourCC("DATA"): @@ -94,7 +108,7 @@ namespace ESM mDataTypes |= DATA_VHGT; break; case fourCC("WNAM"): - esm.getHExact(mWnam.data(), mWnam.size()); + esm.getHT(mWnam); mDataTypes |= DATA_WNAM; break; case fourCC("VCLR"): @@ -137,12 +151,10 @@ namespace ESM { VHGT offsets; offsets.mHeightOffset = mLandData->mHeights[0] / HEIGHT_SCALE; - offsets.mUnk1 = mLandData->mUnk1; - offsets.mUnk2 = mLandData->mUnk2; float prevY = mLandData->mHeights[0]; - int number = 0; // avoid multiplication - for (int i = 0; i < LAND_SIZE; ++i) + size_t number = 0; // avoid multiplication + for (unsigned i = 0; i < LandRecordData::sLandSize; ++i) { float diff = (mLandData->mHeights[number] - prevY) / HEIGHT_SCALE; offsets.mHeightData[number] @@ -151,7 +163,7 @@ namespace ESM float prevX = prevY = mLandData->mHeights[number]; ++number; - for (int j = 1; j < LAND_SIZE; ++j) + for (unsigned j = 1; j < LandRecordData::sLandSize; ++j) { diff = (mLandData->mHeights[number] - prevX) / HEIGHT_SCALE; offsets.mHeightData[number] @@ -161,7 +173,7 @@ namespace ESM ++number; } } - esm.writeHNT("VHGT", offsets, sizeof(VHGT)); + esm.writeNamedComposite("VHGT", offsets); } if (mDataTypes & Land::DATA_WNAM) { @@ -169,13 +181,15 @@ namespace ESM std::int8_t wnam[LAND_GLOBAL_MAP_LOD_SIZE]; constexpr float max = std::numeric_limits::max(); constexpr float min = std::numeric_limits::min(); - constexpr float vertMult = static_cast(Land::LAND_SIZE - 1) / LAND_GLOBAL_MAP_LOD_SIZE_SQRT; - for (int row = 0; row < LAND_GLOBAL_MAP_LOD_SIZE_SQRT; ++row) + constexpr float vertMult + = static_cast(LandRecordData::sLandSize - 1) / LAND_GLOBAL_MAP_LOD_SIZE_SQRT; + for (unsigned row = 0; row < LAND_GLOBAL_MAP_LOD_SIZE_SQRT; ++row) { - for (int col = 0; col < LAND_GLOBAL_MAP_LOD_SIZE_SQRT; ++col) + for (unsigned col = 0; col < LAND_GLOBAL_MAP_LOD_SIZE_SQRT; ++col) { - float height = mLandData->mHeights[static_cast(row * vertMult) * Land::LAND_SIZE - + static_cast(col * vertMult)]; + float height + = mLandData->mHeights[static_cast(row * vertMult) * LandRecordData::sLandSize + + static_cast(col * vertMult)]; height /= height > 0 ? 128.f : 16.f; height = std::clamp(height, min, max); wnam[row * LAND_GLOBAL_MAP_LOD_SIZE_SQRT + col] = static_cast(height); @@ -189,8 +203,8 @@ namespace ESM } if (mDataTypes & Land::DATA_VTEX) { - uint16_t vtex[LAND_NUM_TEXTURES]; - transposeTextureData(mLandData->mTextures, vtex); + uint16_t vtex[LandRecordData::sLandNumTextures]; + transposeTextureData(mLandData->mTextures.data(), vtex); esm.writeHNT("VTEX", vtex); } } @@ -200,25 +214,23 @@ namespace ESM { setPlugin(0); - std::fill(std::begin(mWnam), std::end(mWnam), 0); + mWnam.fill(0); if (mLandData == nullptr) mLandData = std::make_unique(); mLandData->mHeightOffset = 0; - std::fill(std::begin(mLandData->mHeights), std::end(mLandData->mHeights), 0); + mLandData->mHeights.fill(0); mLandData->mMinHeight = 0; mLandData->mMaxHeight = 0; - for (int i = 0; i < LAND_NUM_VERTS; ++i) + for (size_t i = 0; i < LandRecordData::sLandNumVerts; ++i) { mLandData->mNormals[i * 3 + 0] = 0; mLandData->mNormals[i * 3 + 1] = 0; mLandData->mNormals[i * 3 + 2] = 127; } - std::fill(std::begin(mLandData->mTextures), std::end(mLandData->mTextures), 0); - std::fill(std::begin(mLandData->mColours), std::end(mLandData->mColours), 255); - mLandData->mUnk1 = 0; - mLandData->mUnk2 = 0; + mLandData->mTextures.fill(0); + mLandData->mColours.fill(255); mLandData->mDataLoaded = Land::DATA_VNML | Land::DATA_VHGT | Land::DATA_WNAM | Land::DATA_VCLR | Land::DATA_VTEX; mDataTypes = mLandData->mDataLoaded; @@ -259,32 +271,32 @@ namespace ESM if (reader.isNextSub("VNML")) { - condLoad(reader, flags, data.mDataLoaded, DATA_VNML, data.mNormals, sizeof(data.mNormals)); + condLoad(reader, flags, data.mDataLoaded, DATA_VNML, data.mNormals); } if (reader.isNextSub("VHGT")) { VHGT vhgt; - if (condLoad(reader, flags, data.mDataLoaded, DATA_VHGT, &vhgt, sizeof(vhgt))) + if (condLoad(reader, flags, data.mDataLoaded, DATA_VHGT, vhgt)) { data.mMinHeight = std::numeric_limits::max(); data.mMaxHeight = -std::numeric_limits::max(); float rowOffset = vhgt.mHeightOffset; - for (int y = 0; y < LAND_SIZE; y++) + for (unsigned y = 0; y < LandRecordData::sLandSize; y++) { - rowOffset += vhgt.mHeightData[y * LAND_SIZE]; + rowOffset += vhgt.mHeightData[y * LandRecordData::sLandSize]; - data.mHeights[y * LAND_SIZE] = rowOffset * HEIGHT_SCALE; + data.mHeights[y * LandRecordData::sLandSize] = rowOffset * HEIGHT_SCALE; if (rowOffset * HEIGHT_SCALE > data.mMaxHeight) data.mMaxHeight = rowOffset * HEIGHT_SCALE; if (rowOffset * HEIGHT_SCALE < data.mMinHeight) data.mMinHeight = rowOffset * HEIGHT_SCALE; float colOffset = rowOffset; - for (int x = 1; x < LAND_SIZE; x++) + for (unsigned x = 1; x < LandRecordData::sLandSize; x++) { - colOffset += vhgt.mHeightData[y * LAND_SIZE + x]; - data.mHeights[x + y * LAND_SIZE] = colOffset * HEIGHT_SCALE; + colOffset += vhgt.mHeightData[y * LandRecordData::sLandSize + x]; + data.mHeights[x + y * LandRecordData::sLandSize] = colOffset * HEIGHT_SCALE; if (colOffset * HEIGHT_SCALE > data.mMaxHeight) data.mMaxHeight = colOffset * HEIGHT_SCALE; @@ -292,8 +304,6 @@ namespace ESM data.mMinHeight = colOffset * HEIGHT_SCALE; } } - data.mUnk1 = vhgt.mUnk1; - data.mUnk2 = vhgt.mUnk2; } } @@ -301,13 +311,13 @@ namespace ESM reader.skipHSub(); if (reader.isNextSub("VCLR")) - condLoad(reader, flags, data.mDataLoaded, DATA_VCLR, data.mColours, 3 * LAND_NUM_VERTS); + condLoad(reader, flags, data.mDataLoaded, DATA_VCLR, data.mColours); if (reader.isNextSub("VTEX")) { - uint16_t vtex[LAND_NUM_TEXTURES]; - if (condLoad(reader, flags, data.mDataLoaded, DATA_VTEX, vtex, sizeof(vtex))) + uint16_t vtex[LandRecordData::sLandNumTextures]; + if (condLoad(reader, flags, data.mDataLoaded, DATA_VTEX, vtex)) { - transposeTextureData(vtex, data.mTextures); + transposeTextureData(vtex, data.mTextures.data()); } } } diff --git a/components/esm3/loadland.hpp b/components/esm3/loadland.hpp index 0d32407a5d..510f1790a8 100644 --- a/components/esm3/loadland.hpp +++ b/components/esm3/loadland.hpp @@ -86,19 +86,9 @@ namespace ESM // total number of textures per land static constexpr int LAND_NUM_TEXTURES = LandRecordData::sLandNumTextures; - static constexpr int LAND_GLOBAL_MAP_LOD_SIZE = 81; + static constexpr unsigned LAND_GLOBAL_MAP_LOD_SIZE = 81; - static constexpr int LAND_GLOBAL_MAP_LOD_SIZE_SQRT = 9; - -#pragma pack(push, 1) - struct VHGT - { - float mHeightOffset; - std::int8_t mHeightData[LAND_NUM_VERTS]; - std::uint16_t mUnk1; - std::uint8_t mUnk2; - }; -#pragma pack(pop) + static constexpr unsigned LAND_GLOBAL_MAP_LOD_SIZE_SQRT = 9; using LandData = ESM::LandRecordData; @@ -128,16 +118,10 @@ namespace ESM const LandData* getLandData(int flags) const; /// Return land data without loading first anything. Can return a 0-pointer. - const LandData* getLandData() const - { - return mLandData.get(); - } + const LandData* getLandData() const { return mLandData.get(); } /// Return land data without loading first anything. Can return a 0-pointer. - LandData* getLandData() - { - return mLandData.get(); - } + LandData* getLandData() { return mLandData.get(); } /// \attention Must not be called on objects that aren't fully loaded. /// diff --git a/components/esm3/loadlevlist.cpp b/components/esm3/loadlevlist.cpp index 627edbadce..f37009d6f9 100644 --- a/components/esm3/loadlevlist.cpp +++ b/components/esm3/loadlevlist.cpp @@ -87,7 +87,7 @@ namespace ESM esm.writeHNT("DATA", mFlags); esm.writeHNT("NNAM", mChanceNone); - esm.writeHNT("INDX", mList.size()); + esm.writeHNT("INDX", static_cast(mList.size())); for (const auto& item : mList) { diff --git a/components/esm3/loadligh.cpp b/components/esm3/loadligh.cpp index e22f6110c2..bb4f6bac7b 100644 --- a/components/esm3/loadligh.cpp +++ b/components/esm3/loadligh.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mWeight, v.mValue, v.mTime, v.mRadius, v.mColor, v.mFlags); + } + void Light::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -31,7 +39,7 @@ namespace ESM mIcon = esm.getHString(); break; case fourCC("LHDT"): - esm.getHT(mData.mWeight, mData.mValue, mData.mTime, mData.mRadius, mData.mColor, mData.mFlags); + esm.getSubComposite(mData); hasData = true; break; case fourCC("SCRI"): @@ -68,7 +76,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("ITEX", mIcon); - esm.writeHNT("LHDT", mData, 24); + esm.writeNamedComposite("LHDT", mData); esm.writeHNOCRefId("SCRI", mScript); esm.writeHNOCRefId("SNAM", mSound); } diff --git a/components/esm3/loadlock.cpp b/components/esm3/loadlock.cpp index 578a8a36a7..019d6f9952 100644 --- a/components/esm3/loadlock.cpp +++ b/components/esm3/loadlock.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mWeight, v.mValue, v.mQuality, v.mUses); + } + void Lockpick::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -28,7 +36,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("LKDT"): - esm.getHT(mData.mWeight, mData.mValue, mData.mQuality, mData.mUses); + esm.getSubComposite(mData); hasData = true; break; case fourCC("SCRI"): @@ -66,7 +74,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); - esm.writeHNT("LKDT", mData, 16); + esm.writeNamedComposite("LKDT", mData); esm.writeHNORefId("SCRI", mScript); esm.writeHNOCString("ITEX", mIcon); } diff --git a/components/esm3/loadmgef.cpp b/components/esm3/loadmgef.cpp index 8d5b99b0c3..357dd94413 100644 --- a/components/esm3/loadmgef.cpp +++ b/components/esm3/loadmgef.cpp @@ -588,7 +588,7 @@ namespace ESM mData.mRed = 0; mData.mGreen = 0; mData.mBlue = 0; - mData.mSpeed = 0; + mData.mSpeed = 1; mIcon.clear(); mParticle.clear(); diff --git a/components/esm3/loadmisc.cpp b/components/esm3/loadmisc.cpp index b38ce63294..63df1c6551 100644 --- a/components/esm3/loadmisc.cpp +++ b/components/esm3/loadmisc.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mWeight, v.mValue, v.mFlags); + } + void Miscellaneous::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -28,7 +36,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("MCDT"): - esm.getHT(mData.mWeight, mData.mValue, mData.mFlags); + esm.getSubComposite(mData); hasData = true; break; case fourCC("SCRI"): @@ -65,7 +73,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); - esm.writeHNT("MCDT", mData, 12); + esm.writeNamedComposite("MCDT", mData); esm.writeHNOCRefId("SCRI", mScript); esm.writeHNOCString("ITEX", mIcon); } diff --git a/components/esm3/loadnpc.cpp b/components/esm3/loadnpc.cpp index 4a30649372..03c47d4d73 100644 --- a/components/esm3/loadnpc.cpp +++ b/components/esm3/loadnpc.cpp @@ -3,8 +3,34 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + namespace + { + struct NPDTstruct12 + { + NPC::NPDTstruct52& mStruct; + }; + } + + template T> + void decompose(T&& v, const auto& f) + { + char padding1 = 0; + char padding2 = 0; + f(v.mLevel, v.mAttributes, v.mSkills, padding1, v.mHealth, v.mMana, v.mFatigue, v.mDisposition, v.mReputation, + v.mRank, padding2, v.mGold); + } + + template T> + void decompose(T&& v, const auto& f) + { + char padding[] = { 0, 0, 0 }; + f(v.mStruct.mLevel, v.mStruct.mDisposition, v.mStruct.mReputation, v.mStruct.mRank, padding, v.mStruct.mGold); + } + void NPC::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -56,37 +82,25 @@ namespace ESM case fourCC("NPDT"): hasNpdt = true; esm.getSubHeader(); - if (esm.getSubSize() == 52) + if (esm.getSubSize() == getCompositeSize(mNpdt)) { mNpdtType = NPC_DEFAULT; - esm.getT(mNpdt.mLevel); - esm.getT(mNpdt.mAttributes); - esm.getT(mNpdt.mSkills); - esm.getT(mNpdt.mUnknown1); - esm.getT(mNpdt.mHealth); - esm.getT(mNpdt.mMana); - esm.getT(mNpdt.mFatigue); - esm.getT(mNpdt.mDisposition); - esm.getT(mNpdt.mReputation); - esm.getT(mNpdt.mRank); - esm.getT(mNpdt.mUnknown2); - esm.getT(mNpdt.mGold); + esm.getComposite(mNpdt); } - else if (esm.getSubSize() == 12) + else { - mNpdtType = NPC_WITH_AUTOCALCULATED_STATS; - - // Clearing the mNdpt struct to initialize all values - blankNpdt(); - esm.getT(mNpdt.mLevel); - esm.getT(mNpdt.mDisposition); - esm.getT(mNpdt.mReputation); - esm.getT(mNpdt.mRank); - esm.skip(3); - esm.getT(mNpdt.mGold); + NPDTstruct12 data{ mNpdt }; + if (esm.getSubSize() == getCompositeSize(data)) + { + mNpdtType = NPC_WITH_AUTOCALCULATED_STATS; + + // Clearing the mNdpt struct to initialize all values + blankNpdt(); + esm.getComposite(data); + } + else + esm.fail("NPC_NPDT must be 12 or 52 bytes long"); } - else - esm.fail("NPC_NPDT must be 12 or 52 bytes long"); break; case fourCC("FLAG"): hasFlags = true; @@ -102,7 +116,7 @@ namespace ESM mInventory.add(esm); break; case fourCC("AIDT"): - esm.getHExact(&mAiData, sizeof(mAiData)); + esm.getSubComposite(mAiData); break; case fourCC("DODT"): case fourCC("DNAM"): @@ -154,39 +168,18 @@ namespace ESM if (mNpdtType == NPC_DEFAULT) { - esm.startSubRecord("NPDT"); - esm.writeT(mNpdt.mLevel); - esm.writeT(mNpdt.mAttributes); - esm.writeT(mNpdt.mSkills); - esm.writeT(mNpdt.mUnknown1); - esm.writeT(mNpdt.mHealth); - esm.writeT(mNpdt.mMana); - esm.writeT(mNpdt.mFatigue); - esm.writeT(mNpdt.mDisposition); - esm.writeT(mNpdt.mReputation); - esm.writeT(mNpdt.mRank); - esm.writeT(mNpdt.mUnknown2); - esm.writeT(mNpdt.mGold); - esm.endRecord("NPDT"); + esm.writeNamedComposite("NPDT", mNpdt); } else if (mNpdtType == NPC_WITH_AUTOCALCULATED_STATS) { - esm.startSubRecord("NPDT"); - esm.writeT(mNpdt.mLevel); - esm.writeT(mNpdt.mDisposition); - esm.writeT(mNpdt.mReputation); - esm.writeT(mNpdt.mRank); - constexpr char padding[] = { 0, 0, 0 }; - esm.writeT(padding); - esm.writeT(mNpdt.mGold); - esm.endRecord("NPDT"); + esm.writeNamedComposite("NPDT", NPDTstruct12{ const_cast(mNpdt) }); } esm.writeHNT("FLAG", ((mBloodType << 10) + mFlags)); mInventory.save(esm); mSpells.save(esm); - esm.writeHNT("AIDT", mAiData, sizeof(mAiData)); + esm.writeNamedComposite("AIDT", mAiData); mTransport.save(esm); @@ -238,9 +231,7 @@ namespace ESM mNpdt.mReputation = 0; mNpdt.mHealth = mNpdt.mMana = mNpdt.mFatigue = 0; mNpdt.mDisposition = 0; - mNpdt.mUnknown1 = 0; mNpdt.mRank = 0; - mNpdt.mUnknown2 = 0; mNpdt.mGold = 0; } diff --git a/components/esm3/loadnpc.hpp b/components/esm3/loadnpc.hpp index 76930365c8..40ec0f0347 100644 --- a/components/esm3/loadnpc.hpp +++ b/components/esm3/loadnpc.hpp @@ -83,10 +83,8 @@ namespace ESM // mSkill can grow up to 200, it must be unsigned std::array mSkills; - char mUnknown1; uint16_t mHealth, mMana, mFatigue; unsigned char mDisposition, mReputation, mRank; - char mUnknown2; int32_t mGold; }; // 52 bytes diff --git a/components/esm3/loadpgrd.cpp b/components/esm3/loadpgrd.cpp index 4f0a62a9d4..c438fd73eb 100644 --- a/components/esm3/loadpgrd.cpp +++ b/components/esm3/loadpgrd.cpp @@ -3,8 +3,23 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mX, v.mY, v.mGranularity, v.mPoints); + } + + template T> + void decompose(T&& v, const auto& f) + { + char padding[2] = { 0, 0 }; + f(v.mX, v.mY, v.mZ, v.mAutogenerated, v.mConnectionNum, padding); + } + Pathgrid::Point& Pathgrid::Point::operator=(const float rhs[3]) { mX = static_cast(rhs[0]); @@ -12,7 +27,6 @@ namespace ESM mZ = static_cast(rhs[2]); mAutogenerated = 0; mConnectionNum = 0; - mUnknown = 0; return *this; } Pathgrid::Point::Point(const float rhs[3]) @@ -21,7 +35,6 @@ namespace ESM , mZ(static_cast(rhs[2])) , mAutogenerated(0) , mConnectionNum(0) - , mUnknown(0) { } Pathgrid::Point::Point() @@ -30,7 +43,6 @@ namespace ESM , mZ(0) , mAutogenerated(0) , mConnectionNum(0) - , mUnknown(0) { } @@ -54,15 +66,14 @@ namespace ESM mCell = esm.getRefId(); break; case fourCC("DATA"): - esm.getHT(mData.mX, mData.mY, mData.mGranularity, mData.mPoints); + esm.getSubComposite(mData); hasData = true; break; case fourCC("PGRP"): { esm.getSubHeader(); uint32_t size = esm.getSubSize(); - // Check that the sizes match up. Size = 16 * path points - if (size != sizeof(Point) * mData.mPoints) + if (size != getCompositeSize(Point{}) * mData.mPoints) esm.fail("Path point subrecord size mismatch"); else { @@ -70,12 +81,7 @@ namespace ESM for (uint16_t i = 0; i < mData.mPoints; ++i) { Point p; - esm.getT(p.mX); - esm.getT(p.mY); - esm.getT(p.mZ); - esm.getT(p.mAutogenerated); - esm.getT(p.mConnectionNum); - esm.getT(p.mUnknown); + esm.getComposite(p); mPoints.push_back(p); edgeCount += p.mConnectionNum; } @@ -160,7 +166,7 @@ namespace ESM // Save esm.writeHNCRefId("NAME", mCell); - esm.writeHNT("DATA", mData, 12); + esm.writeNamedComposite("DATA", mData); if (isDeleted) { @@ -173,7 +179,7 @@ namespace ESM esm.startSubRecord("PGRP"); for (const Point& point : correctedPoints) { - esm.writeT(point); + esm.writeComposite(point); } esm.endRecord("PGRP"); } diff --git a/components/esm3/loadpgrd.hpp b/components/esm3/loadpgrd.hpp index a343552efb..f2a33f9b9a 100644 --- a/components/esm3/loadpgrd.hpp +++ b/components/esm3/loadpgrd.hpp @@ -35,7 +35,6 @@ namespace ESM int32_t mX, mY, mZ; // Location of point unsigned char mAutogenerated; // autogenerated vs. user coloring flag? unsigned char mConnectionNum; // number of connections for this point - int16_t mUnknown; Point& operator=(const float[3]); Point(const float[3]); Point(); @@ -45,7 +44,6 @@ namespace ESM , mZ(z) , mAutogenerated(0) , mConnectionNum(0) - , mUnknown(0) { } }; // 16 bytes diff --git a/components/esm3/loadprob.cpp b/components/esm3/loadprob.cpp index 3f9ba95bf1..5e3086c7b9 100644 --- a/components/esm3/loadprob.cpp +++ b/components/esm3/loadprob.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mWeight, v.mValue, v.mQuality, v.mUses); + } + void Probe::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -28,7 +36,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("PBDT"): - esm.getHT(mData.mWeight, mData.mValue, mData.mQuality, mData.mUses); + esm.getSubComposite(mData); hasData = true; break; case fourCC("SCRI"): @@ -66,7 +74,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); - esm.writeHNT("PBDT", mData, 16); + esm.writeNamedComposite("PBDT", mData); esm.writeHNORefId("SCRI", mScript); esm.writeHNOCString("ITEX", mIcon); } diff --git a/components/esm3/loadrepa.cpp b/components/esm3/loadrepa.cpp index c911cb1a23..886072ab56 100644 --- a/components/esm3/loadrepa.cpp +++ b/components/esm3/loadrepa.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mWeight, v.mValue, v.mUses, v.mQuality); + } + void Repair::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -28,7 +36,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("RIDT"): - esm.getHT(mData.mWeight, mData.mValue, mData.mUses, mData.mQuality); + esm.getSubComposite(mData); hasData = true; break; case fourCC("SCRI"): @@ -66,7 +74,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); - esm.writeHNT("RIDT", mData, 16); + esm.writeNamedComposite("RIDT", mData); esm.writeHNORefId("SCRI", mScript); esm.writeHNOCString("ITEX", mIcon); } diff --git a/components/esm3/loadscpt.cpp b/components/esm3/loadscpt.cpp index f79f4989ef..ae56a7b4f4 100644 --- a/components/esm3/loadscpt.cpp +++ b/components/esm3/loadscpt.cpp @@ -149,6 +149,8 @@ namespace ESM if (!hasHeader) esm.fail("Missing SCHD subrecord"); + // Reported script data size is not always trustworthy, so override it with actual data size + mData.mScriptDataSize = static_cast(mScriptData.size()); } void Script::save(ESMWriter& esm, bool isDeleted) const diff --git a/components/esm3/loadskil.cpp b/components/esm3/loadskil.cpp index fd53726f90..28ea3eadba 100644 --- a/components/esm3/loadskil.cpp +++ b/components/esm3/loadskil.cpp @@ -3,6 +3,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include #include #include @@ -37,6 +38,12 @@ namespace ESM const SkillId Skill::Speechcraft("Speechcraft"); const SkillId Skill::HandToHand("HandToHand"); + template T> + void decompose(T&& v, const auto& f) + { + f(v.mAttribute, v.mSpecialization, v.mUseValue); + } + void Skill::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; // Skill record can't be deleted now (may be changed in the future) @@ -55,7 +62,7 @@ namespace ESM hasIndex = true; break; case fourCC("SKDT"): - esm.getHT(mData.mAttribute, mData.mSpecialization, mData.mUseValue); + esm.getSubComposite(mData); hasData = true; break; case fourCC("DESC"): @@ -78,7 +85,7 @@ namespace ESM void Skill::save(ESMWriter& esm, bool /*isDeleted*/) const { esm.writeHNT("INDX", refIdToIndex(mId)); - esm.writeHNT("SKDT", mData, 24); + esm.writeNamedComposite("SKDT", mData); esm.writeHNOString("DESC", mDescription); } diff --git a/components/esm3/loadskil.hpp b/components/esm3/loadskil.hpp index 978e3d5dc4..d8e365aca1 100644 --- a/components/esm3/loadskil.hpp +++ b/components/esm3/loadskil.hpp @@ -47,13 +47,45 @@ namespace ESM uint32_t mRecordFlags; SkillId mId; + //! Enum that defines the index into SKDTstruct::mUseValue for all vanilla skill uses + enum UseType + { + // These are shared by multiple skills + Armor_HitByOpponent = 0, + Block_Success = 0, + Spellcast_Success = 0, + Weapon_SuccessfulHit = 0, + + // Skill-specific use types + Alchemy_CreatePotion = 0, + Alchemy_UseIngredient = 1, + Enchant_Recharge = 0, + Enchant_UseMagicItem = 1, + Enchant_CreateMagicItem = 2, + Enchant_CastOnStrike = 3, + Acrobatics_Jump = 0, + Acrobatics_Fall = 1, + Mercantile_Success = 0, + Mercantile_Bribe = 1, //!< \Note This is bugged in vanilla and is not actually in use. + Security_DisarmTrap = 0, + Security_PickLock = 1, + Sneak_AvoidNotice = 0, + Sneak_PickPocket = 1, + Speechcraft_Success = 0, + Speechcraft_Fail = 1, + Armorer_Repair = 0, + Athletics_RunOneSecond = 0, + Athletics_SwimOneSecond = 1, + + }; + struct SKDTstruct { int32_t mAttribute; // see defs.hpp int32_t mSpecialization; // 0 - Combat, 1 - Magic, 2 - Stealth float mUseValue[4]; // How much skill improves through use. Meaning // of each field depends on what skill this - // is. We should document this better later. + // is. See UseType above }; // Total size: 24 bytes SKDTstruct mData; diff --git a/components/esm3/loadsndg.cpp b/components/esm3/loadsndg.cpp index 4e2e2aa3f9..12a68b3afe 100644 --- a/components/esm3/loadsndg.cpp +++ b/components/esm3/loadsndg.cpp @@ -57,7 +57,7 @@ namespace ESM return; } - esm.writeHNT("DATA", mType, 4); + esm.writeHNT("DATA", mType); esm.writeHNOCRefId("CNAM", mCreature); esm.writeHNOCRefId("SNAM", mSound); } diff --git a/components/esm3/loadsoun.cpp b/components/esm3/loadsoun.cpp index fd403e3429..6f72a49a60 100644 --- a/components/esm3/loadsoun.cpp +++ b/components/esm3/loadsoun.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mVolume, v.mMinRange, v.mMaxRange); + } + void Sound::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -25,7 +33,7 @@ namespace ESM mSound = esm.getHString(); break; case fourCC("DATA"): - esm.getHT(mData.mVolume, mData.mMinRange, mData.mMaxRange); + esm.getSubComposite(mData); hasData = true; break; case SREC_DELE: @@ -55,7 +63,7 @@ namespace ESM } esm.writeHNOCString("FNAM", mSound); - esm.writeHNT("DATA", mData, 3); + esm.writeNamedComposite("DATA", mData); } void Sound::blank() diff --git a/components/esm3/loadspel.cpp b/components/esm3/loadspel.cpp index e4f63b8219..e40c03d007 100644 --- a/components/esm3/loadspel.cpp +++ b/components/esm3/loadspel.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mType, v.mCost, v.mFlags); + } + void Spell::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -27,7 +35,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("SPDT"): - esm.getHT(mData.mType, mData.mCost, mData.mFlags); + esm.getSubComposite(mData); hasData = true; break; case fourCC("ENAM"): @@ -60,7 +68,7 @@ namespace ESM } esm.writeHNOCString("FNAM", mName); - esm.writeHNT("SPDT", mData, 12); + esm.writeNamedComposite("SPDT", mData); mEffects.save(esm); } diff --git a/components/esm3/loadweap.cpp b/components/esm3/loadweap.cpp index 31c03b00fe..f06abf4e7c 100644 --- a/components/esm3/loadweap.cpp +++ b/components/esm3/loadweap.cpp @@ -1,11 +1,20 @@ #include "loadweap.hpp" -#include "components/esm/defs.hpp" +#include +#include + #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mWeight, v.mValue, v.mType, v.mHealth, v.mSpeed, v.mReach, v.mEnchant, v.mChop, v.mSlash, v.mThrust, + v.mFlags); + } + void Weapon::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -29,8 +38,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("WPDT"): - esm.getHT(mData.mWeight, mData.mValue, mData.mType, mData.mHealth, mData.mSpeed, mData.mReach, - mData.mEnchant, mData.mChop, mData.mSlash, mData.mThrust, mData.mFlags); + esm.getSubComposite(mData); hasData = true; break; case fourCC("SCRI"): @@ -68,7 +76,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); - esm.writeHNT("WPDT", mData, 32); + esm.writeNamedComposite("WPDT", mData); esm.writeHNOCRefId("SCRI", mScript); esm.writeHNOCString("ITEX", mIcon); esm.writeHNOCRefId("ENAM", mEnchant); @@ -84,9 +92,9 @@ namespace ESM mData.mSpeed = 0; mData.mReach = 0; mData.mEnchant = 0; - mData.mChop[0] = mData.mChop[1] = 0; - mData.mSlash[0] = mData.mSlash[1] = 0; - mData.mThrust[0] = mData.mThrust[1] = 0; + mData.mChop.fill(0); + mData.mSlash.fill(0); + mData.mThrust.fill(0); mData.mFlags = 0; mName.clear(); diff --git a/components/esm3/loadweap.hpp b/components/esm3/loadweap.hpp index ba1599b1df..8323176a64 100644 --- a/components/esm3/loadweap.hpp +++ b/components/esm3/loadweap.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_ESM_WEAP_H #define OPENMW_ESM_WEAP_H +#include #include #include "components/esm/refid.hpp" @@ -59,8 +60,6 @@ namespace ESM Silver = 0x02 }; -#pragma pack(push) -#pragma pack(1) struct WPDTstruct { float mWeight; @@ -69,10 +68,9 @@ namespace ESM uint16_t mHealth; float mSpeed, mReach; uint16_t mEnchant; // Enchantment points. The real value is mEnchant/10.f - unsigned char mChop[2], mSlash[2], mThrust[2]; // Min and max + std::array mChop, mSlash, mThrust; // Min and max int32_t mFlags; }; // 32 bytes -#pragma pack(pop) WPDTstruct mData; diff --git a/components/esm3/objectstate.cpp b/components/esm3/objectstate.cpp index 7d26f431d6..f3017e2d0d 100644 --- a/components/esm3/objectstate.cpp +++ b/components/esm3/objectstate.cpp @@ -31,12 +31,13 @@ namespace ESM if (mVersion <= MaxOldCountFormatVersion) { - mRef.mCount = 1; - esm.getHNOT(mRef.mCount, "COUN"); + if (mVersion <= MaxOldGoldValueFormatVersion) + mRef.mCount = std::max(1, mRef.mCount); + esm.getHNOT("COUN", mRef.mCount); } mPosition = mRef.mPos; - esm.getHNOT("POS_", mPosition.pos, mPosition.rot); + esm.getOptionalComposite("POS_", mPosition); mFlags = 0; esm.getHNOT(mFlags, "FLAG"); @@ -65,10 +66,7 @@ namespace ESM if (!inInventory && mPosition != mRef.mPos) { - std::array pos; - memcpy(pos.data(), mPosition.pos, sizeof(float) * 3); - memcpy(pos.data() + 3, mPosition.rot, sizeof(float) * 3); - esm.writeHNT("POS_", pos, 24); + esm.writeNamedComposite("POS_", mPosition); } if (mFlags != 0) diff --git a/components/esm3/objectstate.hpp b/components/esm3/objectstate.hpp index b3f7bd3d45..c947adcd97 100644 --- a/components/esm3/objectstate.hpp +++ b/components/esm3/objectstate.hpp @@ -4,8 +4,9 @@ #include #include -#include "components/esm/luascripts.hpp" -#include "components/esm3/formatversion.hpp" +#include +#include +#include #include "animationstate.hpp" #include "cellref.hpp" diff --git a/components/esm3/player.cpp b/components/esm3/player.cpp index fd280bf12e..bf5864ce4c 100644 --- a/components/esm3/player.cpp +++ b/components/esm3/player.cpp @@ -15,7 +15,7 @@ namespace ESM esm.getHNT("LKEP", mLastKnownExteriorPosition); - mHasMark = esm.getHNOT("MARK", mMarkedPosition.pos, mMarkedPosition.rot); + mHasMark = esm.getOptionalComposite("MARK", mMarkedPosition); if (mHasMark) mMarkedCell = esm.getCellId(); @@ -90,7 +90,7 @@ namespace ESM if (mHasMark) { - esm.writeHNT("MARK", mMarkedPosition, 24); + esm.writeNamedComposite("MARK", mMarkedPosition); esm.writeCellId(mMarkedCell); } diff --git a/components/esm3/player.hpp b/components/esm3/player.hpp index 0f76a3b5eb..0cc0c22dc3 100644 --- a/components/esm3/player.hpp +++ b/components/esm3/player.hpp @@ -3,11 +3,11 @@ #include -#include "components/esm/defs.hpp" -#include "npcstate.hpp" +#include +#include -#include "components/esm/attr.hpp" #include "loadskil.hpp" +#include "npcstate.hpp" namespace ESM { diff --git a/components/esm3/savedgame.cpp b/components/esm3/savedgame.cpp index 0dc1fb0653..212925b61d 100644 --- a/components/esm3/savedgame.cpp +++ b/components/esm3/savedgame.cpp @@ -3,10 +3,17 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "../misc/algorithm.hpp" +#include +#include namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mGameHour, v.mDay, v.mMonth, v.mYear); + } + void SavedGame::load(ESMReader& esm) { mPlayerName = esm.getHNString("PLNA"); @@ -19,7 +26,7 @@ namespace ESM mPlayerCellName = esm.getHNRefId("PLCE").toString(); else mPlayerCellName = esm.getHNString("PLCE"); - esm.getHNT("TSTM", mInGameTime.mGameHour, mInGameTime.mDay, mInGameTime.mMonth, mInGameTime.mYear); + esm.getNamedComposite("TSTM", mInGameTime); esm.getHNT(mTimePlayed, "TIME"); mDescription = esm.getHNString("DESC"); @@ -47,12 +54,12 @@ namespace ESM esm.writeHNString("PLCN", mPlayerClassName); esm.writeHNString("PLCE", mPlayerCellName); - esm.writeHNT("TSTM", mInGameTime, 16); + esm.writeNamedComposite("TSTM", mInGameTime); esm.writeHNT("TIME", mTimePlayed); esm.writeHNString("DESC", mDescription); - for (std::vector::const_iterator iter(mContentFiles.begin()); iter != mContentFiles.end(); ++iter) - esm.writeHNString("DEPE", *iter); + for (const std::string& dependency : mContentFiles) + esm.writeHNString("DEPE", dependency); esm.startSubRecord("SCRN"); esm.write(mScreenshot.data(), mScreenshot.size()); diff --git a/components/esm3/transport.cpp b/components/esm3/transport.cpp index 8b131b1b5f..a72cdbbaf8 100644 --- a/components/esm3/transport.cpp +++ b/components/esm3/transport.cpp @@ -13,7 +13,7 @@ namespace ESM if (esm.retSubName().toInt() == fourCC("DODT")) { Dest dodt; - esm.getHExact(&dodt.mPos, 24); + esm.getSubComposite(dodt.mPos); mList.push_back(dodt); } else if (esm.retSubName().toInt() == fourCC("DNAM")) @@ -28,11 +28,10 @@ namespace ESM void Transport::save(ESMWriter& esm) const { - typedef std::vector::const_iterator DestIter; - for (DestIter it = mList.begin(); it != mList.end(); ++it) + for (const Dest& dest : mList) { - esm.writeHNT("DODT", it->mPos, sizeof(it->mPos)); - esm.writeHNOCString("DNAM", it->mCellName); + esm.writeNamedComposite("DODT", dest.mPos); + esm.writeHNOCString("DNAM", dest.mCellName); } } diff --git a/components/esm3/transport.hpp b/components/esm3/transport.hpp index 555504c994..69bc1119c2 100644 --- a/components/esm3/transport.hpp +++ b/components/esm3/transport.hpp @@ -4,7 +4,7 @@ #include #include -#include "components/esm/defs.hpp" +#include namespace ESM { diff --git a/components/esm4/common.hpp b/components/esm4/common.hpp index 8f37bfb8d4..a50151b10a 100644 --- a/components/esm4/common.hpp +++ b/components/esm4/common.hpp @@ -176,687 +176,6 @@ namespace ESM4 REC_MSET = fourCC("MSET") // Media Set }; - enum SubRecordTypes - { - SUB_ACBS = fourCC("ACBS"), - SUB_ACEC = fourCC("ACEC"), // TES5 Dawnguard - SUB_ACEP = fourCC("ACEP"), // TES5 Dawnguard - SUB_ACID = fourCC("ACID"), // TES5 Dawnguard - SUB_ACPR = fourCC("ACPR"), // TES5 - SUB_ACSR = fourCC("ACSR"), // TES5 Dawnguard - SUB_ACTV = fourCC("ACTV"), // FO4 - SUB_ACUN = fourCC("ACUN"), // TES5 Dawnguard - SUB_AHCF = fourCC("AHCF"), - SUB_AHCM = fourCC("AHCM"), - SUB_AIDT = fourCC("AIDT"), - SUB_ALCA = fourCC("ALCA"), // TES5 - SUB_ALCC = fourCC("ALCC"), // FO4 - SUB_ALCL = fourCC("ALCL"), // TES5 - SUB_ALCO = fourCC("ALCO"), // TES5 - SUB_ALCS = fourCC("ALCS"), // FO4 - SUB_ALDI = fourCC("ALDI"), // FO4 - SUB_ALDN = fourCC("ALDN"), // TES5 - SUB_ALEA = fourCC("ALEA"), // TES5 - SUB_ALED = fourCC("ALED"), // TES5 - SUB_ALEQ = fourCC("ALEQ"), // TES5 - SUB_ALFA = fourCC("ALFA"), // TES5 - SUB_ALFC = fourCC("ALFC"), // TES5 - SUB_ALFD = fourCC("ALFD"), // TES5 - SUB_ALFE = fourCC("ALFE"), // TES5 - SUB_ALFI = fourCC("ALFI"), // TES5 - SUB_ALFL = fourCC("ALFL"), // TES5 - SUB_ALFR = fourCC("ALFR"), // TES5 - SUB_ALFV = fourCC("ALFV"), // FO4 - SUB_ALID = fourCC("ALID"), // TES5 - SUB_ALLA = fourCC("ALLA"), // FO4 - SUB_ALLS = fourCC("ALLS"), // TES5 - SUB_ALMI = fourCC("ALMI"), // FO4 - SUB_ALNA = fourCC("ALNA"), // TES5 - SUB_ALNT = fourCC("ALNT"), // TES5 - SUB_ALPC = fourCC("ALPC"), // TES5 - SUB_ALRT = fourCC("ALRT"), // TES5 - SUB_ALSP = fourCC("ALSP"), // TES5 - SUB_ALST = fourCC("ALST"), // TES5 - SUB_ALUA = fourCC("ALUA"), // TES5 - SUB_ANAM = fourCC("ANAM"), - SUB_AOR2 = fourCC("AOR2"), // FO4 - SUB_APPR = fourCC("APPR"), // FO4 - SUB_ATKD = fourCC("ATKD"), - SUB_ATKE = fourCC("ATKE"), - SUB_ATKR = fourCC("ATKR"), - SUB_ATKS = fourCC("ATKS"), // FO4 - SUB_ATKT = fourCC("ATKT"), // FO4 - SUB_ATKW = fourCC("ATKW"), // FO4 - SUB_ATTN = fourCC("ATTN"), // FO4 - SUB_ATTR = fourCC("ATTR"), - SUB_ATTX = fourCC("ATTX"), // FO4 - SUB_ATXT = fourCC("ATXT"), - SUB_AVFL = fourCC("AVFL"), // FO4 - SUB_AVSK = fourCC("AVSK"), // TES5 - SUB_BAMT = fourCC("BAMT"), - SUB_BCLF = fourCC("BCLF"), // FO4 - SUB_BIDS = fourCC("BIDS"), - SUB_BIPL = fourCC("BIPL"), // FO3 - SUB_BMCT = fourCC("BMCT"), - SUB_BMDT = fourCC("BMDT"), - SUB_BMMP = fourCC("BMMP"), // FO4 - SUB_BNAM = fourCC("BNAM"), - SUB_BOD2 = fourCC("BOD2"), - SUB_BODT = fourCC("BODT"), - SUB_BPND = fourCC("BPND"), - SUB_BPNI = fourCC("BPNI"), - SUB_BPNN = fourCC("BPNN"), - SUB_BPNT = fourCC("BPNT"), - SUB_BPTN = fourCC("BPTN"), - SUB_BRUS = fourCC("BRUS"), // FONV - SUB_BSIZ = fourCC("BSIZ"), // FO4 - SUB_BSMB = fourCC("BSMB"), // FO4 - SUB_BSMP = fourCC("BSMP"), // FO4 - SUB_BSMS = fourCC("BSMS"), // FO4 - SUB_BTXT = fourCC("BTXT"), - SUB_CDIX = fourCC("CDIX"), // FO4 - SUB_CIS1 = fourCC("CIS1"), // TES5 - SUB_CIS2 = fourCC("CIS2"), // TES5 - SUB_CITC = fourCC("CITC"), // TES5 - SUB_CLSZ = fourCC("CLSZ"), // FO4 - SUB_CNAM = fourCC("CNAM"), - SUB_CNTO = fourCC("CNTO"), - SUB_COCT = fourCC("COCT"), - SUB_COED = fourCC("COED"), - SUB_CRDT = fourCC("CRDT"), - SUB_CRGR = fourCC("CRGR"), // TES5 - SUB_CRIF = fourCC("CRIF"), - SUB_CRIS = fourCC("CRIS"), // FO4 - SUB_CRVA = fourCC("CRVA"), // TES5 - SUB_CS2D = fourCC("CS2D"), // FO4 - SUB_CS2E = fourCC("CS2E"), // FO4 - SUB_CS2F = fourCC("CS2F"), // FO4 - SUB_CS2H = fourCC("CS2H"), // FO4 - SUB_CS2K = fourCC("CS2K"), // FO4 - SUB_CSCR = fourCC("CSCR"), - SUB_CSCV = fourCC("CSCV"), // FO4 - SUB_CSDC = fourCC("CSDC"), - SUB_CSDI = fourCC("CSDI"), - SUB_CSDT = fourCC("CSDT"), - SUB_CSFL = fourCC("CSFL"), // TES5 - SUB_CSGD = fourCC("CSGD"), // TES5 - SUB_CSLR = fourCC("CSLR"), // TES5 - SUB_CSMD = fourCC("CSMD"), // TES5 - SUB_CSME = fourCC("CSME"), // TES5 - SUB_CSRA = fourCC("CSRA"), // FO4 - SUB_CTDA = fourCC("CTDA"), - SUB_CTDT = fourCC("CTDT"), - SUB_CUSD = fourCC("CUSD"), // FO4 - SUB_CVPA = fourCC("CVPA"), // FO4 - SUB_DALC = fourCC("DALC"), // FO3 - SUB_DAMA = fourCC("DAMA"), // FO4 - SUB_DAMC = fourCC("DAMC"), // FO4 - SUB_DAT2 = fourCC("DAT2"), // FONV - SUB_DATA = fourCC("DATA"), - SUB_DELE = fourCC("DELE"), - SUB_DEMO = fourCC("DEMO"), // TES5 - SUB_DESC = fourCC("DESC"), - SUB_DEST = fourCC("DEST"), - SUB_DEVA = fourCC("DEVA"), // TES5 - SUB_DFTF = fourCC("DFTF"), - SUB_DFTM = fourCC("DFTM"), - SUB_DMAX = fourCC("DMAX"), // TES5 - SUB_DMDC = fourCC("DMDC"), // FO4 - SUB_DMDL = fourCC("DMDL"), - SUB_DMDS = fourCC("DMDS"), - SUB_DMDT = fourCC("DMDT"), - SUB_DMIN = fourCC("DMIN"), // TES5 - SUB_DNAM = fourCC("DNAM"), - SUB_DODT = fourCC("DODT"), - SUB_DOFT = fourCC("DOFT"), - SUB_DPLT = fourCC("DPLT"), - SUB_DSTA = fourCC("DSTA"), // FO4 - SUB_DSTD = fourCC("DSTD"), - SUB_DSTF = fourCC("DSTF"), - SUB_DTGT = fourCC("DTGT"), // FO4 - SUB_DTID = fourCC("DTID"), // FO4 - SUB_EAMT = fourCC("EAMT"), - SUB_ECOR = fourCC("ECOR"), - SUB_EDID = fourCC("EDID"), - SUB_EFID = fourCC("EFID"), - SUB_EFIT = fourCC("EFIT"), - SUB_EFSD = fourCC("EFSD"), // FONV DeadMoney - SUB_EITM = fourCC("EITM"), - SUB_ENAM = fourCC("ENAM"), - SUB_ENIT = fourCC("ENIT"), - SUB_EPF2 = fourCC("EPF2"), - SUB_EPF3 = fourCC("EPF3"), - SUB_EPFB = fourCC("EPFB"), // FO4 - SUB_EPFD = fourCC("EPFD"), - SUB_EPFT = fourCC("EPFT"), - SUB_ESCE = fourCC("ESCE"), - SUB_ETYP = fourCC("ETYP"), - SUB_FCHT = fourCC("FCHT"), // TES5 - SUB_FCPL = fourCC("FCPL"), // FO4 - SUB_FFFF = fourCC("FFFF"), - SUB_FGGA = fourCC("FGGA"), - SUB_FGGS = fourCC("FGGS"), - SUB_FGTS = fourCC("FGTS"), - SUB_FIMD = fourCC("FIMD"), // FO4 - SUB_FLMV = fourCC("FLMV"), - SUB_FLTR = fourCC("FLTR"), // TES5 - SUB_FLTV = fourCC("FLTV"), - SUB_FMIN = fourCC("FMIN"), // FO4 - SUB_FMRI = fourCC("FMRI"), // FO4 - SUB_FMRN = fourCC("FMRN"), // FO4 - SUB_FMRS = fourCC("FMRS"), // FO4 - SUB_FNAM = fourCC("FNAM"), - SUB_FNMK = fourCC("FNMK"), - SUB_FNPR = fourCC("FNPR"), - SUB_FPRT = fourCC("FPRT"), // TES5 - SUB_FTSF = fourCC("FTSF"), - SUB_FTSM = fourCC("FTSM"), - SUB_FTST = fourCC("FTST"), - SUB_FTYP = fourCC("FTYP"), // FO4 - SUB_FULL = fourCC("FULL"), - SUB_FVPA = fourCC("FVPA"), // FO4 - SUB_GNAM = fourCC("GNAM"), - SUB_GREE = fourCC("GREE"), // FO4 - SUB_GWOR = fourCC("GWOR"), // TES5 - SUB_HCLF = fourCC("HCLF"), - SUB_HCLR = fourCC("HCLR"), - SUB_HEAD = fourCC("HEAD"), - SUB_HEDR = fourCC("HEDR"), - SUB_HLTX = fourCC("HLTX"), // FO4 - SUB_HNAM = fourCC("HNAM"), - SUB_HTID = fourCC("HTID"), // TES5 - SUB_ICO2 = fourCC("ICO2"), - SUB_ICON = fourCC("ICON"), - SUB_IDLA = fourCC("IDLA"), - SUB_IDLB = fourCC("IDLB"), // FO3 - SUB_IDLC = fourCC("IDLC"), - SUB_IDLF = fourCC("IDLF"), - SUB_IDLT = fourCC("IDLT"), - SUB_IMPF = fourCC("IMPF"), // FO3 Anchorage - SUB_IMPS = fourCC("IMPS"), // FO3 Anchorage - SUB_IMSP = fourCC("IMSP"), // TES5 - SUB_INAM = fourCC("INAM"), - SUB_INCC = fourCC("INCC"), - SUB_INDX = fourCC("INDX"), - SUB_INFC = fourCC("INFC"), // FONV - SUB_INFX = fourCC("INFX"), // FONV - SUB_INRD = fourCC("INRD"), // FO4 - SUB_INTT = fourCC("INTT"), // FO4 - SUB_INTV = fourCC("INTV"), - SUB_IOVR = fourCC("IOVR"), // FO4 - SUB_ISIZ = fourCC("ISIZ"), // FO4 - SUB_ITID = fourCC("ITID"), // FO4 - SUB_ITMC = fourCC("ITMC"), // FO4 - SUB_ITME = fourCC("ITME"), // FO4 - SUB_ITMS = fourCC("ITMS"), // FO4 - SUB_ITXT = fourCC("ITXT"), - SUB_JAIL = fourCC("JAIL"), // TES5 - SUB_JNAM = fourCC("JNAM"), // FONV - SUB_JOUT = fourCC("JOUT"), // TES5 - SUB_KFFZ = fourCC("KFFZ"), - SUB_KNAM = fourCC("KNAM"), - SUB_KSIZ = fourCC("KSIZ"), - SUB_KWDA = fourCC("KWDA"), - SUB_LCEC = fourCC("LCEC"), // TES5 - SUB_LCEP = fourCC("LCEP"), // TES5 - SUB_LCID = fourCC("LCID"), // TES5 - SUB_LCPR = fourCC("LCPR"), // TES5 - SUB_LCSR = fourCC("LCSR"), // TES5 - SUB_LCUN = fourCC("LCUN"), // TES5 - SUB_LFSD = fourCC("LFSD"), // FO4 - SUB_LFSP = fourCC("LFSP"), // FO4 - SUB_LLCT = fourCC("LLCT"), - SUB_LLKC = fourCC("LLKC"), // FO4 - SUB_LNAM = fourCC("LNAM"), - SUB_LTMP = fourCC("LTMP"), - SUB_LTPC = fourCC("LTPC"), // FO4 - SUB_LTPT = fourCC("LTPT"), // FO4 - SUB_LVLD = fourCC("LVLD"), - SUB_LVLF = fourCC("LVLF"), - SUB_LVLG = fourCC("LVLG"), // FO3 - SUB_LVLM = fourCC("LVLM"), // FO4 - SUB_LVLO = fourCC("LVLO"), - SUB_LVSG = fourCC("LVSG"), // FO4 - SUB_MASE = fourCC("MASE"), // FO4 - SUB_MAST = fourCC("MAST"), - SUB_MCHT = fourCC("MCHT"), // TES5 - SUB_MDOB = fourCC("MDOB"), - SUB_MHDT = fourCC("MHDT"), - SUB_MIC2 = fourCC("MIC2"), - SUB_MICO = fourCC("MICO"), - SUB_MLSI = fourCC("MLSI"), // FO4 - SUB_MMRK = fourCC("MMRK"), // FONV - SUB_MNAM = fourCC("MNAM"), - SUB_MO2B = fourCC("MO2B"), - SUB_MO2C = fourCC("MO2C"), // FO4 - SUB_MO2F = fourCC("MO2F"), // FO4 - SUB_MO2S = fourCC("MO2S"), - SUB_MO2T = fourCC("MO2T"), - SUB_MO3B = fourCC("MO3B"), - SUB_MO3C = fourCC("MO3C"), // FO4 - SUB_MO3F = fourCC("MO3F"), // FO4 - SUB_MO3S = fourCC("MO3S"), // FO3 - SUB_MO3T = fourCC("MO3T"), - SUB_MO4B = fourCC("MO4B"), - SUB_MO4C = fourCC("MO4C"), // FO4 - SUB_MO4F = fourCC("MO4F"), // FO4 - SUB_MO4S = fourCC("MO4S"), - SUB_MO4T = fourCC("MO4T"), - SUB_MO5C = fourCC("MO5C"), // FO4 - SUB_MO5F = fourCC("MO5F"), // FO4 - SUB_MO5S = fourCC("MO5S"), // TES5 - SUB_MO5T = fourCC("MO5T"), - SUB_MOD2 = fourCC("MOD2"), - SUB_MOD3 = fourCC("MOD3"), - SUB_MOD4 = fourCC("MOD4"), - SUB_MOD5 = fourCC("MOD5"), - SUB_MODB = fourCC("MODB"), - SUB_MODC = fourCC("MODC"), // FO4 - SUB_MODD = fourCC("MODD"), // FO3 - SUB_MODF = fourCC("MODF"), // FO4 - SUB_MODL = fourCC("MODL"), - SUB_MODQ = fourCC("MODQ"), // FO4 - SUB_MODS = fourCC("MODS"), - SUB_MODT = fourCC("MODT"), - SUB_MOSD = fourCC("MOSD"), // FO3 - SUB_MPAI = fourCC("MPAI"), - SUB_MPAV = fourCC("MPAV"), - SUB_MPCD = fourCC("MPCD"), // FO4 - SUB_MPGN = fourCC("MPGN"), // FO4 - SUB_MPGS = fourCC("MPGS"), // FO4 - SUB_MPPC = fourCC("MPPC"), // FO4 - SUB_MPPF = fourCC("MPPF"), // FO4 - SUB_MPPI = fourCC("MPPI"), // FO4 - SUB_MPPK = fourCC("MPPK"), // FO4 - SUB_MPPM = fourCC("MPPM"), // FO4 - SUB_MPPN = fourCC("MPPN"), // FO4 - SUB_MPPT = fourCC("MPPT"), // FO4 - SUB_MPRT = fourCC("MPRT"), // TES5 - SUB_MRSV = fourCC("MRSV"), // FO4 - SUB_MSDK = fourCC("MSDK"), // FO4 - SUB_MSDV = fourCC("MSDV"), // FO4 - SUB_MSID = fourCC("MSID"), // FO4 - SUB_MSM0 = fourCC("MSM0"), // FO4 - SUB_MSM1 = fourCC("MSM1"), // FO4 - SUB_MTNM = fourCC("MTNM"), - SUB_MTYP = fourCC("MTYP"), - SUB_MWD1 = fourCC("MWD1"), // FONV - SUB_MWD2 = fourCC("MWD2"), // FONV - SUB_MWD3 = fourCC("MWD3"), // FONV - SUB_MWD4 = fourCC("MWD4"), // FONV - SUB_MWD5 = fourCC("MWD5"), // FONV - SUB_MWD6 = fourCC("MWD6"), // FONV - SUB_MWD7 = fourCC("MWD7"), // FONV - SUB_MWGT = fourCC("MWGT"), // FO4 - SUB_NAM0 = fourCC("NAM0"), - SUB_NAM1 = fourCC("NAM1"), - SUB_NAM2 = fourCC("NAM2"), - SUB_NAM3 = fourCC("NAM3"), - SUB_NAM4 = fourCC("NAM4"), - SUB_NAM5 = fourCC("NAM5"), - SUB_NAM6 = fourCC("NAM6"), - SUB_NAM7 = fourCC("NAM7"), - SUB_NAM8 = fourCC("NAM8"), - SUB_NAM9 = fourCC("NAM9"), - SUB_NAMA = fourCC("NAMA"), - SUB_NAME = fourCC("NAME"), - SUB_NETO = fourCC("NETO"), // FO4 - SUB_NEXT = fourCC("NEXT"), // FO3 - SUB_NIFT = fourCC("NIFT"), - SUB_NIFZ = fourCC("NIFZ"), - SUB_NNAM = fourCC("NNAM"), - SUB_NNGS = fourCC("NNGS"), // FO4 - SUB_NNGT = fourCC("NNGT"), // FO4 - SUB_NNUS = fourCC("NNUS"), // FO4 - SUB_NNUT = fourCC("NNUT"), // FO4 - SUB_NONE = fourCC("NONE"), // FO4 - SUB_NPOS = fourCC("NPOS"), // FO4 - SUB_NPOT = fourCC("NPOT"), // FO4 - SUB_NQUS = fourCC("NQUS"), // FO4 - SUB_NQUT = fourCC("NQUT"), // FO4 - SUB_NTOP = fourCC("NTOP"), // FO4 - SUB_NTRM = fourCC("NTRM"), // FO4 - SUB_NULL = fourCC("NULL"), - SUB_NVCA = fourCC("NVCA"), // FO3 - SUB_NVCI = fourCC("NVCI"), // FO3 - SUB_NVDP = fourCC("NVDP"), // FO3 - SUB_NVER = fourCC("NVER"), - SUB_NVEX = fourCC("NVEX"), // FO3 - SUB_NVGD = fourCC("NVGD"), // FO3 - SUB_NVMI = fourCC("NVMI"), - SUB_NVNM = fourCC("NVNM"), - SUB_NVPP = fourCC("NVPP"), - SUB_NVSI = fourCC("NVSI"), - SUB_NVTR = fourCC("NVTR"), // FO3 - SUB_NVVX = fourCC("NVVX"), // FO3 - SUB_OBND = fourCC("OBND"), - SUB_OBTE = fourCC("OBTE"), // FO4 - SUB_OBTF = fourCC("OBTF"), // FO4 - SUB_OBTS = fourCC("OBTS"), // FO4 - SUB_OCOR = fourCC("OCOR"), // TES5 - SUB_OFST = fourCC("OFST"), // TES4 only? - SUB_ONAM = fourCC("ONAM"), - SUB_PCMB = fourCC("PCMB"), // FO4 - SUB_PDTO = fourCC("PDTO"), - SUB_PFIG = fourCC("PFIG"), - SUB_PFO2 = fourCC("PFO2"), // TES5 - SUB_PFOR = fourCC("PFOR"), // TES5 - SUB_PFPC = fourCC("PFPC"), - SUB_PFRN = fourCC("PFRN"), // FO4 - SUB_PGAG = fourCC("PGAG"), - SUB_PGRI = fourCC("PGRI"), - SUB_PGRL = fourCC("PGRL"), - SUB_PGRP = fourCC("PGRP"), - SUB_PGRR = fourCC("PGRR"), - SUB_PHTN = fourCC("PHTN"), - SUB_PHWT = fourCC("PHWT"), - SUB_PKAM = fourCC("PKAM"), // FO3 - SUB_PKC2 = fourCC("PKC2"), // TES5 - SUB_PKCU = fourCC("PKCU"), // TES5 - SUB_PKD2 = fourCC("PKD2"), // FO3 - SUB_PKDD = fourCC("PKDD"), // FO3 - SUB_PKDT = fourCC("PKDT"), - SUB_PKE2 = fourCC("PKE2"), // FO3 - SUB_PKED = fourCC("PKED"), // FO3 - SUB_PKFD = fourCC("PKFD"), // FO3 - SUB_PKID = fourCC("PKID"), - SUB_PKPT = fourCC("PKPT"), // FO3 - SUB_PKW3 = fourCC("PKW3"), // FO3 - SUB_PLCN = fourCC("PLCN"), // TES5 - SUB_PLD2 = fourCC("PLD2"), // FO3 - SUB_PLDT = fourCC("PLDT"), - SUB_PLVD = fourCC("PLVD"), // TES5 - SUB_PNAM = fourCC("PNAM"), - SUB_POBA = fourCC("POBA"), // FO3 - SUB_POCA = fourCC("POCA"), // FO3 - SUB_POEA = fourCC("POEA"), // FO3 - SUB_PRCB = fourCC("PRCB"), // TES5 - SUB_PRKC = fourCC("PRKC"), - SUB_PRKE = fourCC("PRKE"), - SUB_PRKF = fourCC("PRKF"), - SUB_PRKR = fourCC("PRKR"), - SUB_PRKZ = fourCC("PRKZ"), - SUB_PRPS = fourCC("PRPS"), // FO4 - SUB_PSDT = fourCC("PSDT"), - SUB_PTD2 = fourCC("PTD2"), // FO3 - SUB_PTDA = fourCC("PTDA"), // TES5 - SUB_PTDT = fourCC("PTDT"), - SUB_PTOP = fourCC("PTOP"), // FO4 - SUB_PTRN = fourCC("PTRN"), // FO4 - SUB_PUID = fourCC("PUID"), // FO3 - SUB_QNAM = fourCC("QNAM"), - SUB_QOBJ = fourCC("QOBJ"), // FO3 - SUB_QSDT = fourCC("QSDT"), - SUB_QSTA = fourCC("QSTA"), - SUB_QSTI = fourCC("QSTI"), - SUB_QSTR = fourCC("QSTR"), - SUB_QTGL = fourCC("QTGL"), // TES5 - SUB_QTOP = fourCC("QTOP"), // FO4 - SUB_QUAL = fourCC("QUAL"), - SUB_RADR = fourCC("RADR"), // FO4 - SUB_RAGA = fourCC("RAGA"), - SUB_RBPC = fourCC("RBPC"), // FO4 - SUB_RCEC = fourCC("RCEC"), // TES5 - SUB_RCIL = fourCC("RCIL"), // FONV - SUB_RCLR = fourCC("RCLR"), - SUB_RCPR = fourCC("RCPR"), // TES5 Dawnguard - SUB_RCSR = fourCC("RCSR"), // TES5 - SUB_RCUN = fourCC("RCUN"), // TES5 - SUB_RDAT = fourCC("RDAT"), - SUB_RDGS = fourCC("RDGS"), - SUB_RDID = fourCC("RDID"), // FONV - SUB_RDMD = fourCC("RDMD"), // TES4 only? - SUB_RDMO = fourCC("RDMO"), - SUB_RDMP = fourCC("RDMP"), - SUB_RDOT = fourCC("RDOT"), - SUB_RDSA = fourCC("RDSA"), - SUB_RDSB = fourCC("RDSB"), // FONV - SUB_RDSD = fourCC("RDSD"), // TES4 only? - SUB_RDSI = fourCC("RDSI"), // FONV - SUB_RDWT = fourCC("RDWT"), - SUB_REPL = fourCC("REPL"), // FO3 - SUB_REPT = fourCC("REPT"), // FO4 - SUB_RLDM = fourCC("RLDM"), // FO4 - SUB_RNAM = fourCC("RNAM"), - SUB_RNMV = fourCC("RNMV"), - SUB_RPLD = fourCC("RPLD"), - SUB_RPLI = fourCC("RPLI"), - SUB_RPRF = fourCC("RPRF"), - SUB_RPRM = fourCC("RPRM"), - SUB_RVIS = fourCC("RVIS"), // FO4 - SUB_SADD = fourCC("SADD"), // FO4 - SUB_SAKD = fourCC("SAKD"), // FO4 - SUB_SAPT = fourCC("SAPT"), // FO4 - SUB_SCDA = fourCC("SCDA"), - SUB_SCHD = fourCC("SCHD"), - SUB_SCHR = fourCC("SCHR"), - SUB_SCIT = fourCC("SCIT"), - SUB_SCQS = fourCC("SCQS"), // FO4 - SUB_SCRI = fourCC("SCRI"), - SUB_SCRN = fourCC("SCRN"), - SUB_SCRO = fourCC("SCRO"), - SUB_SCRV = fourCC("SCRV"), // FONV - SUB_SCTX = fourCC("SCTX"), - SUB_SCVR = fourCC("SCVR"), // FONV - SUB_SDSC = fourCC("SDSC"), - SUB_SGNM = fourCC("SGNM"), // FO4 - SUB_SHRT = fourCC("SHRT"), - SUB_SLCP = fourCC("SLCP"), - SUB_SLSD = fourCC("SLSD"), // FONV - SUB_SNAM = fourCC("SNAM"), - SUB_SNDD = fourCC("SNDD"), - SUB_SNDX = fourCC("SNDX"), - SUB_SNMV = fourCC("SNMV"), - SUB_SOFT = fourCC("SOFT"), - SUB_SOUL = fourCC("SOUL"), - SUB_SPCT = fourCC("SPCT"), - SUB_SPED = fourCC("SPED"), - SUB_SPIT = fourCC("SPIT"), - SUB_SPLO = fourCC("SPLO"), - SUB_SPMV = fourCC("SPMV"), // TES5 - SUB_SPOR = fourCC("SPOR"), - SUB_SRAC = fourCC("SRAC"), // FO4 - SUB_SRAF = fourCC("SRAF"), // FO4 - SUB_SSPN = fourCC("SSPN"), // FO4 - SUB_STCP = fourCC("STCP"), // FO4 - SUB_STKD = fourCC("STKD"), // FO4 - SUB_STOL = fourCC("STOL"), // TES5 - SUB_STOP = fourCC("STOP"), // FO4 - SUB_STSC = fourCC("STSC"), // FO4 - SUB_SWMV = fourCC("SWMV"), - SUB_TCFU = fourCC("TCFU"), // FONV - SUB_TCLF = fourCC("TCLF"), - SUB_TCLT = fourCC("TCLT"), - SUB_TDUM = fourCC("TDUM"), // FONV - SUB_TEND = fourCC("TEND"), // FO4 - SUB_TETI = fourCC("TETI"), // FO4 - SUB_TIAS = fourCC("TIAS"), - SUB_TIFC = fourCC("TIFC"), // TES5 - SUB_TINC = fourCC("TINC"), - SUB_TIND = fourCC("TIND"), - SUB_TINI = fourCC("TINI"), - SUB_TINL = fourCC("TINL"), - SUB_TINP = fourCC("TINP"), - SUB_TINT = fourCC("TINT"), - SUB_TINV = fourCC("TINV"), - SUB_TIQS = fourCC("TIQS"), // FO4 - SUB_TIRS = fourCC("TIRS"), - SUB_TNAM = fourCC("TNAM"), - SUB_TPIC = fourCC("TPIC"), - SUB_TPLT = fourCC("TPLT"), - SUB_TPTA = fourCC("TPTA"), // FO4 - SUB_TRDA = fourCC("TRDA"), // FO4 - SUB_TRDT = fourCC("TRDT"), - SUB_TSCE = fourCC("TSCE"), // FO4 - SUB_TTEB = fourCC("TTEB"), // FO4 - SUB_TTEC = fourCC("TTEC"), // FO4 - SUB_TTED = fourCC("TTED"), // FO4 - SUB_TTEF = fourCC("TTEF"), // FO4 - SUB_TTET = fourCC("TTET"), // FO4 - SUB_TTGE = fourCC("TTGE"), // FO4 - SUB_TTGP = fourCC("TTGP"), // FO4 - SUB_TVDT = fourCC("TVDT"), - SUB_TWAT = fourCC("TWAT"), // TES5 - SUB_TX00 = fourCC("TX00"), - SUB_TX01 = fourCC("TX01"), - SUB_TX02 = fourCC("TX02"), - SUB_TX03 = fourCC("TX03"), - SUB_TX04 = fourCC("TX04"), - SUB_TX05 = fourCC("TX05"), - SUB_TX06 = fourCC("TX06"), - SUB_TX07 = fourCC("TX07"), - SUB_UNAM = fourCC("UNAM"), - SUB_UNES = fourCC("UNES"), - SUB_UNWP = fourCC("UNWP"), // FO4 - SUB_VANM = fourCC("VANM"), // FONV - SUB_VATS = fourCC("VATS"), // FONV - SUB_VCLR = fourCC("VCLR"), - SUB_VENC = fourCC("VENC"), // TES5 - SUB_VEND = fourCC("VEND"), // TES5 - SUB_VENV = fourCC("VENV"), // TES5 - SUB_VHGT = fourCC("VHGT"), - SUB_VISI = fourCC("VISI"), // FO4 - SUB_VMAD = fourCC("VMAD"), - SUB_VNAM = fourCC("VNAM"), - SUB_VNML = fourCC("VNML"), - SUB_VTCK = fourCC("VTCK"), - SUB_VTEX = fourCC("VTEX"), - SUB_VTXT = fourCC("VTXT"), - SUB_WAIT = fourCC("WAIT"), // TES5 - SUB_WAMD = fourCC("WAMD"), // FO4 - SUB_WBDT = fourCC("WBDT"), - SUB_WCTR = fourCC("WCTR"), - SUB_WGDR = fourCC("WGDR"), // FO4 - SUB_WKMV = fourCC("WKMV"), - SUB_WLEV = fourCC("WLEV"), // FO4 - SUB_WLST = fourCC("WLST"), - SUB_WMAP = fourCC("WMAP"), // FO4 - SUB_WMI1 = fourCC("WMI1"), // FONV - SUB_WMI2 = fourCC("WMI2"), // FONV - SUB_WMI3 = fourCC("WMI3"), // FONV - SUB_WMS1 = fourCC("WMS1"), // FONV - SUB_WMS2 = fourCC("WMS2"), // FONV - SUB_WNAM = fourCC("WNAM"), - SUB_WNM1 = fourCC("WNM1"), // FONV - SUB_WNM2 = fourCC("WNM2"), // FONV - SUB_WNM3 = fourCC("WNM3"), // FONV - SUB_WNM4 = fourCC("WNM4"), // FONV - SUB_WNM5 = fourCC("WNM5"), // FONV - SUB_WNM6 = fourCC("WNM6"), // FONV - SUB_WNM7 = fourCC("WNM7"), // FONV - SUB_WZMD = fourCC("WZMD"), // FO4 - SUB_XACT = fourCC("XACT"), - SUB_XALP = fourCC("XALP"), - SUB_XAMC = fourCC("XAMC"), // FO3 - SUB_XAMT = fourCC("XAMT"), // FO3 - SUB_XAPD = fourCC("XAPD"), - SUB_XAPR = fourCC("XAPR"), - SUB_XASP = fourCC("XASP"), // FO4 - SUB_XATO = fourCC("XATO"), // FONV - SUB_XATP = fourCC("XATP"), // FO4 - SUB_XATR = fourCC("XATR"), - SUB_XBSD = fourCC("XBSD"), // FO4 - SUB_XCAS = fourCC("XCAS"), - SUB_XCCM = fourCC("XCCM"), - SUB_XCCP = fourCC("XCCP"), - SUB_XCET = fourCC("XCET"), // FO3 - SUB_XCGD = fourCC("XCGD"), - SUB_XCHG = fourCC("XCHG"), // thievery.exp - SUB_XCIM = fourCC("XCIM"), - SUB_XCLC = fourCC("XCLC"), - SUB_XCLL = fourCC("XCLL"), - SUB_XCLP = fourCC("XCLP"), // FO3 - SUB_XCLR = fourCC("XCLR"), - SUB_XCLW = fourCC("XCLW"), - SUB_XCMO = fourCC("XCMO"), - SUB_XCMT = fourCC("XCMT"), // TES4 only? - SUB_XCNT = fourCC("XCNT"), - SUB_XCRI = fourCC("XCRI"), // FO4 - SUB_XCVL = fourCC("XCVL"), - SUB_XCVR = fourCC("XCVR"), - SUB_XCWT = fourCC("XCWT"), - SUB_XCZA = fourCC("XCZA"), - SUB_XCZC = fourCC("XCZC"), - SUB_XCZR = fourCC("XCZR"), // TES5 - SUB_XDCR = fourCC("XDCR"), // FO3 - SUB_XEMI = fourCC("XEMI"), - SUB_XESP = fourCC("XESP"), - SUB_XEZN = fourCC("XEZN"), - SUB_XFVC = fourCC("XFVC"), - SUB_XGDR = fourCC("XGDR"), // FO4 - SUB_XGLB = fourCC("XGLB"), - SUB_XHLP = fourCC("XHLP"), // FO3 - SUB_XHLT = fourCC("XHLT"), // Unofficial Oblivion Patch - SUB_XHOR = fourCC("XHOR"), - SUB_XHRS = fourCC("XHRS"), - SUB_XHTW = fourCC("XHTW"), - SUB_XIBS = fourCC("XIBS"), // FO3 - SUB_XILL = fourCC("XILL"), - SUB_XILW = fourCC("XILW"), // FO4 - SUB_XIS2 = fourCC("XIS2"), - SUB_XLCM = fourCC("XLCM"), - SUB_XLCN = fourCC("XLCN"), - SUB_XLIB = fourCC("XLIB"), - SUB_XLIG = fourCC("XLIG"), - SUB_XLKR = fourCC("XLKR"), - SUB_XLKT = fourCC("XLKT"), // FO4 - SUB_XLOC = fourCC("XLOC"), - SUB_XLOD = fourCC("XLOD"), - SUB_XLRL = fourCC("XLRL"), - SUB_XLRM = fourCC("XLRM"), - SUB_XLRT = fourCC("XLRT"), - SUB_XLTW = fourCC("XLTW"), - SUB_XLYR = fourCC("XLYR"), // FO4 - SUB_XMBO = fourCC("XMBO"), - SUB_XMBP = fourCC("XMBP"), - SUB_XMBR = fourCC("XMBR"), - SUB_XMRC = fourCC("XMRC"), - SUB_XMRK = fourCC("XMRK"), - SUB_XMSP = fourCC("XMSP"), // FO4 - SUB_XNAM = fourCC("XNAM"), - SUB_XNDP = fourCC("XNDP"), - SUB_XOCP = fourCC("XOCP"), - SUB_XORD = fourCC("XORD"), // FO3 - SUB_XOWN = fourCC("XOWN"), - SUB_XPCI = fourCC("XPCI"), - SUB_XPDD = fourCC("XPDD"), // FO4 - SUB_XPLK = fourCC("XPLK"), // FO4 - SUB_XPOD = fourCC("XPOD"), - SUB_XPPA = fourCC("XPPA"), - SUB_XPRD = fourCC("XPRD"), - SUB_XPRI = fourCC("XPRI"), // FO4 - SUB_XPRM = fourCC("XPRM"), - SUB_XPTL = fourCC("XPTL"), - SUB_XPWR = fourCC("XPWR"), - SUB_XRAD = fourCC("XRAD"), // FO3 - SUB_XRDO = fourCC("XRDO"), // FO3 - SUB_XRDS = fourCC("XRDS"), - SUB_XRFG = fourCC("XRFG"), // FO4 - SUB_XRGB = fourCC("XRGB"), - SUB_XRGD = fourCC("XRGD"), - SUB_XRMR = fourCC("XRMR"), - SUB_XRNK = fourCC("XRNK"), // TES4 only? - SUB_XRTM = fourCC("XRTM"), - SUB_XSCL = fourCC("XSCL"), - SUB_XSED = fourCC("XSED"), - SUB_XSPC = fourCC("XSPC"), - SUB_XSRD = fourCC("XSRD"), // FONV - SUB_XSRF = fourCC("XSRF"), // FONV - SUB_XTEL = fourCC("XTEL"), - SUB_XTNM = fourCC("XTNM"), - SUB_XTRG = fourCC("XTRG"), - SUB_XTRI = fourCC("XTRI"), - SUB_XWCN = fourCC("XWCN"), - SUB_XWCS = fourCC("XWCS"), - SUB_XWCU = fourCC("XWCU"), - SUB_XWEM = fourCC("XWEM"), - SUB_XWPG = fourCC("XWPG"), // FO4 - SUB_XWPN = fourCC("XWPN"), // FO4 - SUB_XXXX = fourCC("XXXX"), - SUB_YNAM = fourCC("YNAM"), - SUB_ZNAM = fourCC("ZNAM"), - }; - // Based on http://www.uesp.net/wiki/Tes5Mod:Mod_File_Format#Records enum RecordFlag { diff --git a/components/esm4/loadachr.cpp b/components/esm4/loadachr.cpp index dc181dda4b..6ccdc0a8c7 100644 --- a/components/esm4/loadachr.cpp +++ b/components/esm4/loadachr.cpp @@ -42,22 +42,22 @@ void ESM4::ActorCharacter::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getZString(mFullName); break; - case ESM4::SUB_NAME: + case ESM::fourCC("NAME"): reader.getFormId(mBaseObj); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mPos); break; - case ESM4::SUB_XSCL: + case ESM::fourCC("XSCL"): reader.get(mScale); break; - case ESM4::SUB_XOWN: + case ESM::fourCC("XOWN"): { switch (subHdr.dataSize) { @@ -78,57 +78,57 @@ void ESM4::ActorCharacter::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_XESP: + case ESM::fourCC("XESP"): reader.getFormId(mEsp.parent); reader.get(mEsp.flags); break; - case ESM4::SUB_XCNT: + case ESM::fourCC("XCNT"): { reader.get(mCount); break; } - case ESM4::SUB_XRGD: // ragdoll - case ESM4::SUB_XRGB: // ragdoll biped - case ESM4::SUB_XHRS: // horse formId - case ESM4::SUB_XMRC: // merchant container formId + case ESM::fourCC("XRGD"): // ragdoll + case ESM::fourCC("XRGB"): // ragdoll biped + case ESM::fourCC("XHRS"): // horse formId + case ESM::fourCC("XMRC"): // merchant container formId // TES5 - case ESM4::SUB_XAPD: // activation parent - case ESM4::SUB_XAPR: // active parent - case ESM4::SUB_XEZN: // encounter zone - case ESM4::SUB_XHOR: - case ESM4::SUB_XLCM: // levelled creature - case ESM4::SUB_XLCN: // location - case ESM4::SUB_XLKR: // location route? - case ESM4::SUB_XLRT: // location type + case ESM::fourCC("XAPD"): // activation parent + case ESM::fourCC("XAPR"): // active parent + case ESM::fourCC("XEZN"): // encounter zone + case ESM::fourCC("XHOR"): + case ESM::fourCC("XLCM"): // levelled creature + case ESM::fourCC("XLCN"): // location + case ESM::fourCC("XLKR"): // location route? + case ESM::fourCC("XLRT"): // location type // - case ESM4::SUB_XPRD: - case ESM4::SUB_XPPA: - case ESM4::SUB_INAM: - case ESM4::SUB_PDTO: + case ESM::fourCC("XPRD"): + case ESM::fourCC("XPPA"): + case ESM::fourCC("INAM"): + case ESM::fourCC("PDTO"): // - case ESM4::SUB_XIS2: - case ESM4::SUB_XPCI: // formId - case ESM4::SUB_XLOD: - case ESM4::SUB_VMAD: - case ESM4::SUB_XLRL: // Unofficial Skyrim Patch - case ESM4::SUB_XRDS: // FO3 - case ESM4::SUB_XIBS: // FO3 - case ESM4::SUB_SCHR: // FO3 - case ESM4::SUB_TNAM: // FO3 - case ESM4::SUB_XATO: // FONV - case ESM4::SUB_MNAM: // FO4 - case ESM4::SUB_XATP: // FO4 - case ESM4::SUB_XEMI: // FO4 - case ESM4::SUB_XFVC: // FO4 - case ESM4::SUB_XHLT: // FO4 - case ESM4::SUB_XHTW: // FO4 - case ESM4::SUB_XLKT: // FO4 - case ESM4::SUB_XLYR: // FO4 - case ESM4::SUB_XMBR: // FO4 - case ESM4::SUB_XMSP: // FO4 - case ESM4::SUB_XPLK: // FO4 - case ESM4::SUB_XRFG: // FO4 - case ESM4::SUB_XRNK: // FO4 + case ESM::fourCC("XIS2"): + case ESM::fourCC("XPCI"): // formId + case ESM::fourCC("XLOD"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("XLRL"): // Unofficial Skyrim Patch + case ESM::fourCC("XRDS"): // FO3 + case ESM::fourCC("XIBS"): // FO3 + case ESM::fourCC("SCHR"): // FO3 + case ESM::fourCC("TNAM"): // FO3 + case ESM::fourCC("XATO"): // FONV + case ESM::fourCC("MNAM"): // FO4 + case ESM::fourCC("XATP"): // FO4 + case ESM::fourCC("XEMI"): // FO4 + case ESM::fourCC("XFVC"): // FO4 + case ESM::fourCC("XHLT"): // FO4 + case ESM::fourCC("XHTW"): // FO4 + case ESM::fourCC("XLKT"): // FO4 + case ESM::fourCC("XLYR"): // FO4 + case ESM::fourCC("XMBR"): // FO4 + case ESM::fourCC("XMSP"): // FO4 + case ESM::fourCC("XPLK"): // FO4 + case ESM::fourCC("XRFG"): // FO4 + case ESM::fourCC("XRNK"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadachr.hpp b/components/esm4/loadachr.hpp index 8abb47c8bc..526ab4c057 100644 --- a/components/esm4/loadachr.hpp +++ b/components/esm4/loadachr.hpp @@ -30,6 +30,7 @@ #include #include +#include #include #include "reference.hpp" // Placement, EnableParent diff --git a/components/esm4/loadacti.cpp b/components/esm4/loadacti.cpp index 74eaff2dab..0609e4e1bf 100644 --- a/components/esm4/loadacti.cpp +++ b/components/esm4/loadacti.cpp @@ -41,69 +41,69 @@ void ESM4::Activator::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.getFormId(mLoopingSound); break; - case ESM4::SUB_VNAM: + case ESM::fourCC("VNAM"): reader.getFormId(mActivationSound); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_INAM: + case ESM::fourCC("INAM"): reader.getFormId(mRadioTemplate); break; // FONV - case ESM4::SUB_RNAM: + case ESM::fourCC("RNAM"): reader.getFormId(mRadioStation); break; - case ESM4::SUB_XATO: + case ESM::fourCC("XATO"): reader.getZString(mActivationPrompt); break; // FONV - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_FNAM: - case ESM4::SUB_KNAM: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_OBND: - case ESM4::SUB_PNAM: - case ESM4::SUB_VMAD: - case ESM4::SUB_WNAM: - case ESM4::SUB_CTDA: - case ESM4::SUB_CIS1: - case ESM4::SUB_CIS2: - case ESM4::SUB_CITC: - case ESM4::SUB_NVNM: - case ESM4::SUB_ATTX: // FO4 - case ESM4::SUB_FTYP: // FO4 - case ESM4::SUB_NTRM: // FO4 - case ESM4::SUB_PTRN: // FO4 - case ESM4::SUB_PRPS: // FO4 - case ESM4::SUB_RADR: // FO4 - case ESM4::SUB_STCP: // FO4 + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("FNAM"): + case ESM::fourCC("KNAM"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("OBND"): + case ESM::fourCC("PNAM"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("WNAM"): + case ESM::fourCC("CTDA"): + case ESM::fourCC("CIS1"): + case ESM::fourCC("CIS2"): + case ESM::fourCC("CITC"): + case ESM::fourCC("NVNM"): + case ESM::fourCC("ATTX"): // FO4 + case ESM::fourCC("FTYP"): // FO4 + case ESM::fourCC("NTRM"): // FO4 + case ESM::fourCC("PTRN"): // FO4 + case ESM::fourCC("PRPS"): // FO4 + case ESM::fourCC("RADR"): // FO4 + case ESM::fourCC("STCP"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadalch.cpp b/components/esm4/loadalch.cpp index 1ecfda25e8..4a289ab760 100644 --- a/components/esm4/loadalch.cpp +++ b/components/esm4/loadalch.cpp @@ -42,35 +42,35 @@ void ESM4::Potion::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_MICO: + case ESM::fourCC("MICO"): reader.getZString(mMiniIcon); break; // FO3 - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_SCIT: + case ESM::fourCC("SCIT"): reader.get(mEffect); reader.adjustFormId(mEffect.formId); break; - case ESM4::SUB_ENIT: + case ESM::fourCC("ENIT"): if (subHdr.dataSize == 8) // TES4 { reader.get(&mItem, 8); @@ -82,36 +82,36 @@ void ESM4::Potion::load(ESM4::Reader& reader) reader.adjustFormId(mItem.withdrawl); reader.adjustFormId(mItem.sound); break; - case ESM4::SUB_YNAM: + case ESM::fourCC("YNAM"): reader.getFormId(mPickUpSound); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mDropSound); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_EFID: - case ESM4::SUB_EFIT: - case ESM4::SUB_CTDA: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_OBND: - case ESM4::SUB_ETYP: // FO3 - case ESM4::SUB_DESC: - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_DNAM: // FO4 - case ESM4::SUB_PTRN: // FO4 - case ESM4::SUB_CUSD: // FO4 + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("EFID"): + case ESM::fourCC("EFIT"): + case ESM::fourCC("CTDA"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("OBND"): + case ESM::fourCC("ETYP"): // FO3 + case ESM::fourCC("DESC"): + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("DNAM"): // FO4 + case ESM::fourCC("PTRN"): // FO4 + case ESM::fourCC("CUSD"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadaloc.cpp b/components/esm4/loadaloc.cpp index 690684df7c..1b3bfd7e5b 100644 --- a/components/esm4/loadaloc.cpp +++ b/components/esm4/loadaloc.cpp @@ -42,34 +42,34 @@ void ESM4::MediaLocationController::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getZString(mFullName); break; - case ESM4::SUB_GNAM: + case ESM::fourCC("GNAM"): reader.getFormId(mBattleSets.emplace_back()); break; - case ESM4::SUB_LNAM: + case ESM::fourCC("LNAM"): reader.getFormId(mLocationSets.emplace_back()); break; - case ESM4::SUB_YNAM: + case ESM::fourCC("YNAM"): reader.getFormId(mEnemySets.emplace_back()); break; - case ESM4::SUB_HNAM: + case ESM::fourCC("HNAM"): reader.getFormId(mNeutralSets.emplace_back()); break; - case ESM4::SUB_XNAM: + case ESM::fourCC("XNAM"): reader.getFormId(mFriendSets.emplace_back()); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mAllySets.emplace_back()); break; - case ESM4::SUB_RNAM: + case ESM::fourCC("RNAM"): reader.getFormId(mConditionalFaction); break; - case ESM4::SUB_NAM1: + case ESM::fourCC("NAM1"): { reader.get(mMediaFlags); std::uint8_t flags = mMediaFlags.loopingOptions; @@ -77,21 +77,21 @@ void ESM4::MediaLocationController::load(ESM4::Reader& reader) mMediaFlags.factionNotFound = flags & 0x0F; // WARN: overwriting data break; } - case ESM4::SUB_NAM4: + case ESM::fourCC("NAM4"): reader.get(mLocationDelay); break; - case ESM4::SUB_NAM7: + case ESM::fourCC("NAM7"): reader.get(mRetriggerDelay); break; - case ESM4::SUB_NAM5: + case ESM::fourCC("NAM5"): reader.get(mDayStart); break; - case ESM4::SUB_NAM6: + case ESM::fourCC("NAM6"): reader.get(mNightStart); break; - case ESM4::SUB_NAM2: // always 0? 4 bytes - case ESM4::SUB_NAM3: // always 0? 4 bytes - case ESM4::SUB_FNAM: // always 0? 4 bytes + case ESM::fourCC("NAM2"): // always 0? 4 bytes + case ESM::fourCC("NAM3"): // always 0? 4 bytes + case ESM::fourCC("FNAM"): // always 0? 4 bytes { #if 0 std::vector mDataBuf(subHdr.dataSize); diff --git a/components/esm4/loadammo.cpp b/components/esm4/loadammo.cpp index 8c5bc45c85..39c42fc83f 100644 --- a/components/esm4/loadammo.cpp +++ b/components/esm4/loadammo.cpp @@ -41,13 +41,13 @@ void ESM4::Ammunition::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): switch (subHdr.dataSize) { case 18: // TES4 @@ -86,7 +86,7 @@ void ESM4::Ammunition::load(ESM4::Reader& reader) break; } break; - case ESM4::SUB_DAT2: + case ESM::fourCC("DAT2"): if (subHdr.dataSize == 20) { reader.get(mData.mProjPerShot); @@ -100,71 +100,71 @@ void ESM4::Ammunition::load(ESM4::Reader& reader) reader.skipSubRecordData(); } break; - case ESM4::SUB_DNAM: + case ESM::fourCC("DNAM"): reader.getFormId(mData.mProjectile); reader.get(mData.mFlags); mData.mFlags &= 0xFF; reader.get(mData.mDamage); reader.get(mData.mHealth); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_MICO: + case ESM::fourCC("MICO"): reader.getZString(mMiniIcon); break; // FO3 - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_ANAM: + case ESM::fourCC("ANAM"): reader.get(mEnchantmentPoints); break; - case ESM4::SUB_ENAM: + case ESM::fourCC("ENAM"): reader.getFormId(mEnchantment); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_DESC: + case ESM::fourCC("DESC"): reader.getLocalizedString(mText); break; - case ESM4::SUB_YNAM: + case ESM::fourCC("YNAM"): reader.getFormId(mPickUpSound); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mDropSound); break; - case ESM4::SUB_ONAM: + case ESM::fourCC("ONAM"): reader.getLocalizedString(mShortName); break; - case ESM4::SUB_QNAM: // FONV + case ESM::fourCC("QNAM"): // FONV reader.getLocalizedString(mAbbrev); break; - case ESM4::SUB_RCIL: + case ESM::fourCC("RCIL"): reader.getFormId(mAmmoEffects.emplace_back()); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScript); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_OBND: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_PTRN: // FO4 - case ESM4::SUB_NAM1: // FO4 casing model data - case ESM4::SUB_NAM2: // + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("OBND"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("PTRN"): // FO4 + case ESM::fourCC("NAM1"): // FO4 casing model data + case ESM::fourCC("NAM2"): // reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadanio.cpp b/components/esm4/loadanio.cpp index fa440f5ace..a8156eef2b 100644 --- a/components/esm4/loadanio.cpp +++ b/components/esm4/loadanio.cpp @@ -41,25 +41,25 @@ void ESM4::AnimObject::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_BNAM: + case ESM::fourCC("BNAM"): reader.getZString(mUnloadEvent); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.getFormId(mIdleAnim); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadappa.cpp b/components/esm4/loadappa.cpp index 45e12739b9..8c74d020a6 100644 --- a/components/esm4/loadappa.cpp +++ b/components/esm4/loadappa.cpp @@ -41,13 +41,13 @@ void ESM4::Apparatus::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) { reader.get(mData.value); @@ -61,24 +61,24 @@ void ESM4::Apparatus::load(ESM4::Reader& reader) reader.get(mData.quality); } break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_DESC: + case ESM::fourCC("DESC"): reader.getLocalizedString(mText); break; - case ESM4::SUB_MODT: - case ESM4::SUB_OBND: - case ESM4::SUB_QUAL: + case ESM::fourCC("MODT"): + case ESM::fourCC("OBND"): + case ESM::fourCC("QUAL"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadarma.cpp b/components/esm4/loadarma.cpp index 2bb6240ee8..a1a1a10845 100644 --- a/components/esm4/loadarma.cpp +++ b/components/esm4/loadarma.cpp @@ -43,39 +43,39 @@ void ESM4::ArmorAddon::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_MOD2: + case ESM::fourCC("MOD2"): reader.getZString(mModelMale); break; - case ESM4::SUB_MOD3: + case ESM::fourCC("MOD3"): reader.getZString(mModelFemale); break; - case ESM4::SUB_MOD4: - case ESM4::SUB_MOD5: + case ESM::fourCC("MOD4"): + case ESM::fourCC("MOD5"): { std::string model; reader.getZString(model); break; } - case ESM4::SUB_NAM0: + case ESM::fourCC("NAM0"): reader.getFormId(mTextureMale); break; - case ESM4::SUB_NAM1: + case ESM::fourCC("NAM1"): reader.getFormId(mTextureFemale); break; - case ESM4::SUB_RNAM: + case ESM::fourCC("RNAM"): reader.getFormId(mRacePrimary); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): if ((esmVer == ESM::VER_094 || esmVer == ESM::VER_170) && subHdr.dataSize == 4) // TES5 reader.getFormId(mRaces.emplace_back()); else reader.skipSubRecordData(); // FIXME: this should be mModelMale for FO3/FONV break; - case ESM4::SUB_BODT: // body template + case ESM::fourCC("BODT"): // body template reader.get(mBodyTemplate.bodyPart); reader.get(mBodyTemplate.flags); reader.get(mBodyTemplate.unknown1); // probably padding @@ -83,7 +83,7 @@ void ESM4::ArmorAddon::load(ESM4::Reader& reader) reader.get(mBodyTemplate.unknown3); // probably padding reader.get(mBodyTemplate.type); break; - case ESM4::SUB_BOD2: // TES5+ + case ESM::fourCC("BOD2"): // TES5+ reader.get(mBodyTemplate.bodyPart); mBodyTemplate.flags = 0; mBodyTemplate.unknown1 = 0; // probably padding @@ -94,7 +94,7 @@ void ESM4::ArmorAddon::load(ESM4::Reader& reader) reader.get(mBodyTemplate.type); break; - case ESM4::SUB_DNAM: + case ESM::fourCC("DNAM"): if (subHdr.dataSize == 12) { std::uint16_t unknownInt16; @@ -111,40 +111,40 @@ void ESM4::ArmorAddon::load(ESM4::Reader& reader) else reader.skipSubRecordData(); break; - case ESM4::SUB_MO2T: // FIXME: should group with MOD2 - case ESM4::SUB_MO2S: // FIXME: should group with MOD2 - case ESM4::SUB_MO2C: // FIXME: should group with MOD2 - case ESM4::SUB_MO2F: // FIXME: should group with MOD2 - case ESM4::SUB_MO3T: // FIXME: should group with MOD3 - case ESM4::SUB_MO3S: // FIXME: should group with MOD3 - case ESM4::SUB_MO3C: // FIXME: should group with MOD3 - case ESM4::SUB_MO3F: // FIXME: should group with MOD3 - case ESM4::SUB_MOSD: // FO3 // FIXME: should group with MOD3 - case ESM4::SUB_MO4T: // FIXME: should group with MOD4 - case ESM4::SUB_MO4S: // FIXME: should group with MOD4 - case ESM4::SUB_MO4C: // FIXME: should group with MOD4 - case ESM4::SUB_MO4F: // FIXME: should group with MOD4 - case ESM4::SUB_MO5T: - case ESM4::SUB_MO5S: - case ESM4::SUB_MO5C: - case ESM4::SUB_MO5F: - case ESM4::SUB_NAM2: // txst formid male - case ESM4::SUB_NAM3: // txst formid female - case ESM4::SUB_SNDD: // footset sound formid - case ESM4::SUB_BMDT: // FO3 - case ESM4::SUB_DATA: // FO3 - case ESM4::SUB_ETYP: // FO3 - case ESM4::SUB_FULL: // FO3 - case ESM4::SUB_ICO2: // FO3 // female - case ESM4::SUB_ICON: // FO3 // male - case ESM4::SUB_MODT: // FO3 // FIXME: should group with MODL - case ESM4::SUB_MODS: // FO3 // FIXME: should group with MODL - case ESM4::SUB_MODD: // FO3 // FIXME: should group with MODL - case ESM4::SUB_OBND: // FO3 - case ESM4::SUB_BSMB: // FO4 - case ESM4::SUB_BSMP: // FO4 - case ESM4::SUB_BSMS: // FO4 - case ESM4::SUB_ONAM: // FO4 + case ESM::fourCC("MO2T"): // FIXME: should group with MOD2 + case ESM::fourCC("MO2S"): // FIXME: should group with MOD2 + case ESM::fourCC("MO2C"): // FIXME: should group with MOD2 + case ESM::fourCC("MO2F"): // FIXME: should group with MOD2 + case ESM::fourCC("MO3T"): // FIXME: should group with MOD3 + case ESM::fourCC("MO3S"): // FIXME: should group with MOD3 + case ESM::fourCC("MO3C"): // FIXME: should group with MOD3 + case ESM::fourCC("MO3F"): // FIXME: should group with MOD3 + case ESM::fourCC("MOSD"): // FO3 // FIXME: should group with MOD3 + case ESM::fourCC("MO4T"): // FIXME: should group with MOD4 + case ESM::fourCC("MO4S"): // FIXME: should group with MOD4 + case ESM::fourCC("MO4C"): // FIXME: should group with MOD4 + case ESM::fourCC("MO4F"): // FIXME: should group with MOD4 + case ESM::fourCC("MO5T"): + case ESM::fourCC("MO5S"): + case ESM::fourCC("MO5C"): + case ESM::fourCC("MO5F"): + case ESM::fourCC("NAM2"): // txst formid male + case ESM::fourCC("NAM3"): // txst formid female + case ESM::fourCC("SNDD"): // footset sound formid + case ESM::fourCC("BMDT"): // FO3 + case ESM::fourCC("DATA"): // FO3 + case ESM::fourCC("ETYP"): // FO3 + case ESM::fourCC("FULL"): // FO3 + case ESM::fourCC("ICO2"): // FO3 // female + case ESM::fourCC("ICON"): // FO3 // male + case ESM::fourCC("MODT"): // FO3 // FIXME: should group with MODL + case ESM::fourCC("MODS"): // FO3 // FIXME: should group with MODL + case ESM::fourCC("MODD"): // FO3 // FIXME: should group with MODL + case ESM::fourCC("OBND"): // FO3 + case ESM::fourCC("BSMB"): // FO4 + case ESM::fourCC("BSMP"): // FO4 + case ESM::fourCC("BSMS"): // FO4 + case ESM::fourCC("ONAM"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadarmo.cpp b/components/esm4/loadarmo.cpp index 572cbd6ecd..dc926f7a01 100644 --- a/components/esm4/loadarmo.cpp +++ b/components/esm4/loadarmo.cpp @@ -41,13 +41,13 @@ void ESM4::Armor::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { switch (subHdr.dataSize) { @@ -69,12 +69,12 @@ void ESM4::Armor::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_INDX: // FO4 + case ESM::fourCC("INDX"): // FO4 { reader.get(currentIndex); break; } - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): { if (subHdr.dataSize == 4) { @@ -97,28 +97,28 @@ void ESM4::Armor::load(ESM4::Reader& reader) break; } - case ESM4::SUB_MOD2: + case ESM::fourCC("MOD2"): reader.getZString(mModelMaleWorld); break; - case ESM4::SUB_MOD3: + case ESM::fourCC("MOD3"): reader.getZString(mModelFemale); break; - case ESM4::SUB_MOD4: + case ESM::fourCC("MOD4"): reader.getZString(mModelFemaleWorld); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIconMale); break; - case ESM4::SUB_MICO: + case ESM::fourCC("MICO"): reader.getZString(mMiniIconMale); break; - case ESM4::SUB_ICO2: + case ESM::fourCC("ICO2"): reader.getZString(mIconFemale); break; - case ESM4::SUB_MIC2: + case ESM::fourCC("MIC2"): reader.getZString(mMiniIconFemale); break; - case ESM4::SUB_BMDT: + case ESM::fourCC("BMDT"): if (subHdr.dataSize == 8) // FO3 { reader.get(mArmorFlags); @@ -133,7 +133,7 @@ void ESM4::Armor::load(ESM4::Reader& reader) mGeneralFlags |= TYPE_TES4; } break; - case ESM4::SUB_BODT: + case ESM::fourCC("BODT"): { reader.get(mArmorFlags); uint32_t flags = 0; @@ -146,7 +146,7 @@ void ESM4::Armor::load(ESM4::Reader& reader) mGeneralFlags |= TYPE_TES5; break; } - case ESM4::SUB_BOD2: + case ESM::fourCC("BOD2"): // FO4, TES5 if (subHdr.dataSize == 4 || subHdr.dataSize == 8) { @@ -163,75 +163,75 @@ void ESM4::Armor::load(ESM4::Reader& reader) reader.skipSubRecordData(); } break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_ANAM: + case ESM::fourCC("ANAM"): reader.get(mEnchantmentPoints); break; - case ESM4::SUB_ENAM: + case ESM::fourCC("ENAM"): reader.getFormId(mEnchantment); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_DESC: + case ESM::fourCC("DESC"): reader.getLocalizedString(mText); break; - case ESM4::SUB_YNAM: + case ESM::fourCC("YNAM"): reader.getFormId(mPickUpSound); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mDropSound); break; - case ESM4::SUB_MODT: - case ESM4::SUB_MO2B: - case ESM4::SUB_MO3B: - case ESM4::SUB_MO4B: - case ESM4::SUB_MO2T: - case ESM4::SUB_MO2S: - case ESM4::SUB_MO3T: - case ESM4::SUB_MO4T: - case ESM4::SUB_MO4S: - case ESM4::SUB_OBND: - case ESM4::SUB_RNAM: // race formid - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_TNAM: - case ESM4::SUB_DNAM: - case ESM4::SUB_BAMT: - case ESM4::SUB_BIDS: - case ESM4::SUB_ETYP: - case ESM4::SUB_BMCT: - case ESM4::SUB_EAMT: - case ESM4::SUB_EITM: - case ESM4::SUB_VMAD: - case ESM4::SUB_REPL: // FO3 - case ESM4::SUB_BIPL: // FO3 - case ESM4::SUB_MODD: // FO3 - case ESM4::SUB_MOSD: // FO3 - case ESM4::SUB_MODS: // FO3 - case ESM4::SUB_MO3S: // FO3 - case ESM4::SUB_BNAM: // FONV - case ESM4::SUB_SNAM: // FONV - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_APPR: // FO4 - case ESM4::SUB_DAMA: // FO4 - case ESM4::SUB_FNAM: // FO4 - case ESM4::SUB_INRD: // FO4 - case ESM4::SUB_PTRN: // FO4 - case ESM4::SUB_OBTE: // FO4 object template start - case ESM4::SUB_OBTF: - case ESM4::SUB_OBTS: - case ESM4::SUB_STOP: // FO4 object template end + case ESM::fourCC("MODT"): + case ESM::fourCC("MO2B"): + case ESM::fourCC("MO3B"): + case ESM::fourCC("MO4B"): + case ESM::fourCC("MO2T"): + case ESM::fourCC("MO2S"): + case ESM::fourCC("MO3T"): + case ESM::fourCC("MO4T"): + case ESM::fourCC("MO4S"): + case ESM::fourCC("OBND"): + case ESM::fourCC("RNAM"): // race formid + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("TNAM"): + case ESM::fourCC("DNAM"): + case ESM::fourCC("BAMT"): + case ESM::fourCC("BIDS"): + case ESM::fourCC("ETYP"): + case ESM::fourCC("BMCT"): + case ESM::fourCC("EAMT"): + case ESM::fourCC("EITM"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("REPL"): // FO3 + case ESM::fourCC("BIPL"): // FO3 + case ESM::fourCC("MODD"): // FO3 + case ESM::fourCC("MOSD"): // FO3 + case ESM::fourCC("MODS"): // FO3 + case ESM::fourCC("MO3S"): // FO3 + case ESM::fourCC("BNAM"): // FONV + case ESM::fourCC("SNAM"): // FONV + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("APPR"): // FO4 + case ESM::fourCC("DAMA"): // FO4 + case ESM::fourCC("FNAM"): // FO4 + case ESM::fourCC("INRD"): // FO4 + case ESM::fourCC("PTRN"): // FO4 + case ESM::fourCC("OBTE"): // FO4 object template start + case ESM::fourCC("OBTF"): + case ESM::fourCC("OBTS"): + case ESM::fourCC("STOP"): // FO4 object template end reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadaspc.cpp b/components/esm4/loadaspc.cpp index d79df9d8ef..e8fe9d3b34 100644 --- a/components/esm4/loadaspc.cpp +++ b/components/esm4/loadaspc.cpp @@ -41,35 +41,35 @@ void ESM4::AcousticSpace::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_ANAM: + case ESM::fourCC("ANAM"): reader.get(mEnvironmentType); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.getFormId(mAmbientLoopSounds.emplace_back()); break; - case ESM4::SUB_RDAT: + case ESM::fourCC("RDAT"): reader.getFormId(mSoundRegion); break; - case ESM4::SUB_INAM: + case ESM::fourCC("INAM"): reader.get(mIsInterior); break; - case ESM4::SUB_XTRI: + case ESM::fourCC("XTRI"): std::uint8_t isInterior; reader.get(isInterior); mIsInterior = isInterior; break; - case ESM4::SUB_WNAM: + case ESM::fourCC("WNAM"): { // usually 0 for FONV (maybe # of close Actors for Walla to trigger) (4 bytes) // Weather attenuation in FO4 (2 bytes) reader.skipSubRecordData(); break; } - case ESM4::SUB_BNAM: // TES5 reverb formid - case ESM4::SUB_OBND: + case ESM::fourCC("BNAM"): // TES5 reverb formid + case ESM::fourCC("OBND"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadbook.cpp b/components/esm4/loadbook.cpp index f2842e5313..cef942074a 100644 --- a/components/esm4/loadbook.cpp +++ b/components/esm4/loadbook.cpp @@ -42,16 +42,16 @@ void ESM4::Book::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DESC: + case ESM::fourCC("DESC"): reader.getLocalizedString(mText); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { switch (subHdr.dataSize) { @@ -82,53 +82,53 @@ void ESM4::Book::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_ANAM: + case ESM::fourCC("ANAM"): reader.get(mEnchantmentPoints); break; - case ESM4::SUB_ENAM: + case ESM::fourCC("ENAM"): reader.getFormId(mEnchantment); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_YNAM: + case ESM::fourCC("YNAM"): reader.getFormId(mPickUpSound); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mDropSound); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_OBND: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_CNAM: - case ESM4::SUB_INAM: - case ESM4::SUB_VMAD: - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_DNAM: // FO4 - case ESM4::SUB_FIMD: // FO4 - case ESM4::SUB_MICO: // FO4 - case ESM4::SUB_PTRN: // FO4 + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("OBND"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("CNAM"): + case ESM::fourCC("INAM"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("DNAM"): // FO4 + case ESM::fourCC("FIMD"): // FO4 + case ESM::fourCC("MICO"): // FO4 + case ESM::fourCC("PTRN"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadbptd.cpp b/components/esm4/loadbptd.cpp index 509eadfcf1..5ff3b5908b 100644 --- a/components/esm4/loadbptd.cpp +++ b/components/esm4/loadbptd.cpp @@ -56,56 +56,56 @@ void ESM4::BodyPartData::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_BPTN: + case ESM::fourCC("BPTN"): reader.getLocalizedString(bodyPart.mPartName); break; - case ESM4::SUB_BPNN: + case ESM::fourCC("BPNN"): reader.getZString(bodyPart.mNodeName); break; - case ESM4::SUB_BPNT: + case ESM::fourCC("BPNT"): reader.getZString(bodyPart.mVATSTarget); break; - case ESM4::SUB_BPNI: + case ESM::fourCC("BPNI"): reader.getZString(bodyPart.mIKStartNode); break; - case ESM4::SUB_BPND: + case ESM::fourCC("BPND"): if (subHdr.dataSize == sizeof(bodyPart.mData)) reader.get(bodyPart.mData); // FIXME: FO4 else reader.skipSubRecordData(); break; - case ESM4::SUB_NAM1: + case ESM::fourCC("NAM1"): reader.getZString(bodyPart.mLimbReplacementModel); break; - case ESM4::SUB_NAM4: // FIXME: assumed occurs last + case ESM::fourCC("NAM4"): // FIXME: assumed occurs last reader.getZString(bodyPart.mGoreEffectsTarget); // target bone mBodyParts.push_back(bodyPart); // FIXME: possible without copying? bodyPart.clear(); break; - case ESM4::SUB_NAM5: - case ESM4::SUB_RAGA: // ragdoll - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_BNAM: // FO4 - case ESM4::SUB_CNAM: // FO4 - case ESM4::SUB_DNAM: // FO4 - case ESM4::SUB_ENAM: // FO4 - case ESM4::SUB_FNAM: // FO4 - case ESM4::SUB_INAM: // FO4 - case ESM4::SUB_JNAM: // FO4 - case ESM4::SUB_NAM2: // FO4 + case ESM::fourCC("NAM5"): + case ESM::fourCC("RAGA"): // ragdoll + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("BNAM"): // FO4 + case ESM::fourCC("CNAM"): // FO4 + case ESM::fourCC("DNAM"): // FO4 + case ESM::fourCC("ENAM"): // FO4 + case ESM::fourCC("FNAM"): // FO4 + case ESM::fourCC("INAM"): // FO4 + case ESM::fourCC("JNAM"): // FO4 + case ESM::fourCC("NAM2"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadcell.cpp b/components/esm4/loadcell.cpp index 0091ab0bd6..8106c1637f 100644 --- a/components/esm4/loadcell.cpp +++ b/components/esm4/loadcell.cpp @@ -78,7 +78,7 @@ void ESM4::Cell::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): { if (!reader.getZString(mEditorId)) throw std::runtime_error("CELL EDID data read error"); @@ -89,7 +89,7 @@ void ESM4::Cell::load(ESM4::Reader& reader) #endif break; } - case ESM4::SUB_XCLC: + case ESM::fourCC("XCLC"): { //(X, Y) grid location of the cell followed by flags. Always in // exterior cells and never in interior cells. @@ -114,10 +114,10 @@ void ESM4::Cell::load(ESM4::Reader& reader) break; } - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { if (subHdr.dataSize == 2) reader.get(mCellFlags); @@ -131,7 +131,7 @@ void ESM4::Cell::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_XCLR: // for exterior cells + case ESM::fourCC("XCLR"): // for exterior cells { mRegions.resize(subHdr.dataSize / sizeof(ESM::FormId32)); for (std::vector::iterator it = mRegions.begin(); it != mRegions.end(); ++it) @@ -145,7 +145,7 @@ void ESM4::Cell::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_XOWN: + case ESM::fourCC("XOWN"): { switch (subHdr.dataSize) { @@ -166,19 +166,19 @@ void ESM4::Cell::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_XGLB: + case ESM::fourCC("XGLB"): reader.getFormId(mGlobal); break; // Oblivion only? - case ESM4::SUB_XCCM: + case ESM::fourCC("XCCM"): reader.getFormId(mClimate); break; - case ESM4::SUB_XCWT: + case ESM::fourCC("XCWT"): reader.getFormId(mWater); break; - case ESM4::SUB_XCLW: + case ESM::fourCC("XCLW"): reader.get(mWaterHeight); break; - case ESM4::SUB_XCLL: + case ESM::fourCC("XCLL"): { switch (subHdr.dataSize) { @@ -197,45 +197,45 @@ void ESM4::Cell::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_XCMT: + case ESM::fourCC("XCMT"): reader.get(mMusicType); break; // Oblivion only? - case ESM4::SUB_LTMP: + case ESM::fourCC("LTMP"): reader.getFormId(mLightingTemplate); break; - case ESM4::SUB_LNAM: + case ESM::fourCC("LNAM"): reader.get(mLightingTemplateFlags); break; // seems to always follow LTMP - case ESM4::SUB_XCMO: + case ESM::fourCC("XCMO"): reader.getFormId(mMusic); break; - case ESM4::SUB_XCAS: + case ESM::fourCC("XCAS"): reader.getFormId(mAcousticSpace); break; - case ESM4::SUB_TVDT: - case ESM4::SUB_MHDT: - case ESM4::SUB_XCGD: - case ESM4::SUB_XNAM: - case ESM4::SUB_XLCN: - case ESM4::SUB_XWCS: - case ESM4::SUB_XWCU: - case ESM4::SUB_XWCN: - case ESM4::SUB_XCIM: - case ESM4::SUB_XEZN: - case ESM4::SUB_XWEM: - case ESM4::SUB_XILL: - case ESM4::SUB_XRNK: - case ESM4::SUB_XCET: // FO3 - case ESM4::SUB_IMPF: // FO3 Zeta - case ESM4::SUB_CNAM: // FO4 - case ESM4::SUB_PCMB: // FO4 - case ESM4::SUB_RVIS: // FO4 - case ESM4::SUB_VISI: // FO4 - case ESM4::SUB_XGDR: // FO4 - case ESM4::SUB_XILW: // FO4 - case ESM4::SUB_XCRI: // FO4 - case ESM4::SUB_XPRI: // FO4 - case ESM4::SUB_ZNAM: // FO4 + case ESM::fourCC("TVDT"): + case ESM::fourCC("MHDT"): + case ESM::fourCC("XCGD"): + case ESM::fourCC("XNAM"): + case ESM::fourCC("XLCN"): + case ESM::fourCC("XWCS"): + case ESM::fourCC("XWCU"): + case ESM::fourCC("XWCN"): + case ESM::fourCC("XCIM"): + case ESM::fourCC("XEZN"): + case ESM::fourCC("XWEM"): + case ESM::fourCC("XILL"): + case ESM::fourCC("XRNK"): + case ESM::fourCC("XCET"): // FO3 + case ESM::fourCC("IMPF"): // FO3 Zeta + case ESM::fourCC("CNAM"): // FO4 + case ESM::fourCC("PCMB"): // FO4 + case ESM::fourCC("RVIS"): // FO4 + case ESM::fourCC("VISI"): // FO4 + case ESM::fourCC("XGDR"): // FO4 + case ESM::fourCC("XILW"): // FO4 + case ESM::fourCC("XCRI"): // FO4 + case ESM::fourCC("XPRI"): // FO4 + case ESM::fourCC("ZNAM"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadclas.cpp b/components/esm4/loadclas.cpp index 7d232a0aa1..e80b36849e 100644 --- a/components/esm4/loadclas.cpp +++ b/components/esm4/loadclas.cpp @@ -41,21 +41,21 @@ void ESM4::Class::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DESC: + case ESM::fourCC("DESC"): reader.getLocalizedString(mDesc); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_DATA: - case ESM4::SUB_ATTR: - case ESM4::SUB_PRPS: + case ESM::fourCC("DATA"): + case ESM::fourCC("ATTR"): + case ESM::fourCC("PRPS"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadclfm.cpp b/components/esm4/loadclfm.cpp index bc887cd15c..b7157877c7 100644 --- a/components/esm4/loadclfm.cpp +++ b/components/esm4/loadclfm.cpp @@ -41,22 +41,22 @@ void ESM4::Colour::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_CNAM: + case ESM::fourCC("CNAM"): reader.get(mColour.red); reader.get(mColour.green); reader.get(mColour.blue); reader.get(mColour.custom); break; - case ESM4::SUB_FNAM: + case ESM::fourCC("FNAM"): reader.get(mPlayable); break; - case ESM4::SUB_CTDA: + case ESM::fourCC("CTDA"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadclot.cpp b/components/esm4/loadclot.cpp index c67ac3df6b..48999adb18 100644 --- a/components/esm4/loadclot.cpp +++ b/components/esm4/loadclot.cpp @@ -41,55 +41,55 @@ void ESM4::Clothing::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getZString(mFullName); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; - case ESM4::SUB_BMDT: + case ESM::fourCC("BMDT"): reader.get(mClothingFlags); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_ENAM: + case ESM::fourCC("ENAM"): reader.getFormId(mEnchantment); break; - case ESM4::SUB_ANAM: + case ESM::fourCC("ANAM"): reader.get(mEnchantmentPoints); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModelMale); break; - case ESM4::SUB_MOD2: + case ESM::fourCC("MOD2"): reader.getZString(mModelMaleWorld); break; - case ESM4::SUB_MOD3: + case ESM::fourCC("MOD3"): reader.getZString(mModelFemale); break; - case ESM4::SUB_MOD4: + case ESM::fourCC("MOD4"): reader.getZString(mModelFemaleWorld); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIconMale); break; - case ESM4::SUB_ICO2: + case ESM::fourCC("ICO2"): reader.getZString(mIconFemale); break; - case ESM4::SUB_MODT: - case ESM4::SUB_MO2B: - case ESM4::SUB_MO3B: - case ESM4::SUB_MO4B: - case ESM4::SUB_MO2T: - case ESM4::SUB_MO3T: - case ESM4::SUB_MO4T: + case ESM::fourCC("MODT"): + case ESM::fourCC("MO2B"): + case ESM::fourCC("MO3B"): + case ESM::fourCC("MO4B"): + case ESM::fourCC("MO2T"): + case ESM::fourCC("MO3T"): + case ESM::fourCC("MO4T"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadcont.cpp b/components/esm4/loadcont.cpp index d650678093..70f05b4375 100644 --- a/components/esm4/loadcont.cpp +++ b/components/esm4/loadcont.cpp @@ -41,65 +41,65 @@ void ESM4::Container::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mDataFlags); reader.get(mWeight); break; - case ESM4::SUB_CNTO: + case ESM::fourCC("CNTO"): { - static InventoryItem inv; // FIXME: use unique_ptr here? + InventoryItem inv; // FIXME: use unique_ptr here? reader.get(inv); reader.adjustFormId(inv.item); mInventory.push_back(inv); break; } - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.getFormId(mOpenSound); break; - case ESM4::SUB_QNAM: + case ESM::fourCC("QNAM"): reader.getFormId(mCloseSound); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_VMAD: // TES5 only - case ESM4::SUB_OBND: // TES5 only - case ESM4::SUB_COCT: // TES5 only - case ESM4::SUB_COED: // TES5 only - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_ONAM: - case ESM4::SUB_RNAM: // FONV - case ESM4::SUB_TNAM: - case ESM4::SUB_FTYP: // FO4 - case ESM4::SUB_NTRM: // FO4 - case ESM4::SUB_PRPS: // FO4 - case ESM4::SUB_PTRN: // FO4 + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("VMAD"): // TES5 only + case ESM::fourCC("OBND"): // TES5 only + case ESM::fourCC("COCT"): // TES5 only + case ESM::fourCC("COED"): // TES5 only + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("ONAM"): + case ESM::fourCC("RNAM"): // FONV + case ESM::fourCC("TNAM"): + case ESM::fourCC("FTYP"): // FO4 + case ESM::fourCC("NTRM"): // FO4 + case ESM::fourCC("PRPS"): // FO4 + case ESM::fourCC("PTRN"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadcrea.cpp b/components/esm4/loadcrea.cpp index 0c07eb92e3..0af39364b5 100644 --- a/components/esm4/loadcrea.cpp +++ b/components/esm4/loadcrea.cpp @@ -45,93 +45,93 @@ void ESM4::Creature::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getZString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_CNTO: + case ESM::fourCC("CNTO"): { - static InventoryItem inv; // FIXME: use unique_ptr here? + InventoryItem inv; // FIXME: use unique_ptr here? reader.get(inv); reader.adjustFormId(inv.item); mInventory.push_back(inv); break; } - case ESM4::SUB_SPLO: + case ESM::fourCC("SPLO"): reader.getFormId(mSpell.emplace_back()); break; - case ESM4::SUB_PKID: + case ESM::fourCC("PKID"): reader.getFormId(mAIPackages.emplace_back()); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.get(mFaction); reader.adjustFormId(mFaction.faction); break; - case ESM4::SUB_INAM: + case ESM::fourCC("INAM"): reader.getFormId(mDeathItem); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_AIDT: + case ESM::fourCC("AIDT"): if (subHdr.dataSize == 20) // FO3 reader.skipSubRecordData(); else reader.get(mAIData); // 12 bytes break; - case ESM4::SUB_ACBS: + case ESM::fourCC("ACBS"): // if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || mIsFONV) if (subHdr.dataSize == 24) reader.get(mBaseConfig); else reader.get(&mBaseConfig, 16); // TES4 break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): if (subHdr.dataSize == 17) // FO3 reader.skipSubRecordData(); else reader.get(mData); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mCombatStyle); break; - case ESM4::SUB_CSCR: + case ESM::fourCC("CSCR"): reader.getFormId(mSoundBase); break; - case ESM4::SUB_CSDI: + case ESM::fourCC("CSDI"): reader.getFormId(mSound); break; - case ESM4::SUB_CSDC: + case ESM::fourCC("CSDC"): reader.get(mSoundChance); break; - case ESM4::SUB_BNAM: + case ESM::fourCC("BNAM"): reader.get(mBaseScale); break; - case ESM4::SUB_TNAM: + case ESM::fourCC("TNAM"): reader.get(mTurningSpeed); break; - case ESM4::SUB_WNAM: + case ESM::fourCC("WNAM"): reader.get(mFootWeight); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_NAM0: + case ESM::fourCC("NAM0"): reader.getZString(mBloodSpray); break; - case ESM4::SUB_NAM1: + case ESM::fourCC("NAM1"): reader.getZString(mBloodDecal); break; - case ESM4::SUB_NIFZ: + case ESM::fourCC("NIFZ"): if (!reader.getZeroTerminatedStringArray(mNif)) throw std::runtime_error("CREA NIFZ data read error"); break; - case ESM4::SUB_NIFT: + case ESM::fourCC("NIFT"): { if (subHdr.dataSize != 4) // FIXME: FO3 { @@ -147,33 +147,33 @@ void ESM4::Creature::load(ESM4::Reader& reader) Log(Debug::Verbose) << "CREA NIFT " << mId << ", non-zero " << nift; break; } - case ESM4::SUB_KFFZ: + case ESM::fourCC("KFFZ"): if (!reader.getZeroTerminatedStringArray(mKf)) throw std::runtime_error("CREA KFFZ data read error"); break; - case ESM4::SUB_TPLT: + case ESM::fourCC("TPLT"): reader.getFormId(mBaseTemplate); break; // FO3 - case ESM4::SUB_PNAM: // FO3/FONV/TES5 + case ESM::fourCC("PNAM"): // FO3/FONV/TES5 reader.getFormId(mBodyParts.emplace_back()); break; - case ESM4::SUB_MODT: - case ESM4::SUB_RNAM: - case ESM4::SUB_CSDT: - case ESM4::SUB_OBND: // FO3 - case ESM4::SUB_EAMT: // FO3 - case ESM4::SUB_VTCK: // FO3 - case ESM4::SUB_NAM4: // FO3 - case ESM4::SUB_NAM5: // FO3 - case ESM4::SUB_CNAM: // FO3 - case ESM4::SUB_LNAM: // FO3 - case ESM4::SUB_EITM: // FO3 - case ESM4::SUB_DEST: // FO3 - case ESM4::SUB_DSTD: // FO3 - case ESM4::SUB_DSTF: // FO3 - case ESM4::SUB_DMDL: // FO3 - case ESM4::SUB_DMDT: // FO3 - case ESM4::SUB_COED: // FO3 + case ESM::fourCC("MODT"): + case ESM::fourCC("RNAM"): + case ESM::fourCC("CSDT"): + case ESM::fourCC("OBND"): // FO3 + case ESM::fourCC("EAMT"): // FO3 + case ESM::fourCC("VTCK"): // FO3 + case ESM::fourCC("NAM4"): // FO3 + case ESM::fourCC("NAM5"): // FO3 + case ESM::fourCC("CNAM"): // FO3 + case ESM::fourCC("LNAM"): // FO3 + case ESM::fourCC("EITM"): // FO3 + case ESM::fourCC("DEST"): // FO3 + case ESM::fourCC("DSTD"): // FO3 + case ESM::fourCC("DSTF"): // FO3 + case ESM::fourCC("DMDL"): // FO3 + case ESM::fourCC("DMDT"): // FO3 + case ESM::fourCC("COED"): // FO3 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loaddial.cpp b/components/esm4/loaddial.cpp index 19e1099482..3ed9b79e0a 100644 --- a/components/esm4/loaddial.cpp +++ b/components/esm4/loaddial.cpp @@ -42,19 +42,19 @@ void ESM4::Dialogue::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mTopicName); break; - case ESM4::SUB_QSTI: + case ESM::fourCC("QSTI"): reader.getFormId(mQuests.emplace_back()); break; - case ESM4::SUB_QSTR: // Seems never used in TES4 + case ESM::fourCC("QSTR"): // Seems never used in TES4 reader.getFormId(mQuestsRemoved.emplace_back()); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { if (subHdr.dataSize == 4) // TES5 { @@ -74,20 +74,20 @@ void ESM4::Dialogue::load(ESM4::Reader& reader) break; } - case ESM4::SUB_PNAM: + case ESM::fourCC("PNAM"): reader.get(mPriority); break; // FO3/FONV - case ESM4::SUB_TDUM: + case ESM::fourCC("TDUM"): reader.getZString(mTextDumb); break; // FONV - case ESM4::SUB_SCRI: - case ESM4::SUB_INFC: // FONV info connection - case ESM4::SUB_INFX: // FONV info index - case ESM4::SUB_QNAM: // TES5 - case ESM4::SUB_BNAM: // TES5 - case ESM4::SUB_SNAM: // TES5 - case ESM4::SUB_TIFC: // TES5 - case ESM4::SUB_KNAM: // FO4 + case ESM::fourCC("SCRI"): + case ESM::fourCC("INFC"): // FONV info connection + case ESM::fourCC("INFX"): // FONV info index + case ESM::fourCC("QNAM"): // TES5 + case ESM::fourCC("BNAM"): // TES5 + case ESM::fourCC("SNAM"): // TES5 + case ESM::fourCC("TIFC"): // TES5 + case ESM::fourCC("KNAM"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loaddobj.cpp b/components/esm4/loaddobj.cpp index 50135fc7a1..9c0c193f81 100644 --- a/components/esm4/loaddobj.cpp +++ b/components/esm4/loaddobj.cpp @@ -44,10 +44,10 @@ void ESM4::DefaultObj::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; // "DefaultObjectManager" - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.getFormId(mData.stimpack); reader.getFormId(mData.superStimpack); reader.getFormId(mData.radX); @@ -87,7 +87,7 @@ void ESM4::DefaultObj::load(ESM4::Reader& reader) reader.getFormId(mData.cateyeMobileEffectNYI); } break; - case ESM4::SUB_DNAM: + case ESM::fourCC("DNAM"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loaddoor.cpp b/components/esm4/loaddoor.cpp index 7fe38b6b7a..10171085c3 100644 --- a/components/esm4/loaddoor.cpp +++ b/components/esm4/loaddoor.cpp @@ -41,57 +41,57 @@ void ESM4::Door::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.getFormId(mOpenSound); break; - case ESM4::SUB_ANAM: + case ESM::fourCC("ANAM"): reader.getFormId(mCloseSound); break; - case ESM4::SUB_BNAM: + case ESM::fourCC("BNAM"): reader.getFormId(mLoopSound); break; - case ESM4::SUB_FNAM: + case ESM::fourCC("FNAM"): reader.get(mDoorFlags); break; - case ESM4::SUB_TNAM: + case ESM::fourCC("TNAM"): reader.getFormId(mRandomTeleport); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_OBND: - case ESM4::SUB_VMAD: - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_CNAM: // FO4 - case ESM4::SUB_NTRM: // FO4 - case ESM4::SUB_ONAM: // FO4 - case ESM4::SUB_PTRN: // FO4 + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("OBND"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("CNAM"): // FO4 + case ESM::fourCC("NTRM"): // FO4 + case ESM::fourCC("ONAM"): // FO4 + case ESM::fourCC("PTRN"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadeyes.cpp b/components/esm4/loadeyes.cpp index 28f6d33c6e..7e356889d9 100644 --- a/components/esm4/loadeyes.cpp +++ b/components/esm4/loadeyes.cpp @@ -41,16 +41,16 @@ void ESM4::Eyes::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; default: diff --git a/components/esm4/loadflor.cpp b/components/esm4/loadflor.cpp index 69f1c82b13..164f97eff1 100644 --- a/components/esm4/loadflor.cpp +++ b/components/esm4/loadflor.cpp @@ -41,53 +41,53 @@ void ESM4::Flora::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_PFIG: + case ESM::fourCC("PFIG"): reader.getFormId(mIngredient); break; - case ESM4::SUB_PFPC: + case ESM::fourCC("PFPC"): reader.get(mPercentHarvest); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.getFormId(mSound); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_FNAM: - case ESM4::SUB_OBND: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_PNAM: - case ESM4::SUB_RNAM: - case ESM4::SUB_VMAD: - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_PRPS: // FO4 - case ESM4::SUB_PTRN: // FO4 - case ESM4::SUB_ATTX: // FO4 + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("FNAM"): + case ESM::fourCC("OBND"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("PNAM"): + case ESM::fourCC("RNAM"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("PRPS"): // FO4 + case ESM::fourCC("PTRN"): // FO4 + case ESM::fourCC("ATTX"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadflst.cpp b/components/esm4/loadflst.cpp index 9da17bc84b..4acf4d28d2 100644 --- a/components/esm4/loadflst.cpp +++ b/components/esm4/loadflst.cpp @@ -41,13 +41,13 @@ void ESM4::FormIdList::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_LNAM: + case ESM::fourCC("LNAM"): reader.getFormId(mObjects.emplace_back()); break; default: diff --git a/components/esm4/loadfurn.cpp b/components/esm4/loadfurn.cpp index 41ddca07a2..75dc3751a6 100644 --- a/components/esm4/loadfurn.cpp +++ b/components/esm4/loadfurn.cpp @@ -41,10 +41,10 @@ void ESM4::Furniture::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): { std::string name; reader.getLocalizedString(name); @@ -53,65 +53,65 @@ void ESM4::Furniture::load(ESM4::Reader& reader) mFullName = std::move(name); break; } - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_MNAM: + case ESM::fourCC("MNAM"): reader.get(mActiveMarkerFlags); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_ENAM: - case ESM4::SUB_FNAM: - case ESM4::SUB_FNMK: - case ESM4::SUB_FNPR: - case ESM4::SUB_KNAM: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_NAM0: - case ESM4::SUB_OBND: - case ESM4::SUB_PNAM: - case ESM4::SUB_VMAD: - case ESM4::SUB_WBDT: - case ESM4::SUB_XMRK: - case ESM4::SUB_PRPS: - case ESM4::SUB_CTDA: - case ESM4::SUB_CIS1: - case ESM4::SUB_CIS2: - case ESM4::SUB_APPR: // FO4 - case ESM4::SUB_ATTX: // FO4 - case ESM4::SUB_CITC: // FO4 - case ESM4::SUB_CNTO: // FO4 - case ESM4::SUB_COCT: // FO4 - case ESM4::SUB_COED: // FO4 - case ESM4::SUB_FTYP: // FO4 - case ESM4::SUB_NAM1: // FO4 - case ESM4::SUB_NTRM: // FO4 - case ESM4::SUB_NVNM: // FO4 - case ESM4::SUB_PTRN: // FO4 - case ESM4::SUB_SNAM: // FO4 - case ESM4::SUB_WNAM: // FO4 - case ESM4::SUB_OBTE: // FO4 object template start - case ESM4::SUB_OBTF: - case ESM4::SUB_OBTS: - case ESM4::SUB_STOP: // FO4 object template end + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("ENAM"): + case ESM::fourCC("FNAM"): + case ESM::fourCC("FNMK"): + case ESM::fourCC("FNPR"): + case ESM::fourCC("KNAM"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("NAM0"): + case ESM::fourCC("OBND"): + case ESM::fourCC("PNAM"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("WBDT"): + case ESM::fourCC("XMRK"): + case ESM::fourCC("PRPS"): + case ESM::fourCC("CTDA"): + case ESM::fourCC("CIS1"): + case ESM::fourCC("CIS2"): + case ESM::fourCC("APPR"): // FO4 + case ESM::fourCC("ATTX"): // FO4 + case ESM::fourCC("CITC"): // FO4 + case ESM::fourCC("CNTO"): // FO4 + case ESM::fourCC("COCT"): // FO4 + case ESM::fourCC("COED"): // FO4 + case ESM::fourCC("FTYP"): // FO4 + case ESM::fourCC("NAM1"): // FO4 + case ESM::fourCC("NTRM"): // FO4 + case ESM::fourCC("NVNM"): // FO4 + case ESM::fourCC("PTRN"): // FO4 + case ESM::fourCC("SNAM"): // FO4 + case ESM::fourCC("WNAM"): // FO4 + case ESM::fourCC("OBTE"): // FO4 object template start + case ESM::fourCC("OBTF"): + case ESM::fourCC("OBTS"): + case ESM::fourCC("STOP"): // FO4 object template end reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadglob.cpp b/components/esm4/loadglob.cpp index 436f3e34ae..4349bcb072 100644 --- a/components/esm4/loadglob.cpp +++ b/components/esm4/loadglob.cpp @@ -41,16 +41,16 @@ void ESM4::GlobalVariable::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; case ESM::fourCC("XALG"): // FO76 reader.get(mExtraFlags2); break; - case ESM4::SUB_FNAM: + case ESM::fourCC("FNAM"): reader.get(mType); break; - case ESM4::SUB_FLTV: + case ESM::fourCC("FLTV"): reader.get(mValue); break; case ESM::fourCC("NTWK"): // FO76 diff --git a/components/esm4/loadgmst.cpp b/components/esm4/loadgmst.cpp index f22ed5848d..0b2df075f2 100644 --- a/components/esm4/loadgmst.cpp +++ b/components/esm4/loadgmst.cpp @@ -67,10 +67,10 @@ namespace ESM4 const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): mData = readData(mId, mEditorId, reader); break; default: diff --git a/components/esm4/loadgras.cpp b/components/esm4/loadgras.cpp index ebcdde04a1..8514b3aa0a 100644 --- a/components/esm4/loadgras.cpp +++ b/components/esm4/loadgras.cpp @@ -41,23 +41,23 @@ void ESM4::Grass::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_OBND: + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("OBND"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadhair.cpp b/components/esm4/loadhair.cpp index 3ab983d6b6..f3e5a8a1c3 100644 --- a/components/esm4/loadhair.cpp +++ b/components/esm4/loadhair.cpp @@ -41,25 +41,25 @@ void ESM4::Hair::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getZString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_MODT: + case ESM::fourCC("MODT"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadhdpt.cpp b/components/esm4/loadhdpt.cpp index c560ff5fac..9b7e27bdf9 100644 --- a/components/esm4/loadhdpt.cpp +++ b/components/esm4/loadhdpt.cpp @@ -45,32 +45,32 @@ void ESM4::HeadPart::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; case ESM::fourCC("XALG"): // FO76 reader.get(mExtraFlags2); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_HNAM: + case ESM::fourCC("HNAM"): reader.getFormId(mExtraParts.emplace_back()); break; - case ESM4::SUB_NAM0: // TES5 + case ESM::fourCC("NAM0"): // TES5 { std::uint32_t value; reader.get(value); type = value; break; } - case ESM4::SUB_NAM1: // TES5 + case ESM::fourCC("NAM1"): // TES5 { std::string file; reader.getZString(file); @@ -87,29 +87,29 @@ void ESM4::HeadPart::load(ESM4::Reader& reader) mTriFile[*type] = std::move(file); break; } - case ESM4::SUB_TNAM: + case ESM::fourCC("TNAM"): reader.getFormId(mBaseTexture); break; - case ESM4::SUB_CNAM: + case ESM::fourCC("CNAM"): reader.getFormId(mColor); break; - case ESM4::SUB_RNAM: + case ESM::fourCC("RNAM"): reader.getFormId(mValidRaces.emplace_back()); break; - case ESM4::SUB_PNAM: + case ESM::fourCC("PNAM"): reader.get(mType); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): case ESM::fourCC("ENLM"): case ESM::fourCC("XFLG"): case ESM::fourCC("ENLT"): case ESM::fourCC("ENLS"): case ESM::fourCC("AUUV"): case ESM::fourCC("MODD"): // Model data end - case ESM4::SUB_CTDA: + case ESM::fourCC("CTDA"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadidle.cpp b/components/esm4/loadidle.cpp index 310c43b2e1..18a408f053 100644 --- a/components/esm4/loadidle.cpp +++ b/components/esm4/loadidle.cpp @@ -41,16 +41,16 @@ void ESM4::IdleAnimation::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_DNAM: + case ESM::fourCC("DNAM"): reader.getZString(mCollision); break; - case ESM4::SUB_ENAM: + case ESM::fourCC("ENAM"): reader.getZString(mEvent); break; - case ESM4::SUB_ANAM: + case ESM::fourCC("ANAM"): { switch (subHdr.dataSize) { @@ -74,21 +74,21 @@ void ESM4::IdleAnimation::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_CTDA: // formId - case ESM4::SUB_CTDT: - case ESM4::SUB_CIS1: - case ESM4::SUB_CIS2: - case ESM4::SUB_DATA: - case ESM4::SUB_MODD: - case ESM4::SUB_MODS: - case ESM4::SUB_MODT: - case ESM4::SUB_GNAM: // FO4 + case ESM::fourCC("CTDA"): // formId + case ESM::fourCC("CTDT"): + case ESM::fourCC("CIS1"): + case ESM::fourCC("CIS2"): + case ESM::fourCC("DATA"): + case ESM::fourCC("MODD"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODT"): + case ESM::fourCC("GNAM"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadidlm.cpp b/components/esm4/loadidlm.cpp index 3f1ed9518c..0aec281c6c 100644 --- a/components/esm4/loadidlm.cpp +++ b/components/esm4/loadidlm.cpp @@ -43,13 +43,13 @@ void ESM4::IdleMarker::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_IDLF: + case ESM::fourCC("IDLF"): reader.get(mIdleFlags); break; - case ESM4::SUB_IDLC: + case ESM::fourCC("IDLC"): if (subHdr.dataSize != 1) // FO3 can have 4? { reader.skipSubRecordData(); @@ -58,10 +58,10 @@ void ESM4::IdleMarker::load(ESM4::Reader& reader) reader.get(mIdleCount); break; - case ESM4::SUB_IDLT: + case ESM::fourCC("IDLT"): reader.get(mIdleTimer); break; - case ESM4::SUB_IDLA: + case ESM::fourCC("IDLA"): { bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; if (esmVer == ESM::VER_094 || isFONV) // FO3? 4 or 8 bytes @@ -75,17 +75,17 @@ void ESM4::IdleMarker::load(ESM4::Reader& reader) reader.getFormId(value); break; } - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_OBND: // object bounds - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_QNAM: + case ESM::fourCC("OBND"): // object bounds + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("QNAM"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadimod.cpp b/components/esm4/loadimod.cpp index 0359f6d23b..76f51357a3 100644 --- a/components/esm4/loadimod.cpp +++ b/components/esm4/loadimod.cpp @@ -43,53 +43,53 @@ void ESM4::ItemMod::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_DESC: + case ESM::fourCC("DESC"): reader.getLocalizedString(mText); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_MICO: + case ESM::fourCC("MICO"): reader.getZString(mMiniIcon); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_YNAM: + case ESM::fourCC("YNAM"): reader.getFormId(mPickUpSound); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mDropSound); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData.mValue); reader.get(mData.mWeight); break; - case ESM4::SUB_OBND: - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODS: - case ESM4::SUB_MODD: // Model data end - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end + case ESM::fourCC("OBND"): + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODS"): + case ESM::fourCC("MODD"): // Model data end + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadinfo.cpp b/components/esm4/loadinfo.cpp index 1b001c1665..d3339d350a 100644 --- a/components/esm4/loadinfo.cpp +++ b/components/esm4/loadinfo.cpp @@ -41,7 +41,6 @@ void ESM4::DialogInfo::load(ESM4::Reader& reader) mEditorId = ESM::RefId(mId).serializeText(); // FIXME: quick workaround to use existing code - static ScriptLocalVariableData localVar; bool ignore = false; while (reader.getSubRecordHeader()) @@ -49,13 +48,13 @@ void ESM4::DialogInfo::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_QSTI: + case ESM::fourCC("QSTI"): reader.getFormId(mQuest); break; // FormId quest id - case ESM4::SUB_SNDD: + case ESM::fourCC("SNDD"): reader.getFormId(mSound); break; // FO3 (not used in FONV?) - case ESM4::SUB_TRDT: + case ESM::fourCC("TRDT"): { if (subHdr.dataSize == 16) // TES4 reader.get(&mResponseData, 16); @@ -70,16 +69,16 @@ void ESM4::DialogInfo::load(ESM4::Reader& reader) break; } - case ESM4::SUB_NAM1: + case ESM::fourCC("NAM1"): reader.getLocalizedString(mResponse); break; // response text - case ESM4::SUB_NAM2: + case ESM::fourCC("NAM2"): reader.getZString(mNotes); break; // actor notes - case ESM4::SUB_NAM3: + case ESM::fourCC("NAM3"): reader.getZString(mEdits); break; // not in TES4 - case ESM4::SUB_CTDA: // FIXME: how to detect if 1st/2nd param is a formid? + case ESM::fourCC("CTDA"): // FIXME: how to detect if 1st/2nd param is a formid? { if (subHdr.dataSize == 24) // TES4 reader.get(&mTargetCondition, 24); @@ -105,7 +104,7 @@ void ESM4::DialogInfo::load(ESM4::Reader& reader) break; } - case ESM4::SUB_SCHR: + case ESM::fourCC("SCHR"): { if (!ignore) reader.get(mScript.scriptHeader); @@ -114,37 +113,39 @@ void ESM4::DialogInfo::load(ESM4::Reader& reader) break; } - case ESM4::SUB_SCDA: + case ESM::fourCC("SCDA"): reader.skipSubRecordData(); break; // compiled script data - case ESM4::SUB_SCTX: + case ESM::fourCC("SCTX"): reader.getString(mScript.scriptSource); break; - case ESM4::SUB_SCRO: + case ESM::fourCC("SCRO"): reader.getFormId(mScript.globReference); break; - case ESM4::SUB_SLSD: + case ESM::fourCC("SLSD"): { - localVar.clear(); + ScriptLocalVariableData localVar; reader.get(localVar.index); reader.get(localVar.unknown1); reader.get(localVar.unknown2); reader.get(localVar.unknown3); reader.get(localVar.type); reader.get(localVar.unknown4); + mScript.localVarData.push_back(std::move(localVar)); // WARN: assumes SCVR will follow immediately break; } - case ESM4::SUB_SCVR: // assumed always pair with SLSD + case ESM::fourCC("SCVR"): // assumed always pair with SLSD { - reader.getZString(localVar.variableName); - - mScript.localVarData.push_back(localVar); + if (!mScript.localVarData.empty()) + reader.getZString(mScript.localVarData.back().variableName); + else + reader.skipSubRecordData(); break; } - case ESM4::SUB_SCRV: + case ESM::fourCC("SCRV"): { std::uint32_t index; reader.get(index); @@ -153,13 +154,13 @@ void ESM4::DialogInfo::load(ESM4::Reader& reader) break; } - case ESM4::SUB_NEXT: // FO3/FONV marker for next script header + case ESM::fourCC("NEXT"): // FO3/FONV marker for next script header { ignore = true; break; } - case ESM4::SUB_DATA: // always 3 for TES4 ? + case ESM::fourCC("DATA"): // always 3 for TES4 ? { if (subHdr.dataSize == 4) // FO3/FONV { @@ -171,48 +172,48 @@ void ESM4::DialogInfo::load(ESM4::Reader& reader) reader.skipSubRecordData(); // FIXME break; } - case ESM4::SUB_NAME: // FormId add topic (not always present) - case ESM4::SUB_CTDT: // older version of CTDA? 20 bytes - case ESM4::SUB_SCHD: // 28 bytes - case ESM4::SUB_TCLT: // FormId choice - case ESM4::SUB_TCLF: // FormId - case ESM4::SUB_PNAM: // TES4 DLC - case ESM4::SUB_TPIC: // TES4 DLC - case ESM4::SUB_ANAM: // FO3 speaker formid - case ESM4::SUB_DNAM: // FO3 speech challenge - case ESM4::SUB_KNAM: // FO3 formid - case ESM4::SUB_LNAM: // FONV - case ESM4::SUB_TCFU: // FONV - case ESM4::SUB_TIFC: // TES5 - case ESM4::SUB_TWAT: // TES5 - case ESM4::SUB_CIS1: // TES5 - case ESM4::SUB_CIS2: // TES5 - case ESM4::SUB_CNAM: // TES5 - case ESM4::SUB_ENAM: // TES5 - case ESM4::SUB_EDID: // TES5 - case ESM4::SUB_VMAD: // TES5 - case ESM4::SUB_BNAM: // TES5 - case ESM4::SUB_SNAM: // TES5 - case ESM4::SUB_ONAM: // TES5 - case ESM4::SUB_QNAM: // TES5 for mScript - case ESM4::SUB_RNAM: // TES5 - case ESM4::SUB_ALFA: // FO4 - case ESM4::SUB_GNAM: // FO4 - case ESM4::SUB_GREE: // FO4 - case ESM4::SUB_INAM: // FO4 - case ESM4::SUB_INCC: // FO4 - case ESM4::SUB_INTV: // FO4 - case ESM4::SUB_IOVR: // FO4 - case ESM4::SUB_MODQ: // FO4 - case ESM4::SUB_NAM0: // FO4 - case ESM4::SUB_NAM4: // FO4 - case ESM4::SUB_NAM9: // FO4 - case ESM4::SUB_SRAF: // FO4 - case ESM4::SUB_TIQS: // FO4 - case ESM4::SUB_TNAM: // FO4 - case ESM4::SUB_TRDA: // FO4 - case ESM4::SUB_TSCE: // FO4 - case ESM4::SUB_WZMD: // FO4 + case ESM::fourCC("NAME"): // FormId add topic (not always present) + case ESM::fourCC("CTDT"): // older version of CTDA? 20 bytes + case ESM::fourCC("SCHD"): // 28 bytes + case ESM::fourCC("TCLT"): // FormId choice + case ESM::fourCC("TCLF"): // FormId + case ESM::fourCC("PNAM"): // TES4 DLC + case ESM::fourCC("TPIC"): // TES4 DLC + case ESM::fourCC("ANAM"): // FO3 speaker formid + case ESM::fourCC("DNAM"): // FO3 speech challenge + case ESM::fourCC("KNAM"): // FO3 formid + case ESM::fourCC("LNAM"): // FONV + case ESM::fourCC("TCFU"): // FONV + case ESM::fourCC("TIFC"): // TES5 + case ESM::fourCC("TWAT"): // TES5 + case ESM::fourCC("CIS1"): // TES5 + case ESM::fourCC("CIS2"): // TES5 + case ESM::fourCC("CNAM"): // TES5 + case ESM::fourCC("ENAM"): // TES5 + case ESM::fourCC("EDID"): // TES5 + case ESM::fourCC("VMAD"): // TES5 + case ESM::fourCC("BNAM"): // TES5 + case ESM::fourCC("SNAM"): // TES5 + case ESM::fourCC("ONAM"): // TES5 + case ESM::fourCC("QNAM"): // TES5 for mScript + case ESM::fourCC("RNAM"): // TES5 + case ESM::fourCC("ALFA"): // FO4 + case ESM::fourCC("GNAM"): // FO4 + case ESM::fourCC("GREE"): // FO4 + case ESM::fourCC("INAM"): // FO4 + case ESM::fourCC("INCC"): // FO4 + case ESM::fourCC("INTV"): // FO4 + case ESM::fourCC("IOVR"): // FO4 + case ESM::fourCC("MODQ"): // FO4 + case ESM::fourCC("NAM0"): // FO4 + case ESM::fourCC("NAM4"): // FO4 + case ESM::fourCC("NAM9"): // FO4 + case ESM::fourCC("SRAF"): // FO4 + case ESM::fourCC("TIQS"): // FO4 + case ESM::fourCC("TNAM"): // FO4 + case ESM::fourCC("TRDA"): // FO4 + case ESM::fourCC("TSCE"): // FO4 + case ESM::fourCC("WZMD"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadingr.cpp b/components/esm4/loadingr.cpp index d0b81fd4a1..64103058e5 100644 --- a/components/esm4/loadingr.cpp +++ b/components/esm4/loadingr.cpp @@ -42,10 +42,10 @@ void ESM4::Ingredient::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): { if (mFullName.empty()) { @@ -64,7 +64,7 @@ void ESM4::Ingredient::load(ESM4::Reader& reader) break; } } - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { // if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) if (subHdr.dataSize == 8) // FO3 is size 4 even though VER_094 @@ -74,49 +74,49 @@ void ESM4::Ingredient::load(ESM4::Reader& reader) break; } - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_ENIT: + case ESM::fourCC("ENIT"): reader.get(mEnchantment); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_SCIT: + case ESM::fourCC("SCIT"): { reader.get(mEffect); reader.adjustFormId(mEffect.formId); break; } - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_EFID: - case ESM4::SUB_EFIT: - case ESM4::SUB_OBND: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_VMAD: - case ESM4::SUB_YNAM: - case ESM4::SUB_ZNAM: - case ESM4::SUB_ETYP: // FO3 - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("EFID"): + case ESM::fourCC("EFIT"): + case ESM::fourCC("OBND"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("YNAM"): + case ESM::fourCC("ZNAM"): + case ESM::fourCC("ETYP"): // FO3 + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end { reader.skipSubRecordData(); break; diff --git a/components/esm4/loadkeym.cpp b/components/esm4/loadkeym.cpp index 9b0c280b8b..b430f7ce3d 100644 --- a/components/esm4/loadkeym.cpp +++ b/components/esm4/loadkeym.cpp @@ -41,54 +41,54 @@ void ESM4::Key::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_MICO: + case ESM::fourCC("MICO"): reader.getZString(mMiniIcon); break; // FO3 - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_YNAM: + case ESM::fourCC("YNAM"): reader.getFormId(mPickUpSound); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mDropSound); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_OBND: - case ESM4::SUB_VMAD: - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_PTRN: // FO4 + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("OBND"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("PTRN"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadland.cpp b/components/esm4/loadland.cpp index 2215b56dd1..53fb1de083 100644 --- a/components/esm4/loadland.cpp +++ b/components/esm4/loadland.cpp @@ -65,18 +65,18 @@ void ESM4::Land::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { reader.get(mLandFlags); break; } - case ESM4::SUB_VNML: // vertex normals, 33x33x(1+1+1) = 3267 + case ESM::fourCC("VNML"): // vertex normals, 33x33x(1+1+1) = 3267 { reader.get(mVertNorm); mDataTypes |= LAND_VNML; break; } - case ESM4::SUB_VHGT: // vertex height gradient, 4+33x33+3 = 4+1089+3 = 1096 + case ESM::fourCC("VHGT"): // vertex height gradient, 4+33x33+3 = 4+1089+3 = 1096 { #if 0 reader.get(mHeightMap.heightOffset); @@ -88,13 +88,13 @@ void ESM4::Land::load(ESM4::Reader& reader) mDataTypes |= LAND_VHGT; break; } - case ESM4::SUB_VCLR: // vertex colours, 24bit RGB, 33x33x(1+1+1) = 3267 + case ESM::fourCC("VCLR"): // vertex colours, 24bit RGB, 33x33x(1+1+1) = 3267 { reader.get(mVertColr); mDataTypes |= LAND_VCLR; break; } - case ESM4::SUB_BTXT: + case ESM::fourCC("BTXT"): { BTXT base; if (reader.getExact(base)) @@ -112,7 +112,7 @@ void ESM4::Land::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_ATXT: + case ESM::fourCC("ATXT"): { if (currentAddQuad != -1) { @@ -144,7 +144,7 @@ void ESM4::Land::load(ESM4::Reader& reader) currentAddQuad = layer.texture.quadrant; break; } - case ESM4::SUB_VTXT: + case ESM::fourCC("VTXT"): { if (currentAddQuad == -1) throw std::runtime_error("VTXT without ATXT found"); @@ -177,7 +177,7 @@ void ESM4::Land::load(ESM4::Reader& reader) // std::cout << "VTXT: count " << std::dec << count << std::endl; break; } - case ESM4::SUB_VTEX: // only in Oblivion? + case ESM::fourCC("VTEX"): // only in Oblivion? { const std::uint16_t count = reader.subRecordHeader().dataSize / sizeof(ESM::FormId32); if ((reader.subRecordHeader().dataSize % sizeof(ESM::FormId32)) != 0) @@ -191,7 +191,7 @@ void ESM4::Land::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_MPCD: // FO4 + case ESM::fourCC("MPCD"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadlgtm.cpp b/components/esm4/loadlgtm.cpp index 0959be10a2..ce895ea5b8 100644 --- a/components/esm4/loadlgtm.cpp +++ b/components/esm4/loadlgtm.cpp @@ -44,10 +44,10 @@ void ESM4::LightingTemplate::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): if (subHdr.dataSize == 36) // TES4 reader.get(&mLighting, 36); if (subHdr.dataSize == 40) // FO3/FONV @@ -60,7 +60,7 @@ void ESM4::LightingTemplate::load(ESM4::Reader& reader) else reader.skipSubRecordData(); // throw? break; - case ESM4::SUB_DALC: // TES5 + case ESM::fourCC("DALC"): // TES5 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadligh.cpp b/components/esm4/loadligh.cpp index 0848ee8435..a0d467bafc 100644 --- a/components/esm4/loadligh.cpp +++ b/components/esm4/loadligh.cpp @@ -40,13 +40,13 @@ void ESM4::Light::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { if (subHdr.dataSize != 32 && subHdr.dataSize != 48 && subHdr.dataSize != 64) { @@ -78,47 +78,47 @@ void ESM4::Light::load(ESM4::Reader& reader) reader.get(mData.weight); break; } - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.getFormId(mSound); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_FNAM: + case ESM::fourCC("FNAM"): reader.get(mFade); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_OBND: - case ESM4::SUB_VMAD: - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_LNAM: // FO4 - case ESM4::SUB_MICO: // FO4 - case ESM4::SUB_NAM0: // FO4 - case ESM4::SUB_PRPS: // FO4 - case ESM4::SUB_PTRN: // FO4 - case ESM4::SUB_WGDR: // FO4 + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("OBND"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("LNAM"): // FO4 + case ESM::fourCC("MICO"): // FO4 + case ESM::fourCC("NAM0"): // FO4 + case ESM::fourCC("PRPS"): // FO4 + case ESM::fourCC("PTRN"): // FO4 + case ESM::fourCC("WGDR"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadltex.cpp b/components/esm4/loadltex.cpp index 9b2d12034f..a8b6c9ec81 100644 --- a/components/esm4/loadltex.cpp +++ b/components/esm4/loadltex.cpp @@ -41,10 +41,10 @@ void ESM4::LandTexture::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_HNAM: + case ESM::fourCC("HNAM"): { switch (subHdr.dataSize) { @@ -61,22 +61,22 @@ void ESM4::LandTexture::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mTextureFile); break; // Oblivion only? - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.get(mTextureSpecular); break; - case ESM4::SUB_GNAM: + case ESM::fourCC("GNAM"): reader.getFormId(mGrass.emplace_back()); break; - case ESM4::SUB_TNAM: + case ESM::fourCC("TNAM"): reader.getFormId(mTexture); break; // TES5, FO4 - case ESM4::SUB_MNAM: + case ESM::fourCC("MNAM"): reader.getFormId(mMaterial); break; // TES5, FO4 - case ESM4::SUB_INAM: + case ESM::fourCC("INAM"): reader.get(mMaterialFlags); break; // SSE default: diff --git a/components/esm4/loadlvlc.cpp b/components/esm4/loadlvlc.cpp index b1a0a0f241..844c27f8da 100644 --- a/components/esm4/loadlvlc.cpp +++ b/components/esm4/loadlvlc.cpp @@ -41,24 +41,24 @@ void ESM4::LevelledCreature::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_TNAM: + case ESM::fourCC("TNAM"): reader.getFormId(mTemplate); break; - case ESM4::SUB_LVLD: + case ESM::fourCC("LVLD"): reader.get(mChanceNone); break; - case ESM4::SUB_LVLF: + case ESM::fourCC("LVLF"): reader.get(mLvlCreaFlags); break; - case ESM4::SUB_LVLO: + case ESM::fourCC("LVLO"): { - static LVLO lvlo; + LVLO lvlo; if (subHdr.dataSize != 12) { if (subHdr.dataSize == 8) @@ -83,7 +83,7 @@ void ESM4::LevelledCreature::load(ESM4::Reader& reader) mLvlObject.push_back(lvlo); break; } - case ESM4::SUB_OBND: // FO3 + case ESM::fourCC("OBND"): // FO3 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadlvli.cpp b/components/esm4/loadlvli.cpp index cab8db4a21..aa99a9dd12 100644 --- a/components/esm4/loadlvli.cpp +++ b/components/esm4/loadlvli.cpp @@ -41,22 +41,22 @@ void ESM4::LevelledItem::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_LVLD: + case ESM::fourCC("LVLD"): reader.get(mChanceNone); break; - case ESM4::SUB_LVLF: + case ESM::fourCC("LVLF"): reader.get(mLvlItemFlags); mHasLvlItemFlags = true; break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; - case ESM4::SUB_LVLO: + case ESM::fourCC("LVLO"): { - static LVLO lvlo; + LVLO lvlo; if (subHdr.dataSize != 12) { if (subHdr.dataSize == 8) @@ -76,14 +76,14 @@ void ESM4::LevelledItem::load(ESM4::Reader& reader) mLvlObject.push_back(lvlo); break; } - case ESM4::SUB_LLCT: - case ESM4::SUB_OBND: // FO3/FONV - case ESM4::SUB_COED: // FO3/FONV - case ESM4::SUB_LVLG: // FO3/FONV - case ESM4::SUB_LLKC: // FO4 - case ESM4::SUB_LVLM: // FO4 - case ESM4::SUB_LVSG: // FO4 - case ESM4::SUB_ONAM: // FO4 + case ESM::fourCC("LLCT"): + case ESM::fourCC("OBND"): // FO3/FONV + case ESM::fourCC("COED"): // FO3/FONV + case ESM::fourCC("LVLG"): // FO3/FONV + case ESM::fourCC("LLKC"): // FO4 + case ESM::fourCC("LVLM"): // FO4 + case ESM::fourCC("LVSG"): // FO4 + case ESM::fourCC("ONAM"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadlvln.cpp b/components/esm4/loadlvln.cpp index febdcbeca9..03efc4a08f 100644 --- a/components/esm4/loadlvln.cpp +++ b/components/esm4/loadlvln.cpp @@ -42,24 +42,24 @@ void ESM4::LevelledNpc::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_LLCT: + case ESM::fourCC("LLCT"): reader.get(mListCount); break; - case ESM4::SUB_LVLD: + case ESM::fourCC("LVLD"): reader.get(mChanceNone); break; - case ESM4::SUB_LVLF: + case ESM::fourCC("LVLF"): reader.get(mLvlActorFlags); break; - case ESM4::SUB_LVLO: + case ESM::fourCC("LVLO"): { - static LVLO lvlo; + LVLO lvlo; if (subHdr.dataSize != 12) { if (subHdr.dataSize == 8) @@ -89,15 +89,15 @@ void ESM4::LevelledNpc::load(ESM4::Reader& reader) mLvlObject.push_back(lvlo); break; } - case ESM4::SUB_COED: // owner - case ESM4::SUB_OBND: // object bounds - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_LLKC: // FO4 - case ESM4::SUB_LVLG: // FO4 - case ESM4::SUB_LVLM: // FO4 + case ESM::fourCC("COED"): // owner + case ESM::fourCC("OBND"): // object bounds + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("LLKC"): // FO4 + case ESM::fourCC("LVLG"): // FO4 + case ESM::fourCC("LVLM"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadmato.cpp b/components/esm4/loadmato.cpp index 13d5e7d83d..6d45b689ba 100644 --- a/components/esm4/loadmato.cpp +++ b/components/esm4/loadmato.cpp @@ -41,18 +41,18 @@ void ESM4::Material::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_DNAM: - case ESM4::SUB_DATA: - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end + case ESM::fourCC("DNAM"): + case ESM::fourCC("DATA"): + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadmisc.cpp b/components/esm4/loadmisc.cpp index 6dfd69148d..b27e38f055 100644 --- a/components/esm4/loadmisc.cpp +++ b/components/esm4/loadmisc.cpp @@ -41,58 +41,58 @@ void ESM4::MiscItem::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_MICO: + case ESM::fourCC("MICO"): reader.getZString(mMiniIcon); break; // FO3 - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_YNAM: + case ESM::fourCC("YNAM"): reader.getFormId(mPickUpSound); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mDropSound); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_OBND: - case ESM4::SUB_VMAD: - case ESM4::SUB_RNAM: // FONV - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_CDIX: // FO4 - case ESM4::SUB_CVPA: // FO4 - case ESM4::SUB_FIMD: // FO4 - case ESM4::SUB_PTRN: // FO4 + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("OBND"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("RNAM"): // FONV + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("CDIX"): // FO4 + case ESM::fourCC("CVPA"): // FO4 + case ESM::fourCC("FIMD"): // FO4 + case ESM::fourCC("PTRN"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadmset.cpp b/components/esm4/loadmset.cpp index e15c508bc1..f7c088c47f 100644 --- a/components/esm4/loadmset.cpp +++ b/components/esm4/loadmset.cpp @@ -41,91 +41,91 @@ void ESM4::MediaSet::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getZString(mFullName); break; - case ESM4::SUB_NAM1: + case ESM::fourCC("NAM1"): reader.get(mSetType); break; - case ESM4::SUB_PNAM: + case ESM::fourCC("PNAM"): reader.get(mEnabled); break; - case ESM4::SUB_NAM2: + case ESM::fourCC("NAM2"): reader.getZString(mSet2); break; - case ESM4::SUB_NAM3: + case ESM::fourCC("NAM3"): reader.getZString(mSet3); break; - case ESM4::SUB_NAM4: + case ESM::fourCC("NAM4"): reader.getZString(mSet4); break; - case ESM4::SUB_NAM5: + case ESM::fourCC("NAM5"): reader.getZString(mSet5); break; - case ESM4::SUB_NAM6: + case ESM::fourCC("NAM6"): reader.getZString(mSet6); break; - case ESM4::SUB_NAM7: + case ESM::fourCC("NAM7"): reader.getZString(mSet7); break; - case ESM4::SUB_HNAM: + case ESM::fourCC("HNAM"): reader.getFormId(mSoundIntro); break; - case ESM4::SUB_INAM: + case ESM::fourCC("INAM"): reader.getFormId(mSoundOutro); break; - case ESM4::SUB_NAM8: + case ESM::fourCC("NAM8"): reader.get(mLevel8); break; - case ESM4::SUB_NAM9: + case ESM::fourCC("NAM9"): reader.get(mLevel9); break; - case ESM4::SUB_NAM0: + case ESM::fourCC("NAM0"): reader.get(mLevel0); break; - case ESM4::SUB_ANAM: + case ESM::fourCC("ANAM"): reader.get(mLevelA); break; - case ESM4::SUB_BNAM: + case ESM::fourCC("BNAM"): reader.get(mLevelB); break; - case ESM4::SUB_CNAM: + case ESM::fourCC("CNAM"): reader.get(mLevelC); break; - case ESM4::SUB_JNAM: + case ESM::fourCC("JNAM"): reader.get(mBoundaryDayOuter); break; - case ESM4::SUB_KNAM: + case ESM::fourCC("KNAM"): reader.get(mBoundaryDayMiddle); break; - case ESM4::SUB_LNAM: + case ESM::fourCC("LNAM"): reader.get(mBoundaryDayInner); break; - case ESM4::SUB_MNAM: + case ESM::fourCC("MNAM"): reader.get(mBoundaryNightOuter); break; - case ESM4::SUB_NNAM: + case ESM::fourCC("NNAM"): reader.get(mBoundaryNightMiddle); break; - case ESM4::SUB_ONAM: + case ESM::fourCC("ONAM"): reader.get(mBoundaryNightInner); break; - case ESM4::SUB_DNAM: + case ESM::fourCC("DNAM"): reader.get(mTime1); break; - case ESM4::SUB_ENAM: + case ESM::fourCC("ENAM"): reader.get(mTime2); break; - case ESM4::SUB_FNAM: + case ESM::fourCC("FNAM"): reader.get(mTime3); break; - case ESM4::SUB_GNAM: + case ESM::fourCC("GNAM"): reader.get(mTime4); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadmstt.cpp b/components/esm4/loadmstt.cpp index 14091e96f0..3e0cc9ea2d 100644 --- a/components/esm4/loadmstt.cpp +++ b/components/esm4/loadmstt.cpp @@ -41,41 +41,41 @@ void ESM4::MovableStatic::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.getFormId(mLoopingSound); break; - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_OBND: // object bounds - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_VMAD: - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_MODB: - case ESM4::SUB_PRPS: - case ESM4::SUB_PTRN: // FO4 + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("OBND"): // object bounds + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("MODB"): + case ESM::fourCC("PRPS"): + case ESM::fourCC("PTRN"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadmusc.cpp b/components/esm4/loadmusc.cpp index 47ed71b2cf..a06b4dc81c 100644 --- a/components/esm4/loadmusc.cpp +++ b/components/esm4/loadmusc.cpp @@ -43,16 +43,16 @@ void ESM4::Music::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FNAM: + case ESM::fourCC("FNAM"): reader.getZString(mMusicFile); break; - case ESM4::SUB_ANAM: // FONV float (attenuation in db? loop if positive?) - case ESM4::SUB_WNAM: // TES5 - case ESM4::SUB_PNAM: // TES5 - case ESM4::SUB_TNAM: // TES5 + case ESM::fourCC("ANAM"): // FONV float (attenuation in db? loop if positive?) + case ESM::fourCC("WNAM"): // TES5 + case ESM::fourCC("PNAM"): // TES5 + case ESM::fourCC("TNAM"): // TES5 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadnavi.cpp b/components/esm4/loadnavi.cpp index 5b73af606e..47befbf268 100644 --- a/components/esm4/loadnavi.cpp +++ b/components/esm4/loadnavi.cpp @@ -241,13 +241,13 @@ void ESM4::Navigation::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: // seems to be unused? + case ESM::fourCC("EDID"): // seems to be unused? { if (!reader.getZString(mEditorId)) throw std::runtime_error("NAVI EDID data read error"); break; } - case ESM4::SUB_NVPP: + case ESM::fourCC("NVPP"): { // FIXME: FO4 updates the format if (reader.hasFormVersion() && (esmVer == ESM::VER_095 || esmVer == ESM::VER_100)) @@ -330,14 +330,14 @@ void ESM4::Navigation::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_NVER: + case ESM::fourCC("NVER"): { std::uint32_t version; // always the same? (0x0c) reader.get(version); // TODO: store this or use it for merging? // std::cout << "NAVI version " << std::dec << version << std::endl; break; } - case ESM4::SUB_NVMI: // multiple + case ESM::fourCC("NVMI"): // multiple { // Can only read TES4 navmesh data // Note FO4 FIXME above @@ -353,8 +353,8 @@ void ESM4::Navigation::load(ESM4::Reader& reader) mNavMeshInfo.push_back(nvmi); break; } - case ESM4::SUB_NVSI: // from Dawnguard onwards - case ESM4::SUB_NVCI: // FO3 + case ESM::fourCC("NVSI"): // from Dawnguard onwards + case ESM::fourCC("NVCI"): // FO3 { reader.skipSubRecordData(); // FIXME: break; diff --git a/components/esm4/loadnavm.cpp b/components/esm4/loadnavm.cpp index 828fd77ca1..ebe6a7dbbb 100644 --- a/components/esm4/loadnavm.cpp +++ b/components/esm4/loadnavm.cpp @@ -209,7 +209,7 @@ void ESM4::NavMesh::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_NVNM: + case ESM::fourCC("NVNM"): { // See FIXME in ESM4::Navigation::load. // FO4 updates the format @@ -224,19 +224,19 @@ void ESM4::NavMesh::load(ESM4::Reader& reader) mData.push_back(nvnm); // FIXME try swap break; } - case ESM4::SUB_ONAM: - case ESM4::SUB_PNAM: - case ESM4::SUB_NNAM: - case ESM4::SUB_NVER: // FO3 - case ESM4::SUB_DATA: // FO3 - case ESM4::SUB_NVVX: // FO3 - case ESM4::SUB_NVTR: // FO3 - case ESM4::SUB_NVCA: // FO3 - case ESM4::SUB_NVDP: // FO3 - case ESM4::SUB_NVGD: // FO3 - case ESM4::SUB_NVEX: // FO3 - case ESM4::SUB_EDID: // FO3 - case ESM4::SUB_MNAM: // FO4 + case ESM::fourCC("ONAM"): + case ESM::fourCC("PNAM"): + case ESM::fourCC("NNAM"): + case ESM::fourCC("NVER"): // FO3 + case ESM::fourCC("DATA"): // FO3 + case ESM::fourCC("NVVX"): // FO3 + case ESM::fourCC("NVTR"): // FO3 + case ESM::fourCC("NVCA"): // FO3 + case ESM::fourCC("NVDP"): // FO3 + case ESM::fourCC("NVGD"): // FO3 + case ESM::fourCC("NVEX"): // FO3 + case ESM::fourCC("EDID"): // FO3 + case ESM::fourCC("MNAM"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadnote.cpp b/components/esm4/loadnote.cpp index 9c1b4b3140..aee7909e88 100644 --- a/components/esm4/loadnote.cpp +++ b/components/esm4/loadnote.cpp @@ -41,41 +41,41 @@ void ESM4::Note::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_YNAM: + case ESM::fourCC("YNAM"): reader.getFormId(mPickUpSound); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mDropSound); break; - case ESM4::SUB_DATA: - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_ONAM: - case ESM4::SUB_SNAM: - case ESM4::SUB_TNAM: - case ESM4::SUB_XNAM: - case ESM4::SUB_OBND: - case ESM4::SUB_VMAD: - case ESM4::SUB_DNAM: // FO4 - case ESM4::SUB_PNAM: // FO4 - case ESM4::SUB_PTRN: // FO4 + case ESM::fourCC("DATA"): + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("ONAM"): + case ESM::fourCC("SNAM"): + case ESM::fourCC("TNAM"): + case ESM::fourCC("XNAM"): + case ESM::fourCC("OBND"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("DNAM"): // FO4 + case ESM::fourCC("PNAM"): // FO4 + case ESM::fourCC("PTRN"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadnpc.cpp b/components/esm4/loadnpc.cpp index 885263d67b..f3aba6998f 100644 --- a/components/esm4/loadnpc.cpp +++ b/components/esm4/loadnpc.cpp @@ -48,30 +48,30 @@ void ESM4::Npc::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; // not for TES5, see Race - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_CNTO: + case ESM::fourCC("CNTO"): { - static InventoryItem inv; // FIXME: use unique_ptr here? + InventoryItem inv; // FIXME: use unique_ptr here? reader.get(inv); reader.adjustFormId(inv.item); mInventory.push_back(inv); break; } - case ESM4::SUB_SPLO: + case ESM::fourCC("SPLO"): reader.getFormId(mSpell.emplace_back()); break; - case ESM4::SUB_PKID: + case ESM::fourCC("PKID"): reader.getFormId(mAIPackages.emplace_back()); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): { // FO4, FO76 if (subHdr.dataSize == 5) @@ -81,27 +81,27 @@ void ESM4::Npc::load(ESM4::Reader& reader) reader.adjustFormId(mFaction.faction); break; } - case ESM4::SUB_RNAM: + case ESM::fourCC("RNAM"): reader.getFormId(mRace); break; - case ESM4::SUB_CNAM: + case ESM::fourCC("CNAM"): reader.getFormId(mClass); break; - case ESM4::SUB_HNAM: + case ESM::fourCC("HNAM"): reader.getFormId(mHair); break; // not for TES5 - case ESM4::SUB_ENAM: + case ESM::fourCC("ENAM"): reader.getFormId(mEyes); break; // - case ESM4::SUB_INAM: + case ESM::fourCC("INAM"): reader.getFormId(mDeathItem); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; // - case ESM4::SUB_AIDT: + case ESM::fourCC("AIDT"): { if (subHdr.dataSize != 12) { @@ -112,7 +112,7 @@ void ESM4::Npc::load(ESM4::Reader& reader) reader.get(mAIData); // TES4 break; } - case ESM4::SUB_ACBS: + case ESM::fourCC("ACBS"): { switch (subHdr.dataSize) { @@ -129,7 +129,7 @@ void ESM4::Npc::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { if (subHdr.dataSize == 0) break; @@ -140,19 +140,19 @@ void ESM4::Npc::load(ESM4::Reader& reader) reader.skipSubRecordData(); break; } - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mCombatStyle); break; - case ESM4::SUB_CSCR: + case ESM::fourCC("CSCR"): reader.getFormId(mSoundBase); break; - case ESM4::SUB_CSDI: + case ESM::fourCC("CSDI"): reader.getFormId(mSound); break; - case ESM4::SUB_CSDC: + case ESM::fourCC("CSDC"): reader.get(mSoundChance); break; - case ESM4::SUB_WNAM: + case ESM::fourCC("WNAM"): { // FIXME: should be read into mWornArmor for FO4 if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) @@ -161,10 +161,10 @@ void ESM4::Npc::load(ESM4::Reader& reader) reader.get(mFootWeight); break; } - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_KFFZ: + case ESM::fourCC("KFFZ"): { // Seems to be only below 3, and only happens 3 times while loading TES4: // Forward_SheogorathWithCane.kf @@ -174,10 +174,10 @@ void ESM4::Npc::load(ESM4::Reader& reader) throw std::runtime_error("NPC_ KFFZ data read error"); break; } - case ESM4::SUB_LNAM: + case ESM::fourCC("LNAM"): reader.get(mHairLength); break; - case ESM4::SUB_HCLR: + case ESM::fourCC("HCLR"): { reader.get(mHairColour.red); reader.get(mHairColour.green); @@ -186,10 +186,10 @@ void ESM4::Npc::load(ESM4::Reader& reader) break; } - case ESM4::SUB_TPLT: + case ESM::fourCC("TPLT"): reader.getFormId(mBaseTemplate); break; - case ESM4::SUB_FGGS: + case ESM::fourCC("FGGS"): { mSymShapeModeCoefficients.resize(50); for (std::size_t i = 0; i < 50; ++i) @@ -197,7 +197,7 @@ void ESM4::Npc::load(ESM4::Reader& reader) break; } - case ESM4::SUB_FGGA: + case ESM::fourCC("FGGA"): { mAsymShapeModeCoefficients.resize(30); for (std::size_t i = 0; i < 30; ++i) @@ -205,7 +205,7 @@ void ESM4::Npc::load(ESM4::Reader& reader) break; } - case ESM4::SUB_FGTS: + case ESM::fourCC("FGTS"): { mSymTextureModeCoefficients.resize(50); for (std::size_t i = 0; i < 50; ++i) @@ -213,122 +213,122 @@ void ESM4::Npc::load(ESM4::Reader& reader) break; } - case ESM4::SUB_FNAM: + case ESM::fourCC("FNAM"): { reader.get(mFgRace); // std::cout << "race " << mEditorId << " " << mRace << std::endl; // FIXME // std::cout << "fg race " << mEditorId << " " << mFgRace << std::endl; // FIXME break; } - case ESM4::SUB_PNAM: // FO3/FONV/TES5 + case ESM::fourCC("PNAM"): // FO3/FONV/TES5 reader.getFormId(mHeadParts.emplace_back()); break; - case ESM4::SUB_HCLF: // TES5 hair colour + case ESM::fourCC("HCLF"): // TES5 hair colour { reader.getFormId(mHairColourId); break; } - case ESM4::SUB_BCLF: + case ESM::fourCC("BCLF"): { reader.getFormId(mBeardColourId); break; } - case ESM4::SUB_COCT: // TES5 + case ESM::fourCC("COCT"): // TES5 { std::uint32_t count; reader.get(count); break; } - case ESM4::SUB_DOFT: + case ESM::fourCC("DOFT"): reader.getFormId(mDefaultOutfit); break; - case ESM4::SUB_SOFT: + case ESM::fourCC("SOFT"): reader.getFormId(mSleepOutfit); break; - case ESM4::SUB_DPLT: + case ESM::fourCC("DPLT"): reader.getFormId(mDefaultPkg); break; // AI package list - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_NAM6: // height mult - case ESM4::SUB_NAM7: // weight mult - case ESM4::SUB_ATKR: - case ESM4::SUB_CRIF: - case ESM4::SUB_CSDT: - case ESM4::SUB_DNAM: - case ESM4::SUB_ECOR: - case ESM4::SUB_ANAM: - case ESM4::SUB_ATKD: - case ESM4::SUB_ATKE: - case ESM4::SUB_FTST: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_NAM5: - case ESM4::SUB_NAM8: - case ESM4::SUB_NAM9: - case ESM4::SUB_NAMA: - case ESM4::SUB_OBND: - case ESM4::SUB_PRKR: - case ESM4::SUB_PRKZ: - case ESM4::SUB_QNAM: - case ESM4::SUB_SPCT: - case ESM4::SUB_TIAS: - case ESM4::SUB_TINC: - case ESM4::SUB_TINI: - case ESM4::SUB_TINV: - case ESM4::SUB_VMAD: - case ESM4::SUB_VTCK: - case ESM4::SUB_GNAM: - case ESM4::SUB_SHRT: - case ESM4::SUB_SPOR: - case ESM4::SUB_EAMT: // FO3 - case ESM4::SUB_NAM4: // FO3 - case ESM4::SUB_COED: // FO3 - case ESM4::SUB_APPR: // FO4 - case ESM4::SUB_ATKS: // FO4 - case ESM4::SUB_ATKT: // FO4 - case ESM4::SUB_ATKW: // FO4 - case ESM4::SUB_ATTX: // FO4 - case ESM4::SUB_FTYP: // FO4 - case ESM4::SUB_LTPT: // FO4 - case ESM4::SUB_LTPC: // FO4 - case ESM4::SUB_MWGT: // FO4 - case ESM4::SUB_NTRM: // FO4 - case ESM4::SUB_PFRN: // FO4 - case ESM4::SUB_PRPS: // FO4 - case ESM4::SUB_PTRN: // FO4 - case ESM4::SUB_STCP: // FO4 - case ESM4::SUB_TETI: // FO4 - case ESM4::SUB_TEND: // FO4 - case ESM4::SUB_TPTA: // FO4 - case ESM4::SUB_OBTE: // FO4 object template start - case ESM4::SUB_OBTF: // - case ESM4::SUB_OBTS: // - case ESM4::SUB_STOP: // FO4 object template end - case ESM4::SUB_OCOR: // FO4 new package lists start - case ESM4::SUB_GWOR: // - case ESM4::SUB_FCPL: // - case ESM4::SUB_RCLR: // FO4 new package lists end - case ESM4::SUB_CS2D: // FO4 actor sound subrecords - case ESM4::SUB_CS2E: // - case ESM4::SUB_CS2F: // - case ESM4::SUB_CS2H: // - case ESM4::SUB_CS2K: // FO4 actor sound subrecords end - case ESM4::SUB_MSDK: // FO4 morph subrecords start - case ESM4::SUB_MSDV: // - case ESM4::SUB_MRSV: // - case ESM4::SUB_FMRI: // - case ESM4::SUB_FMRS: // - case ESM4::SUB_FMIN: // FO4 morph subrecords end + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("NAM6"): // height mult + case ESM::fourCC("NAM7"): // weight mult + case ESM::fourCC("ATKR"): + case ESM::fourCC("CRIF"): + case ESM::fourCC("CSDT"): + case ESM::fourCC("DNAM"): + case ESM::fourCC("ECOR"): + case ESM::fourCC("ANAM"): + case ESM::fourCC("ATKD"): + case ESM::fourCC("ATKE"): + case ESM::fourCC("FTST"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("NAM5"): + case ESM::fourCC("NAM8"): + case ESM::fourCC("NAM9"): + case ESM::fourCC("NAMA"): + case ESM::fourCC("OBND"): + case ESM::fourCC("PRKR"): + case ESM::fourCC("PRKZ"): + case ESM::fourCC("QNAM"): + case ESM::fourCC("SPCT"): + case ESM::fourCC("TIAS"): + case ESM::fourCC("TINC"): + case ESM::fourCC("TINI"): + case ESM::fourCC("TINV"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("VTCK"): + case ESM::fourCC("GNAM"): + case ESM::fourCC("SHRT"): + case ESM::fourCC("SPOR"): + case ESM::fourCC("EAMT"): // FO3 + case ESM::fourCC("NAM4"): // FO3 + case ESM::fourCC("COED"): // FO3 + case ESM::fourCC("APPR"): // FO4 + case ESM::fourCC("ATKS"): // FO4 + case ESM::fourCC("ATKT"): // FO4 + case ESM::fourCC("ATKW"): // FO4 + case ESM::fourCC("ATTX"): // FO4 + case ESM::fourCC("FTYP"): // FO4 + case ESM::fourCC("LTPT"): // FO4 + case ESM::fourCC("LTPC"): // FO4 + case ESM::fourCC("MWGT"): // FO4 + case ESM::fourCC("NTRM"): // FO4 + case ESM::fourCC("PFRN"): // FO4 + case ESM::fourCC("PRPS"): // FO4 + case ESM::fourCC("PTRN"): // FO4 + case ESM::fourCC("STCP"): // FO4 + case ESM::fourCC("TETI"): // FO4 + case ESM::fourCC("TEND"): // FO4 + case ESM::fourCC("TPTA"): // FO4 + case ESM::fourCC("OBTE"): // FO4 object template start + case ESM::fourCC("OBTF"): // + case ESM::fourCC("OBTS"): // + case ESM::fourCC("STOP"): // FO4 object template end + case ESM::fourCC("OCOR"): // FO4 new package lists start + case ESM::fourCC("GWOR"): // + case ESM::fourCC("FCPL"): // + case ESM::fourCC("RCLR"): // FO4 new package lists end + case ESM::fourCC("CS2D"): // FO4 actor sound subrecords + case ESM::fourCC("CS2E"): // + case ESM::fourCC("CS2F"): // + case ESM::fourCC("CS2H"): // + case ESM::fourCC("CS2K"): // FO4 actor sound subrecords end + case ESM::fourCC("MSDK"): // FO4 morph subrecords start + case ESM::fourCC("MSDV"): // + case ESM::fourCC("MRSV"): // + case ESM::fourCC("FMRI"): // + case ESM::fourCC("FMRS"): // + case ESM::fourCC("FMIN"): // FO4 morph subrecords end reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadotft.cpp b/components/esm4/loadotft.cpp index b980de4a8c..a5fec9b002 100644 --- a/components/esm4/loadotft.cpp +++ b/components/esm4/loadotft.cpp @@ -41,10 +41,10 @@ void ESM4::Outfit::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_INAM: + case ESM::fourCC("INAM"): { mInventory.resize(subHdr.dataSize / sizeof(ESM::FormId32)); for (ESM::FormId& formId : mInventory) diff --git a/components/esm4/loadpack.cpp b/components/esm4/loadpack.cpp index ab75598121..eed1574cef 100644 --- a/components/esm4/loadpack.cpp +++ b/components/esm4/loadpack.cpp @@ -42,10 +42,10 @@ void ESM4::AIPackage::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_PKDT: + case ESM::fourCC("PKDT"): { if (subHdr.dataSize != sizeof(PKDT) && subHdr.dataSize == 4) { @@ -60,7 +60,7 @@ void ESM4::AIPackage::load(ESM4::Reader& reader) break; } - case ESM4::SUB_PSDT: // reader.get(mSchedule); break; + case ESM::fourCC("PSDT"): // reader.get(mSchedule); break; { if (subHdr.dataSize != sizeof(mSchedule)) reader.skipSubRecordData(); // FIXME: @@ -69,7 +69,7 @@ void ESM4::AIPackage::load(ESM4::Reader& reader) break; } - case ESM4::SUB_PLDT: + case ESM::fourCC("PLDT"): { if (subHdr.dataSize != sizeof(mLocation)) reader.skipSubRecordData(); // FIXME: @@ -82,7 +82,7 @@ void ESM4::AIPackage::load(ESM4::Reader& reader) break; } - case ESM4::SUB_PTDT: + case ESM::fourCC("PTDT"): { if (subHdr.dataSize != sizeof(mTarget)) reader.skipSubRecordData(); // FIXME: FO3 @@ -95,7 +95,7 @@ void ESM4::AIPackage::load(ESM4::Reader& reader) break; } - case ESM4::SUB_CTDA: + case ESM::fourCC("CTDA"): { if (subHdr.dataSize != sizeof(CTDA)) { @@ -103,7 +103,7 @@ void ESM4::AIPackage::load(ESM4::Reader& reader) break; } - static CTDA condition; + CTDA condition; reader.get(condition); // FIXME: how to "unadjust" if not FormId? // adjustFormId(condition.param1); @@ -112,55 +112,55 @@ void ESM4::AIPackage::load(ESM4::Reader& reader) break; } - case ESM4::SUB_CTDT: // always 20 for TES4 - case ESM4::SUB_TNAM: // FO3 - case ESM4::SUB_INAM: // FO3 - case ESM4::SUB_CNAM: // FO3 - case ESM4::SUB_SCHR: // FO3 - case ESM4::SUB_POBA: // FO3 - case ESM4::SUB_POCA: // FO3 - case ESM4::SUB_POEA: // FO3 - case ESM4::SUB_SCTX: // FO3 - case ESM4::SUB_SCDA: // FO3 - case ESM4::SUB_SCRO: // FO3 - case ESM4::SUB_IDLA: // FO3 - case ESM4::SUB_IDLC: // FO3 - case ESM4::SUB_IDLF: // FO3 - case ESM4::SUB_IDLT: // FO3 - case ESM4::SUB_PKDD: // FO3 - case ESM4::SUB_PKD2: // FO3 - case ESM4::SUB_PKPT: // FO3 - case ESM4::SUB_PKED: // FO3 - case ESM4::SUB_PKE2: // FO3 - case ESM4::SUB_PKAM: // FO3 - case ESM4::SUB_PUID: // FO3 - case ESM4::SUB_PKW3: // FO3 - case ESM4::SUB_PTD2: // FO3 - case ESM4::SUB_PLD2: // FO3 - case ESM4::SUB_PKFD: // FO3 - case ESM4::SUB_SLSD: // FO3 - case ESM4::SUB_SCVR: // FO3 - case ESM4::SUB_SCRV: // FO3 - case ESM4::SUB_IDLB: // FO3 - case ESM4::SUB_ANAM: // TES5 - case ESM4::SUB_BNAM: // TES5 - case ESM4::SUB_FNAM: // TES5 - case ESM4::SUB_PNAM: // TES5 - case ESM4::SUB_QNAM: // TES5 - case ESM4::SUB_UNAM: // TES5 - case ESM4::SUB_XNAM: // TES5 - case ESM4::SUB_PDTO: // TES5 - case ESM4::SUB_PTDA: // TES5 - case ESM4::SUB_PFOR: // TES5 - case ESM4::SUB_PFO2: // TES5 - case ESM4::SUB_PRCB: // TES5 - case ESM4::SUB_PKCU: // TES5 - case ESM4::SUB_PKC2: // TES5 - case ESM4::SUB_CITC: // TES5 - case ESM4::SUB_CIS1: // TES5 - case ESM4::SUB_CIS2: // TES5 - case ESM4::SUB_VMAD: // TES5 - case ESM4::SUB_TPIC: // TES5 + case ESM::fourCC("CTDT"): // always 20 for TES4 + case ESM::fourCC("TNAM"): // FO3 + case ESM::fourCC("INAM"): // FO3 + case ESM::fourCC("CNAM"): // FO3 + case ESM::fourCC("SCHR"): // FO3 + case ESM::fourCC("POBA"): // FO3 + case ESM::fourCC("POCA"): // FO3 + case ESM::fourCC("POEA"): // FO3 + case ESM::fourCC("SCTX"): // FO3 + case ESM::fourCC("SCDA"): // FO3 + case ESM::fourCC("SCRO"): // FO3 + case ESM::fourCC("IDLA"): // FO3 + case ESM::fourCC("IDLC"): // FO3 + case ESM::fourCC("IDLF"): // FO3 + case ESM::fourCC("IDLT"): // FO3 + case ESM::fourCC("PKDD"): // FO3 + case ESM::fourCC("PKD2"): // FO3 + case ESM::fourCC("PKPT"): // FO3 + case ESM::fourCC("PKED"): // FO3 + case ESM::fourCC("PKE2"): // FO3 + case ESM::fourCC("PKAM"): // FO3 + case ESM::fourCC("PUID"): // FO3 + case ESM::fourCC("PKW3"): // FO3 + case ESM::fourCC("PTD2"): // FO3 + case ESM::fourCC("PLD2"): // FO3 + case ESM::fourCC("PKFD"): // FO3 + case ESM::fourCC("SLSD"): // FO3 + case ESM::fourCC("SCVR"): // FO3 + case ESM::fourCC("SCRV"): // FO3 + case ESM::fourCC("IDLB"): // FO3 + case ESM::fourCC("ANAM"): // TES5 + case ESM::fourCC("BNAM"): // TES5 + case ESM::fourCC("FNAM"): // TES5 + case ESM::fourCC("PNAM"): // TES5 + case ESM::fourCC("QNAM"): // TES5 + case ESM::fourCC("UNAM"): // TES5 + case ESM::fourCC("XNAM"): // TES5 + case ESM::fourCC("PDTO"): // TES5 + case ESM::fourCC("PTDA"): // TES5 + case ESM::fourCC("PFOR"): // TES5 + case ESM::fourCC("PFO2"): // TES5 + case ESM::fourCC("PRCB"): // TES5 + case ESM::fourCC("PKCU"): // TES5 + case ESM::fourCC("PKC2"): // TES5 + case ESM::fourCC("CITC"): // TES5 + case ESM::fourCC("CIS1"): // TES5 + case ESM::fourCC("CIS2"): // TES5 + case ESM::fourCC("VMAD"): // TES5 + case ESM::fourCC("TPIC"): // TES5 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadpgrd.cpp b/components/esm4/loadpgrd.cpp index 12cbf6f28b..9b050be38d 100644 --- a/components/esm4/loadpgrd.cpp +++ b/components/esm4/loadpgrd.cpp @@ -44,10 +44,10 @@ void ESM4::Pathgrid::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; - case ESM4::SUB_PGRP: + case ESM::fourCC("PGRP"): { std::size_t numNodes = subHdr.dataSize / sizeof(PGRP); if (numNodes != std::size_t(mData)) // keep gcc quiet @@ -66,9 +66,9 @@ void ESM4::Pathgrid::load(ESM4::Reader& reader) break; } - case ESM4::SUB_PGRR: + case ESM::fourCC("PGRR"): { - static PGRR link; + PGRR link; for (std::size_t i = 0; i < std::size_t(mData); ++i) // keep gcc quiet { @@ -91,7 +91,7 @@ void ESM4::Pathgrid::load(ESM4::Reader& reader) break; } - case ESM4::SUB_PGRI: + case ESM::fourCC("PGRI"): { std::size_t numForeign = subHdr.dataSize / sizeof(PGRI); mForeign.resize(numForeign); @@ -103,9 +103,9 @@ void ESM4::Pathgrid::load(ESM4::Reader& reader) break; } - case ESM4::SUB_PGRL: + case ESM::fourCC("PGRL"): { - static PGRL objLink; + PGRL objLink; reader.getFormId(objLink.object); // object linkedNode std::size_t numNodes = (subHdr.dataSize - sizeof(int32_t)) / sizeof(int32_t); @@ -118,7 +118,7 @@ void ESM4::Pathgrid::load(ESM4::Reader& reader) break; } - case ESM4::SUB_PGAG: + case ESM::fourCC("PGAG"): { #if 0 std::vector mDataBuf(subHdr.dataSize); diff --git a/components/esm4/loadpgre.cpp b/components/esm4/loadpgre.cpp index 4e473bd47a..123d2c967a 100644 --- a/components/esm4/loadpgre.cpp +++ b/components/esm4/loadpgre.cpp @@ -43,51 +43,51 @@ void ESM4::PlacedGrenade::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_NAME: - case ESM4::SUB_XEZN: - case ESM4::SUB_XRGD: - case ESM4::SUB_XRGB: - case ESM4::SUB_XPRD: - case ESM4::SUB_XPPA: - case ESM4::SUB_INAM: - case ESM4::SUB_TNAM: - case ESM4::SUB_XOWN: - case ESM4::SUB_XRNK: - case ESM4::SUB_XCNT: - case ESM4::SUB_XRDS: - case ESM4::SUB_XHLP: - case ESM4::SUB_XPWR: - case ESM4::SUB_XDCR: - case ESM4::SUB_XLKR: - case ESM4::SUB_XLKT: // FO4 - case ESM4::SUB_XCLP: - case ESM4::SUB_XAPD: - case ESM4::SUB_XAPR: - case ESM4::SUB_XATO: - case ESM4::SUB_XESP: - case ESM4::SUB_XEMI: - case ESM4::SUB_XMBR: - case ESM4::SUB_XIBS: - case ESM4::SUB_XSCL: - case ESM4::SUB_DATA: - case ESM4::SUB_VMAD: - case ESM4::SUB_MNAM: // FO4 - case ESM4::SUB_XAMC: // FO4 - case ESM4::SUB_XASP: // FO4 - case ESM4::SUB_XATP: // FO4 - case ESM4::SUB_XCVR: // FO4 - case ESM4::SUB_XFVC: // FO4 - case ESM4::SUB_XHTW: // FO4 - case ESM4::SUB_XIS2: // FO4 - case ESM4::SUB_XLOD: // FO4 - case ESM4::SUB_XLRL: // FO4 - case ESM4::SUB_XLRT: // FO4 - case ESM4::SUB_XLYR: // FO4 - case ESM4::SUB_XMSP: // FO4 - case ESM4::SUB_XRFG: // FO4 + case ESM::fourCC("NAME"): + case ESM::fourCC("XEZN"): + case ESM::fourCC("XRGD"): + case ESM::fourCC("XRGB"): + case ESM::fourCC("XPRD"): + case ESM::fourCC("XPPA"): + case ESM::fourCC("INAM"): + case ESM::fourCC("TNAM"): + case ESM::fourCC("XOWN"): + case ESM::fourCC("XRNK"): + case ESM::fourCC("XCNT"): + case ESM::fourCC("XRDS"): + case ESM::fourCC("XHLP"): + case ESM::fourCC("XPWR"): + case ESM::fourCC("XDCR"): + case ESM::fourCC("XLKR"): + case ESM::fourCC("XLKT"): // FO4 + case ESM::fourCC("XCLP"): + case ESM::fourCC("XAPD"): + case ESM::fourCC("XAPR"): + case ESM::fourCC("XATO"): + case ESM::fourCC("XESP"): + case ESM::fourCC("XEMI"): + case ESM::fourCC("XMBR"): + case ESM::fourCC("XIBS"): + case ESM::fourCC("XSCL"): + case ESM::fourCC("DATA"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("MNAM"): // FO4 + case ESM::fourCC("XAMC"): // FO4 + case ESM::fourCC("XASP"): // FO4 + case ESM::fourCC("XATP"): // FO4 + case ESM::fourCC("XCVR"): // FO4 + case ESM::fourCC("XFVC"): // FO4 + case ESM::fourCC("XHTW"): // FO4 + case ESM::fourCC("XIS2"): // FO4 + case ESM::fourCC("XLOD"): // FO4 + case ESM::fourCC("XLRL"): // FO4 + case ESM::fourCC("XLRT"): // FO4 + case ESM::fourCC("XLYR"): // FO4 + case ESM::fourCC("XMSP"): // FO4 + case ESM::fourCC("XRFG"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadpwat.cpp b/components/esm4/loadpwat.cpp index 339ae63daf..33a2c86546 100644 --- a/components/esm4/loadpwat.cpp +++ b/components/esm4/loadpwat.cpp @@ -43,12 +43,12 @@ void ESM4::PlaceableWater::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_OBND: - case ESM4::SUB_MODL: - case ESM4::SUB_DNAM: + case ESM::fourCC("OBND"): + case ESM::fourCC("MODL"): + case ESM::fourCC("DNAM"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadqust.cpp b/components/esm4/loadqust.cpp index b7f9b33db9..27c23d92f1 100644 --- a/components/esm4/loadqust.cpp +++ b/components/esm4/loadqust.cpp @@ -42,16 +42,16 @@ void ESM4::Quest::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getZString(mQuestName); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mFileName); break; // TES4 (none in FO3/FONV) - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { if (subHdr.dataSize == 2) // TES4 { @@ -66,10 +66,10 @@ void ESM4::Quest::load(ESM4::Reader& reader) break; } - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mQuestScript); break; - case ESM4::SUB_CTDA: // FIXME: how to detect if 1st/2nd param is a formid? + case ESM::fourCC("CTDA"): // FIXME: how to detect if 1st/2nd param is a formid? { if (subHdr.dataSize == 24) // TES4 { @@ -95,80 +95,80 @@ void ESM4::Quest::load(ESM4::Reader& reader) break; } - case ESM4::SUB_SCHR: + case ESM::fourCC("SCHR"): reader.get(mScript.scriptHeader); break; - case ESM4::SUB_SCDA: + case ESM::fourCC("SCDA"): reader.skipSubRecordData(); break; // compiled script data - case ESM4::SUB_SCTX: + case ESM::fourCC("SCTX"): reader.getString(mScript.scriptSource); break; - case ESM4::SUB_SCRO: + case ESM::fourCC("SCRO"): reader.getFormId(mScript.globReference); break; - case ESM4::SUB_INDX: - case ESM4::SUB_QSDT: - case ESM4::SUB_CNAM: - case ESM4::SUB_QSTA: - case ESM4::SUB_NNAM: // FO3 - case ESM4::SUB_QOBJ: // FO3 - case ESM4::SUB_NAM0: // FO3 - case ESM4::SUB_ANAM: // TES5 - case ESM4::SUB_DNAM: // TES5 - case ESM4::SUB_ENAM: // TES5 - case ESM4::SUB_FNAM: // TES5 - case ESM4::SUB_NEXT: // TES5 - case ESM4::SUB_ALCA: // TES5 - case ESM4::SUB_ALCL: // TES5 - case ESM4::SUB_ALCO: // TES5 - case ESM4::SUB_ALDN: // TES5 - case ESM4::SUB_ALEA: // TES5 - case ESM4::SUB_ALED: // TES5 - case ESM4::SUB_ALEQ: // TES5 - case ESM4::SUB_ALFA: // TES5 - case ESM4::SUB_ALFC: // TES5 - case ESM4::SUB_ALFD: // TES5 - case ESM4::SUB_ALFE: // TES5 - case ESM4::SUB_ALFI: // TES5 - case ESM4::SUB_ALFL: // TES5 - case ESM4::SUB_ALFR: // TES5 - case ESM4::SUB_ALID: // TES5 - case ESM4::SUB_ALLS: // TES5 - case ESM4::SUB_ALNA: // TES5 - case ESM4::SUB_ALNT: // TES5 - case ESM4::SUB_ALPC: // TES5 - case ESM4::SUB_ALRT: // TES5 - case ESM4::SUB_ALSP: // TES5 - case ESM4::SUB_ALST: // TES5 - case ESM4::SUB_ALUA: // TES5 - case ESM4::SUB_CIS1: // TES5 - case ESM4::SUB_CIS2: // TES5 - case ESM4::SUB_CNTO: // TES5 - case ESM4::SUB_COCT: // TES5 - case ESM4::SUB_ECOR: // TES5 - case ESM4::SUB_FLTR: // TES5 - case ESM4::SUB_KNAM: // TES5 - case ESM4::SUB_KSIZ: // TES5 - case ESM4::SUB_KWDA: // TES5 - case ESM4::SUB_QNAM: // TES5 - case ESM4::SUB_QTGL: // TES5 - case ESM4::SUB_SPOR: // TES5 - case ESM4::SUB_VMAD: // TES5 - case ESM4::SUB_VTCK: // TES5 - case ESM4::SUB_ALCC: // FO4 - case ESM4::SUB_ALCS: // FO4 - case ESM4::SUB_ALDI: // FO4 - case ESM4::SUB_ALFV: // FO4 - case ESM4::SUB_ALLA: // FO4 - case ESM4::SUB_ALMI: // FO4 - case ESM4::SUB_GNAM: // FO4 - case ESM4::SUB_GWOR: // FO4 - case ESM4::SUB_LNAM: // FO4 - case ESM4::SUB_NAM2: // FO4 - case ESM4::SUB_OCOR: // FO4 - case ESM4::SUB_SNAM: // FO4 - case ESM4::SUB_XNAM: // FO4 + case ESM::fourCC("INDX"): + case ESM::fourCC("QSDT"): + case ESM::fourCC("CNAM"): + case ESM::fourCC("QSTA"): + case ESM::fourCC("NNAM"): // FO3 + case ESM::fourCC("QOBJ"): // FO3 + case ESM::fourCC("NAM0"): // FO3 + case ESM::fourCC("ANAM"): // TES5 + case ESM::fourCC("DNAM"): // TES5 + case ESM::fourCC("ENAM"): // TES5 + case ESM::fourCC("FNAM"): // TES5 + case ESM::fourCC("NEXT"): // TES5 + case ESM::fourCC("ALCA"): // TES5 + case ESM::fourCC("ALCL"): // TES5 + case ESM::fourCC("ALCO"): // TES5 + case ESM::fourCC("ALDN"): // TES5 + case ESM::fourCC("ALEA"): // TES5 + case ESM::fourCC("ALED"): // TES5 + case ESM::fourCC("ALEQ"): // TES5 + case ESM::fourCC("ALFA"): // TES5 + case ESM::fourCC("ALFC"): // TES5 + case ESM::fourCC("ALFD"): // TES5 + case ESM::fourCC("ALFE"): // TES5 + case ESM::fourCC("ALFI"): // TES5 + case ESM::fourCC("ALFL"): // TES5 + case ESM::fourCC("ALFR"): // TES5 + case ESM::fourCC("ALID"): // TES5 + case ESM::fourCC("ALLS"): // TES5 + case ESM::fourCC("ALNA"): // TES5 + case ESM::fourCC("ALNT"): // TES5 + case ESM::fourCC("ALPC"): // TES5 + case ESM::fourCC("ALRT"): // TES5 + case ESM::fourCC("ALSP"): // TES5 + case ESM::fourCC("ALST"): // TES5 + case ESM::fourCC("ALUA"): // TES5 + case ESM::fourCC("CIS1"): // TES5 + case ESM::fourCC("CIS2"): // TES5 + case ESM::fourCC("CNTO"): // TES5 + case ESM::fourCC("COCT"): // TES5 + case ESM::fourCC("ECOR"): // TES5 + case ESM::fourCC("FLTR"): // TES5 + case ESM::fourCC("KNAM"): // TES5 + case ESM::fourCC("KSIZ"): // TES5 + case ESM::fourCC("KWDA"): // TES5 + case ESM::fourCC("QNAM"): // TES5 + case ESM::fourCC("QTGL"): // TES5 + case ESM::fourCC("SPOR"): // TES5 + case ESM::fourCC("VMAD"): // TES5 + case ESM::fourCC("VTCK"): // TES5 + case ESM::fourCC("ALCC"): // FO4 + case ESM::fourCC("ALCS"): // FO4 + case ESM::fourCC("ALDI"): // FO4 + case ESM::fourCC("ALFV"): // FO4 + case ESM::fourCC("ALLA"): // FO4 + case ESM::fourCC("ALMI"): // FO4 + case ESM::fourCC("GNAM"): // FO4 + case ESM::fourCC("GWOR"): // FO4 + case ESM::fourCC("LNAM"): // FO4 + case ESM::fourCC("NAM2"): // FO4 + case ESM::fourCC("OCOR"): // FO4 + case ESM::fourCC("SNAM"): // FO4 + case ESM::fourCC("XNAM"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadrace.cpp b/components/esm4/loadrace.cpp index 7434a7f87f..02f6f953b4 100644 --- a/components/esm4/loadrace.cpp +++ b/components/esm4/loadrace.cpp @@ -52,7 +52,7 @@ void ESM4::Race::load(ESM4::Reader& reader) // std::cout << "RACE " << ESM::printName(subHdr.typeId) << std::endl; switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): { reader.getZString(mEditorId); // TES4 @@ -73,10 +73,10 @@ void ESM4::Race::load(ESM4::Reader& reader) // Imperial 0x00000907 break; } - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DESC: + case ESM::fourCC("DESC"): { if (subHdr.dataSize == 1) // FO3? { @@ -87,10 +87,10 @@ void ESM4::Race::load(ESM4::Reader& reader) reader.getLocalizedString(mDesc); break; } - case ESM4::SUB_SPLO: // bonus spell formid (TES5 may have SPCT and multiple SPLO) + case ESM::fourCC("SPLO"): // bonus spell formid (TES5 may have SPCT and multiple SPLO) reader.getFormId(mBonusSpells.emplace_back()); break; - case ESM4::SUB_DATA: // ?? different length for TES5 + case ESM::fourCC("DATA"): // ?? different length for TES5 { // DATA:size 128 // 0f 0f ff 00 ff 00 ff 00 ff 00 ff 00 ff 00 00 00 @@ -210,14 +210,14 @@ void ESM4::Race::load(ESM4::Reader& reader) #endif break; } - case ESM4::SUB_DNAM: + case ESM::fourCC("DNAM"): { reader.getFormId(mDefaultHair[0]); // male reader.getFormId(mDefaultHair[1]); // female break; } - case ESM4::SUB_CNAM: + case ESM::fourCC("CNAM"): // CNAM SNAM VNAM // Sheogorath 0x0 0000 98 2b 10011000 00101011 // Golden Saint 0x3 0011 26 46 00100110 01000110 @@ -238,13 +238,13 @@ void ESM4::Race::load(ESM4::Reader& reader) reader.skipSubRecordData(); break; } - case ESM4::SUB_PNAM: + case ESM::fourCC("PNAM"): reader.get(mFaceGenMainClamp); break; // 0x40A00000 = 5.f - case ESM4::SUB_UNAM: + case ESM::fourCC("UNAM"): reader.get(mFaceGenFaceClamp); break; // 0x40400000 = 3.f - case ESM4::SUB_ATTR: // Only in TES4? + case ESM::fourCC("ATTR"): // Only in TES4? { if (subHdr.dataSize == 2) // FO3? { @@ -276,7 +276,7 @@ void ESM4::Race::load(ESM4::Reader& reader) // | | // +-------------+ // - case ESM4::SUB_NAM0: // start marker head data /* 1 */ + case ESM::fourCC("NAM0"): // start marker head data /* 1 */ { curr_part = 0; // head part @@ -296,7 +296,7 @@ void ESM4::Race::load(ESM4::Reader& reader) currentIndex = 0xffffffff; break; } - case ESM4::SUB_INDX: + case ESM::fourCC("INDX"): { reader.get(currentIndex); // FIXME: below check is rather useless @@ -313,7 +313,7 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): { if (currentIndex == 0xffffffff) { @@ -350,10 +350,10 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.skipSubRecordData(); break; // always 0x0000? - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): { if (currentIndex == 0xffffffff) { @@ -379,7 +379,7 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } // - case ESM4::SUB_NAM1: // start marker body data /* 4 */ + case ESM::fourCC("NAM1"): // start marker body data /* 4 */ { if (isFO3 || isFONV) @@ -406,14 +406,14 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_MNAM: + case ESM::fourCC("MNAM"): isMale = true; break; /* 2, 5, 7 */ - case ESM4::SUB_FNAM: + case ESM::fourCC("FNAM"): isMale = false; break; /* 3, 6, 8 */ // - case ESM4::SUB_HNAM: + case ESM::fourCC("HNAM"): { // FIXME: this is a texture name in FO4 if (subHdr.dataSize % sizeof(ESM::FormId32) != 0) @@ -428,7 +428,7 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_ENAM: + case ESM::fourCC("ENAM"): { std::size_t numEyeChoices = subHdr.dataSize / sizeof(ESM::FormId32); mEyeChoices.resize(numEyeChoices); @@ -437,7 +437,7 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_FGGS: + case ESM::fourCC("FGGS"): { if (isMale || isTES4) { @@ -454,7 +454,7 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_FGGA: + case ESM::fourCC("FGGA"): { if (isMale || isTES4) { @@ -471,7 +471,7 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_FGTS: + case ESM::fourCC("FGTS"): { if (isMale || isTES4) { @@ -489,12 +489,12 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } // - case ESM4::SUB_SNAM: // skipping...2 // only in TES4? + case ESM::fourCC("SNAM"): // skipping...2 // only in TES4? { reader.skipSubRecordData(); break; } - case ESM4::SUB_XNAM: + case ESM::fourCC("XNAM"): { ESM::FormId race; std::int32_t adjustment; @@ -504,7 +504,7 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_VNAM: + case ESM::fourCC("VNAM"): { if (subHdr.dataSize == 8) // TES4 { @@ -528,7 +528,7 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } // - case ESM4::SUB_ANAM: // TES5 + case ESM::fourCC("ANAM"): // TES5 { if (isMale) reader.getZString(mModelMale); @@ -536,10 +536,10 @@ void ESM4::Race::load(ESM4::Reader& reader) reader.getZString(mModelFemale); break; } - case ESM4::SUB_KSIZ: + case ESM::fourCC("KSIZ"): reader.get(mNumKeywords); break; - case ESM4::SUB_KWDA: + case ESM::fourCC("KWDA"): { ESM::FormId formid; for (unsigned int i = 0; i < mNumKeywords; ++i) @@ -547,13 +547,13 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } // - case ESM4::SUB_WNAM: // ARMO FormId + case ESM::fourCC("WNAM"): // ARMO FormId { reader.getFormId(mSkin); // std::cout << mEditorId << " skin " << formIdToString(mSkin) << std::endl; // FIXME break; } - case ESM4::SUB_BODT: // body template + case ESM::fourCC("BODT"): // body template { reader.get(mBodyTemplate.bodyPart); reader.get(mBodyTemplate.flags); @@ -564,7 +564,7 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_BOD2: + case ESM::fourCC("BOD2"): { if (subHdr.dataSize == 8 || subHdr.dataSize == 4) // TES5, FO4 { @@ -584,7 +584,7 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_HEAD: // TES5 + case ESM::fourCC("HEAD"): // TES5 { ESM::FormId formId; reader.getFormId(formId); @@ -611,7 +611,7 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_NAM3: // start of hkx model + case ESM::fourCC("NAM3"): // start of hkx model { curr_part = 3; // for TES5 NAM3 indicates the start of hkx model @@ -651,7 +651,7 @@ void ESM4::Race::load(ESM4::Reader& reader) // ManakinRace // ManakinRace // ManakinRace FX0 - case ESM4::SUB_NAME: // TES5 biped object names (x32) + case ESM::fourCC("NAME"): // TES5 biped object names (x32) { std::string name; reader.getZString(name); @@ -659,112 +659,112 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_MTNM: // movement type - case ESM4::SUB_ATKD: // attack data - case ESM4::SUB_ATKE: // attach event - case ESM4::SUB_GNAM: // body part data - case ESM4::SUB_NAM4: // material type - case ESM4::SUB_NAM5: // unarmed impact? - case ESM4::SUB_LNAM: // close loot sound - case ESM4::SUB_QNAM: // equipment slot formid - case ESM4::SUB_HCLF: // default hair colour - case ESM4::SUB_UNES: // unarmed equipment slot formid - case ESM4::SUB_TINC: - case ESM4::SUB_TIND: - case ESM4::SUB_TINI: - case ESM4::SUB_TINL: - case ESM4::SUB_TINP: - case ESM4::SUB_TINT: - case ESM4::SUB_TINV: - case ESM4::SUB_TIRS: - case ESM4::SUB_PHWT: - case ESM4::SUB_AHCF: - case ESM4::SUB_AHCM: - case ESM4::SUB_MPAI: - case ESM4::SUB_MPAV: - case ESM4::SUB_DFTF: - case ESM4::SUB_DFTM: - case ESM4::SUB_FLMV: - case ESM4::SUB_FTSF: - case ESM4::SUB_FTSM: - case ESM4::SUB_MTYP: - case ESM4::SUB_NAM7: - case ESM4::SUB_NAM8: - case ESM4::SUB_PHTN: - case ESM4::SUB_RNAM: - case ESM4::SUB_RNMV: - case ESM4::SUB_RPRF: - case ESM4::SUB_RPRM: - case ESM4::SUB_SNMV: - case ESM4::SUB_SPCT: - case ESM4::SUB_SPED: - case ESM4::SUB_SWMV: - case ESM4::SUB_WKMV: - case ESM4::SUB_SPMV: - case ESM4::SUB_ATKR: - case ESM4::SUB_CTDA: - case ESM4::SUB_CIS1: - case ESM4::SUB_CIS2: - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end + case ESM::fourCC("MTNM"): // movement type + case ESM::fourCC("ATKD"): // attack data + case ESM::fourCC("ATKE"): // attach event + case ESM::fourCC("GNAM"): // body part data + case ESM::fourCC("NAM4"): // material type + case ESM::fourCC("NAM5"): // unarmed impact? + case ESM::fourCC("LNAM"): // close loot sound + case ESM::fourCC("QNAM"): // equipment slot formid + case ESM::fourCC("HCLF"): // default hair colour + case ESM::fourCC("UNES"): // unarmed equipment slot formid + case ESM::fourCC("TINC"): + case ESM::fourCC("TIND"): + case ESM::fourCC("TINI"): + case ESM::fourCC("TINL"): + case ESM::fourCC("TINP"): + case ESM::fourCC("TINT"): + case ESM::fourCC("TINV"): + case ESM::fourCC("TIRS"): + case ESM::fourCC("PHWT"): + case ESM::fourCC("AHCF"): + case ESM::fourCC("AHCM"): + case ESM::fourCC("MPAI"): + case ESM::fourCC("MPAV"): + case ESM::fourCC("DFTF"): + case ESM::fourCC("DFTM"): + case ESM::fourCC("FLMV"): + case ESM::fourCC("FTSF"): + case ESM::fourCC("FTSM"): + case ESM::fourCC("MTYP"): + case ESM::fourCC("NAM7"): + case ESM::fourCC("NAM8"): + case ESM::fourCC("PHTN"): + case ESM::fourCC("RNAM"): + case ESM::fourCC("RNMV"): + case ESM::fourCC("RPRF"): + case ESM::fourCC("RPRM"): + case ESM::fourCC("SNMV"): + case ESM::fourCC("SPCT"): + case ESM::fourCC("SPED"): + case ESM::fourCC("SWMV"): + case ESM::fourCC("WKMV"): + case ESM::fourCC("SPMV"): + case ESM::fourCC("ATKR"): + case ESM::fourCC("CTDA"): + case ESM::fourCC("CIS1"): + case ESM::fourCC("CIS2"): + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end // - case ESM4::SUB_YNAM: // FO3 - case ESM4::SUB_NAM2: // FO3 - case ESM4::SUB_VTCK: // FO3 - case ESM4::SUB_MODD: // FO3 - case ESM4::SUB_ONAM: // FO3 - case ESM4::SUB_APPR: // FO4 - case ESM4::SUB_ATKS: // FO4 - case ESM4::SUB_ATKT: // FO4 - case ESM4::SUB_ATKW: // FO4 - case ESM4::SUB_BMMP: // FO4 - case ESM4::SUB_BSMB: // FO4 - case ESM4::SUB_BSMP: // FO4 - case ESM4::SUB_BSMS: // FO4 - - case ESM4::SUB_FMRI: // FO4 - case ESM4::SUB_FMRN: // FO4 - case ESM4::SUB_HLTX: // FO4 - case ESM4::SUB_MLSI: // FO4 - case ESM4::SUB_MPGN: // FO4 - case ESM4::SUB_MPGS: // FO4 - case ESM4::SUB_MPPC: // FO4 - case ESM4::SUB_MPPF: // FO4 - case ESM4::SUB_MPPI: // FO4 - case ESM4::SUB_MPPK: // FO4 - case ESM4::SUB_MPPM: // FO4 - case ESM4::SUB_MPPN: // FO4 - case ESM4::SUB_MPPT: // FO4 - case ESM4::SUB_MSID: // FO4 - case ESM4::SUB_MSM0: // FO4 - case ESM4::SUB_MSM1: // FO4 - case ESM4::SUB_NNAM: // FO4 - case ESM4::SUB_NTOP: // FO4 - case ESM4::SUB_PRPS: // FO4 - case ESM4::SUB_PTOP: // FO4 - case ESM4::SUB_QSTI: // FO4 - case ESM4::SUB_RBPC: // FO4 - case ESM4::SUB_SADD: // FO4 - case ESM4::SUB_SAKD: // FO4 - case ESM4::SUB_SAPT: // FO4 - case ESM4::SUB_SGNM: // FO4 - case ESM4::SUB_SRAC: // FO4 - case ESM4::SUB_SRAF: // FO4 - case ESM4::SUB_STCP: // FO4 - case ESM4::SUB_STKD: // FO4 - case ESM4::SUB_TETI: // FO4 - case ESM4::SUB_TTEB: // FO4 - case ESM4::SUB_TTEC: // FO4 - case ESM4::SUB_TTED: // FO4 - case ESM4::SUB_TTEF: // FO4 - case ESM4::SUB_TTET: // FO4 - case ESM4::SUB_TTGE: // FO4 - case ESM4::SUB_TTGP: // FO4 - case ESM4::SUB_UNWP: // FO4 - case ESM4::SUB_WMAP: // FO4 - case ESM4::SUB_ZNAM: // FO4 + case ESM::fourCC("YNAM"): // FO3 + case ESM::fourCC("NAM2"): // FO3 + case ESM::fourCC("VTCK"): // FO3 + case ESM::fourCC("MODD"): // FO3 + case ESM::fourCC("ONAM"): // FO3 + case ESM::fourCC("APPR"): // FO4 + case ESM::fourCC("ATKS"): // FO4 + case ESM::fourCC("ATKT"): // FO4 + case ESM::fourCC("ATKW"): // FO4 + case ESM::fourCC("BMMP"): // FO4 + case ESM::fourCC("BSMB"): // FO4 + case ESM::fourCC("BSMP"): // FO4 + case ESM::fourCC("BSMS"): // FO4 + + case ESM::fourCC("FMRI"): // FO4 + case ESM::fourCC("FMRN"): // FO4 + case ESM::fourCC("HLTX"): // FO4 + case ESM::fourCC("MLSI"): // FO4 + case ESM::fourCC("MPGN"): // FO4 + case ESM::fourCC("MPGS"): // FO4 + case ESM::fourCC("MPPC"): // FO4 + case ESM::fourCC("MPPF"): // FO4 + case ESM::fourCC("MPPI"): // FO4 + case ESM::fourCC("MPPK"): // FO4 + case ESM::fourCC("MPPM"): // FO4 + case ESM::fourCC("MPPN"): // FO4 + case ESM::fourCC("MPPT"): // FO4 + case ESM::fourCC("MSID"): // FO4 + case ESM::fourCC("MSM0"): // FO4 + case ESM::fourCC("MSM1"): // FO4 + case ESM::fourCC("NNAM"): // FO4 + case ESM::fourCC("NTOP"): // FO4 + case ESM::fourCC("PRPS"): // FO4 + case ESM::fourCC("PTOP"): // FO4 + case ESM::fourCC("QSTI"): // FO4 + case ESM::fourCC("RBPC"): // FO4 + case ESM::fourCC("SADD"): // FO4 + case ESM::fourCC("SAKD"): // FO4 + case ESM::fourCC("SAPT"): // FO4 + case ESM::fourCC("SGNM"): // FO4 + case ESM::fourCC("SRAC"): // FO4 + case ESM::fourCC("SRAF"): // FO4 + case ESM::fourCC("STCP"): // FO4 + case ESM::fourCC("STKD"): // FO4 + case ESM::fourCC("TETI"): // FO4 + case ESM::fourCC("TTEB"): // FO4 + case ESM::fourCC("TTEC"): // FO4 + case ESM::fourCC("TTED"): // FO4 + case ESM::fourCC("TTEF"): // FO4 + case ESM::fourCC("TTET"): // FO4 + case ESM::fourCC("TTGE"): // FO4 + case ESM::fourCC("TTGP"): // FO4 + case ESM::fourCC("UNWP"): // FO4 + case ESM::fourCC("WMAP"): // FO4 + case ESM::fourCC("ZNAM"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadrefr.cpp b/components/esm4/loadrefr.cpp index fb26e39546..5ac3a5f077 100644 --- a/components/esm4/loadrefr.cpp +++ b/components/esm4/loadrefr.cpp @@ -46,26 +46,26 @@ void ESM4::Reference::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_NAME: + case ESM::fourCC("NAME"): { ESM::FormId BaseId; reader.getFormId(BaseId); mBaseObj = BaseId; break; } - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mPos); break; - case ESM4::SUB_XSCL: + case ESM::fourCC("XSCL"): reader.get(mScale); break; - case ESM4::SUB_XOWN: + case ESM::fourCC("XOWN"): { switch (subHdr.dataSize) { @@ -86,13 +86,13 @@ void ESM4::Reference::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_XGLB: + case ESM::fourCC("XGLB"): reader.getFormId(mGlobal); break; - case ESM4::SUB_XRNK: + case ESM::fourCC("XRNK"): reader.get(mFactionRank); break; - case ESM4::SUB_XESP: + case ESM::fourCC("XESP"): { reader.getFormId(mEsp.parent); reader.get(mEsp.flags); @@ -100,7 +100,7 @@ void ESM4::Reference::load(ESM4::Reader& reader) // << ", 0x" << std::hex << (mEsp.flags & 0xff) << std::endl;// FIXME break; } - case ESM4::SUB_XTEL: + case ESM::fourCC("XTEL"): { switch (subHdr.dataSize) { @@ -125,7 +125,7 @@ void ESM4::Reference::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_XSED: + case ESM::fourCC("XSED"): { // 1 or 4 bytes if (subHdr.dataSize == 1) @@ -147,7 +147,7 @@ void ESM4::Reference::load(ESM4::Reader& reader) reader.skipSubRecordData(); break; } - case ESM4::SUB_XLOD: + case ESM::fourCC("XLOD"): { // 12 bytes if (subHdr.dataSize == 12) @@ -168,7 +168,7 @@ void ESM4::Reference::load(ESM4::Reader& reader) reader.skipSubRecordData(); break; } - case ESM4::SUB_XACT: + case ESM::fourCC("XACT"): { if (subHdr.dataSize == 4) { @@ -182,7 +182,7 @@ void ESM4::Reference::load(ESM4::Reader& reader) reader.skipSubRecordData(); break; } - case ESM4::SUB_XRTM: // formId + case ESM::fourCC("XRTM"): // formId { // seems like another ref, e.g. 00064583 has base object 00000034 which is "XMarkerHeading" // e.g. some are doors (prob. quest related) @@ -199,7 +199,7 @@ void ESM4::Reference::load(ESM4::Reader& reader) // std::cout << "REFR " << mEditorId << " XRTM : " << formIdToString(marker) << std::endl;// FIXME break; } - case ESM4::SUB_TNAM: // reader.get(mMapMarker); break; + case ESM::fourCC("TNAM"): // reader.get(mMapMarker); break; { if (subHdr.dataSize != sizeof(mMapMarker)) // reader.skipSubRecordData(); // FIXME: FO3 @@ -209,26 +209,26 @@ void ESM4::Reference::load(ESM4::Reader& reader) break; } - case ESM4::SUB_XMRK: + case ESM::fourCC("XMRK"): mIsMapMarker = true; break; // all have mBaseObj 0x00000010 "MapMarker" - case ESM4::SUB_FNAM: + case ESM::fourCC("FNAM"): { // std::cout << "REFR " << ESM::printName(subHdr.typeId) << " skipping..." // << subHdr.dataSize << std::endl; reader.skipSubRecordData(); break; } - case ESM4::SUB_XTRG: // formId + case ESM::fourCC("XTRG"): // formId { reader.getFormId(mTargetRef); // std::cout << "REFR XRTG : " << formIdToString(id) << std::endl;// FIXME break; } - case ESM4::SUB_CNAM: + case ESM::fourCC("CNAM"): reader.getFormId(mAudioLocation); break; // FONV - case ESM4::SUB_XRDO: // FO3 + case ESM::fourCC("XRDO"): // FO3 { // FIXME: completely different meaning in FO4 reader.get(mRadio.rangeRadius); @@ -238,14 +238,14 @@ void ESM4::Reference::load(ESM4::Reader& reader) break; } - case ESM4::SUB_SCRO: // FO3 + case ESM::fourCC("SCRO"): // FO3 { reader.getFormId(sid); // if (mFormId == 0x0016b74B) // std::cout << "REFR SCRO : " << formIdToString(sid) << std::endl;// FIXME break; } - case ESM4::SUB_XLOC: + case ESM::fourCC("XLOC"): { mIsLocked = true; std::int8_t dummy; // FIXME: very poor code @@ -268,97 +268,97 @@ void ESM4::Reference::load(ESM4::Reader& reader) break; } - case ESM4::SUB_XCNT: + case ESM::fourCC("XCNT"): { reader.get(mCount); break; } // lighting - case ESM4::SUB_LNAM: // lighting template formId - case ESM4::SUB_XLIG: // struct, FOV, fade, etc - case ESM4::SUB_XEMI: // LIGH formId - case ESM4::SUB_XRDS: // Radius or Radiance - case ESM4::SUB_XRGB: - case ESM4::SUB_XRGD: // tangent data? - case ESM4::SUB_XALP: // alpha cutoff + case ESM::fourCC("LNAM"): // lighting template formId + case ESM::fourCC("XLIG"): // struct, FOV, fade, etc + case ESM::fourCC("XEMI"): // LIGH formId + case ESM::fourCC("XRDS"): // Radius or Radiance + case ESM::fourCC("XRGB"): + case ESM::fourCC("XRGD"): // tangent data? + case ESM::fourCC("XALP"): // alpha cutoff // - case ESM4::SUB_XPCI: // formId - case ESM4::SUB_XLCM: - case ESM4::SUB_ONAM: - case ESM4::SUB_VMAD: - case ESM4::SUB_XPRM: - case ESM4::SUB_INAM: - case ESM4::SUB_PDTO: - case ESM4::SUB_SCHR: - case ESM4::SUB_SCTX: - case ESM4::SUB_XAPD: - case ESM4::SUB_XAPR: - case ESM4::SUB_XCVL: - case ESM4::SUB_XCZA: - case ESM4::SUB_XCZC: - case ESM4::SUB_XEZN: - case ESM4::SUB_XFVC: - case ESM4::SUB_XHTW: - case ESM4::SUB_XIS2: - case ESM4::SUB_XLCN: - case ESM4::SUB_XLIB: - case ESM4::SUB_XLKR: - case ESM4::SUB_XLRM: - case ESM4::SUB_XLRT: - case ESM4::SUB_XLTW: - case ESM4::SUB_XMBO: - case ESM4::SUB_XMBP: - case ESM4::SUB_XMBR: - case ESM4::SUB_XNDP: - case ESM4::SUB_XOCP: - case ESM4::SUB_XPOD: - case ESM4::SUB_XPTL: - case ESM4::SUB_XPPA: - case ESM4::SUB_XPRD: - case ESM4::SUB_XPWR: - case ESM4::SUB_XRMR: - case ESM4::SUB_XSPC: - case ESM4::SUB_XTNM: - case ESM4::SUB_XTRI: - case ESM4::SUB_XWCN: - case ESM4::SUB_XWCU: - case ESM4::SUB_XATR: - case ESM4::SUB_XHLT: // Unofficial Oblivion Patch - case ESM4::SUB_XCHG: // thievery.exp - case ESM4::SUB_XHLP: // FO3 - case ESM4::SUB_XAMT: // FO3 - case ESM4::SUB_XAMC: // FO3 - case ESM4::SUB_XRAD: // FO3 - case ESM4::SUB_XIBS: // FO3 - case ESM4::SUB_XORD: // FO3 - case ESM4::SUB_XCLP: // FO3 - case ESM4::SUB_SCDA: // FO3 - case ESM4::SUB_RCLR: // FO3 - case ESM4::SUB_BNAM: // FONV - case ESM4::SUB_MMRK: // FONV - case ESM4::SUB_MNAM: // FONV - case ESM4::SUB_NNAM: // FONV - case ESM4::SUB_XATO: // FONV - case ESM4::SUB_SCRV: // FONV - case ESM4::SUB_SCVR: // FONV - case ESM4::SUB_SLSD: // FONV - case ESM4::SUB_XSRF: // FONV - case ESM4::SUB_XSRD: // FONV - case ESM4::SUB_WMI1: // FONV - case ESM4::SUB_XLRL: // Unofficial Skyrim Patch - case ESM4::SUB_XASP: // FO4 - case ESM4::SUB_XATP: // FO4 - case ESM4::SUB_XBSD: // FO4 - case ESM4::SUB_XCVR: // FO4 - case ESM4::SUB_XCZR: // FO4 - case ESM4::SUB_XLKT: // FO4 - case ESM4::SUB_XLYR: // FO4 - case ESM4::SUB_XMSP: // FO4 - case ESM4::SUB_XPDD: // FO4 - case ESM4::SUB_XPLK: // FO4 - case ESM4::SUB_XRFG: // FO4 - case ESM4::SUB_XWPG: // FO4 - case ESM4::SUB_XWPN: // FO4 + case ESM::fourCC("XPCI"): // formId + case ESM::fourCC("XLCM"): + case ESM::fourCC("ONAM"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("XPRM"): + case ESM::fourCC("INAM"): + case ESM::fourCC("PDTO"): + case ESM::fourCC("SCHR"): + case ESM::fourCC("SCTX"): + case ESM::fourCC("XAPD"): + case ESM::fourCC("XAPR"): + case ESM::fourCC("XCVL"): + case ESM::fourCC("XCZA"): + case ESM::fourCC("XCZC"): + case ESM::fourCC("XEZN"): + case ESM::fourCC("XFVC"): + case ESM::fourCC("XHTW"): + case ESM::fourCC("XIS2"): + case ESM::fourCC("XLCN"): + case ESM::fourCC("XLIB"): + case ESM::fourCC("XLKR"): + case ESM::fourCC("XLRM"): + case ESM::fourCC("XLRT"): + case ESM::fourCC("XLTW"): + case ESM::fourCC("XMBO"): + case ESM::fourCC("XMBP"): + case ESM::fourCC("XMBR"): + case ESM::fourCC("XNDP"): + case ESM::fourCC("XOCP"): + case ESM::fourCC("XPOD"): + case ESM::fourCC("XPTL"): + case ESM::fourCC("XPPA"): + case ESM::fourCC("XPRD"): + case ESM::fourCC("XPWR"): + case ESM::fourCC("XRMR"): + case ESM::fourCC("XSPC"): + case ESM::fourCC("XTNM"): + case ESM::fourCC("XTRI"): + case ESM::fourCC("XWCN"): + case ESM::fourCC("XWCU"): + case ESM::fourCC("XATR"): + case ESM::fourCC("XHLT"): // Unofficial Oblivion Patch + case ESM::fourCC("XCHG"): // thievery.exp + case ESM::fourCC("XHLP"): // FO3 + case ESM::fourCC("XAMT"): // FO3 + case ESM::fourCC("XAMC"): // FO3 + case ESM::fourCC("XRAD"): // FO3 + case ESM::fourCC("XIBS"): // FO3 + case ESM::fourCC("XORD"): // FO3 + case ESM::fourCC("XCLP"): // FO3 + case ESM::fourCC("SCDA"): // FO3 + case ESM::fourCC("RCLR"): // FO3 + case ESM::fourCC("BNAM"): // FONV + case ESM::fourCC("MMRK"): // FONV + case ESM::fourCC("MNAM"): // FONV + case ESM::fourCC("NNAM"): // FONV + case ESM::fourCC("XATO"): // FONV + case ESM::fourCC("SCRV"): // FONV + case ESM::fourCC("SCVR"): // FONV + case ESM::fourCC("SLSD"): // FONV + case ESM::fourCC("XSRF"): // FONV + case ESM::fourCC("XSRD"): // FONV + case ESM::fourCC("WMI1"): // FONV + case ESM::fourCC("XLRL"): // Unofficial Skyrim Patch + case ESM::fourCC("XASP"): // FO4 + case ESM::fourCC("XATP"): // FO4 + case ESM::fourCC("XBSD"): // FO4 + case ESM::fourCC("XCVR"): // FO4 + case ESM::fourCC("XCZR"): // FO4 + case ESM::fourCC("XLKT"): // FO4 + case ESM::fourCC("XLYR"): // FO4 + case ESM::fourCC("XMSP"): // FO4 + case ESM::fourCC("XPDD"): // FO4 + case ESM::fourCC("XPLK"): // FO4 + case ESM::fourCC("XRFG"): // FO4 + case ESM::fourCC("XWPG"): // FO4 + case ESM::fourCC("XWPN"): // FO4 // if (mFormId == 0x0007e90f) // XPRM XPOD // if (mBaseObj == 0x17) //XPRM XOCP occlusion plane data XMBO bound half extents reader.skipSubRecordData(); diff --git a/components/esm4/loadrefr.hpp b/components/esm4/loadrefr.hpp index ec76928827..99826200c9 100644 --- a/components/esm4/loadrefr.hpp +++ b/components/esm4/loadrefr.hpp @@ -32,6 +32,7 @@ #include "reference.hpp" // EnableParent #include +#include #include namespace ESM4 diff --git a/components/esm4/loadregn.cpp b/components/esm4/loadregn.cpp index 2f10ea22d8..c8cc9663d9 100644 --- a/components/esm4/loadregn.cpp +++ b/components/esm4/loadregn.cpp @@ -41,22 +41,22 @@ void ESM4::Region::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_RCLR: + case ESM::fourCC("RCLR"): reader.get(mColour); break; - case ESM4::SUB_WNAM: + case ESM::fourCC("WNAM"): reader.getFormId(mWorldId); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mShader); break; - case ESM4::SUB_RPLI: + case ESM::fourCC("RPLI"): reader.get(mEdgeFalloff); break; - case ESM4::SUB_RPLD: + case ESM::fourCC("RPLD"): { mRPLD.resize(subHdr.dataSize / sizeof(std::uint32_t)); for (std::vector::iterator it = mRPLD.begin(); it != mRPLD.end(); ++it) @@ -71,10 +71,10 @@ void ESM4::Region::load(ESM4::Reader& reader) break; } - case ESM4::SUB_RDAT: + case ESM::fourCC("RDAT"): reader.get(mData); break; - case ESM4::SUB_RDMP: + case ESM::fourCC("RDMP"): { if (mData.type != RDAT_Map) throw std::runtime_error("REGN unexpected data type"); @@ -83,7 +83,7 @@ void ESM4::Region::load(ESM4::Reader& reader) } // FO3 only 2: DemoMegatonSound and DC01 (both 0 RDMD) // FONV none - case ESM4::SUB_RDMD: // music type; 0 default, 1 public, 2 dungeon + case ESM::fourCC("RDMD"): // music type; 0 default, 1 public, 2 dungeon { #if 0 int dummy; @@ -94,14 +94,14 @@ void ESM4::Region::load(ESM4::Reader& reader) #endif break; } - case ESM4::SUB_RDMO: // not seen in FO3/FONV? + case ESM::fourCC("RDMO"): // not seen in FO3/FONV? { // std::cout << "REGN " << ESM::printName(subHdr.typeId) << " skipping..." // << subHdr.dataSize << std::endl; reader.skipSubRecordData(); break; } - case ESM4::SUB_RDSD: // Possibly the same as RDSA + case ESM::fourCC("RDSD"): // Possibly the same as RDSA { if (mData.type != RDAT_Sound) throw std::runtime_error( @@ -114,16 +114,16 @@ void ESM4::Region::load(ESM4::Reader& reader) break; } - case ESM4::SUB_RDGS: // Only in Oblivion? (ToddTestRegion1) // formId - case ESM4::SUB_RDSA: - case ESM4::SUB_RDWT: // formId - case ESM4::SUB_RDOT: // formId - case ESM4::SUB_RDID: // FONV - case ESM4::SUB_RDSB: // FONV - case ESM4::SUB_RDSI: // FONV - case ESM4::SUB_NVMI: // TES5 - case ESM4::SUB_ANAM: // FO4 - case ESM4::SUB_RLDM: // FO4 + case ESM::fourCC("RDGS"): // Only in Oblivion? (ToddTestRegion1) // formId + case ESM::fourCC("RDSA"): + case ESM::fourCC("RDWT"): // formId + case ESM::fourCC("RDOT"): // formId + case ESM::fourCC("RDID"): // FONV + case ESM::fourCC("RDSB"): // FONV + case ESM::fourCC("RDSI"): // FONV + case ESM::fourCC("NVMI"): // TES5 + case ESM::fourCC("ANAM"): // FO4 + case ESM::fourCC("RLDM"): // FO4 // RDAT skipping... following is a map // RDMP skipping... map name // diff --git a/components/esm4/loadroad.cpp b/components/esm4/loadroad.cpp index 8a33ab1c1d..71a7b55196 100644 --- a/components/esm4/loadroad.cpp +++ b/components/esm4/loadroad.cpp @@ -45,7 +45,7 @@ void ESM4::Road::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_PGRP: + case ESM::fourCC("PGRP"): { std::size_t numNodes = subHdr.dataSize / sizeof(PGRP); @@ -57,10 +57,10 @@ void ESM4::Road::load(ESM4::Reader& reader) break; } - case ESM4::SUB_PGRR: + case ESM::fourCC("PGRR"): { - static PGRR link; - static RDRP linkPt; + PGRR link; + RDRP linkPt; for (std::size_t i = 0; i < mNodes.size(); ++i) { diff --git a/components/esm4/loadsbsp.cpp b/components/esm4/loadsbsp.cpp index a874331dab..c9450492b8 100644 --- a/components/esm4/loadsbsp.cpp +++ b/components/esm4/loadsbsp.cpp @@ -41,10 +41,10 @@ void ESM4::SubSpace::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_DNAM: + case ESM::fourCC("DNAM"): { reader.get(mDimension.x); reader.get(mDimension.y); diff --git a/components/esm4/loadscol.cpp b/components/esm4/loadscol.cpp index dea900fe17..00775edaa5 100644 --- a/components/esm4/loadscol.cpp +++ b/components/esm4/loadscol.cpp @@ -43,22 +43,22 @@ void ESM4::StaticCollection::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_OBND: - case ESM4::SUB_MODL: // Model data start - case ESM4::SUB_MODT: - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_ONAM: - case ESM4::SUB_DATA: - case ESM4::SUB_FLTR: // FO4 - case ESM4::SUB_PTRN: // FO4 + case ESM::fourCC("OBND"): + case ESM::fourCC("MODL"): // Model data start + case ESM::fourCC("MODT"): + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("ONAM"): + case ESM::fourCC("DATA"): + case ESM::fourCC("FLTR"): // FO4 + case ESM::fourCC("PTRN"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadscpt.cpp b/components/esm4/loadscpt.cpp index b4071ed21d..dbc75b75d6 100644 --- a/components/esm4/loadscpt.cpp +++ b/components/esm4/loadscpt.cpp @@ -36,19 +36,17 @@ void ESM4::Script::load(ESM4::Reader& reader) mId = reader.getFormIdFromHeader(); mFlags = reader.hdr().record.flags; - static ScriptLocalVariableData localVar; - while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): { reader.getZString(mEditorId); break; } - case ESM4::SUB_SCHR: + case ESM::fourCC("SCHR"): { // For debugging only #if 0 @@ -73,12 +71,12 @@ void ESM4::Script::load(ESM4::Reader& reader) #endif break; } - case ESM4::SUB_SCTX: + case ESM::fourCC("SCTX"): reader.getString(mScript.scriptSource); // if (mEditorId == "CTrapLogs01SCRIPT") // std::cout << mScript.scriptSource << std::endl; break; - case ESM4::SUB_SCDA: // compiled script data + case ESM::fourCC("SCDA"): // compiled script data { // For debugging only #if 0 @@ -112,27 +110,31 @@ void ESM4::Script::load(ESM4::Reader& reader) #endif break; } - case ESM4::SUB_SCRO: + case ESM::fourCC("SCRO"): reader.getFormId(mScript.globReference); break; - case ESM4::SUB_SLSD: + case ESM::fourCC("SLSD"): { - localVar.clear(); + ScriptLocalVariableData localVar; reader.get(localVar.index); reader.get(localVar.unknown1); reader.get(localVar.unknown2); reader.get(localVar.unknown3); reader.get(localVar.type); reader.get(localVar.unknown4); + mScript.localVarData.push_back(std::move(localVar)); // WARN: assumes SCVR will follow immediately break; } - case ESM4::SUB_SCVR: // assumed always pair with SLSD - reader.getZString(localVar.variableName); - mScript.localVarData.push_back(localVar); + case ESM::fourCC("SCVR"): // assumed always pair with SLSD + if (!mScript.localVarData.empty()) + reader.getZString(mScript.localVarData.back().variableName); + else + reader.skipSubRecordData(); + break; - case ESM4::SUB_SCRV: + case ESM::fourCC("SCRV"): { std::uint32_t index; reader.get(index); diff --git a/components/esm4/loadscrl.cpp b/components/esm4/loadscrl.cpp index 954ddf4e36..f88854f8db 100644 --- a/components/esm4/loadscrl.cpp +++ b/components/esm4/loadscrl.cpp @@ -41,40 +41,40 @@ void ESM4::Scroll::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DESC: + case ESM::fourCC("DESC"): reader.getLocalizedString(mText); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData.value); reader.get(mData.weight); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_YNAM: + case ESM::fourCC("YNAM"): reader.getFormId(mPickUpSound); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mDropSound); break; - // case ESM4::SUB_MODB: reader.get(mBoundRadius); break; - case ESM4::SUB_OBND: - case ESM4::SUB_CTDA: - case ESM4::SUB_EFID: - case ESM4::SUB_EFIT: - case ESM4::SUB_ETYP: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_MDOB: - case ESM4::SUB_MODT: - case ESM4::SUB_SPIT: - case ESM4::SUB_CIS2: + // case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; + case ESM::fourCC("OBND"): + case ESM::fourCC("CTDA"): + case ESM::fourCC("EFID"): + case ESM::fourCC("EFIT"): + case ESM::fourCC("ETYP"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("MDOB"): + case ESM::fourCC("MODT"): + case ESM::fourCC("SPIT"): + case ESM::fourCC("CIS2"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadsgst.cpp b/components/esm4/loadsgst.cpp index c4284eb9bc..d93709f537 100644 --- a/components/esm4/loadsgst.cpp +++ b/components/esm4/loadsgst.cpp @@ -42,10 +42,10 @@ void ESM4::SigilStone::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): { if (mFullName.empty()) { @@ -62,34 +62,34 @@ void ESM4::SigilStone::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { reader.get(mData.uses); reader.get(mData.value); reader.get(mData.weight); break; } - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_SCIT: + case ESM::fourCC("SCIT"): { reader.get(mEffect); reader.adjustFormId(mEffect.formId); break; } - case ESM4::SUB_MODT: - case ESM4::SUB_EFID: - case ESM4::SUB_EFIT: + case ESM::fourCC("MODT"): + case ESM::fourCC("EFID"): + case ESM::fourCC("EFIT"): { reader.skipSubRecordData(); break; diff --git a/components/esm4/loadslgm.cpp b/components/esm4/loadslgm.cpp index 635b73312e..cb16f36857 100644 --- a/components/esm4/loadslgm.cpp +++ b/components/esm4/loadslgm.cpp @@ -41,38 +41,38 @@ void ESM4::SoulGem::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_SOUL: + case ESM::fourCC("SOUL"): reader.get(mSoul); break; - case ESM4::SUB_SLCP: + case ESM::fourCC("SLCP"): reader.get(mSoulCapacity); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_MODT: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_NAM0: - case ESM4::SUB_OBND: + case ESM::fourCC("MODT"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("NAM0"): + case ESM::fourCC("OBND"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadsndr.cpp b/components/esm4/loadsndr.cpp index 830cdfdc54..21ed9f93e4 100644 --- a/components/esm4/loadsndr.cpp +++ b/components/esm4/loadsndr.cpp @@ -41,10 +41,10 @@ void ESM4::SoundReference::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_CTDA: + case ESM::fourCC("CTDA"): reader.get(&mTargetCondition, 20); reader.get(mTargetCondition.runOn); reader.get(mTargetCondition.reference); @@ -52,22 +52,22 @@ void ESM4::SoundReference::load(ESM4::Reader& reader) reader.adjustFormId(mTargetCondition.reference); reader.skipSubRecordData(4); // unknown break; - case ESM4::SUB_GNAM: + case ESM::fourCC("GNAM"): reader.getFormId(mSoundCategory); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.getFormId(mSoundId); break; - case ESM4::SUB_ONAM: + case ESM::fourCC("ONAM"): reader.getFormId(mOutputModel); break; - case ESM4::SUB_ANAM: + case ESM::fourCC("ANAM"): reader.getZString(mSoundFile); break; - case ESM4::SUB_LNAM: + case ESM::fourCC("LNAM"): reader.get(mLoopInfo); break; - case ESM4::SUB_BNAM: + case ESM::fourCC("BNAM"): { if (subHdr.dataSize == 6) reader.get(mData); @@ -77,16 +77,16 @@ void ESM4::SoundReference::load(ESM4::Reader& reader) reader.skipSubRecordData(); break; } - case ESM4::SUB_CIS1: - case ESM4::SUB_CIS2: - case ESM4::SUB_CNAM: // CRC32 hash - case ESM4::SUB_DNAM: // FO4 - case ESM4::SUB_FNAM: // unknown - case ESM4::SUB_INTV: // FO4 - case ESM4::SUB_ITMC: // FO4 - case ESM4::SUB_ITME: // FO4 - case ESM4::SUB_ITMS: // FO4 - case ESM4::SUB_NNAM: // FO4 + case ESM::fourCC("CIS1"): + case ESM::fourCC("CIS2"): + case ESM::fourCC("CNAM"): // CRC32 hash + case ESM::fourCC("DNAM"): // FO4 + case ESM::fourCC("FNAM"): // unknown + case ESM::fourCC("INTV"): // FO4 + case ESM::fourCC("ITMC"): // FO4 + case ESM::fourCC("ITME"): // FO4 + case ESM::fourCC("ITMS"): // FO4 + case ESM::fourCC("NNAM"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadsoun.cpp b/components/esm4/loadsoun.cpp index a5ae005fe2..4fc3b6609f 100644 --- a/components/esm4/loadsoun.cpp +++ b/components/esm4/loadsoun.cpp @@ -41,16 +41,16 @@ void ESM4::Sound::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FNAM: + case ESM::fourCC("FNAM"): reader.getZString(mSoundFile); break; - case ESM4::SUB_SNDX: + case ESM::fourCC("SNDX"): reader.get(mData); break; - case ESM4::SUB_SNDD: + case ESM::fourCC("SNDD"): if (subHdr.dataSize == 8) reader.get(&mData, 8); else @@ -59,13 +59,13 @@ void ESM4::Sound::load(ESM4::Reader& reader) reader.get(mExtra); } break; - case ESM4::SUB_OBND: // TES5 only - case ESM4::SUB_SDSC: // TES5 only - case ESM4::SUB_ANAM: // FO3 - case ESM4::SUB_GNAM: // FO3 - case ESM4::SUB_HNAM: // FO3 - case ESM4::SUB_RNAM: // FONV - case ESM4::SUB_REPT: // FO4 + case ESM::fourCC("OBND"): // TES5 only + case ESM::fourCC("SDSC"): // TES5 only + case ESM::fourCC("ANAM"): // FO3 + case ESM::fourCC("GNAM"): // FO3 + case ESM::fourCC("HNAM"): // FO3 + case ESM::fourCC("RNAM"): // FONV + case ESM::fourCC("REPT"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadstat.cpp b/components/esm4/loadstat.cpp index e3b51633cf..9d85374ae4 100644 --- a/components/esm4/loadstat.cpp +++ b/components/esm4/loadstat.cpp @@ -41,19 +41,19 @@ void ESM4::Static::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_MODT: + case ESM::fourCC("MODT"): { // version is only availabe in TES5 (seems to be 27 or 28?) // if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) @@ -72,7 +72,7 @@ void ESM4::Static::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_MNAM: + case ESM::fourCC("MNAM"): { for (std::string& level : mLOD) { @@ -84,18 +84,18 @@ void ESM4::Static::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_MODC: // More model data - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_OBND: - case ESM4::SUB_DNAM: - case ESM4::SUB_BRUS: // FONV - case ESM4::SUB_RNAM: // FONV - case ESM4::SUB_FTYP: // FO4 - case ESM4::SUB_NVNM: // FO4 - case ESM4::SUB_PRPS: // FO4 - case ESM4::SUB_PTRN: // FO4 - case ESM4::SUB_VMAD: // FO4 + case ESM::fourCC("MODC"): // More model data + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("OBND"): + case ESM::fourCC("DNAM"): + case ESM::fourCC("BRUS"): // FONV + case ESM::fourCC("RNAM"): // FONV + case ESM::fourCC("FTYP"): // FO4 + case ESM::fourCC("NVNM"): // FO4 + case ESM::fourCC("PRPS"): // FO4 + case ESM::fourCC("PTRN"): // FO4 + case ESM::fourCC("VMAD"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadtact.cpp b/components/esm4/loadtact.cpp index ad5efb9fbf..453df85504 100644 --- a/components/esm4/loadtact.cpp +++ b/components/esm4/loadtact.cpp @@ -41,44 +41,44 @@ void ESM4::TalkingActivator::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_VNAM: + case ESM::fourCC("VNAM"): reader.getFormId(mVoiceType); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.getFormId(mLoopSound); break; - case ESM4::SUB_INAM: + case ESM::fourCC("INAM"): reader.getFormId(mRadioTemplate); break; // FONV - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_FNAM: - case ESM4::SUB_PNAM: - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_OBND: - case ESM4::SUB_VMAD: + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("FNAM"): + case ESM::fourCC("PNAM"): + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("OBND"): + case ESM::fourCC("VMAD"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadterm.cpp b/components/esm4/loadterm.cpp index 39f26391e8..1fb8ef117d 100644 --- a/components/esm4/loadterm.cpp +++ b/components/esm4/loadterm.cpp @@ -41,73 +41,73 @@ void ESM4::Terminal::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DESC: + case ESM::fourCC("DESC"): reader.getLocalizedString(mText); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_PNAM: + case ESM::fourCC("PNAM"): reader.getFormId(mPasswordNote); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): if (subHdr.dataSize == 4) reader.getFormId(mSound); // FIXME: FO4 sound marker params else reader.skipSubRecordData(); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_RNAM: + case ESM::fourCC("RNAM"): reader.getZString(mResultText); break; - case ESM4::SUB_DNAM: // difficulty - case ESM4::SUB_ANAM: // flags - case ESM4::SUB_CTDA: - case ESM4::SUB_CIS1: - case ESM4::SUB_CIS2: - case ESM4::SUB_INAM: - case ESM4::SUB_ITXT: // Menu Item - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_SCDA: - case ESM4::SUB_SCHR: - case ESM4::SUB_SCRO: - case ESM4::SUB_SCRV: - case ESM4::SUB_SCTX: - case ESM4::SUB_SCVR: - case ESM4::SUB_SLSD: - case ESM4::SUB_TNAM: - case ESM4::SUB_OBND: - case ESM4::SUB_VMAD: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_BSIZ: // FO4 - case ESM4::SUB_BTXT: // FO4 - case ESM4::SUB_COCT: // FO4 - case ESM4::SUB_CNTO: // FO4 - case ESM4::SUB_FNAM: // FO4 - case ESM4::SUB_ISIZ: // FO4 - case ESM4::SUB_ITID: // FO4 - case ESM4::SUB_MNAM: // FO4 - case ESM4::SUB_NAM0: // FO4 - case ESM4::SUB_PRPS: // FO4 - case ESM4::SUB_PTRN: // FO4 - case ESM4::SUB_UNAM: // FO4 - case ESM4::SUB_VNAM: // FO4 - case ESM4::SUB_WBDT: // FO4 - case ESM4::SUB_WNAM: // FO4 - case ESM4::SUB_XMRK: // FO4 + case ESM::fourCC("DNAM"): // difficulty + case ESM::fourCC("ANAM"): // flags + case ESM::fourCC("CTDA"): + case ESM::fourCC("CIS1"): + case ESM::fourCC("CIS2"): + case ESM::fourCC("INAM"): + case ESM::fourCC("ITXT"): // Menu Item + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("SCDA"): + case ESM::fourCC("SCHR"): + case ESM::fourCC("SCRO"): + case ESM::fourCC("SCRV"): + case ESM::fourCC("SCTX"): + case ESM::fourCC("SCVR"): + case ESM::fourCC("SLSD"): + case ESM::fourCC("TNAM"): + case ESM::fourCC("OBND"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("BSIZ"): // FO4 + case ESM::fourCC("BTXT"): // FO4 + case ESM::fourCC("COCT"): // FO4 + case ESM::fourCC("CNTO"): // FO4 + case ESM::fourCC("FNAM"): // FO4 + case ESM::fourCC("ISIZ"): // FO4 + case ESM::fourCC("ITID"): // FO4 + case ESM::fourCC("MNAM"): // FO4 + case ESM::fourCC("NAM0"): // FO4 + case ESM::fourCC("PRPS"): // FO4 + case ESM::fourCC("PTRN"): // FO4 + case ESM::fourCC("UNAM"): // FO4 + case ESM::fourCC("VNAM"): // FO4 + case ESM::fourCC("WBDT"): // FO4 + case ESM::fourCC("WNAM"): // FO4 + case ESM::fourCC("XMRK"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadtes4.cpp b/components/esm4/loadtes4.cpp index 0cbf91c52e..19db9b9d09 100644 --- a/components/esm4/loadtes4.cpp +++ b/components/esm4/loadtes4.cpp @@ -41,7 +41,7 @@ void ESM4::Header::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_HEDR: + case ESM::fourCC("HEDR"): { if (!reader.getExact(mData.version) || !reader.getExact(mData.records) || !reader.getExact(mData.nextObjectId)) @@ -51,13 +51,13 @@ void ESM4::Header::load(ESM4::Reader& reader) throw std::runtime_error("TES4 HEDR data size mismatch"); break; } - case ESM4::SUB_CNAM: + case ESM::fourCC("CNAM"): reader.getZString(mAuthor); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.getZString(mDesc); break; - case ESM4::SUB_MAST: // multiple + case ESM::fourCC("MAST"): // multiple { ESM::MasterData m; if (!reader.getZString(m.name)) @@ -68,7 +68,7 @@ void ESM4::Header::load(ESM4::Reader& reader) mMaster.push_back(m); break; } - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { if (mMaster.empty()) throw std::runtime_error( @@ -78,7 +78,7 @@ void ESM4::Header::load(ESM4::Reader& reader) throw std::runtime_error("TES4 DATA data read error"); break; } - case ESM4::SUB_ONAM: + case ESM::fourCC("ONAM"): { mOverrides.resize(subHdr.dataSize / sizeof(ESM::FormId32)); for (ESM::FormId& mOverride : mOverrides) @@ -95,11 +95,11 @@ void ESM4::Header::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_INTV: - case ESM4::SUB_INCC: - case ESM4::SUB_OFST: // Oblivion only? - case ESM4::SUB_DELE: // Oblivion only? - case ESM4::SUB_TNAM: // Fallout 4 (CK only) + case ESM::fourCC("INTV"): + case ESM::fourCC("INCC"): + case ESM::fourCC("OFST"): // Oblivion only? + case ESM::fourCC("DELE"): // Oblivion only? + case ESM::fourCC("TNAM"): // Fallout 4 (CK only) case ESM::fourCC("MMSB"): // Fallout 76 reader.skipSubRecordData(); break; diff --git a/components/esm4/loadtree.cpp b/components/esm4/loadtree.cpp index 9290ae79c4..c433f11564 100644 --- a/components/esm4/loadtree.cpp +++ b/components/esm4/loadtree.cpp @@ -41,29 +41,29 @@ void ESM4::Tree::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mLeafTexture); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_CNAM: - case ESM4::SUB_BNAM: - case ESM4::SUB_SNAM: - case ESM4::SUB_FULL: - case ESM4::SUB_OBND: - case ESM4::SUB_PFIG: - case ESM4::SUB_PFPC: + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("CNAM"): + case ESM::fourCC("BNAM"): + case ESM::fourCC("SNAM"): + case ESM::fourCC("FULL"): + case ESM::fourCC("OBND"): + case ESM::fourCC("PFIG"): + case ESM::fourCC("PFPC"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadtxst.cpp b/components/esm4/loadtxst.cpp index 3b5f04f265..69d48cc049 100644 --- a/components/esm4/loadtxst.cpp +++ b/components/esm4/loadtxst.cpp @@ -41,37 +41,37 @@ void ESM4::TextureSet::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; case ESM::fourCC("FLTR"): // FO76 reader.getZString(mFilter); break; - case ESM4::SUB_TX00: + case ESM::fourCC("TX00"): reader.getZString(mDiffuse); break; - case ESM4::SUB_TX01: + case ESM::fourCC("TX01"): reader.getZString(mNormalMap); break; - case ESM4::SUB_TX02: + case ESM::fourCC("TX02"): // This is a "wrinkle map" in FO4/76 reader.getZString(mEnvMask); break; - case ESM4::SUB_TX03: + case ESM::fourCC("TX03"): // This is a glow map in FO4/76 reader.getZString(mToneMap); break; - case ESM4::SUB_TX04: + case ESM::fourCC("TX04"): // This is a height map in FO4/76 reader.getZString(mDetailMap); break; - case ESM4::SUB_TX05: + case ESM::fourCC("TX05"): reader.getZString(mEnvMap); break; - case ESM4::SUB_TX06: + case ESM::fourCC("TX06"): reader.getZString(mMultiLayer); break; - case ESM4::SUB_TX07: + case ESM::fourCC("TX07"): // This is a "smooth specular" map in FO4/76 reader.getZString(mSpecular); break; @@ -84,14 +84,14 @@ void ESM4::TextureSet::load(ESM4::Reader& reader) case ESM::fourCC("TX10"): // FO76 reader.getZString(mFlow); break; - case ESM4::SUB_DNAM: + case ESM::fourCC("DNAM"): reader.get(mDataFlags); break; - case ESM4::SUB_MNAM: + case ESM::fourCC("MNAM"): reader.getZString(mMaterial); break; - case ESM4::SUB_DODT: // Decal data - case ESM4::SUB_OBND: // object bounds + case ESM::fourCC("DODT"): // Decal data + case ESM::fourCC("OBND"): // object bounds case ESM::fourCC("OPDS"): // Object placement defaults, FO76 reader.skipSubRecordData(); break; diff --git a/components/esm4/loadweap.cpp b/components/esm4/loadweap.cpp index 2b80305690..81b0d4286a 100644 --- a/components/esm4/loadweap.cpp +++ b/components/esm4/loadweap.cpp @@ -43,13 +43,13 @@ void ESM4::Weapon::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { // if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) if (subHdr.dataSize == 10) // FO3 has 15 bytes even though VER_094 @@ -79,126 +79,126 @@ void ESM4::Weapon::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_MICO: + case ESM::fourCC("MICO"): reader.getZString(mMiniIcon); break; // FO3 - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_ANAM: + case ESM::fourCC("ANAM"): reader.get(mEnchantmentPoints); break; - case ESM4::SUB_ENAM: + case ESM::fourCC("ENAM"): reader.getFormId(mEnchantment); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_DESC: + case ESM::fourCC("DESC"): reader.getLocalizedString(mText); break; - case ESM4::SUB_YNAM: + case ESM::fourCC("YNAM"): reader.getFormId(mPickUpSound); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mDropSound); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_BAMT: - case ESM4::SUB_BIDS: - case ESM4::SUB_INAM: - case ESM4::SUB_CNAM: - case ESM4::SUB_CRDT: - case ESM4::SUB_DNAM: - case ESM4::SUB_EAMT: - case ESM4::SUB_EITM: - case ESM4::SUB_ETYP: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_NAM8: - case ESM4::SUB_NAM9: - case ESM4::SUB_OBND: - case ESM4::SUB_SNAM: - case ESM4::SUB_TNAM: - case ESM4::SUB_UNAM: - case ESM4::SUB_VMAD: - case ESM4::SUB_VNAM: - case ESM4::SUB_WNAM: - case ESM4::SUB_XNAM: // Dawnguard only? - case ESM4::SUB_NNAM: - case ESM4::SUB_NAM0: // FO3 - case ESM4::SUB_REPL: // FO3 - case ESM4::SUB_MOD2: // FO3 - case ESM4::SUB_MO2T: // FO3 - case ESM4::SUB_MO2S: // FO3 - case ESM4::SUB_NAM6: // FO3 - case ESM4::SUB_MOD4: // First person model data - case ESM4::SUB_MO4T: - case ESM4::SUB_MO4S: - case ESM4::SUB_MO4C: - case ESM4::SUB_MO4F: // First person model data end - case ESM4::SUB_BIPL: // FO3 - case ESM4::SUB_NAM7: // FO3 - case ESM4::SUB_MOD3: // FO3 - case ESM4::SUB_MO3T: // FO3 - case ESM4::SUB_MO3S: // FO3 - case ESM4::SUB_MODD: // FO3 - // case ESM4::SUB_MOSD: // FO3 - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_VATS: // FONV - case ESM4::SUB_VANM: // FONV - case ESM4::SUB_MWD1: // FONV - case ESM4::SUB_MWD2: // FONV - case ESM4::SUB_MWD3: // FONV - case ESM4::SUB_MWD4: // FONV - case ESM4::SUB_MWD5: // FONV - case ESM4::SUB_MWD6: // FONV - case ESM4::SUB_MWD7: // FONV - case ESM4::SUB_WMI1: // FONV - case ESM4::SUB_WMI2: // FONV - case ESM4::SUB_WMI3: // FONV - case ESM4::SUB_WMS1: // FONV - case ESM4::SUB_WMS2: // FONV - case ESM4::SUB_WNM1: // FONV - case ESM4::SUB_WNM2: // FONV - case ESM4::SUB_WNM3: // FONV - case ESM4::SUB_WNM4: // FONV - case ESM4::SUB_WNM5: // FONV - case ESM4::SUB_WNM6: // FONV - case ESM4::SUB_WNM7: // FONV - case ESM4::SUB_EFSD: // FONV DeadMoney - case ESM4::SUB_APPR: // FO4 - case ESM4::SUB_DAMA: // FO4 - case ESM4::SUB_FLTR: // FO4 - case ESM4::SUB_FNAM: // FO4 - case ESM4::SUB_INRD: // FO4 - case ESM4::SUB_LNAM: // FO4 - case ESM4::SUB_MASE: // FO4 - case ESM4::SUB_PTRN: // FO4 - case ESM4::SUB_STCP: // FO4 - case ESM4::SUB_WAMD: // FO4 - case ESM4::SUB_WZMD: // FO4 - case ESM4::SUB_OBTE: // FO4 object template start - case ESM4::SUB_OBTF: - case ESM4::SUB_OBTS: - case ESM4::SUB_STOP: // FO4 object template end + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("BAMT"): + case ESM::fourCC("BIDS"): + case ESM::fourCC("INAM"): + case ESM::fourCC("CNAM"): + case ESM::fourCC("CRDT"): + case ESM::fourCC("DNAM"): + case ESM::fourCC("EAMT"): + case ESM::fourCC("EITM"): + case ESM::fourCC("ETYP"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("NAM8"): + case ESM::fourCC("NAM9"): + case ESM::fourCC("OBND"): + case ESM::fourCC("SNAM"): + case ESM::fourCC("TNAM"): + case ESM::fourCC("UNAM"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("VNAM"): + case ESM::fourCC("WNAM"): + case ESM::fourCC("XNAM"): // Dawnguard only? + case ESM::fourCC("NNAM"): + case ESM::fourCC("NAM0"): // FO3 + case ESM::fourCC("REPL"): // FO3 + case ESM::fourCC("MOD2"): // FO3 + case ESM::fourCC("MO2T"): // FO3 + case ESM::fourCC("MO2S"): // FO3 + case ESM::fourCC("NAM6"): // FO3 + case ESM::fourCC("MOD4"): // First person model data + case ESM::fourCC("MO4T"): + case ESM::fourCC("MO4S"): + case ESM::fourCC("MO4C"): + case ESM::fourCC("MO4F"): // First person model data end + case ESM::fourCC("BIPL"): // FO3 + case ESM::fourCC("NAM7"): // FO3 + case ESM::fourCC("MOD3"): // FO3 + case ESM::fourCC("MO3T"): // FO3 + case ESM::fourCC("MO3S"): // FO3 + case ESM::fourCC("MODD"): // FO3 + // case ESM::fourCC("MOSD"): // FO3 + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("VATS"): // FONV + case ESM::fourCC("VANM"): // FONV + case ESM::fourCC("MWD1"): // FONV + case ESM::fourCC("MWD2"): // FONV + case ESM::fourCC("MWD3"): // FONV + case ESM::fourCC("MWD4"): // FONV + case ESM::fourCC("MWD5"): // FONV + case ESM::fourCC("MWD6"): // FONV + case ESM::fourCC("MWD7"): // FONV + case ESM::fourCC("WMI1"): // FONV + case ESM::fourCC("WMI2"): // FONV + case ESM::fourCC("WMI3"): // FONV + case ESM::fourCC("WMS1"): // FONV + case ESM::fourCC("WMS2"): // FONV + case ESM::fourCC("WNM1"): // FONV + case ESM::fourCC("WNM2"): // FONV + case ESM::fourCC("WNM3"): // FONV + case ESM::fourCC("WNM4"): // FONV + case ESM::fourCC("WNM5"): // FONV + case ESM::fourCC("WNM6"): // FONV + case ESM::fourCC("WNM7"): // FONV + case ESM::fourCC("EFSD"): // FONV DeadMoney + case ESM::fourCC("APPR"): // FO4 + case ESM::fourCC("DAMA"): // FO4 + case ESM::fourCC("FLTR"): // FO4 + case ESM::fourCC("FNAM"): // FO4 + case ESM::fourCC("INRD"): // FO4 + case ESM::fourCC("LNAM"): // FO4 + case ESM::fourCC("MASE"): // FO4 + case ESM::fourCC("PTRN"): // FO4 + case ESM::fourCC("STCP"): // FO4 + case ESM::fourCC("WAMD"): // FO4 + case ESM::fourCC("WZMD"): // FO4 + case ESM::fourCC("OBTE"): // FO4 object template start + case ESM::fourCC("OBTF"): + case ESM::fourCC("OBTS"): + case ESM::fourCC("STOP"): // FO4 object template end reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadwrld.cpp b/components/esm4/loadwrld.cpp index b29cb37eb5..d9bae15385 100644 --- a/components/esm4/loadwrld.cpp +++ b/components/esm4/loadwrld.cpp @@ -56,46 +56,46 @@ void ESM4::World::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_WCTR: // TES5+ + case ESM::fourCC("WCTR"): // TES5+ reader.get(mCenterCell); break; - case ESM4::SUB_WNAM: + case ESM::fourCC("WNAM"): reader.getFormId(mParent); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.get(mSound); break; // sound, Oblivion only? - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mMapFile); break; - case ESM4::SUB_CNAM: + case ESM::fourCC("CNAM"): reader.getFormId(mClimate); break; - case ESM4::SUB_NAM2: + case ESM::fourCC("NAM2"): reader.getFormId(mWater); break; - case ESM4::SUB_NAM0: + case ESM::fourCC("NAM0"): { reader.get(mMinX); reader.get(mMinY); break; } - case ESM4::SUB_NAM9: + case ESM::fourCC("NAM9"): { reader.get(mMaxX); reader.get(mMaxY); break; } - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mWorldFlags); break; - case ESM4::SUB_MNAM: + case ESM::fourCC("MNAM"): { reader.get(mMap.width); reader.get(mMap.height); @@ -113,7 +113,7 @@ void ESM4::World::load(ESM4::Reader& reader) break; } - case ESM4::SUB_DNAM: // defaults + case ESM::fourCC("DNAM"): // defaults { reader.get(mLandLevel); // -2700.f for TES5 reader.get(mWaterLevel); // -14000.f for TES5 @@ -135,37 +135,37 @@ void ESM4::World::load(ESM4::Reader& reader) // 00119D2E freeside\freeside_01.mp3 0012D94D FreesideNorthWorld (Freeside) // 00119D2E freeside\freeside_01.mp3 0012D94E FreesideFortWorld (Old Mormon Fort) // NOTE: FONV DefaultObjectManager has 00090908 "explore" as the default music - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mMusic); break; - case ESM4::SUB_PNAM: + case ESM::fourCC("PNAM"): reader.get(mParentUseFlags); break; - case ESM4::SUB_OFST: - case ESM4::SUB_RNAM: // multiple - case ESM4::SUB_MHDT: - case ESM4::SUB_LTMP: - case ESM4::SUB_XEZN: - case ESM4::SUB_XLCN: - case ESM4::SUB_NAM3: - case ESM4::SUB_NAM4: - case ESM4::SUB_NAMA: - case ESM4::SUB_ONAM: - case ESM4::SUB_TNAM: - case ESM4::SUB_UNAM: - case ESM4::SUB_XWEM: - case ESM4::SUB_MODL: // Model data start - case ESM4::SUB_MODT: - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_INAM: // FO3 - case ESM4::SUB_NNAM: // FO3 - case ESM4::SUB_XNAM: // FO3 - case ESM4::SUB_IMPS: // FO3 Anchorage - case ESM4::SUB_IMPF: // FO3 Anchorage - case ESM4::SUB_CLSZ: // FO4 - case ESM4::SUB_WLEV: // FO4 + case ESM::fourCC("OFST"): + case ESM::fourCC("RNAM"): // multiple + case ESM::fourCC("MHDT"): + case ESM::fourCC("LTMP"): + case ESM::fourCC("XEZN"): + case ESM::fourCC("XLCN"): + case ESM::fourCC("NAM3"): + case ESM::fourCC("NAM4"): + case ESM::fourCC("NAMA"): + case ESM::fourCC("ONAM"): + case ESM::fourCC("TNAM"): + case ESM::fourCC("UNAM"): + case ESM::fourCC("XWEM"): + case ESM::fourCC("MODL"): // Model data start + case ESM::fourCC("MODT"): + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("INAM"): // FO3 + case ESM::fourCC("NNAM"): // FO3 + case ESM::fourCC("XNAM"): // FO3 + case ESM::fourCC("IMPS"): // FO3 Anchorage + case ESM::fourCC("IMPF"): // FO3 Anchorage + case ESM::fourCC("CLSZ"): // FO4 + case ESM::fourCC("WLEV"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/reader.cpp b/components/esm4/reader.cpp index a3ea438d65..2d9a929bb2 100644 --- a/components/esm4/reader.cpp +++ b/components/esm4/reader.cpp @@ -83,8 +83,8 @@ namespace ESM4 stream.next_in = reinterpret_cast(compressed.data()); stream.next_out = reinterpret_cast(decompressed.data()); - stream.avail_in = compressed.size(); - stream.avail_out = decompressed.size(); + stream.avail_in = static_cast(compressed.size()); + stream.avail_out = static_cast(decompressed.size()); if (const int ec = inflateInit(&stream); ec != Z_OK) return getError("inflateInit error", ec, stream.msg); @@ -112,9 +112,9 @@ namespace ESM4 const auto prevTotalIn = stream.total_in; const auto prevTotalOut = stream.total_out; stream.next_in = reinterpret_cast(compressed.data()); - stream.avail_in = std::min(blockSize, compressed.size()); + stream.avail_in = static_cast(std::min(blockSize, compressed.size())); stream.next_out = reinterpret_cast(decompressed.data()); - stream.avail_out = std::min(blockSize, decompressed.size()); + stream.avail_out = static_cast(std::min(blockSize, decompressed.size())); const int ec = inflate(&stream, Z_NO_FLUSH); if (ec == Z_STREAM_END) break; @@ -588,7 +588,7 @@ namespace ESM4 // Extended storage subrecord redefines the following subrecord's size. // Would need to redesign the loader to support that, so skip over both subrecords. - if (result && mCtx.subRecordHeader.typeId == ESM4::SUB_XXXX) + if (result && mCtx.subRecordHeader.typeId == ESM::fourCC("XXXX")) { std::uint32_t extDataSize; get(extDataSize); diff --git a/components/esm4/reader.hpp b/components/esm4/reader.hpp index 92dc00b96d..914fa4a647 100644 --- a/components/esm4/reader.hpp +++ b/components/esm4/reader.hpp @@ -335,7 +335,7 @@ namespace ESM4 // Get a subrecord of a particular type and data type template - bool getSubRecord(const ESM4::SubRecordTypes type, T& t) + bool getSubRecord(const std::uint32_t type, T& t) { ESM4::SubRecordHeader hdr; if (!getExact(hdr) || (hdr.typeId != type) || (hdr.dataSize != sizeof(T))) diff --git a/components/esm4/reference.hpp b/components/esm4/reference.hpp index 9d6efdfd82..33e8fa82f3 100644 --- a/components/esm4/reference.hpp +++ b/components/esm4/reference.hpp @@ -30,8 +30,8 @@ #include #include -#include #include +#include namespace ESM4 { diff --git a/components/esm4/script.hpp b/components/esm4/script.hpp index 57dd85367b..3715ea55d4 100644 --- a/components/esm4/script.hpp +++ b/components/esm4/script.hpp @@ -365,13 +365,6 @@ namespace ESM4 std::uint32_t unknown4; // SCVR std::string variableName; - - void clear() - { - index = 0; - type = 0; - variableName.clear(); - } }; struct ScriptDefinition diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 210261cdf4..7b4cbac864 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -68,6 +68,9 @@ namespace Files bool silent = mSilent; mSilent = quiet; + // ensure defaults are present + bpo::store(bpo::parsed_options(&description), variables); + std::optional config = loadConfig(mFixedPath.getLocalPath(), description); if (config) mActiveConfigPaths.push_back(mFixedPath.getLocalPath()); @@ -411,11 +414,6 @@ namespace Files return mFixedPath.getLocalPath(); } - const std::filesystem::path& ConfigurationManager::getGlobalDataPath() const - { - return mFixedPath.getGlobalDataPath(); - } - const std::filesystem::path& ConfigurationManager::getCachePath() const { return mFixedPath.getCachePath(); diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index 8a5bca0869..aec7799fea 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -45,7 +45,6 @@ namespace Files const std::filesystem::path& getGlobalPath() const; const std::filesystem::path& getLocalPath() const; - const std::filesystem::path& getGlobalDataPath() const; const std::filesystem::path& getUserConfigPath() const; const std::filesystem::path& getUserDataPath() const; const std::filesystem::path& getLocalDataPath() const; diff --git a/components/files/hash.cpp b/components/files/hash.cpp index afb59b2e9e..1f1839ed0c 100644 --- a/components/files/hash.cpp +++ b/components/files/hash.cpp @@ -1,5 +1,4 @@ #include "hash.hpp" -#include "conversion.hpp" #include @@ -10,7 +9,7 @@ namespace Files { - std::array getHash(const std::filesystem::path& fileName, std::istream& stream) + std::array getHash(std::string_view fileName, std::istream& stream) { std::array hash{ 0, 0 }; try @@ -35,8 +34,11 @@ namespace Files } catch (const std::exception& e) { - throw std::runtime_error( - "Error while reading \"" + Files::pathToUnicodeString(fileName) + "\" to get hash: " + e.what()); + std::string message = "Error while reading \""; + message += fileName; + message += "\" to get hash: "; + message += e.what(); + throw std::runtime_error(message); } return hash; } diff --git a/components/files/hash.hpp b/components/files/hash.hpp index 0e6ce29ab5..48c373b971 100644 --- a/components/files/hash.hpp +++ b/components/files/hash.hpp @@ -3,12 +3,12 @@ #include #include -#include #include +#include namespace Files { - std::array getHash(const std::filesystem::path& fileName, std::istream& stream); + std::array getHash(std::string_view fileName, std::istream& stream); } #endif diff --git a/components/files/qtconfigpath.hpp b/components/files/qtconfigpath.hpp index e6d7b4202c..16e0499cd5 100644 --- a/components/files/qtconfigpath.hpp +++ b/components/files/qtconfigpath.hpp @@ -22,6 +22,16 @@ namespace Files { return Files::pathToQString(cfgMgr.getGlobalPath() / openmwCfgFile); } + + inline QStringList getActiveConfigPathsQString(const Files::ConfigurationManager& cfgMgr) + { + const auto& activePaths = cfgMgr.getActiveConfigPaths(); + QStringList result; + result.reserve(static_cast(activePaths.size())); + for (const auto& path : activePaths) + result.append(Files::pathToQString(path / openmwCfgFile)); + return result; + } } #endif // OPENMW_COMPONENTS_FILES_QTCONFIGPATH_H diff --git a/components/files/windowspath.cpp b/components/files/windowspath.cpp index 9be3d13a46..bbe0325b58 100644 --- a/components/files/windowspath.cpp +++ b/components/files/windowspath.cpp @@ -101,7 +101,7 @@ namespace Files { // Key existed, let's try to read the install dir std::array buf{}; - DWORD len = buf.size() * sizeof(wchar_t); + DWORD len = static_cast(buf.size() * sizeof(wchar_t)); if (RegQueryValueExW(hKey, L"Installed Path", nullptr, nullptr, reinterpret_cast(buf.data()), &len) == ERROR_SUCCESS) diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index 64aa32310b..c84392c421 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -448,6 +448,63 @@ namespace Gui source->addAttribute("value", bitmapFilename); MyGUI::xml::ElementPtr codes = root->createChild("Codes"); + // Fall back from unavailable Windows-1252 encoding symbols to similar characters available in the game + // fonts + std::multimap additional; // fallback glyph index, unicode + additional.emplace(156, 0x00A2); // cent sign + additional.emplace(89, 0x00A5); // yen sign + additional.emplace(221, 0x00A6); // broken bar + additional.emplace(99, 0x00A9); // copyright sign + additional.emplace(97, 0x00AA); // prima ordinal indicator + additional.emplace(60, 0x00AB); // double left-pointing angle quotation mark + additional.emplace(45, 0x00AD); // soft hyphen + additional.emplace(114, 0x00AE); // registered trademark symbol + additional.emplace(45, 0x00AF); // macron + additional.emplace(241, 0x00B1); // plus-minus sign + additional.emplace(50, 0x00B2); // superscript two + additional.emplace(51, 0x00B3); // superscript three + additional.emplace(44, 0x00B8); // cedilla + additional.emplace(49, 0x00B9); // superscript one + additional.emplace(111, 0x00BA); // primo ordinal indicator + additional.emplace(62, 0x00BB); // double right-pointing angle quotation mark + additional.emplace(63, 0x00BF); // inverted question mark + additional.emplace(65, 0x00C6); // latin capital ae ligature + additional.emplace(79, 0x00D8); // latin capital o with stroke + additional.emplace(97, 0x00E6); // latin small ae ligature + additional.emplace(111, 0x00F8); // latin small o with stroke + additional.emplace(79, 0x0152); // latin capital oe ligature + additional.emplace(111, 0x0153); // latin small oe ligature + additional.emplace(83, 0x015A); // latin capital s with caron + additional.emplace(115, 0x015B); // latin small s with caron + additional.emplace(89, 0x0178); // latin capital y with diaresis + additional.emplace(90, 0x017D); // latin capital z with caron + additional.emplace(122, 0x017E); // latin small z with caron + additional.emplace(102, 0x0192); // latin small f with hook + additional.emplace(94, 0x02C6); // circumflex modifier + additional.emplace(126, 0x02DC); // small tilde + additional.emplace(69, 0x0401); // cyrillic capital io (no diaeresis latin e is available) + additional.emplace(137, 0x0451); // cyrillic small io + additional.emplace(45, 0x2012); // figure dash + additional.emplace(45, 0x2013); // en dash + additional.emplace(45, 0x2014); // em dash + additional.emplace(39, 0x2018); // left single quotation mark + additional.emplace(39, 0x2019); // right single quotation mark + additional.emplace(44, 0x201A); // single low quotation mark + additional.emplace(39, 0x201B); // single high quotation mark (reversed) + additional.emplace(34, 0x201C); // left double quotation mark + additional.emplace(34, 0x201D); // right double quotation mark + additional.emplace(44, 0x201E); // double low quotation mark + additional.emplace(34, 0x201F); // double high quotation mark (reversed) + additional.emplace(43, 0x2020); // dagger + additional.emplace(216, 0x2021); // double dagger (note: this glyph is not available) + additional.emplace(46, 0x2026); // ellipsis + additional.emplace(37, 0x2030); // per mille sign + additional.emplace(60, 0x2039); // single left-pointing angle quotation mark + additional.emplace(62, 0x203A); // single right-pointing angle quotation mark + additional.emplace(101, 0x20AC); // euro sign + additional.emplace(84, 0x2122); // trademark sign + additional.emplace(45, 0x2212); // minus sign + for (int i = 0; i < 256; i++) { float x1 = data[i].top_left.x * width; @@ -470,69 +527,10 @@ namespace Gui code->addAttribute( "size", MyGUI::IntSize(static_cast(data[i].width), static_cast(data[i].height))); - // Fall back from unavailable Windows-1252 encoding symbols to similar characters available in the game - // fonts - std::multimap additional; // fallback glyph index, unicode - additional.insert(std::make_pair(156, 0x00A2)); // cent sign - additional.insert(std::make_pair(89, 0x00A5)); // yen sign - additional.insert(std::make_pair(221, 0x00A6)); // broken bar - additional.insert(std::make_pair(99, 0x00A9)); // copyright sign - additional.insert(std::make_pair(97, 0x00AA)); // prima ordinal indicator - additional.insert(std::make_pair(60, 0x00AB)); // double left-pointing angle quotation mark - additional.insert(std::make_pair(45, 0x00AD)); // soft hyphen - additional.insert(std::make_pair(114, 0x00AE)); // registered trademark symbol - additional.insert(std::make_pair(45, 0x00AF)); // macron - additional.insert(std::make_pair(241, 0x00B1)); // plus-minus sign - additional.insert(std::make_pair(50, 0x00B2)); // superscript two - additional.insert(std::make_pair(51, 0x00B3)); // superscript three - additional.insert(std::make_pair(44, 0x00B8)); // cedilla - additional.insert(std::make_pair(49, 0x00B9)); // superscript one - additional.insert(std::make_pair(111, 0x00BA)); // primo ordinal indicator - additional.insert(std::make_pair(62, 0x00BB)); // double right-pointing angle quotation mark - additional.insert(std::make_pair(63, 0x00BF)); // inverted question mark - additional.insert(std::make_pair(65, 0x00C6)); // latin capital ae ligature - additional.insert(std::make_pair(79, 0x00D8)); // latin capital o with stroke - additional.insert(std::make_pair(97, 0x00E6)); // latin small ae ligature - additional.insert(std::make_pair(111, 0x00F8)); // latin small o with stroke - additional.insert(std::make_pair(79, 0x0152)); // latin capital oe ligature - additional.insert(std::make_pair(111, 0x0153)); // latin small oe ligature - additional.insert(std::make_pair(83, 0x015A)); // latin capital s with caron - additional.insert(std::make_pair(115, 0x015B)); // latin small s with caron - additional.insert(std::make_pair(89, 0x0178)); // latin capital y with diaresis - additional.insert(std::make_pair(90, 0x017D)); // latin capital z with caron - additional.insert(std::make_pair(122, 0x017E)); // latin small z with caron - additional.insert(std::make_pair(102, 0x0192)); // latin small f with hook - additional.insert(std::make_pair(94, 0x02C6)); // circumflex modifier - additional.insert(std::make_pair(126, 0x02DC)); // small tilde - additional.insert(std::make_pair(69, 0x0401)); // cyrillic capital io (no diaeresis latin e is available) - additional.insert(std::make_pair(137, 0x0451)); // cyrillic small io - additional.insert(std::make_pair(45, 0x2012)); // figure dash - additional.insert(std::make_pair(45, 0x2013)); // en dash - additional.insert(std::make_pair(45, 0x2014)); // em dash - additional.insert(std::make_pair(39, 0x2018)); // left single quotation mark - additional.insert(std::make_pair(39, 0x2019)); // right single quotation mark - additional.insert(std::make_pair(44, 0x201A)); // single low quotation mark - additional.insert(std::make_pair(39, 0x201B)); // single high quotation mark (reversed) - additional.insert(std::make_pair(34, 0x201C)); // left double quotation mark - additional.insert(std::make_pair(34, 0x201D)); // right double quotation mark - additional.insert(std::make_pair(44, 0x201E)); // double low quotation mark - additional.insert(std::make_pair(34, 0x201F)); // double high quotation mark (reversed) - additional.insert(std::make_pair(43, 0x2020)); // dagger - additional.insert(std::make_pair(216, 0x2021)); // double dagger (note: this glyph is not available) - additional.insert(std::make_pair(46, 0x2026)); // ellipsis - additional.insert(std::make_pair(37, 0x2030)); // per mille sign - additional.insert(std::make_pair(60, 0x2039)); // single left-pointing angle quotation mark - additional.insert(std::make_pair(62, 0x203A)); // single right-pointing angle quotation mark - additional.insert(std::make_pair(101, 0x20AC)); // euro sign - additional.insert(std::make_pair(84, 0x2122)); // trademark sign - additional.insert(std::make_pair(45, 0x2212)); // minus sign - - for (const auto& [key, value] : additional) + for (auto [it, end] = additional.equal_range(i); it != end; ++it) { - if (key != i) - continue; code = codes->createChild("Code"); - code->addAttribute("index", value); + code->addAttribute("index", it->second); code->addAttribute("coord", MyGUI::utility::toString(x1) + " " + MyGUI::utility::toString(y1) + " " + MyGUI::utility::toString(w) + " " + MyGUI::utility::toString(h)); diff --git a/components/fx/lexer.hpp b/components/fx/lexer.hpp index 01b3a3a56a..fc7d4ec9d7 100644 --- a/components/fx/lexer.hpp +++ b/components/fx/lexer.hpp @@ -30,7 +30,7 @@ namespace fx public: struct Block { - int line; + std::size_t line; std::string_view content; }; diff --git a/components/fx/technique.cpp b/components/fx/technique.cpp index defb581cfc..f6bc881f78 100644 --- a/components/fx/technique.cpp +++ b/components/fx/technique.cpp @@ -37,11 +37,22 @@ namespace namespace fx { + namespace + { + VFS::Path::Normalized makeFilePath(std::string_view name) + { + std::string fileName(name); + fileName += Technique::sExt; + VFS::Path::Normalized result(Technique::sSubdir); + result /= fileName; + return result; + } + } + Technique::Technique(const VFS::Manager& vfs, Resource::ImageManager& imageManager, std::string name, int width, int height, bool ubo, bool supportsNormals) : mName(std::move(name)) - , mFileName(Files::pathToUnicodeString( - (Files::pathFromUnicodeString(Technique::sSubdir) / (mName + Technique::sExt)))) + , mFilePath(makeFilePath(mName)) , mLastModificationTime(std::filesystem::file_time_type::clock::now()) , mWidth(width) , mHeight(height) @@ -98,9 +109,9 @@ namespace fx { clear(); - if (!mVFS.exists(mFileName)) + if (!mVFS.exists(mFilePath)) { - Log(Debug::Error) << "Could not load technique, file does not exist '" << mFileName << "'"; + Log(Debug::Error) << "Could not load technique, file does not exist '" << mFilePath << "'"; mStatus = Status::File_Not_exists; return false; @@ -167,7 +178,7 @@ namespace fx mStatus = Status::Parse_Error; mLastError = "Failed parsing technique '" + getName() + "' " + e.what(); - ; + Log(Debug::Error) << mLastError; } @@ -179,11 +190,6 @@ namespace fx return mName; } - std::string Technique::getFileName() const - { - return mFileName; - } - bool Technique::setLastModificationTime(std::filesystem::file_time_type timeStamp) { const bool isDirty = timeStamp != mLastModificationTime; diff --git a/components/fx/technique.hpp b/components/fx/technique.hpp index fa66996aeb..01943a2fbe 100644 --- a/components/fx/technique.hpp +++ b/components/fx/technique.hpp @@ -13,6 +13,8 @@ #include #include +#include + #include "lexer.hpp" #include "pass.hpp" #include "types.hpp" @@ -103,8 +105,8 @@ namespace fx using UniformMap = std::vector>; using RenderTargetMap = std::unordered_map; - inline static std::string sExt = ".omwfx"; - inline static std::string sSubdir = "shaders"; + static constexpr std::string_view sExt = ".omwfx"; + static constexpr std::string_view sSubdir = "shaders"; enum class Status { @@ -128,7 +130,7 @@ namespace fx std::string getName() const; - std::string getFileName() const; + const VFS::Path::Normalized& getFileName() const { return mFilePath; } bool setLastModificationTime(std::filesystem::file_time_type timeStamp); @@ -251,7 +253,7 @@ namespace fx std::string mShared; std::string mName; - std::string mFileName; + VFS::Path::Normalized mFilePath; std::string_view mBlockName; std::string_view mAuthor; std::string_view mDescription; diff --git a/components/l10n/manager.cpp b/components/l10n/manager.cpp index 10cad81587..77474cd3f5 100644 --- a/components/l10n/manager.cpp +++ b/components/l10n/manager.cpp @@ -36,11 +36,13 @@ namespace l10n void Manager::readLangData(const std::string& name, MessageBundles& ctx, const icu::Locale& lang) { - std::string path = "l10n/"; - path.append(name); - path.append("/"); - path.append(lang.getName()); - path.append(".yaml"); + std::string langName(lang.getName()); + langName += ".yaml"; + + VFS::Path::Normalized path("l10n"); + path /= name; + path /= langName; + if (!mVFS->exists(path)) return; diff --git a/components/l10n/messagebundles.cpp b/components/l10n/messagebundles.cpp index 4656116487..a46b05c6f4 100644 --- a/components/l10n/messagebundles.cpp +++ b/components/l10n/messagebundles.cpp @@ -77,7 +77,8 @@ namespace l10n { const auto key = it.first.as(); const auto value = it.second.as(); - icu::UnicodeString pattern = icu::UnicodeString::fromUTF8(icu::StringPiece(value.data(), value.size())); + icu::UnicodeString pattern = icu::UnicodeString::fromUTF8( + icu::StringPiece(value.data(), static_cast(value.size()))); icu::ErrorCode status; UParseError parseError; icu::MessageFormat message(pattern, langOrEn, parseError, status); @@ -115,7 +116,8 @@ namespace l10n std::vector argValues; for (auto& [k, v] : args) { - argNames.push_back(icu::UnicodeString::fromUTF8(icu::StringPiece(k.data(), k.size()))); + argNames.push_back( + icu::UnicodeString::fromUTF8(icu::StringPiece(k.data(), static_cast(k.size())))); argValues.push_back(v); } return formatMessage(key, argNames, argValues); @@ -160,9 +162,9 @@ namespace l10n if (message) { if (!args.empty() && !argNames.empty()) - message->format(argNames.data(), args.data(), args.size(), result, success); + message->format(argNames.data(), args.data(), static_cast(args.size()), result, success); else - message->format(nullptr, nullptr, args.size(), result, success); + message->format(nullptr, nullptr, static_cast(args.size()), result, success); checkSuccess(success, std::string("Failed to format message ") + key.data()); result.toUTF8String(resultString); return resultString; @@ -174,15 +176,17 @@ namespace l10n } UParseError parseError; icu::MessageFormat defaultMessage( - icu::UnicodeString::fromUTF8(icu::StringPiece(key.data(), key.size())), defaultLocale, parseError, success); + icu::UnicodeString::fromUTF8(icu::StringPiece(key.data(), static_cast(key.size()))), + defaultLocale, parseError, success); if (!checkSuccess(success, std::string("Failed to create message ") + key.data(), parseError)) // If we can't parse the key as a pattern, just return the key return std::string(key); if (!args.empty() && !argNames.empty()) - defaultMessage.format(argNames.data(), args.data(), args.size(), result, success); + defaultMessage.format( + argNames.data(), args.data(), static_cast(args.size()), result, success); else - defaultMessage.format(nullptr, nullptr, args.size(), result, success); + defaultMessage.format(nullptr, nullptr, static_cast(args.size()), result, success); checkSuccess(success, std::string("Failed to format message ") + key.data()); result.toUTF8String(resultString); return resultString; diff --git a/components/l10n/qttranslations.cpp b/components/l10n/qttranslations.cpp new file mode 100644 index 0000000000..9bc146699e --- /dev/null +++ b/components/l10n/qttranslations.cpp @@ -0,0 +1,37 @@ +#include "qttranslations.hpp" + +#include +#include + +namespace l10n +{ + QTranslator AppTranslator{}; + QTranslator ComponentsTranslator{}; + QTranslator QtBaseAppTranslator{}; + + void installQtTranslations(QApplication& app, const QString& appName, const QString& resourcesPath) + { + // Try to load OpenMW translations from resources folder first. + // If we loaded them, try to load Qt translations from both + // resources folder and default translations folder as well. +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + auto qtPath = QLibraryInfo::path(QLibraryInfo::TranslationsPath); +#else + auto qtPath = QLibraryInfo::location(QLibraryInfo::TranslationsPath); +#endif + auto localPath = resourcesPath + "/translations"; + + if (AppTranslator.load(QLocale::system(), appName, "_", localPath) + && ComponentsTranslator.load(QLocale::system(), "components", "_", localPath)) + { + app.installTranslator(&AppTranslator); + app.installTranslator(&ComponentsTranslator); + + if (QtBaseAppTranslator.load(QLocale::system(), "qtbase", "_", localPath) + || QtBaseAppTranslator.load(QLocale::system(), "qt", "_", localPath) + || QtBaseAppTranslator.load(QLocale::system(), "qtbase", "_", qtPath) + || QtBaseAppTranslator.load(QLocale::system(), "qt", "_", qtPath)) + app.installTranslator(&QtBaseAppTranslator); + } + } +} diff --git a/components/l10n/qttranslations.hpp b/components/l10n/qttranslations.hpp new file mode 100644 index 0000000000..3ce87f0837 --- /dev/null +++ b/components/l10n/qttranslations.hpp @@ -0,0 +1,16 @@ +#ifndef COMPONENTS_L10N_QTTRANSLATIONS_H +#define COMPONENTS_L10N_QTTRANSLATIONS_H + +#include +#include + +namespace l10n +{ + extern QTranslator AppTranslator; + extern QTranslator ComponentsTranslator; + extern QTranslator QtBaseAppTranslator; + + void installQtTranslations(QApplication& app, const QString& appName, const QString& resourcesPath); +} + +#endif // COMPONENTS_L10N_QTTRANSLATIONS_H diff --git a/components/lua/asyncpackage.cpp b/components/lua/asyncpackage.cpp index 8316ab2cde..5d563e6276 100644 --- a/components/lua/asyncpackage.cpp +++ b/components/lua/asyncpackage.cpp @@ -24,7 +24,30 @@ namespace LuaUtil Callback Callback::fromLua(const sol::table& t) { - return Callback{ t.raw_get(1), t.raw_get(2).mHiddenData }; + const sol::object& function = t.raw_get(1); + const sol::object& asyncPackageId = t.raw_get(2); + if (!function.is() || !asyncPackageId.is()) + throw std::domain_error("Expected an async:callback, received a table"); + return Callback{ function.as(), asyncPackageId.as().mHiddenData }; + } + + sol::table Callback::makeMetatable(lua_State* L) + { + sol::table callbackMeta = sol::table::create(L); + callbackMeta[sol::meta_function::call] = [](const sol::table& callback, sol::variadic_args va) { + return Callback::fromLua(callback).call(sol::as_args(va)); + }; + callbackMeta[sol::meta_function::to_string] = [] { return "Callback"; }; + callbackMeta[sol::meta_function::metatable] = false; + callbackMeta["isCallback"] = true; + return callbackMeta; + } + sol::table Callback::make(const AsyncPackageId& asyncId, sol::main_protected_function fn, sol::table metatable) + { + sol::table c = sol::table::create(fn.lua_state(), 2); + c.raw_set(1, std::move(fn), 2, asyncId); + c[sol::metatable_key] = metatable; + return c; } bool Callback::isLuaCallback(const sol::object& t) @@ -69,18 +92,9 @@ namespace LuaUtil TimerType::GAME_TIME, gameTimeFn() + delay, asyncId.mScriptId, std::move(callback)); }; - sol::table callbackMeta = sol::table::create(L); - callbackMeta[sol::meta_function::call] = [](const sol::table& callback, sol::variadic_args va) { - return Callback::fromLua(callback).call(sol::as_args(va)); - }; - callbackMeta[sol::meta_function::to_string] = [] { return "Callback"; }; - callbackMeta[sol::meta_function::metatable] = false; - callbackMeta["isCallback"] = true; + sol::table callbackMeta = Callback::makeMetatable(L); api["callback"] = [callbackMeta](const AsyncPackageId& asyncId, sol::main_protected_function fn) -> sol::table { - sol::table c = sol::table::create(fn.lua_state(), 2); - c.raw_set(1, std::move(fn), 2, asyncId); - c[sol::metatable_key] = callbackMeta; - return c; + return Callback::make(asyncId, std::move(fn), callbackMeta); }; auto initializer = [](sol::table hiddenData) { diff --git a/components/lua/asyncpackage.hpp b/components/lua/asyncpackage.hpp index e8d5f04271..7e9c36de09 100644 --- a/components/lua/asyncpackage.hpp +++ b/components/lua/asyncpackage.hpp @@ -24,6 +24,8 @@ namespace LuaUtil static bool isLuaCallback(const sol::object&); static Callback fromLua(const sol::table&); + static sol::table makeMetatable(lua_State* L); + static sol::table make(const AsyncPackageId& asyncId, sol::main_protected_function fn, sol::table metatable); bool isValid() const { return mHiddenData[ScriptsContainer::sScriptIdKey] != sol::nil; } diff --git a/components/lua/luastate.cpp b/components/lua/luastate.cpp index 0a350a2d9f..3cf6378f2f 100644 --- a/components/lua/luastate.cpp +++ b/components/lua/luastate.cpp @@ -376,8 +376,8 @@ namespace LuaUtil sol::protected_function_result LuaState::throwIfError(sol::protected_function_result&& res) { - if (!res.valid() && static_cast(res.get_type()) == LUA_TSTRING) - throw std::runtime_error("Lua error: " + res.get()); + if (!res.valid()) + throw std::runtime_error(std::string("Lua error: ") += res.get().what()); else return std::move(res); } @@ -397,7 +397,7 @@ namespace LuaUtil std::string fileContent(std::istreambuf_iterator(*mVFS->get(path)), {}); sol::load_result res = mSol.load(fileContent, path, sol::load_mode::text); if (!res.valid()) - throw std::runtime_error("Lua error: " + res.get()); + throw std::runtime_error(std::string("Lua error: ") += res.get().what()); return res; } diff --git a/components/lua/luastate.hpp b/components/lua/luastate.hpp index aea1e32590..509b5e16e1 100644 --- a/components/lua/luastate.hpp +++ b/components/lua/luastate.hpp @@ -232,16 +232,36 @@ namespace LuaUtil } } + // work around for a (likely) sol3 bug + // when the index meta method throws, simply calling table.get crashes instead of re-throwing the error + template + sol::object safeGet(const sol::table& table, const Key& key) + { + auto index = table.traverse_raw_get>( + sol::metatable_key, sol::meta_function::index); + if (index) + { + sol::protected_function_result result = index.value()(table, key); + if (result.valid()) + return result.get(); + else + throw result.get(); + } + else + return table.raw_get(key); + } + // getFieldOrNil(table, "a", "b", "c") returns table["a"]["b"]["c"] or nil if some of the fields doesn't exist. template sol::object getFieldOrNil(const sol::object& table, std::string_view first, const Str&... str) { if (!table.is()) return sol::nil; + sol::object value = safeGet(table.as(), first); if constexpr (sizeof...(str) == 0) - return table.as()[first]; + return value; else - return getFieldOrNil(table.as()[first], str...); + return getFieldOrNil(value, str...); } // String representation of a Lua object. Should be used for debugging/logging purposes only. diff --git a/components/lua/scriptscontainer.cpp b/components/lua/scriptscontainer.cpp index 9b4a119ba4..ff45b963ca 100644 --- a/components/lua/scriptscontainer.cpp +++ b/components/lua/scriptscontainer.cpp @@ -590,7 +590,7 @@ namespace LuaUtil updateTimerQueue(mGameTimersQueue, gameTime); } - static constexpr float instructionCountAvgCoef = 1.0 / 30; // averaging over approximately 30 frames + static constexpr float instructionCountAvgCoef = 1.0f / 30; // averaging over approximately 30 frames void ScriptsContainer::statsNextFrame() { diff --git a/components/lua/storage.cpp b/components/lua/storage.cpp index dd53fdffcb..063dbf0d10 100644 --- a/components/lua/storage.cpp +++ b/components/lua/storage.cpp @@ -17,6 +17,15 @@ namespace LuaUtil { LuaStorage::Value LuaStorage::Section::sEmpty; + void LuaStorage::registerLifeTime(LuaUtil::LuaState& luaState, sol::table& res) + { + res["LIFE_TIME"] = LuaUtil::makeStrictReadOnly(luaState.tableFromPairs({ + { "Persistent", Section::LifeTime::Persistent }, + { "GameSession", Section::LifeTime::GameSession }, + { "Temporary", Section::LifeTime::Temporary }, + })); + } + sol::object LuaStorage::Value::getCopy(lua_State* L) const { return deserialize(L, mSerializedValue); @@ -142,7 +151,12 @@ namespace LuaUtil sview["removeOnExit"] = [](const SectionView& section) { if (section.mReadOnly) throw std::runtime_error("Access to storage is read only"); - section.mSection->mPermanent = false; + section.mSection->mLifeTime = Section::Temporary; + }; + sview["setLifeTime"] = [](const SectionView& section, Section::LifeTime lifeTime) { + if (section.mReadOnly) + throw std::runtime_error("Access to storage is read only"); + section.mSection->mLifeTime = lifeTime; }; sview["set"] = [](const SectionView& section, std::string_view key, const sol::object& value) { if (section.mReadOnly) @@ -151,26 +165,33 @@ namespace LuaUtil }; } - sol::table LuaStorage::initGlobalPackage(lua_State* lua, LuaStorage* globalStorage) + sol::table LuaStorage::initGlobalPackage(LuaUtil::LuaState& luaState, LuaStorage* globalStorage) { - sol::table res(lua, sol::create); + sol::table res(luaState.sol(), sol::create); + registerLifeTime(luaState, res); + res["globalSection"] = [globalStorage](std::string_view section) { return globalStorage->getMutableSection(section); }; res["allGlobalSections"] = [globalStorage]() { return globalStorage->getAllSections(); }; return LuaUtil::makeReadOnly(res); } - sol::table LuaStorage::initLocalPackage(lua_State* lua, LuaStorage* globalStorage) + sol::table LuaStorage::initLocalPackage(LuaUtil::LuaState& luaState, LuaStorage* globalStorage) { - sol::table res(lua, sol::create); + sol::table res(luaState.sol(), sol::create); + registerLifeTime(luaState, res); + res["globalSection"] = [globalStorage](std::string_view section) { return globalStorage->getReadOnlySection(section); }; return LuaUtil::makeReadOnly(res); } - sol::table LuaStorage::initPlayerPackage(lua_State* lua, LuaStorage* globalStorage, LuaStorage* playerStorage) + sol::table LuaStorage::initPlayerPackage( + LuaUtil::LuaState& luaState, LuaStorage* globalStorage, LuaStorage* playerStorage) { - sol::table res(lua, sol::create); + sol::table res(luaState.sol(), sol::create); + registerLifeTime(luaState, res); + res["globalSection"] = [globalStorage](std::string_view section) { return globalStorage->getReadOnlySection(section); }; res["playerSection"] @@ -179,9 +200,12 @@ namespace LuaUtil return LuaUtil::makeReadOnly(res); } - sol::table LuaStorage::initMenuPackage(lua_State* lua, LuaStorage* globalStorage, LuaStorage* playerStorage) + sol::table LuaStorage::initMenuPackage( + LuaUtil::LuaState& luaState, LuaStorage* globalStorage, LuaStorage* playerStorage) { - sol::table res(lua, sol::create); + sol::table res(luaState.sol(), sol::create); + registerLifeTime(luaState, res); + res["playerSection"] = [playerStorage](std::string_view section) { return playerStorage->getMutableSection(section, /*forMenuScripts=*/true); }; @@ -199,7 +223,7 @@ namespace LuaUtil it->second->mCallbacks.clear(); // Note that we don't clear menu callbacks for permanent sections // because starting/loading a game doesn't reset menu scripts. - if (!it->second->mPermanent) + if (it->second->mLifeTime == Section::Temporary) { it->second->mMenuScriptsCallbacks.clear(); it->second->mValues.clear(); @@ -215,8 +239,11 @@ namespace LuaUtil assert(mData.empty()); // Shouldn't be used before loading try { - Log(Debug::Info) << "Loading Lua storage \"" << path << "\" (" << std::filesystem::file_size(path) - << " bytes)"; + std::uintmax_t fileSize = std::filesystem::file_size(path); + Log(Debug::Info) << "Loading Lua storage \"" << path << "\" (" << fileSize << " bytes)"; + if (fileSize == 0) + throw std::runtime_error("Storage file has zero length"); + std::ifstream fin(path, std::fstream::binary); std::string serializedData((std::istreambuf_iterator(fin)), std::istreambuf_iterator()); sol::table data = deserialize(mLua, serializedData); @@ -229,7 +256,7 @@ namespace LuaUtil } catch (std::exception& e) { - Log(Debug::Error) << "Can not read \"" << path << "\": " << e.what(); + Log(Debug::Error) << "Cannot read \"" << path << "\": " << e.what(); } } @@ -238,7 +265,7 @@ namespace LuaUtil sol::table data(mLua, sol::create); for (const auto& [sectionName, section] : mData) { - if (section->mPermanent && !section->mValues.empty()) + if (section->mLifeTime == Section::Persistent && !section->mValues.empty()) data[sectionName] = section->asTable(); } std::string serializedData = serialize(data); diff --git a/components/lua/storage.hpp b/components/lua/storage.hpp index 75e0e14a16..061a5aace3 100644 --- a/components/lua/storage.hpp +++ b/components/lua/storage.hpp @@ -14,11 +14,13 @@ namespace LuaUtil class LuaStorage { public: - static void initLuaBindings(lua_State*); - static sol::table initGlobalPackage(lua_State* lua, LuaStorage* globalStorage); - static sol::table initLocalPackage(lua_State* lua, LuaStorage* globalStorage); - static sol::table initPlayerPackage(lua_State* lua, LuaStorage* globalStorage, LuaStorage* playerStorage); - static sol::table initMenuPackage(lua_State* lua, LuaStorage* globalStorage, LuaStorage* playerStorage); + static void initLuaBindings(lua_State* L); + static sol::table initGlobalPackage(LuaUtil::LuaState& luaState, LuaStorage* globalStorage); + static sol::table initLocalPackage(LuaUtil::LuaState& luaState, LuaStorage* globalStorage); + static sol::table initPlayerPackage( + LuaUtil::LuaState& luaState, LuaStorage* globalStorage, LuaStorage* playerStorage); + static sol::table initMenuPackage( + LuaUtil::LuaState& luaState, LuaStorage* globalStorage, LuaStorage* playerStorage); explicit LuaStorage(lua_State* lua) : mLua(lua) @@ -78,6 +80,13 @@ namespace LuaUtil struct Section { + enum LifeTime + { + Persistent, + GameSession, + Temporary + }; + explicit Section(LuaStorage* storage, std::string name) : mStorage(storage) , mSectionName(std::move(name)) @@ -96,7 +105,7 @@ namespace LuaUtil std::vector mCallbacks; std::vector mMenuScriptsCallbacks; // menu callbacks are in a separate vector because we don't // remove them in clear() - bool mPermanent = true; + LifeTime mLifeTime = Persistent; static Value sEmpty; void checkIfActive() const { mStorage->checkIfActive(); } @@ -120,6 +129,7 @@ namespace LuaUtil if (!mActive) throw std::logic_error("Trying to access inactive storage"); } + static void registerLifeTime(LuaUtil::LuaState& luaState, sol::table& res); }; } diff --git a/components/lua/utf8.cpp b/components/lua/utf8.cpp index b486766b6a..2a585dac2d 100644 --- a/components/lua/utf8.cpp +++ b/components/lua/utf8.cpp @@ -14,7 +14,7 @@ namespace return (arg.get_type() == sol::type::lua_nil || arg.get_type() == sol::type::none); } - inline double getInteger(const sol::stack_proxy arg, const size_t n, std::string_view name) + inline std::int64_t getInteger(const sol::stack_proxy arg, const size_t n, std::string_view name) { double integer; if (!arg.is()) @@ -25,7 +25,7 @@ namespace throw std::runtime_error( Misc::StringUtils::format("bad argument #%i to '%s' (number has no integer representation)", n, name)); - return integer; + return static_cast(integer); } // If the input 'pos' is negative, it is treated as counting from the end of the string, @@ -104,7 +104,8 @@ namespace LuaUtf8 throw std::runtime_error( "bad argument #" + std::to_string(i + 1) + " to 'char' (value out of range)"); - result += converter.to_bytes(codepoint); + // this feels dodgy if wchar_t is 16-bit as MAXUTF won't fit in sixteen bits + result += converter.to_bytes(static_cast(codepoint)); } return result; }; diff --git a/components/lua/yamlloader.cpp b/components/lua/yamlloader.cpp new file mode 100644 index 0000000000..df83af6253 --- /dev/null +++ b/components/lua/yamlloader.cpp @@ -0,0 +1,280 @@ +#include "yamlloader.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include + +namespace LuaUtil +{ + namespace + { + constexpr uint64_t maxDepth = 250; + + enum class ScalarType + { + Boolean, + Decimal, + Float, + Hexadecimal, + Infinity, + NotNumber, + Null, + Octal, + String + }; + + sol::object loadAll(const std::vector& rootNodes, const sol::state_view& lua); + + sol::object getNode(const YAML::Node& node, const sol::state_view& lua, uint64_t depth); + + sol::table getMap(const YAML::Node& node, const sol::state_view& lua, uint64_t depth); + + sol::table getArray(const YAML::Node& node, const sol::state_view& lua, uint64_t depth); + + ScalarType getScalarType(const YAML::Node& node); + + sol::object getScalar(const YAML::Node& node, const sol::state_view& lua); + + [[noreturn]] void nodeError(const YAML::Node& node, const std::string& message); + } + + sol::object loadYaml(const std::string& input, const sol::state_view& lua) + { + std::vector rootNodes = YAML::LoadAll(input); + return loadAll(rootNodes, lua); + } + + sol::object loadYaml(std::istream& input, const sol::state_view& lua) + { + std::vector rootNodes = YAML::LoadAll(input); + return loadAll(rootNodes, lua); + } + + namespace + { + sol::object loadAll(const std::vector& rootNodes, const sol::state_view& lua) + { + if (rootNodes.empty()) + return sol::nil; + + if (rootNodes.size() == 1) + return getNode(rootNodes[0], lua, 0); + + sol::table documentsTable(lua, sol::create); + for (const auto& root : rootNodes) + { + documentsTable.add(getNode(root, lua, 1)); + } + + return documentsTable; + } + + sol::object getNode(const YAML::Node& node, const sol::state_view& lua, uint64_t depth) + { + if (depth >= maxDepth) + throw std::runtime_error("Maximum layers depth exceeded, probably caused by a circular reference"); + + ++depth; + + if (node.IsMap()) + return getMap(node, lua, depth); + else if (node.IsSequence()) + return getArray(node, lua, depth); + else if (node.IsScalar()) + return getScalar(node, lua); + else if (node.IsNull()) + return sol::nil; + + nodeError(node, "An unknown YAML node encountered"); + } + + sol::table getMap(const YAML::Node& node, const sol::state_view& lua, uint64_t depth) + { + sol::table childTable(lua, sol::create); + + for (const auto& pair : node) + { + if (pair.first.IsMap()) + nodeError(pair.first, "Only scalar nodes can be used as keys, encountered map instead"); + if (pair.first.IsSequence()) + nodeError(pair.first, "Only scalar nodes can be used as keys, encountered array instead"); + if (pair.first.IsNull()) + nodeError(pair.first, "Only scalar nodes can be used as keys, encountered null instead"); + + auto key = getNode(pair.first, lua, depth); + if (key.get_type() == sol::type::number && std::isnan(key.as())) + nodeError(pair.first, "Only scalar nodes can be used as keys, encountered nan instead"); + + childTable[key] = getNode(pair.second, lua, depth); + } + + return childTable; + } + + sol::table getArray(const YAML::Node& node, const sol::state_view& lua, uint64_t depth) + { + sol::table childTable(lua, sol::create); + + for (const auto& child : node) + { + childTable.add(getNode(child, lua, depth)); + } + + return childTable; + } + + ScalarType getScalarType(const YAML::Node& node) + { + const auto& tag = node.Tag(); + const auto& value = node.Scalar(); + if (tag == "!") + return ScalarType::String; + + // Note that YAML allows to explicitely specify a scalar type via tag (e.g. "!!bool"), but it makes no + // sense in Lua: + // 1. Both integers and floats use the "number" type prior to Lua 5.3 + // 2. Strings can be quoted, which is more readable than "!!str" + // 3. Most of possible conversions are invalid or their result is unclear + // So ignore this feature for now. + if (tag != "?") + nodeError(node, "An invalid tag '" + tag + "' encountered"); + + if (value.empty()) + return ScalarType::Null; + + // Resolve type according to YAML 1.2 Core Schema (see https://yaml.org/spec/1.2.2/#103-core-schema) + static const std::regex boolRegex("true|True|TRUE|false|False|FALSE", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), boolRegex)) + return ScalarType::Boolean; + + static const std::regex decimalRegex("[-+]?[0-9]+", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), decimalRegex)) + return ScalarType::Decimal; + + static const std::regex floatRegex( + "[-+]?([.][0-9]+|[0-9]+([.][0-9]*)?)([eE][-+]?[0-9]+)?", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), floatRegex)) + return ScalarType::Float; + + static const std::regex octalRegex("0o[0-7]+", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), octalRegex)) + return ScalarType::Octal; + + static const std::regex hexdecimalRegex("0x[0-9a-fA-F]+", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), hexdecimalRegex)) + return ScalarType::Hexadecimal; + + static const std::regex infinityRegex("[-+]?([.]inf|[.]Inf|[.]INF)", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), infinityRegex)) + return ScalarType::Infinity; + + static const std::regex nanRegex("[.]nan|[.]NaN|[.]NAN", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), nanRegex)) + return ScalarType::NotNumber; + + static const std::regex nullRegex("null|Null|NULL|~", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), nullRegex)) + return ScalarType::Null; + + return ScalarType::String; + } + + sol::object getScalar(const YAML::Node& node, const sol::state_view& lua) + { + auto type = getScalarType(node); + const auto& value = node.Scalar(); + + switch (type) + { + case ScalarType::Null: + return sol::nil; + case ScalarType::String: + return sol::make_object(lua, value); + case ScalarType::NotNumber: + return sol::make_object(lua, std::nan("")); + case ScalarType::Infinity: + { + if (!value.empty() && value[0] == '-') + return sol::make_object(lua, -std::numeric_limits::infinity()); + + return sol::make_object(lua, std::numeric_limits::infinity()); + } + case ScalarType::Boolean: + { + if (Misc::StringUtils::lowerCase(value) == "true") + return sol::make_object(lua, true); + + if (Misc::StringUtils::lowerCase(value) == "false") + return sol::make_object(lua, false); + + nodeError(node, "Can not read a boolean value '" + value + "'"); + } + case ScalarType::Decimal: + { + int offset = 0; + + // std::from_chars does not support "+" sign + if (!value.empty() && value[0] == '+') + ++offset; + + int result = 0; + const auto status = std::from_chars(value.data() + offset, value.data() + value.size(), result); + if (status.ec == std::errc()) + return sol::make_object(lua, result); + + nodeError(node, "Can not read a decimal value '" + value + "'"); + } + case ScalarType::Float: + { + // Not all compilers support std::from_chars for floats + double result = 0.0; + bool success = YAML::convert::decode(node, result); + if (success) + return sol::make_object(lua, result); + + nodeError(node, "Can not read a float value '" + value + "'"); + } + case ScalarType::Hexadecimal: + { + int result = 0; + const auto status = std::from_chars(value.data() + 2, value.data() + value.size(), result, 16); + if (status.ec == std::errc()) + return sol::make_object(lua, result); + + nodeError(node, "Can not read a hexadecimal value '" + value + "'"); + } + case ScalarType::Octal: + { + int result = 0; + const auto status = std::from_chars(value.data() + 2, value.data() + value.size(), result, 8); + if (status.ec == std::errc()) + return sol::make_object(lua, result); + + nodeError(node, "Can not read an octal value '" + value + "'"); + } + default: + nodeError(node, "An unknown scalar '" + value + "' encountered"); + } + } + + [[noreturn]] void nodeError(const YAML::Node& node, const std::string& message) + { + const auto& mark = node.Mark(); + std::string error = Misc::StringUtils::format( + " at line=%d column=%d position=%d", mark.line + 1, mark.column + 1, mark.pos + 1); + throw std::runtime_error(message + error); + } + } +} diff --git a/components/lua/yamlloader.hpp b/components/lua/yamlloader.hpp new file mode 100644 index 0000000000..6f28da66ce --- /dev/null +++ b/components/lua/yamlloader.hpp @@ -0,0 +1,16 @@ +#ifndef COMPONENTS_LUA_YAMLLOADER_H +#define COMPONENTS_LUA_YAMLLOADER_H + +#include +#include + +#include + +namespace LuaUtil +{ + sol::object loadYaml(const std::string& input, const sol::state_view& lua); + + sol::object loadYaml(std::istream& input, const sol::state_view& lua); +} + +#endif // COMPONENTS_LUA_YAMLLOADER_H diff --git a/components/lua_ui/container.cpp b/components/lua_ui/container.cpp index 52fea684d7..1999be8169 100644 --- a/components/lua_ui/container.cpp +++ b/components/lua_ui/container.cpp @@ -10,12 +10,12 @@ namespace LuaUi updateSizeToFit(); } - MyGUI::IntSize LuaContainer::childScalingSize() + MyGUI::IntSize LuaContainer::childScalingSize() const { return MyGUI::IntSize(); } - MyGUI::IntSize LuaContainer::templateScalingSize() + MyGUI::IntSize LuaContainer::templateScalingSize() const { return mInnerSize; } @@ -23,14 +23,14 @@ namespace LuaUi void LuaContainer::updateSizeToFit() { MyGUI::IntSize innerSize = MyGUI::IntSize(); - for (auto w : children()) + for (const auto w : children()) { MyGUI::IntCoord coord = w->calculateCoord(); innerSize.width = std::max(innerSize.width, coord.left + coord.width); innerSize.height = std::max(innerSize.height, coord.top + coord.height); } MyGUI::IntSize outerSize = innerSize; - for (auto w : templateChildren()) + for (const auto w : templateChildren()) { MyGUI::IntCoord coord = w->calculateCoord(); outerSize.width = std::max(outerSize.width, coord.left + coord.width); @@ -40,7 +40,7 @@ namespace LuaUi mOuterSize = outerSize; } - MyGUI::IntSize LuaContainer::calculateSize() + MyGUI::IntSize LuaContainer::calculateSize() const { return mOuterSize; } diff --git a/components/lua_ui/container.hpp b/components/lua_ui/container.hpp index 16f19d3c12..ef13dd0638 100644 --- a/components/lua_ui/container.hpp +++ b/components/lua_ui/container.hpp @@ -10,13 +10,13 @@ namespace LuaUi MYGUI_RTTI_DERIVED(LuaContainer) public: - MyGUI::IntSize calculateSize() override; + MyGUI::IntSize calculateSize() const override; void updateCoord() override; protected: void updateChildren() override; - MyGUI::IntSize childScalingSize() override; - MyGUI::IntSize templateScalingSize() override; + MyGUI::IntSize childScalingSize() const override; + MyGUI::IntSize templateScalingSize() const override; private: void updateSizeToFit(); diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index 5a54cd91b5..f3f873a583 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -54,25 +54,19 @@ namespace LuaUi if (!ext->isRoot()) destroyWidget(ext); else - ext->widget()->detachFromWidget(); + ext->detachFromParent(); } void detachElements(WidgetExtension* ext) { - for (auto* child : ext->children()) - { - if (child->isRoot()) - child->widget()->detachFromWidget(); - else - detachElements(child); - } - for (auto* child : ext->templateChildren()) - { + auto predicate = [](WidgetExtension* child) { if (child->isRoot()) - child->widget()->detachFromWidget(); - else - detachElements(child); - } + return true; + detachElements(child); + return false; + }; + ext->detachChildrenIf(predicate); + ext->detachTemplateChildrenIf(predicate); } void destroyRoot(WidgetExtension* ext) @@ -89,25 +83,20 @@ namespace LuaUi root->updateCoord(); } - WidgetExtension* pluckElementRoot(const sol::object& child) + WidgetExtension* pluckElementRoot(const sol::object& child, uint64_t depth) { std::shared_ptr element = child.as>(); - WidgetExtension* root = element->mRoot; - if (!root) + if (element->mState == Element::Destroyed || element->mState == Element::Destroy) throw std::logic_error("Using a destroyed element as a layout child"); - WidgetExtension* parent = root->getParent(); - if (parent) - { - auto children = parent->children(); - std::erase(children, root); - parent->setChildren(children); - root->widget()->detachFromWidget(); - } - root->updateCoord(); + // child Element was created in the same frame and its action hasn't been processed yet + if (element->mState == Element::New) + element->create(depth + 1); + WidgetExtension* root = element->mRoot; + assert(root); return root; } - WidgetExtension* createWidget(const sol::table& layout, uint64_t depth); + WidgetExtension* createWidget(const sol::table& layout, bool isRoot, uint64_t depth); void updateWidget(WidgetExtension* ext, const sol::table& layout, uint64_t depth); std::vector updateContent( @@ -130,7 +119,7 @@ namespace LuaUi sol::object child = content.at(i); if (child.is()) { - WidgetExtension* root = pluckElementRoot(child); + WidgetExtension* root = pluckElementRoot(child, depth); if (ext != root) destroyChild(ext); result[i] = root; @@ -145,7 +134,7 @@ namespace LuaUi else { destroyChild(ext); - ext = createWidget(newLayout, depth); + ext = createWidget(newLayout, false, depth); } result[i] = ext; } @@ -156,9 +145,9 @@ namespace LuaUi { sol::object child = content.at(i); if (child.is()) - result[i] = pluckElementRoot(child); + result[i] = pluckElementRoot(child, depth); else - result[i] = createWidget(child.as(), depth); + result[i] = createWidget(child.as(), false, depth); } return result; } @@ -191,7 +180,7 @@ namespace LuaUi }); } - WidgetExtension* createWidget(const sol::table& layout, uint64_t depth) + WidgetExtension* createWidget(const sol::table& layout, bool isRoot, uint64_t depth) { static auto widgetTypeMap = widgetTypeToName(); std::string type = widgetType(layout); @@ -199,13 +188,13 @@ namespace LuaUi throw std::logic_error(std::string("Invalid widget type ") += type); std::string name = layout.get_or(LayoutKeys::name, std::string()); - MyGUI::Widget* widget = MyGUI::Gui::getInstancePtr()->createWidgetT( - type, "", MyGUI::IntCoord(), MyGUI::Align::Default, std::string(), name); + MyGUI::Widget* widget + = MyGUI::Gui::getInstancePtr()->createWidgetT(type, {}, {}, MyGUI::Align::Default, {}, name); WidgetExtension* ext = dynamic_cast(widget); if (!ext) throw std::runtime_error("Invalid widget!"); - ext->initialize(layout.lua_state(), widget, depth == 0); + ext->initialize(layout.lua_state(), widget, isRoot); updateWidget(ext, layout, depth); return ext; @@ -247,8 +236,7 @@ namespace LuaUi : mRoot(nullptr) , mLayout(std::move(layout)) , mLayer() - , mUpdate(false) - , mDestroy(false) + , mState(Element::New) { } @@ -267,28 +255,30 @@ namespace LuaUi sGameElements.erase(element); } - void Element::create() + void Element::create(uint64_t depth) { - assert(!mRoot); - if (!mRoot) + if (mState == New) { - mRoot = createWidget(layout(), 0); + assert(!mRoot); + mRoot = createWidget(layout(), true, depth); mLayer = setLayer(mRoot, layout()); updateRootCoord(mRoot); + mState = Created; } } void Element::update() { - if (mRoot && mUpdate) + if (mState == Update) { + assert(mRoot); if (mRoot->widget()->getTypeName() != widgetType(layout())) { destroyRoot(mRoot); WidgetExtension* parent = mRoot->getParent(); auto children = parent->children(); auto it = std::find(children.begin(), children.end(), mRoot); - mRoot = createWidget(layout(), 0); + mRoot = createWidget(layout(), true, 0); assert(it != children.end()); *it = mRoot; parent->setChildren(children); @@ -300,17 +290,21 @@ namespace LuaUi } mLayer = setLayer(mRoot, layout()); updateRootCoord(mRoot); + mState = Created; } - mUpdate = false; } void Element::destroy() { - if (mRoot) + if (mState != Destroyed) { - destroyRoot(mRoot); - mRoot = nullptr; + if (mRoot != nullptr) + { + destroyRoot(mRoot); + mRoot = nullptr; + } mLayout = sol::make_object(mLayout.lua_state(), sol::nil); } + mState = Destroyed; } } diff --git a/components/lua_ui/element.hpp b/components/lua_ui/element.hpp index 4398a769df..39a1fdd769 100644 --- a/components/lua_ui/element.hpp +++ b/components/lua_ui/element.hpp @@ -21,10 +21,18 @@ namespace LuaUi WidgetExtension* mRoot; sol::object mLayout; std::string mLayer; - bool mUpdate; - bool mDestroy; - void create(); + enum State + { + New, + Created, + Update, + Destroy, + Destroyed, + }; + State mState; + + void create(uint64_t dept = 0); void update(); diff --git a/components/lua_ui/flex.cpp b/components/lua_ui/flex.cpp index c55b48ddb7..1a3293d406 100644 --- a/components/lua_ui/flex.cpp +++ b/components/lua_ui/flex.cpp @@ -79,7 +79,7 @@ namespace LuaUi WidgetExtension::updateChildren(); } - MyGUI::IntSize LuaFlex::childScalingSize() + MyGUI::IntSize LuaFlex::childScalingSize() const { // Call the base method to prevent relativeSize feedback loop MyGUI::IntSize size = WidgetExtension::calculateSize(); @@ -88,7 +88,7 @@ namespace LuaUi return size; } - MyGUI::IntSize LuaFlex::calculateSize() + MyGUI::IntSize LuaFlex::calculateSize() const { MyGUI::IntSize size = WidgetExtension::calculateSize(); if (mAutoSized) diff --git a/components/lua_ui/flex.hpp b/components/lua_ui/flex.hpp index 944daaec77..c91ffd00a2 100644 --- a/components/lua_ui/flex.hpp +++ b/components/lua_ui/flex.hpp @@ -11,10 +11,10 @@ namespace LuaUi MYGUI_RTTI_DERIVED(LuaFlex) protected: - MyGUI::IntSize calculateSize() override; + MyGUI::IntSize calculateSize() const override; void updateProperties() override; void updateChildren() override; - MyGUI::IntSize childScalingSize() override; + MyGUI::IntSize childScalingSize() const override; void updateCoord() override; @@ -26,25 +26,37 @@ namespace LuaUi Alignment mArrange; template - T& primary(MyGUI::types::TPoint& point) + T& primary(MyGUI::types::TPoint& point) const { return mHorizontal ? point.left : point.top; } template - T& secondary(MyGUI::types::TPoint& point) + T& secondary(MyGUI::types::TPoint& point) const { return mHorizontal ? point.top : point.left; } template - T& primary(MyGUI::types::TSize& size) + T& primary(MyGUI::types::TSize& size) const { return mHorizontal ? size.width : size.height; } template - T& secondary(MyGUI::types::TSize& size) + T& secondary(MyGUI::types::TSize& size) const + { + return mHorizontal ? size.height : size.width; + } + + template + T primary(const MyGUI::types::TSize& size) const + { + return mHorizontal ? size.width : size.height; + } + + template + T secondary(const MyGUI::types::TSize& size) const { return mHorizontal ? size.height : size.width; } diff --git a/components/lua_ui/text.cpp b/components/lua_ui/text.cpp index e55f1750b9..35aa9402bf 100644 --- a/components/lua_ui/text.cpp +++ b/components/lua_ui/text.cpp @@ -46,7 +46,7 @@ namespace LuaUi updateCoord(); } - MyGUI::IntSize LuaText::calculateSize() + MyGUI::IntSize LuaText::calculateSize() const { if (mAutoSized) return getTextSize(); diff --git a/components/lua_ui/text.hpp b/components/lua_ui/text.hpp index fffe34a3ba..87c01a37e8 100644 --- a/components/lua_ui/text.hpp +++ b/components/lua_ui/text.hpp @@ -21,7 +21,7 @@ namespace LuaUi bool mAutoSized; protected: - MyGUI::IntSize calculateSize() override; + MyGUI::IntSize calculateSize() const override; }; } diff --git a/components/lua_ui/textedit.cpp b/components/lua_ui/textedit.cpp index e12bd20c35..9bd241884a 100644 --- a/components/lua_ui/textedit.cpp +++ b/components/lua_ui/textedit.cpp @@ -63,7 +63,7 @@ namespace LuaUi mEditBox->attachToWidget(this); } - MyGUI::IntSize LuaTextEdit::calculateSize() + MyGUI::IntSize LuaTextEdit::calculateSize() const { MyGUI::IntSize normalSize = WidgetExtension::calculateSize(); if (mAutoSize) diff --git a/components/lua_ui/textedit.hpp b/components/lua_ui/textedit.hpp index 8f23b51746..57e1209aff 100644 --- a/components/lua_ui/textedit.hpp +++ b/components/lua_ui/textedit.hpp @@ -20,7 +20,7 @@ namespace LuaUi void updateProperties() override; void updateCoord() override; void updateChildren() override; - MyGUI::IntSize calculateSize() override; + MyGUI::IntSize calculateSize() const override; private: void textChange(MyGUI::EditBox*); diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index 9550c9de73..e7e1053ab7 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -100,6 +100,16 @@ namespace LuaUi void WidgetExtension::attach(WidgetExtension* ext) { + if (ext->mParent != this) + { + if (ext->mParent) + { + auto children = ext->mParent->children(); + std::erase(children, this); + ext->mParent->setChildren(children); + } + ext->detachFromParent(); + } ext->mParent = this; ext->mTemplateChild = false; ext->widget()->attachToWidget(mSlot->widget()); @@ -112,6 +122,12 @@ namespace LuaUi ext->widget()->attachToWidget(widget()); } + void WidgetExtension::detachFromParent() + { + mParent = nullptr; + widget()->detachFromWidget(); + } + WidgetExtension* WidgetExtension::findDeep(std::string_view flagName) { for (WidgetExtension* w : mChildren) @@ -293,7 +309,7 @@ namespace LuaUi w->updateCoord(); } - MyGUI::IntSize WidgetExtension::parentSize() + MyGUI::IntSize WidgetExtension::parentSize() const { if (!mParent) return widget()->getParentSize(); // size of the layer @@ -303,7 +319,7 @@ namespace LuaUi return mParent->childScalingSize(); } - MyGUI::IntSize WidgetExtension::calculateSize() + MyGUI::IntSize WidgetExtension::calculateSize() const { if (mForceSize) return mForcedCoord.size(); @@ -316,7 +332,7 @@ namespace LuaUi return newSize; } - MyGUI::IntPoint WidgetExtension::calculatePosition(const MyGUI::IntSize& size) + MyGUI::IntPoint WidgetExtension::calculatePosition(const MyGUI::IntSize& size) const { if (mForcePosition) return mForcedCoord.point(); @@ -328,7 +344,7 @@ namespace LuaUi return newPosition; } - MyGUI::IntCoord WidgetExtension::calculateCoord() + MyGUI::IntCoord WidgetExtension::calculateCoord() const { MyGUI::IntCoord newCoord; newCoord = calculateSize(); @@ -336,12 +352,12 @@ namespace LuaUi return newCoord; } - MyGUI::IntSize WidgetExtension::childScalingSize() + MyGUI::IntSize WidgetExtension::childScalingSize() const { return mSlot->widget()->getSize(); } - MyGUI::IntSize WidgetExtension::templateScalingSize() + MyGUI::IntSize WidgetExtension::templateScalingSize() const { return widget()->getSize(); } diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp index 591c885ce9..24962f6820 100644 --- a/components/lua_ui/widget.hpp +++ b/components/lua_ui/widget.hpp @@ -31,10 +31,13 @@ namespace LuaUi virtual void deinitialize(); MyGUI::Widget* widget() const { return mWidget; } - WidgetExtension* slot() const { return mSlot; } bool isRoot() const { return mElementRoot; } WidgetExtension* getParent() const { return mParent; } + void detachFromParent(); + + void detachChildrenIf(auto&& predicate) { detachChildrenIf(predicate, mChildren); } + void detachTemplateChildrenIf(auto&& predicate) { detachChildrenIf(predicate, mTemplateChildren); } void reset(); @@ -64,14 +67,14 @@ namespace LuaUi void setLayout(const sol::table& layout) { mLayout = layout; } template - T externalValue(std::string_view name, const T& defaultValue) + T externalValue(std::string_view name, const T& defaultValue) const { return parseExternal(mExternal, name, defaultValue); } - virtual MyGUI::IntSize calculateSize(); - virtual MyGUI::IntPoint calculatePosition(const MyGUI::IntSize& size); - MyGUI::IntCoord calculateCoord(); + virtual MyGUI::IntSize calculateSize() const; + virtual MyGUI::IntPoint calculatePosition(const MyGUI::IntSize& size) const; + MyGUI::IntCoord calculateCoord() const; virtual bool isTextInput() { return false; } @@ -84,9 +87,9 @@ namespace LuaUi sol::object keyEvent(MyGUI::KeyCode) const; sol::object mouseEvent(int left, int top, MyGUI::MouseButton button) const; - MyGUI::IntSize parentSize(); - virtual MyGUI::IntSize childScalingSize(); - virtual MyGUI::IntSize templateScalingSize(); + MyGUI::IntSize parentSize() const; + virtual MyGUI::IntSize childScalingSize() const; + virtual MyGUI::IntSize templateScalingSize() const; template T propertyValue(std::string_view name, const T& defaultValue) @@ -175,6 +178,20 @@ namespace LuaUi void focusLoss(MyGUI::Widget*, MyGUI::Widget*); void updateVisible(); + + void detachChildrenIf(auto&& predicate, std::vector children) + { + for (auto it = children.begin(); it != children.end();) + { + if (predicate(*it)) + { + (*it)->detachFromParent(); + it = children.erase(it); + } + else + ++it; + } + } }; class LuaWidget : public MyGUI::Widget, public WidgetExtension diff --git a/components/misc/convert.hpp b/components/misc/convert.hpp index a66fac9125..5d936b5d5f 100644 --- a/components/misc/convert.hpp +++ b/components/misc/convert.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_COMPONENTS_MISC_CONVERT_H #define OPENMW_COMPONENTS_MISC_CONVERT_H -#include +#include #include #include diff --git a/components/misc/helpviewer.cpp b/components/misc/helpviewer.cpp index 0ff4abb9d3..ebfca9ad14 100644 --- a/components/misc/helpviewer.cpp +++ b/components/misc/helpviewer.cpp @@ -4,9 +4,12 @@ #include #include +#include + void Misc::HelpViewer::openHelp(const char* url) { - QString link{ OPENMW_DOC_BASEURL }; + std::string_view docsUrl = Version::getDocumentationUrl(); + QString link = QString::fromUtf8(docsUrl.data(), docsUrl.size()); link.append(url); QDesktopServices::openUrl(QUrl(link)); } diff --git a/components/misc/osgpluginchecker.cpp.in b/components/misc/osgpluginchecker.cpp.in new file mode 100644 index 0000000000..8e57d9a5ce --- /dev/null +++ b/components/misc/osgpluginchecker.cpp.in @@ -0,0 +1,53 @@ +#include "components/misc/osgpluginchecker.hpp" + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace Misc +{ +#if defined(OSG_LIBRARY_STATIC) || defined(__APPLE__) + + bool checkRequiredOSGPluginsArePresent() + { + // assume they were linked in at build time and CMake would have failed if they were missing + // true-ish for MacOS - they're copied into the package and that'd fail if they were missing, + // but if you don't actually make a MacOS package and run a local build, this won't notice. + // the workaround in the real implementation isn't powerful enough to make MacOS work, though. + return true; + } + +#else + + namespace + { + constexpr auto USED_OSG_PLUGIN_NAMES = std::to_array({${USED_OSG_PLUGIN_NAMES_FORMATTED}}); + } + + bool checkRequiredOSGPluginsArePresent() + { + // osgDB::listAllAvailablePlugins() lies, so don't use it + bool haveAllPlugins = true; + for (std::string_view plugin : USED_OSG_PLUGIN_NAMES) + { + std::string libraryName = osgDB::Registry::instance()->createLibraryNameForExtension(std::string{ plugin }); + if (osgDB::findLibraryFile(libraryName).empty()) + { + Log(Debug::Error) << "Missing OSG plugin: " << libraryName; + haveAllPlugins = false; + } + } + return haveAllPlugins; + } + +#endif +} diff --git a/components/misc/osgpluginchecker.hpp b/components/misc/osgpluginchecker.hpp new file mode 100644 index 0000000000..2f5ea09700 --- /dev/null +++ b/components/misc/osgpluginchecker.hpp @@ -0,0 +1,9 @@ +#ifndef OPENMW_COMPONENTS_MISC_OSGPLUGINCHECKER_HPP +#define OPENMW_COMPONENTS_MISC_OSGPLUGINCHECKER_HPP + +namespace Misc +{ + bool checkRequiredOSGPluginsArePresent(); +} + +#endif diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index aa0e0dec7d..5ed0bcc972 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -47,7 +47,7 @@ bool Misc::ResourceHelpers::changeExtensionToDds(std::string& path) } std::string Misc::ResourceHelpers::correctResourcePath( - std::string_view topLevelDirectory, std::string_view resPath, const VFS::Manager* vfs) + std::span topLevelDirectories, std::string_view resPath, const VFS::Manager* vfs) { /* Bethesda at some point converted all their BSA * textures from tga to dds for increased load speed, but all @@ -66,16 +66,30 @@ std::string Misc::ResourceHelpers::correctResourcePath( correctedPath.erase(0, 1); // Handle top level directory - if (!correctedPath.starts_with(topLevelDirectory) || correctedPath.size() <= topLevelDirectory.size() - || correctedPath[topLevelDirectory.size()] != '\\') + bool needsPrefix = true; + for (std::string_view potentialTopLevelDirectory : topLevelDirectories) { - std::string topLevelPrefix = std::string{ topLevelDirectory } + '\\'; - size_t topLevelPos = correctedPath.find('\\' + topLevelPrefix); - if (topLevelPos == std::string::npos) - correctedPath = topLevelPrefix + correctedPath; + if (correctedPath.starts_with(potentialTopLevelDirectory) + && correctedPath.size() > potentialTopLevelDirectory.size() + && correctedPath[potentialTopLevelDirectory.size()] == '\\') + { + needsPrefix = false; + break; + } else - correctedPath.erase(0, topLevelPos + 1); + { + std::string topLevelPrefix = std::string{ potentialTopLevelDirectory } + '\\'; + size_t topLevelPos = correctedPath.find('\\' + topLevelPrefix); + if (topLevelPos != std::string::npos) + { + correctedPath.erase(0, topLevelPos + 1); + needsPrefix = false; + break; + } + } } + if (needsPrefix) + correctedPath = std::string{ topLevelDirectories.front() } + '\\' + correctedPath; std::string origExt = correctedPath; @@ -90,7 +104,7 @@ std::string Misc::ResourceHelpers::correctResourcePath( return origExt; // fall back to a resource in the top level directory if it exists - std::string fallback{ topLevelDirectory }; + std::string fallback{ topLevelDirectories.front() }; fallback += '\\'; fallback += getBasename(correctedPath); if (vfs->exists(fallback)) @@ -98,7 +112,7 @@ std::string Misc::ResourceHelpers::correctResourcePath( if (changedToDds) { - fallback = topLevelDirectory; + fallback = topLevelDirectories.front(); fallback += '\\'; fallback += getBasename(origExt); if (vfs->exists(fallback)) @@ -110,17 +124,17 @@ std::string Misc::ResourceHelpers::correctResourcePath( std::string Misc::ResourceHelpers::correctTexturePath(std::string_view resPath, const VFS::Manager* vfs) { - return correctResourcePath("textures", resPath, vfs); + return correctResourcePath({ { "textures", "bookart" } }, resPath, vfs); } std::string Misc::ResourceHelpers::correctIconPath(std::string_view resPath, const VFS::Manager* vfs) { - return correctResourcePath("icons", resPath, vfs); + return correctResourcePath({ { "icons" } }, resPath, vfs); } std::string Misc::ResourceHelpers::correctBookartPath(std::string_view resPath, const VFS::Manager* vfs) { - return correctResourcePath("bookart", resPath, vfs); + return correctResourcePath({ { "bookart", "textures" } }, resPath, vfs); } std::string Misc::ResourceHelpers::correctBookartPath( @@ -159,6 +173,11 @@ std::string Misc::ResourceHelpers::correctActorModelPath(const std::string& resP return mdlname; } +std::string Misc::ResourceHelpers::correctMaterialPath(std::string_view resPath, const VFS::Manager* vfs) +{ + return correctResourcePath({ { "materials" } }, resPath, vfs); +} + std::string Misc::ResourceHelpers::correctMeshPath(std::string_view resPath) { std::string res = "meshes\\"; @@ -166,14 +185,16 @@ std::string Misc::ResourceHelpers::correctMeshPath(std::string_view resPath) return res; } -std::string Misc::ResourceHelpers::correctSoundPath(const std::string& resPath) +VFS::Path::Normalized Misc::ResourceHelpers::correctSoundPath(VFS::Path::NormalizedView resPath) { - return "sound\\" + resPath; + static constexpr VFS::Path::NormalizedView prefix("sound"); + return prefix / resPath; } -std::string Misc::ResourceHelpers::correctMusicPath(const std::string& resPath) +VFS::Path::Normalized Misc::ResourceHelpers::correctMusicPath(VFS::Path::NormalizedView resPath) { - return "music\\" + resPath; + static constexpr VFS::Path::NormalizedView prefix("music"); + return prefix / resPath; } std::string_view Misc::ResourceHelpers::meshPathForESM3(std::string_view resPath) @@ -187,17 +208,17 @@ std::string_view Misc::ResourceHelpers::meshPathForESM3(std::string_view resPath return resPath.substr(prefix.size() + 1); } -std::string Misc::ResourceHelpers::correctSoundPath(std::string_view resPath, const VFS::Manager* vfs) +VFS::Path::Normalized Misc::ResourceHelpers::correctSoundPath( + VFS::Path::NormalizedView resPath, const VFS::Manager& vfs) { // Workaround: Bethesda at some point converted some of the files to mp3, but the references were kept as .wav. - if (!vfs->exists(resPath)) + if (!vfs.exists(resPath)) { - std::string sound{ resPath }; - changeExtension(sound, ".mp3"); - VFS::Path::normalizeFilenameInPlace(sound); + VFS::Path::Normalized sound(resPath); + sound.changeExtension("mp3"); return sound; } - return VFS::Path::normalizeFilename(resPath); + return VFS::Path::Normalized(resPath); } bool Misc::ResourceHelpers::isHiddenMarker(const ESM::RefId& id) diff --git a/components/misc/resourcehelpers.hpp b/components/misc/resourcehelpers.hpp index f2b576813b..069d921fa9 100644 --- a/components/misc/resourcehelpers.hpp +++ b/components/misc/resourcehelpers.hpp @@ -1,6 +1,9 @@ #ifndef MISC_RESOURCEHELPERS_H #define MISC_RESOURCEHELPERS_H +#include + +#include #include #include @@ -23,7 +26,7 @@ namespace Misc { bool changeExtensionToDds(std::string& path); std::string correctResourcePath( - std::string_view topLevelDirectory, std::string_view resPath, const VFS::Manager* vfs); + std::span topLevelDirectories, std::string_view resPath, const VFS::Manager* vfs); std::string correctTexturePath(std::string_view resPath, const VFS::Manager* vfs); std::string correctIconPath(std::string_view resPath, const VFS::Manager* vfs); std::string correctBookartPath(std::string_view resPath, const VFS::Manager* vfs); @@ -31,20 +34,21 @@ namespace Misc /// Use "xfoo.nif" instead of "foo.nif" if "xfoo.kf" is available /// Note that if "xfoo.nif" is actually unavailable, we can't fall back to "foo.nif". :( std::string correctActorModelPath(const std::string& resPath, const VFS::Manager* vfs); + std::string correctMaterialPath(std::string_view resPath, const VFS::Manager* vfs); // Adds "meshes\\". std::string correctMeshPath(std::string_view resPath); - // Adds "sound\\". - std::string correctSoundPath(const std::string& resPath); + // Prepends "sound/". + VFS::Path::Normalized correctSoundPath(VFS::Path::NormalizedView resPath); - // Adds "music\\". - std::string correctMusicPath(const std::string& resPath); + // Prepends "music/". + VFS::Path::Normalized correctMusicPath(VFS::Path::NormalizedView resPath); // Removes "meshes\\". std::string_view meshPathForESM3(std::string_view resPath); - std::string correctSoundPath(std::string_view resPath, const VFS::Manager* vfs); + VFS::Path::Normalized correctSoundPath(VFS::Path::NormalizedView resPath, const VFS::Manager& vfs); /// marker objects that have a hardcoded function in the game logic, should be hidden from the player bool isHiddenMarker(const ESM::RefId& id); diff --git a/components/misc/strings/algorithm.hpp b/components/misc/strings/algorithm.hpp index 28bc696cd3..18f72104bd 100644 --- a/components/misc/strings/algorithm.hpp +++ b/components/misc/strings/algorithm.hpp @@ -15,6 +15,13 @@ namespace Misc::StringUtils bool operator()(char x, char y) const { return toLower(x) < toLower(y); } }; + inline std::string underscoresToSpaces(const std::string_view oldName) + { + std::string newName(oldName); + std::replace(newName.begin(), newName.end(), '_', ' '); + return newName; + } + inline bool ciLess(std::string_view x, std::string_view y) { return std::lexicographical_compare(x.begin(), x.end(), y.begin(), y.end(), CiCharLess()); diff --git a/components/misc/utf8stream.hpp b/components/misc/utf8stream.hpp index 271376834d..5eb5f99b84 100644 --- a/components/misc/utf8stream.hpp +++ b/components/misc/utf8stream.hpp @@ -75,7 +75,7 @@ public: return std::make_pair(chr, cur); } - int octets; + std::size_t octets; UnicodeChar chr; std::tie(octets, chr) = getOctetCount(*cur++); diff --git a/components/nif/exception.hpp b/components/nif/exception.hpp index 15f0e76d70..b123d6dc4f 100644 --- a/components/nif/exception.hpp +++ b/components/nif/exception.hpp @@ -1,18 +1,21 @@ #ifndef OPENMW_COMPONENTS_NIF_EXCEPTION_HPP #define OPENMW_COMPONENTS_NIF_EXCEPTION_HPP -#include #include #include -#include - namespace Nif { struct Exception : std::runtime_error { - explicit Exception(const std::string& message, const std::filesystem::path& path) - : std::runtime_error("NIFFile Error: " + message + " when reading " + Files::pathToUnicodeString(path)) + explicit Exception(std::string_view message, std::string_view path) + : std::runtime_error([&] { + std::string result = "NIFFile Error: "; + result += message; + result += " when reading "; + result += path; + return result; + }()) { } }; diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index 993e9b7eea..6bff30a225 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -4,7 +4,7 @@ #define OPENMW_COMPONENTS_NIF_NIFFILE_HPP #include -#include +#include #include #include @@ -45,7 +45,7 @@ namespace Nif std::uint32_t mBethVersion = 0; /// File name, used for error messages and opening the file - std::filesystem::path mPath; + std::string mPath; std::string mHash; /// Record list @@ -56,7 +56,7 @@ namespace Nif bool mUseSkinning = false; - explicit NIFFile(const std::filesystem::path& path) + explicit NIFFile(std::string_view path) : mPath(path) { } @@ -77,7 +77,7 @@ namespace Nif std::size_t numRoots() const { return mFile->mRoots.size(); } /// Get the name of the file - const std::filesystem::path& getFilename() const { return mFile->mPath; } + const std::string& getFilename() const { return mFile->mPath; } const std::string& getHash() const { return mFile->mHash; } @@ -104,7 +104,7 @@ namespace Nif std::uint32_t& mBethVersion; /// File name, used for error messages and opening the file - std::filesystem::path& mFilename; + std::string_view mFilename; std::string& mHash; /// Record list @@ -144,7 +144,7 @@ namespace Nif void setUseSkinning(bool skinning); /// Get the name of the file - std::filesystem::path getFilename() const { return mFilename; } + std::string_view getFilename() const { return mFilename; } /// Get the version of the NIF format used std::uint32_t getVersion() const { return mVersion; } diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 0737d0a165..a57e8b3c06 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -50,7 +50,7 @@ namespace NifBullet if (node) roots.emplace_back(node); } - mShape->mFileName = Files::pathToUnicodeString(nif.getFilename()); + mShape->mFileName = nif.getFilename(); if (roots.empty()) { warn("Found no root nodes in NIF file " + mShape->mFileName); diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 8d46b0f751..05a8378c11 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include // particle @@ -42,6 +43,7 @@ #include #include +#include #include #include #include @@ -247,6 +249,8 @@ namespace NifOsg } std::filesystem::path mFilename; unsigned int mVersion, mUserVersion, mBethVersion; + Resource::BgsmFileManager* mMaterialManager{ nullptr }; + Resource::ImageManager* mImageManager{ nullptr }; size_t mFirstRootTextureIndex{ ~0u }; bool mFoundFirstRootTexturingProperty = false; @@ -339,7 +343,6 @@ namespace NifOsg struct HandleNodeArgs { unsigned int mNifVersion; - Resource::ImageManager* mImageManager; SceneUtil::TextKeyMap* mTextKeys; std::vector mBoundTextures = {}; int mAnimFlags = 0; @@ -349,7 +352,7 @@ namespace NifOsg osg::Node* mRootNode = nullptr; }; - osg::ref_ptr load(Nif::FileView nif, Resource::ImageManager* imageManager) + osg::ref_ptr load(Nif::FileView nif) { const size_t numRoots = nif.numRoots(); std::vector roots; @@ -371,10 +374,8 @@ namespace NifOsg created->setDataVariance(osg::Object::STATIC); for (const Nif::NiAVObject* root : roots) { - auto node = handleNode(root, nullptr, nullptr, - { .mNifVersion = nif.getVersion(), - .mImageManager = imageManager, - .mTextKeys = &textkeys->mTextKeys }); + auto node = handleNode( + root, nullptr, nullptr, { .mNifVersion = nif.getVersion(), .mTextKeys = &textkeys->mTextKeys }); created->addChild(node); } if (mHasNightDayLabel) @@ -405,8 +406,7 @@ namespace NifOsg } void applyNodeProperties(const Nif::NiAVObject* nifNode, osg::Node* applyTo, - SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, - std::vector& boundTextures, int animflags) + SceneUtil::CompositeStateSetUpdater* composite, std::vector& boundTextures, int animflags) { bool hasStencilProperty = false; @@ -444,8 +444,7 @@ namespace NifOsg if (property.getPtr()->recIndex == mFirstRootTextureIndex) applyTo->setUserValue("overrideFx", 1); } - handleProperty(property.getPtr(), applyTo, composite, imageManager, boundTextures, animflags, - hasStencilProperty); + handleProperty(property.getPtr(), applyTo, composite, boundTextures, animflags, hasStencilProperty); } } @@ -457,8 +456,7 @@ namespace NifOsg shaderprop = static_cast(nifNode)->mShaderProperty; if (!shaderprop.empty()) - handleProperty(shaderprop.getPtr(), applyTo, composite, imageManager, boundTextures, animflags, - hasStencilProperty); + handleProperty(shaderprop.getPtr(), applyTo, composite, boundTextures, animflags, hasStencilProperty); } static void setupController(const Nif::NiTimeController* ctrl, SceneUtil::Controller* toSetup, int animflags) @@ -522,32 +520,21 @@ namespace NifOsg sequenceNode->setMode(osg::Sequence::START); } - osg::ref_ptr handleSourceTexture( - const Nif::NiSourceTexture* st, Resource::ImageManager* imageManager) + osg::ref_ptr handleSourceTexture(const Nif::NiSourceTexture* st) const { - if (!st) - return nullptr; - - osg::ref_ptr image; - if (st->mExternal) + if (st) { - std::string filename = Misc::ResourceHelpers::correctTexturePath(st->mFile, imageManager->getVFS()); - image = imageManager->getImage(filename); - } - else if (!st->mData.empty()) - { - image = handleInternalTexture(st->mData.getPtr()); + if (st->mExternal) + return getTextureImage(st->mFile); + + if (!st->mData.empty()) + return handleInternalTexture(st->mData.getPtr()); } - return image; - } - void handleTextureWrapping(osg::Texture2D* texture, bool wrapS, bool wrapT) - { - texture->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); - texture->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); + return nullptr; } - bool handleEffect(const Nif::NiAVObject* nifNode, osg::StateSet* stateset, Resource::ImageManager* imageManager) + bool handleEffect(const Nif::NiAVObject* nifNode, osg::StateSet* stateset) { if (nifNode->recType != Nif::RC_NiTextureEffect) { @@ -590,16 +577,12 @@ namespace NifOsg return false; } - osg::ref_ptr image(handleSourceTexture(textureEffect->mTexture.getPtr(), imageManager)); - osg::ref_ptr texture2d(new osg::Texture2D(image)); - if (image) - texture2d->setTextureSize(image->s(), image->t()); - texture2d->setName("envMap"); - handleTextureWrapping(texture2d, textureEffect->wrapS(), textureEffect->wrapT()); - - int texUnit = 3; // FIXME - - stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); + const unsigned int uvSet = 0; + const unsigned int texUnit = 3; // FIXME + std::vector boundTextures; + boundTextures.resize(3); // Dummy vector for attachNiSourceTexture + attachNiSourceTexture("envMap", textureEffect->mTexture.getPtr(), textureEffect->wrapS(), + textureEffect->wrapT(), uvSet, stateset, boundTextures); stateset->setTextureAttributeAndModes(texUnit, texGen, osg::StateAttribute::ON); stateset->setTextureAttributeAndModes(texUnit, createEmissiveTexEnv(), osg::StateAttribute::ON); @@ -761,7 +744,7 @@ namespace NifOsg osg::ref_ptr composite = new SceneUtil::CompositeStateSetUpdater; - applyNodeProperties(nifNode, node, composite, args.mImageManager, args.mBoundTextures, args.mAnimFlags); + applyNodeProperties(nifNode, node, composite, args.mBoundTextures, args.mAnimFlags); const bool isNiGeometry = isTypeNiGeometry(nifNode->recType); const bool isBSGeometry = isTypeBSGeometry(nifNode->recType); @@ -769,7 +752,7 @@ namespace NifOsg if (isGeometry && !args.mSkipMeshes) { - bool skip; + bool skip = false; if (args.mNifVersion <= Nif::NIFFile::NIFVersion::VER_MW) { skip = (args.mHasMarkers && Misc::StringUtils::ciStartsWith(nifNode->mName, "tri editormarker")) @@ -777,7 +760,11 @@ namespace NifOsg || Misc::StringUtils::ciStartsWith(nifNode->mName, "tri shadow"); } else - skip = args.mHasMarkers && Misc::StringUtils::ciStartsWith(nifNode->mName, "EditorMarker"); + { + if (args.mHasMarkers) + skip = Misc::StringUtils::ciStartsWith(nifNode->mName, "EditorMarker") + || Misc::StringUtils::ciStartsWith(nifNode->mName, "VisibilityEditorMarker"); + } if (!skip) { if (isNiGeometry) @@ -859,7 +846,7 @@ namespace NifOsg if (!effect.empty()) { osg::ref_ptr effectStateSet = new osg::StateSet; - if (handleEffect(effect.getPtr(), effectStateSet, args.mImageManager)) + if (handleEffect(effect.getPtr(), effectStateSet)) for (unsigned int i = 0; i < currentNode->getNumChildren(); ++i) currentNode->getChild(i)->getOrCreateStateSet()->merge(*effectStateSet); } @@ -1025,9 +1012,56 @@ namespace NifOsg } } + osg::ref_ptr getTextureImage(std::string_view path) const + { + if (!mImageManager) + return nullptr; + + std::string filename = Misc::ResourceHelpers::correctTexturePath(path, mImageManager->getVFS()); + return mImageManager->getImage(filename); + } + + osg::ref_ptr attachTexture(const std::string& name, osg::ref_ptr image, bool wrapS, + bool wrapT, unsigned int uvSet, osg::StateSet* stateset, std::vector& boundTextures) const + { + osg::ref_ptr texture2d = new osg::Texture2D(image); + if (image) + texture2d->setTextureSize(image->s(), image->t()); + texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); + texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); + unsigned int texUnit = boundTextures.size(); + if (stateset) + stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); + texture2d->setName(name); + boundTextures.emplace_back(uvSet); + return texture2d; + } + + osg::ref_ptr attachExternalTexture(const std::string& name, const std::string& path, bool wrapS, + bool wrapT, unsigned int uvSet, osg::StateSet* stateset, std::vector& boundTextures) const + { + return attachTexture(name, getTextureImage(path), wrapS, wrapT, uvSet, stateset, boundTextures); + } + + osg::ref_ptr attachNiSourceTexture(const std::string& name, const Nif::NiSourceTexture* st, + bool wrapS, bool wrapT, unsigned int uvSet, osg::StateSet* stateset, + std::vector& boundTextures) const + { + return attachTexture(name, handleSourceTexture(st), wrapS, wrapT, uvSet, stateset, boundTextures); + } + + static void clearBoundTextures(osg::StateSet* stateset, std::vector& boundTextures) + { + if (!boundTextures.empty()) + { + for (unsigned int i = 0; i < boundTextures.size(); ++i) + stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); + boundTextures.clear(); + } + } + void handleTextureControllers(const Nif::NiProperty* texProperty, - SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, - osg::StateSet* stateset, int animflags) + SceneUtil::CompositeStateSetUpdater* composite, osg::StateSet* stateset, int animflags) { for (Nif::NiTimeControllerPtr ctrl = texProperty->mController; !ctrl.empty(); ctrl = ctrl->mNext) { @@ -1056,17 +1090,16 @@ namespace NifOsg wrapT = inherit->getWrap(osg::Texture2D::WRAP_T); } + const unsigned int uvSet = 0; + std::vector boundTextures; // Dummy list for attachTexture for (const auto& source : flipctrl->mSources) { if (source.empty()) continue; - osg::ref_ptr image(handleSourceTexture(source.getPtr(), imageManager)); - osg::ref_ptr texture(new osg::Texture2D(image)); - if (image) - texture->setTextureSize(image->s(), image->t()); - texture->setWrap(osg::Texture::WRAP_S, wrapS); - texture->setWrap(osg::Texture::WRAP_T, wrapT); + // NB: not changing the stateset + osg::ref_ptr texture + = attachNiSourceTexture({}, source.getPtr(), wrapS, wrapT, uvSet, nullptr, boundTextures); textures.push_back(texture); } osg::ref_ptr callback(new FlipController(flipctrl, textures)); @@ -1811,7 +1844,7 @@ namespace NifOsg } } - osg::ref_ptr handleInternalTexture(const Nif::NiPixelData* pixelData) + osg::ref_ptr handleInternalTexture(const Nif::NiPixelData* pixelData) const { if (pixelData->mMipmaps.empty()) return nullptr; @@ -1946,7 +1979,7 @@ namespace NifOsg return image; } - osg::ref_ptr createEmissiveTexEnv() + static osg::ref_ptr createEmissiveTexEnv() { osg::ref_ptr texEnv(new osg::TexEnvCombine); // Sum the previous colour and the emissive colour. @@ -1977,33 +2010,42 @@ namespace NifOsg void handleTextureProperty(const Nif::NiTexturingProperty* texprop, const std::string& nodeName, osg::StateSet* stateset, SceneUtil::CompositeStateSetUpdater* composite, - Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags) + std::vector& boundTextures, int animflags) { - if (!boundTextures.empty()) - { - // overriding a parent NiTexturingProperty, so remove what was previously bound - for (unsigned int i = 0; i < boundTextures.size(); ++i) - stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); - boundTextures.clear(); - } + // overriding a parent NiTexturingProperty, so remove what was previously bound + clearBoundTextures(stateset, boundTextures); // If this loop is changed such that the base texture isn't guaranteed to end up in texture unit 0, the // shadow casting shader will need to be updated accordingly. for (size_t i = 0; i < texprop->mTextures.size(); ++i) { - if (texprop->mTextures[i].mEnabled - || (i == Nif::NiTexturingProperty::BaseTexture && !texprop->mController.empty())) + const Nif::NiTexturingProperty::Texture& tex = texprop->mTextures[i]; + if (tex.mEnabled || (i == Nif::NiTexturingProperty::BaseTexture && !texprop->mController.empty())) { + std::string textureName; switch (i) { // These are handled later on case Nif::NiTexturingProperty::BaseTexture: + textureName = "diffuseMap"; + break; case Nif::NiTexturingProperty::GlowTexture: + textureName = "glowMap"; + break; case Nif::NiTexturingProperty::DarkTexture: + textureName = "darkMap"; + break; case Nif::NiTexturingProperty::BumpTexture: + textureName = "bumpMap"; + break; case Nif::NiTexturingProperty::DetailTexture: + textureName = "detailMap"; + break; case Nif::NiTexturingProperty::DecalTexture: + textureName = "decalMap"; + break; case Nif::NiTexturingProperty::GlossTexture: + textureName = "glossMap"; break; default: { @@ -2013,12 +2055,9 @@ namespace NifOsg } } - unsigned int uvSet = 0; - // create a new texture, will later attempt to share using the SharedStateManager - osg::ref_ptr texture2d; - if (texprop->mTextures[i].mEnabled) + const unsigned int texUnit = boundTextures.size(); + if (tex.mEnabled) { - const Nif::NiTexturingProperty::Texture& tex = texprop->mTextures[i]; if (tex.mSourceTexture.empty() && texprop->mController.empty()) { if (i == 0) @@ -2028,32 +2067,18 @@ namespace NifOsg } if (!tex.mSourceTexture.empty()) - { - const Nif::NiSourceTexture* st = tex.mSourceTexture.getPtr(); - osg::ref_ptr image = handleSourceTexture(st, imageManager); - texture2d = new osg::Texture2D(image); - if (image) - texture2d->setTextureSize(image->s(), image->t()); - } + attachNiSourceTexture(textureName, tex.mSourceTexture.getPtr(), tex.wrapS(), tex.wrapT(), + tex.mUVSet, stateset, boundTextures); else - texture2d = new osg::Texture2D; - - handleTextureWrapping(texture2d, tex.wrapS(), tex.wrapT()); - - uvSet = tex.mUVSet; + attachTexture( + textureName, nullptr, tex.wrapS(), tex.wrapT(), tex.mUVSet, stateset, boundTextures); } else { // Texture only comes from NiFlipController, so tex is ignored, set defaults - texture2d = new osg::Texture2D; - handleTextureWrapping(texture2d, true, true); - uvSet = 0; + attachTexture(textureName, nullptr, true, true, 0, stateset, boundTextures); } - unsigned int texUnit = boundTextures.size(); - - stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); - if (i == Nif::NiTexturingProperty::GlowTexture) { stateset->setTextureAttributeAndModes(texUnit, createEmissiveTexEnv(), osg::StateAttribute::ON); @@ -2121,51 +2146,165 @@ namespace NifOsg texEnv->setOperand0_Alpha(osg::TexEnvCombine::SRC_ALPHA); stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); } + } + } + handleTextureControllers(texprop, composite, stateset, animflags); + } - switch (i) - { - case Nif::NiTexturingProperty::BaseTexture: - texture2d->setName("diffuseMap"); - break; - case Nif::NiTexturingProperty::BumpTexture: - texture2d->setName("bumpMap"); - break; - case Nif::NiTexturingProperty::GlowTexture: - texture2d->setName("emissiveMap"); - break; - case Nif::NiTexturingProperty::DarkTexture: - texture2d->setName("darkMap"); - break; - case Nif::NiTexturingProperty::DetailTexture: - texture2d->setName("detailMap"); - break; - case Nif::NiTexturingProperty::DecalTexture: - texture2d->setName("decalMap"); - break; - case Nif::NiTexturingProperty::GlossTexture: - texture2d->setName("glossMap"); - break; - default: - break; - } + static Bgsm::MaterialFilePtr getShaderMaterial( + std::string_view path, Resource::BgsmFileManager* materialManager) + { + if (!materialManager) + return nullptr; + + if (!Misc::StringUtils::ciEndsWith(path, ".bgem") && !Misc::StringUtils::ciEndsWith(path, ".bgsm")) + return nullptr; + + std::string normalizedPath = Misc::ResourceHelpers::correctMaterialPath(path, materialManager->getVFS()); + try + { + return materialManager->get(VFS::Path::Normalized(normalizedPath)); + } + catch (std::exception& e) + { + Log(Debug::Error) << "Failed to load shader material: " << e.what(); + return nullptr; + } + } + + void handleShaderMaterialNodeProperties( + Bgsm::MaterialFilePtr material, osg::StateSet* stateset, std::vector& boundTextures) + { + const unsigned int uvSet = 0; + const bool wrapS = (material->mClamp >> 1) & 0x1; + const bool wrapT = material->mClamp & 0x1; + if (material->mShaderType == Bgsm::ShaderType::Lighting) + { + const Bgsm::BGSMFile* bgsm = static_cast(material.get()); + + if (!bgsm->mDiffuseMap.empty()) + attachExternalTexture( + "diffuseMap", bgsm->mDiffuseMap, wrapS, wrapT, uvSet, stateset, boundTextures); + + if (!bgsm->mNormalMap.empty()) + attachExternalTexture("normalMap", bgsm->mNormalMap, wrapS, wrapT, uvSet, stateset, boundTextures); + + if (bgsm->mGlowMapEnabled && !bgsm->mGlowMap.empty()) + attachExternalTexture("emissiveMap", bgsm->mGlowMap, wrapS, wrapT, uvSet, stateset, boundTextures); + + if (bgsm->mTree) + stateset->addUniform(new osg::Uniform("useTreeAnim", true)); + } + else if (material->mShaderType == Bgsm::ShaderType::Effect) + { + const Bgsm::BGEMFile* bgem = static_cast(material.get()); + + if (!bgem->mBaseMap.empty()) + attachExternalTexture("diffuseMap", bgem->mBaseMap, wrapS, wrapT, uvSet, stateset, boundTextures); + + bool useFalloff = bgem->mFalloff; + stateset->addUniform(new osg::Uniform("useFalloff", useFalloff)); + if (useFalloff) + stateset->addUniform(new osg::Uniform("falloffParams", bgem->mFalloffParams)); + } + + if (material->mTwoSided) + stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + handleDepthFlags(stateset, material->mDepthTest, material->mDepthWrite); + } + + void handleDecal(bool enabled, bool hasSortAlpha, osg::Node& node) + { + if (!enabled) + return; + osg::ref_ptr stateset = node.getOrCreateStateSet(); + osg::ref_ptr polygonOffset(new osg::PolygonOffset); + polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f); + polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f); + polygonOffset = shareAttribute(polygonOffset); + stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); + if (!mPushedSorter && !hasSortAlpha) + stateset->setRenderBinDetails(1, "SORT_BACK_TO_FRONT"); + } + + void handleAlphaTesting( + bool enabled, osg::AlphaFunc::ComparisonFunction function, int threshold, osg::Node& node) + { + if (enabled) + { + osg::ref_ptr alphaFunc(new osg::AlphaFunc(function, threshold / 255.f)); + alphaFunc = shareAttribute(alphaFunc); + node.getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); + } + else if (osg::StateSet* stateset = node.getStateSet()) + { + stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC); + stateset->removeMode(GL_ALPHA_TEST); + } + } + + void handleAlphaBlending( + bool enabled, int sourceMode, int destMode, bool sort, bool& hasSortAlpha, osg::Node& node) + { + if (enabled) + { + osg::ref_ptr stateset = node.getOrCreateStateSet(); + osg::ref_ptr blendFunc( + new osg::BlendFunc(getBlendMode(sourceMode), getBlendMode(destMode))); + // on AMD hardware, alpha still seems to be stored with an RGBA framebuffer with OpenGL. + // This might be mandated by the OpenGL 2.1 specification section 2.14.9, or might be a bug. + // Either way, D3D8.1 doesn't do that, so adapt the destination factor. + if (blendFunc->getDestination() == GL_DST_ALPHA) + blendFunc->setDestination(GL_ONE); + blendFunc = shareAttribute(blendFunc); + stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); - boundTextures.push_back(uvSet); + if (sort) + { + hasSortAlpha = true; + if (!mPushedSorter) + stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + } + else if (!mPushedSorter) + { + stateset->setRenderBinToInherit(); } } - handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + else if (osg::ref_ptr stateset = node.getStateSet()) + { + stateset->removeAttribute(osg::StateAttribute::BLENDFUNC); + stateset->removeMode(GL_BLEND); + if (!mPushedSorter) + stateset->setRenderBinToInherit(); + } } - void handleTextureSet(const Nif::BSShaderTextureSet* textureSet, unsigned int clamp, - const std::string& nodeName, osg::StateSet* stateset, Resource::ImageManager* imageManager, - std::vector& boundTextures) + void handleShaderMaterialDrawableProperties( + Bgsm::MaterialFilePtr shaderMat, osg::ref_ptr mat, osg::Node& node, bool& hasSortAlpha) { - if (!boundTextures.empty()) + mat->setAlpha(osg::Material::FRONT_AND_BACK, shaderMat->mTransparency); + handleAlphaTesting(shaderMat->mAlphaTest, osg::AlphaFunc::GREATER, shaderMat->mAlphaTestThreshold, node); + handleAlphaBlending(shaderMat->mAlphaBlend, shaderMat->mSourceBlendMode, shaderMat->mDestinationBlendMode, + true, hasSortAlpha, node); + handleDecal(shaderMat->mDecal, hasSortAlpha, node); + if (shaderMat->mShaderType == Bgsm::ShaderType::Lighting) { - for (unsigned int i = 0; i < boundTextures.size(); ++i) - stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); - boundTextures.clear(); + auto bgsm = static_cast(shaderMat.get()); + mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(bgsm->mEmittanceColor, 1.f)); + mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(bgsm->mSpecularColor, 1.f)); + } + else if (shaderMat->mShaderType == Bgsm::ShaderType::Effect) + { + auto bgem = static_cast(shaderMat.get()); + mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(bgem->mEmittanceColor, 1.f)); + if (bgem->mSoft) + SceneUtil::setupSoftEffect(node, bgem->mSoftDepth, true, bgem->mSoftDepth); } + } + void handleTextureSet(const Nif::BSShaderTextureSet* textureSet, bool wrapS, bool wrapT, + const std::string& nodeName, osg::StateSet* stateset, std::vector& boundTextures) + { const unsigned int uvSet = 0; for (size_t i = 0; i < textureSet->mTextures.size(); ++i) @@ -2175,8 +2314,16 @@ namespace NifOsg switch (static_cast(i)) { case Nif::BSShaderTextureSet::TextureType::Base: + attachExternalTexture( + "diffuseMap", textureSet->mTextures[i], wrapS, wrapT, uvSet, stateset, boundTextures); + break; case Nif::BSShaderTextureSet::TextureType::Normal: + attachExternalTexture( + "normalMap", textureSet->mTextures[i], wrapS, wrapT, uvSet, stateset, boundTextures); + break; case Nif::BSShaderTextureSet::TextureType::Glow: + attachExternalTexture( + "emissiveMap", textureSet->mTextures[i], wrapS, wrapT, uvSet, stateset, boundTextures); break; default: { @@ -2185,31 +2332,6 @@ namespace NifOsg continue; } } - std::string filename - = Misc::ResourceHelpers::correctTexturePath(textureSet->mTextures[i], imageManager->getVFS()); - osg::ref_ptr image = imageManager->getImage(filename); - osg::ref_ptr texture2d = new osg::Texture2D(image); - if (image) - texture2d->setTextureSize(image->s(), image->t()); - handleTextureWrapping(texture2d, (clamp >> 1) & 0x1, clamp & 0x1); - unsigned int texUnit = boundTextures.size(); - stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); - // BSShaderTextureSet presence means there's no need for FFP support for the affected node - switch (static_cast(i)) - { - case Nif::BSShaderTextureSet::TextureType::Base: - texture2d->setName("diffuseMap"); - break; - case Nif::BSShaderTextureSet::TextureType::Normal: - texture2d->setName("normalMap"); - break; - case Nif::BSShaderTextureSet::TextureType::Glow: - texture2d->setName("emissiveMap"); - break; - default: - break; - } - boundTextures.emplace_back(uvSet); } } @@ -2269,8 +2391,8 @@ namespace NifOsg } void handleProperty(const Nif::NiProperty* property, osg::Node* node, - SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, - std::vector& boundTextures, int animflags, bool hasStencilProperty) + SceneUtil::CompositeStateSetUpdater* composite, std::vector& boundTextures, int animflags, + bool hasStencilProperty) { switch (property->recType) { @@ -2352,8 +2474,7 @@ namespace NifOsg { const Nif::NiTexturingProperty* texprop = static_cast(property); osg::StateSet* stateset = node->getOrCreateStateSet(); - handleTextureProperty( - texprop, node->getName(), stateset, composite, imageManager, boundTextures, animflags); + handleTextureProperty(texprop, node->getName(), stateset, composite, boundTextures, animflags); node->setUserValue("applyMode", static_cast(texprop->mApplyMode)); break; } @@ -2364,13 +2485,13 @@ namespace NifOsg node->setUserValue("shaderPrefix", std::string(getBSShaderPrefix(texprop->mType))); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); + clearBoundTextures(stateset, boundTextures); + const bool wrapS = (texprop->mClamp >> 1) & 0x1; + const bool wrapT = texprop->mClamp & 0x1; if (!texprop->mTextureSet.empty()) - { - auto textureSet = texprop->mTextureSet.getPtr(); handleTextureSet( - textureSet, texprop->mClamp, node->getName(), stateset, imageManager, boundTextures); - } - handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + texprop->mTextureSet.getPtr(), wrapS, wrapT, node->getName(), stateset, boundTextures); + handleTextureControllers(texprop, composite, stateset, animflags); if (texprop->refraction()) SceneUtil::setupDistortion(*node, texprop->mRefraction.mStrength); break; @@ -2383,34 +2504,20 @@ namespace NifOsg node->setUserValue("shaderPrefix", std::string(getBSShaderPrefix(texprop->mType))); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); + clearBoundTextures(stateset, boundTextures); if (!texprop->mFilename.empty()) { - if (!boundTextures.empty()) - { - for (unsigned int i = 0; i < boundTextures.size(); ++i) - stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); - boundTextures.clear(); - } - std::string filename - = Misc::ResourceHelpers::correctTexturePath(texprop->mFilename, imageManager->getVFS()); - osg::ref_ptr image = imageManager->getImage(filename); - osg::ref_ptr texture2d = new osg::Texture2D(image); - texture2d->setName("diffuseMap"); - if (image) - texture2d->setTextureSize(image->s(), image->t()); - handleTextureWrapping(texture2d, texprop->wrapS(), texprop->wrapT()); - const unsigned int texUnit = 0; const unsigned int uvSet = 0; - stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); - boundTextures.push_back(uvSet); - if (mBethVersion >= 27) - { - useFalloff = true; - stateset->addUniform(new osg::Uniform("falloffParams", texprop->mFalloffParams)); - } + attachExternalTexture("diffuseMap", texprop->mFilename, texprop->wrapS(), texprop->wrapT(), + uvSet, stateset, boundTextures); + } + if (mBethVersion >= 27) + { + useFalloff = true; + stateset->addUniform(new osg::Uniform("falloffParams", texprop->mFalloffParams)); } stateset->addUniform(new osg::Uniform("useFalloff", useFalloff)); - handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + handleTextureControllers(texprop, composite, stateset, animflags); handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite()); break; } @@ -2421,10 +2528,18 @@ namespace NifOsg node->setUserValue("shaderPrefix", std::string(getBSLightingShaderPrefix(texprop->mType))); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); + clearBoundTextures(stateset, boundTextures); + if (Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName, mMaterialManager)) + { + handleShaderMaterialNodeProperties(material, stateset, boundTextures); + break; + } + const bool wrapS = (texprop->mClamp >> 1) & 0x1; + const bool wrapT = texprop->mClamp & 0x1; if (!texprop->mTextureSet.empty()) - handleTextureSet(texprop->mTextureSet.getPtr(), texprop->mClamp, node->getName(), stateset, - imageManager, boundTextures); - handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + handleTextureSet( + texprop->mTextureSet.getPtr(), wrapS, wrapT, node->getName(), stateset, boundTextures); + handleTextureControllers(texprop, composite, stateset, animflags); if (texprop->doubleSided()) stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); if (texprop->treeAnim()) @@ -2442,27 +2557,20 @@ namespace NifOsg node->setUserValue("shaderPrefix", std::string("bs/nolighting")); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); + clearBoundTextures(stateset, boundTextures); + if (Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName, mMaterialManager)) + { + handleShaderMaterialNodeProperties(material, stateset, boundTextures); + break; + } if (!texprop->mSourceTexture.empty()) { - if (!boundTextures.empty()) - { - for (unsigned int i = 0; i < boundTextures.size(); ++i) - stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); - boundTextures.clear(); - } - std::string filename = Misc::ResourceHelpers::correctTexturePath( - texprop->mSourceTexture, imageManager->getVFS()); - osg::ref_ptr image = imageManager->getImage(filename); - osg::ref_ptr texture2d = new osg::Texture2D(image); - texture2d->setName("diffuseMap"); - if (image) - texture2d->setTextureSize(image->s(), image->t()); - handleTextureWrapping(texture2d, (texprop->mClamp >> 1) & 0x1, texprop->mClamp & 0x1); - const unsigned int texUnit = 0; const unsigned int uvSet = 0; - stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); - boundTextures.push_back(uvSet); - + const bool wrapS = (texprop->mClamp >> 1) & 0x1; + const bool wrapT = texprop->mClamp & 0x1; + unsigned int texUnit = boundTextures.size(); + attachExternalTexture( + "diffuseMap", texprop->mSourceTexture, wrapS, wrapT, uvSet, stateset, boundTextures); { osg::ref_ptr texMat(new osg::TexMat); // This handles 20.2.0.7 UV settings like 4.0.0.2 UV settings (see NifOsg::UVController) @@ -2484,7 +2592,7 @@ namespace NifOsg stateset->addUniform(new osg::Uniform("useFalloff", useFalloff)); if (useFalloff) stateset->addUniform(new osg::Uniform("falloffParams", texprop->mFalloffParams)); - handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + handleTextureControllers(texprop, composite, stateset, animflags); if (texprop->doubleSided()) stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite()); @@ -2566,12 +2674,9 @@ namespace NifOsg bool hasMatCtrl = false; bool hasSortAlpha = false; - osg::StateSet* blendFuncStateSet = nullptr; - auto setBin_Transparent = [](osg::StateSet* ss) { ss->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); }; auto setBin_BackToFront = [](osg::StateSet* ss) { ss->setRenderBinDetails(0, "SORT_BACK_TO_FRONT"); }; auto setBin_Traversal = [](osg::StateSet* ss) { ss->setRenderBinDetails(2, "TraversalOrderBin"); }; - auto setBin_Inherit = [](osg::StateSet* ss) { ss->setRenderBinToInherit(); }; auto lightmode = Nif::NiVertexColorProperty::LightMode::LightMode_EmiAmbDif; float emissiveMult = 1.f; @@ -2657,52 +2762,10 @@ namespace NifOsg case Nif::RC_NiAlphaProperty: { const Nif::NiAlphaProperty* alphaprop = static_cast(property); - if (alphaprop->useAlphaBlending()) - { - osg::ref_ptr blendFunc( - new osg::BlendFunc(getBlendMode(alphaprop->sourceBlendMode()), - getBlendMode(alphaprop->destinationBlendMode()))); - // on AMD hardware, alpha still seems to be stored with an RGBA framebuffer with OpenGL. - // This might be mandated by the OpenGL 2.1 specification section 2.14.9, or might be a bug. - // Either way, D3D8.1 doesn't do that, so adapt the destination factor. - if (blendFunc->getDestination() == GL_DST_ALPHA) - blendFunc->setDestination(GL_ONE); - blendFunc = shareAttribute(blendFunc); - node->getOrCreateStateSet()->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); - - if (!alphaprop->noSorter()) - { - hasSortAlpha = true; - if (!mPushedSorter) - setBin_Transparent(node->getStateSet()); - } - else - { - if (!mPushedSorter) - setBin_Inherit(node->getStateSet()); - } - } - else if (osg::StateSet* stateset = node->getStateSet()) - { - stateset->removeAttribute(osg::StateAttribute::BLENDFUNC); - stateset->removeMode(GL_BLEND); - blendFuncStateSet = stateset; - if (!mPushedSorter) - blendFuncStateSet->setRenderBinToInherit(); - } - - if (alphaprop->useAlphaTesting()) - { - osg::ref_ptr alphaFunc(new osg::AlphaFunc( - getTestMode(alphaprop->alphaTestMode()), alphaprop->mThreshold / 255.f)); - alphaFunc = shareAttribute(alphaFunc); - node->getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); - } - else if (osg::StateSet* stateset = node->getStateSet()) - { - stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC); - stateset->removeMode(GL_ALPHA_TEST); - } + handleAlphaBlending(alphaprop->useAlphaBlending(), alphaprop->sourceBlendMode(), + alphaprop->destinationBlendMode(), !alphaprop->noSorter(), hasSortAlpha, *node); + handleAlphaTesting(alphaprop->useAlphaTesting(), getTestMode(alphaprop->alphaTestMode()), + alphaprop->mThreshold, *node); break; } case Nif::RC_BSShaderPPLightingProperty: @@ -2714,6 +2777,18 @@ namespace NifOsg case Nif::RC_BSLightingShaderProperty: { auto shaderprop = static_cast(property); + if (Bgsm::MaterialFilePtr shaderMat = getShaderMaterial(shaderprop->mName, mMaterialManager)) + { + handleShaderMaterialDrawableProperties(shaderMat, mat, *node, hasSortAlpha); + if (shaderMat->mShaderType == Bgsm::ShaderType::Lighting) + { + auto bgsm = static_cast(shaderMat.get()); + specEnabled = false; // bgsm->mSpecularEnabled; TODO: PBR specular lighting + specStrength = 1.f; // bgsm->mSpecularMult; + emissiveMult = bgsm->mEmittanceMult; + } + break; + } mat->setAlpha(osg::Material::FRONT_AND_BACK, shaderprop->mAlpha); mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(shaderprop->mEmissive, 1.f)); mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(shaderprop->mSpecular, 1.f)); @@ -2722,31 +2797,18 @@ namespace NifOsg emissiveMult = shaderprop->mEmissiveMult; specStrength = shaderprop->mSpecStrength; specEnabled = shaderprop->specular(); - if (shaderprop->decal()) - { - osg::StateSet* stateset = node->getOrCreateStateSet(); - if (!mPushedSorter && !hasSortAlpha) - stateset->setRenderBinDetails(1, "SORT_BACK_TO_FRONT"); - osg::ref_ptr polygonOffset(new osg::PolygonOffset); - polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f); - polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f); - stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); - } + handleDecal(shaderprop->decal(), hasSortAlpha, *node); break; } case Nif::RC_BSEffectShaderProperty: { auto shaderprop = static_cast(property); - if (shaderprop->decal()) + if (Bgsm::MaterialFilePtr shaderMat = getShaderMaterial(shaderprop->mName, mMaterialManager)) { - osg::StateSet* stateset = node->getOrCreateStateSet(); - if (!mPushedSorter && !hasSortAlpha) - stateset->setRenderBinDetails(1, "SORT_BACK_TO_FRONT"); - osg::ref_ptr polygonOffset(new osg::PolygonOffset); - polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f); - polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f); - stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); + handleShaderMaterialDrawableProperties(shaderMat, mat, *node, hasSortAlpha); + break; } + handleDecal(shaderprop->decal(), hasSortAlpha, *node); if (shaderprop->softEffect()) SceneUtil::setupSoftEffect( *node, shaderprop->mFalloffDepth, true, shaderprop->mFalloffDepth); @@ -2860,10 +2922,13 @@ namespace NifOsg } }; - osg::ref_ptr Loader::load(Nif::FileView file, Resource::ImageManager* imageManager) + osg::ref_ptr Loader::load( + Nif::FileView file, Resource::ImageManager* imageManager, Resource::BgsmFileManager* materialManager) { LoaderImpl impl(file.getFilename(), file.getVersion(), file.getUserVersion(), file.getBethVersion()); - return impl.load(file, imageManager); + impl.mMaterialManager = materialManager; + impl.mImageManager = imageManager; + return impl.load(file); } void Loader::loadKf(Nif::FileView kf, SceneUtil::KeyframeHolder& target) diff --git a/components/nifosg/nifloader.hpp b/components/nifosg/nifloader.hpp index 21e0ae097c..14f16088cc 100644 --- a/components/nifosg/nifloader.hpp +++ b/components/nifosg/nifloader.hpp @@ -18,6 +18,7 @@ namespace osg namespace Resource { class ImageManager; + class BgsmFileManager; } namespace NifOsg @@ -30,7 +31,8 @@ namespace NifOsg public: /// Create a scene graph for the given NIF. Auto-detects when skinning is used and wraps the graph in a Skeleton /// if so. - static osg::ref_ptr load(Nif::FileView file, Resource::ImageManager* imageManager); + static osg::ref_ptr load( + Nif::FileView file, Resource::ImageManager* imageManager, Resource::BgsmFileManager* materialManager); /// Load keyframe controllers from the given kf file. static void loadKf(Nif::FileView kf, SceneUtil::KeyframeHolder& target); diff --git a/components/platform/platform.cpp b/components/platform/platform.cpp index 787cfa522e..9743c14337 100644 --- a/components/platform/platform.cpp +++ b/components/platform/platform.cpp @@ -1,12 +1,15 @@ #include "platform.hpp" +#ifdef WIN32 +#include +#endif + namespace Platform { static void increaseFileHandleLimit() { #ifdef WIN32 -#include // Increase limit for open files at the stream I/O level, see // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/setmaxstdio?view=msvc-170#remarks _setmaxstdio(8192); diff --git a/components/resource/bgsmfilemanager.cpp b/components/resource/bgsmfilemanager.cpp new file mode 100644 index 0000000000..7d93608603 --- /dev/null +++ b/components/resource/bgsmfilemanager.cpp @@ -0,0 +1,55 @@ +#include "bgsmfilemanager.hpp" + +#include + +#include + +#include "objectcache.hpp" + +namespace Resource +{ + + class BgsmFileHolder : public osg::Object + { + public: + BgsmFileHolder(const Bgsm::MaterialFilePtr& file) + : mBgsmFile(file) + { + } + BgsmFileHolder(const BgsmFileHolder& copy, const osg::CopyOp& copyop) + : mBgsmFile(copy.mBgsmFile) + { + } + + BgsmFileHolder() = default; + + META_Object(Resource, BgsmFileHolder) + + Bgsm::MaterialFilePtr mBgsmFile; + }; + + BgsmFileManager::BgsmFileManager(const VFS::Manager* vfs, double expiryDelay) + : ResourceManager(vfs, expiryDelay) + { + } + + Bgsm::MaterialFilePtr BgsmFileManager::get(VFS::Path::NormalizedView name) + { + osg::ref_ptr obj = mCache->getRefFromObjectCache(name); + if (obj) + return static_cast(obj.get())->mBgsmFile; + else + { + Bgsm::MaterialFilePtr file = Bgsm::parse(mVFS->get(name)); + obj = new BgsmFileHolder(file); + mCache->addEntryToObjectCache(name.value(), obj); + return file; + } + } + + void BgsmFileManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const + { + Resource::reportStats("BSShader Material", frameNumber, mCache->getStats(), *stats); + } + +} diff --git a/components/resource/bgsmfilemanager.hpp b/components/resource/bgsmfilemanager.hpp new file mode 100644 index 0000000000..3c77c2c665 --- /dev/null +++ b/components/resource/bgsmfilemanager.hpp @@ -0,0 +1,27 @@ +#ifndef OPENMW_COMPONENTS_RESOURCE_BGSMFILEMANAGER_H +#define OPENMW_COMPONENTS_RESOURCE_BGSMFILEMANAGER_H + +#include + +#include "resourcemanager.hpp" + +namespace Resource +{ + + /// @brief Handles caching of material files. + /// @note May be used from any thread. + class BgsmFileManager : public ResourceManager + { + public: + BgsmFileManager(const VFS::Manager* vfs, double expiryDelay); + ~BgsmFileManager() = default; + + /// Retrieve a material file from the cache or load it from the VFS if not cached yet. + Bgsm::MaterialFilePtr get(VFS::Path::NormalizedView name); + + void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; + }; + +} + +#endif diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index f817d6b89a..93c53d8cb0 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -110,7 +110,7 @@ namespace Resource osg::ref_ptr BulletShapeManager::getShape(const std::string& name) { - const std::string normalized = VFS::Path::normalizeFilename(name); + const VFS::Path::Normalized normalized(name); osg::ref_ptr shape; osg::ref_ptr obj = mCache->getRefFromObjectCache(normalized); @@ -213,8 +213,8 @@ namespace Resource void BulletShapeManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const { - stats->setAttribute(frameNumber, "Shape", mCache->getCacheSize()); - stats->setAttribute(frameNumber, "Shape Instance", mInstanceCache->getCacheSize()); + Resource::reportStats("Shape", frameNumber, mCache->getStats(), *stats); + Resource::reportStats("Shape Instance", frameNumber, mInstanceCache->getStats(), *stats); } } diff --git a/components/resource/cachestats.cpp b/components/resource/cachestats.cpp new file mode 100644 index 0000000000..9cc0cea517 --- /dev/null +++ b/components/resource/cachestats.cpp @@ -0,0 +1,40 @@ +#include "cachestats.hpp" + +#include + +namespace Resource +{ + namespace + { + std::string makeAttribute(std::string_view prefix, std::string_view suffix) + { + std::string result; + result.reserve(prefix.size() + 1 + suffix.size()); + result += prefix; + result += ' '; + result += suffix; + return result; + } + } + + void addCacheStatsAttibutes(std::string_view prefix, std::vector& out) + { + constexpr std::string_view suffixes[] = { + "Count", + "Get", + "Hit", + "Expired", + }; + + for (std::string_view suffix : suffixes) + out.push_back(makeAttribute(prefix, suffix)); + } + + void reportStats(std::string_view prefix, unsigned frameNumber, const CacheStats& src, osg::Stats& dst) + { + dst.setAttribute(frameNumber, makeAttribute(prefix, "Count"), static_cast(src.mSize)); + dst.setAttribute(frameNumber, makeAttribute(prefix, "Get"), static_cast(src.mGet)); + dst.setAttribute(frameNumber, makeAttribute(prefix, "Hit"), static_cast(src.mHit)); + dst.setAttribute(frameNumber, makeAttribute(prefix, "Expired"), static_cast(src.mExpired)); + } +} diff --git a/components/resource/cachestats.hpp b/components/resource/cachestats.hpp new file mode 100644 index 0000000000..c25f801dba --- /dev/null +++ b/components/resource/cachestats.hpp @@ -0,0 +1,28 @@ +#ifndef OPENMW_COMPONENTS_RESOURCE_CACHESATS +#define OPENMW_COMPONENTS_RESOURCE_CACHESATS + +#include +#include +#include + +namespace osg +{ + class Stats; +} + +namespace Resource +{ + struct CacheStats + { + std::size_t mSize = 0; + std::size_t mGet = 0; + std::size_t mHit = 0; + std::size_t mExpired = 0; + }; + + void addCacheStatsAttibutes(std::string_view prefix, std::vector& out); + + void reportStats(std::string_view prefix, unsigned frameNumber, const CacheStats& src, osg::Stats& dst); +} + +#endif diff --git a/components/resource/foreachbulletobject.hpp b/components/resource/foreachbulletobject.hpp index fe39a8ed8c..d7e99cf0b5 100644 --- a/components/resource/foreachbulletobject.hpp +++ b/components/resource/foreachbulletobject.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_COMPONENTS_RESOURCE_FOREACHBULLETOBJECT_H #define OPENMW_COMPONENTS_RESOURCE_FOREACHBULLETOBJECT_H -#include +#include #include #include diff --git a/components/resource/imagemanager.cpp b/components/resource/imagemanager.cpp index 26fd60d7ea..e7cc9f03e5 100644 --- a/components/resource/imagemanager.cpp +++ b/components/resource/imagemanager.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -65,19 +66,19 @@ namespace Resource case (GL_COMPRESSED_RGBA_S3TC_DXT3_EXT): case (GL_COMPRESSED_RGBA_S3TC_DXT5_EXT): { - osg::GLExtensions* exts = osg::GLExtensions::Get(0, false); - if (exts - && !exts->isTextureCompressionS3TCSupported + if (!SceneUtil::glExtensionsReady()) + return true; // hashtag yolo (CS might not have context when loading assets) + osg::GLExtensions& exts = SceneUtil::getGLExtensions(); + if (!exts.isTextureCompressionS3TCSupported // This one works too. Should it be included in isTextureCompressionS3TCSupported()? Submitted as a // patch to OSG. - && !osg::isGLExtensionSupported(0, "GL_S3_s3tc")) + && !osg::isGLExtensionSupported(exts.contextID, "GL_S3_s3tc")) { return false; } break; } - // not bothering with checks for other compression formats right now, we are unlikely to ever use those - // anyway + // not bothering with checks for other compression formats right now default: return true; } @@ -200,7 +201,7 @@ namespace Resource void ImageManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const { - stats->setAttribute(frameNumber, "Image", mCache->getCacheSize()); + Resource::reportStats("Image", frameNumber, mCache->getStats(), *stats); } } diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 68b7adbe9a..84e7a7e311 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -4,7 +4,6 @@ #include -#include #include #include #include @@ -38,11 +37,11 @@ namespace Resource bool RetrieveAnimationsVisitor::belongsToLeftUpperExtremity(const std::string& name) { - static const std::array boneNames = { "bip01_l_clavicle", "left_clavicle", "bip01_l_upperarm", "left_upper_arm", - "bip01_l_forearm", "bip01_l_hand", "left_hand", "left_wrist", "shield_bone", "bip01_l_pinky1", - "bip01_l_pinky2", "bip01_l_pinky3", "bip01_l_ring1", "bip01_l_ring2", "bip01_l_ring3", "bip01_l_middle1", - "bip01_l_middle2", "bip01_l_middle3", "bip01_l_pointer1", "bip01_l_pointer2", "bip01_l_pointer3", - "bip01_l_thumb1", "bip01_l_thumb2", "bip01_l_thumb3", "left_forearm" }; + static const std::array boneNames = { "bip01 l clavicle", "left clavicle", "bip01 l upperarm", "left upper arm", + "bip01 l forearm", "bip01 l hand", "left hand", "left wrist", "shield bone", "bip01 l pinky1", + "bip01 l pinky2", "bip01 l pinky3", "bip01 l ring1", "bip01 l ring2", "bip01 l ring3", "bip01 l middle1", + "bip01 l middle2", "bip01 l middle3", "bip01 l pointer1", "bip01 l pointer2", "bip01 l pointer3", + "bip01 l thumb1", "bip01 l thumb2", "bip01 l thumb3", "left forearm" }; if (std::find(boneNames.begin(), boneNames.end(), name) != boneNames.end()) return true; @@ -52,11 +51,11 @@ namespace Resource bool RetrieveAnimationsVisitor::belongsToRightUpperExtremity(const std::string& name) { - static const std::array boneNames = { "bip01_r_clavicle", "right_clavicle", "bip01_r_upperarm", - "right_upper_arm", "bip01_r_forearm", "bip01_r_hand", "right_hand", "right_wrist", "bip01_r_thumb1", - "bip01_r_thumb2", "bip01_r_thumb3", "weapon_bone", "bip01_r_pinky1", "bip01_r_pinky2", "bip01_r_pinky3", - "bip01_r_ring1", "bip01_r_ring2", "bip01_r_ring3", "bip01_r_middle1", "bip01_r_middle2", "bip01_r_middle3", - "bip01_r_pointer1", "bip01_r_pointer2", "bip01_r_pointer3", "right_forearm" }; + static const std::array boneNames = { "bip01 r clavicle", "right clavicle", "bip01 r upperarm", + "right upper arm", "bip01 r forearm", "bip01 r hand", "right hand", "right wrist", "bip01 r thumb1", + "bip01 r thumb2", "bip01 r thumb3", "weapon bone", "bip01 r pinky1", "bip01 r pinky2", "bip01 r pinky3", + "bip01 r ring1", "bip01 r ring2", "bip01 r ring3", "bip01 r middle1", "bip01 r middle2", "bip01 r middle3", + "bip01 r pointer1", "bip01 r pointer2", "bip01 r pointer3", "right forearm" }; if (std::find(boneNames.begin(), boneNames.end(), name) != boneNames.end()) return true; @@ -67,7 +66,7 @@ namespace Resource bool RetrieveAnimationsVisitor::belongsToTorso(const std::string& name) { static const std::array boneNames - = { "bip01_spine1", "bip01_spine2", "bip01_neck", "bip01_head", "head", "neck", "chest", "groin" }; + = { "bip01 spine1", "bip01 spine2", "bip01 neck", "bip01 head", "head", "neck", "chest", "groin" }; if (std::find(boneNames.begin(), boneNames.end(), name) != boneNames.end()) return true; @@ -100,6 +99,9 @@ namespace Resource const osgAnimation::ChannelList& channels = animation->getChannels(); for (const auto& channel : channels) { + // Replace channel target name to match the renamed bones/transforms + channel->setTargetName(Misc::StringUtils::underscoresToSpaces(channel->getTargetName())); + if (name == "Bip01 R Clavicle") { if (!belongsToRightUpperExtremity(channel->getTargetName())) @@ -250,7 +252,7 @@ namespace Resource void KeyframeManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const { - stats->setAttribute(frameNumber, "Keyframe", mCache->getCacheSize()); + Resource::reportStats("Keyframe", frameNumber, mCache->getStats(), *stats); } } diff --git a/components/resource/multiobjectcache.cpp b/components/resource/multiobjectcache.cpp index f94dabc77c..71500b0ceb 100644 --- a/components/resource/multiobjectcache.cpp +++ b/components/resource/multiobjectcache.cpp @@ -25,6 +25,7 @@ namespace Resource { objectsToRemove.push_back(oitr->second); _objectCache.erase(oitr++); + ++mExpired; } else { @@ -57,13 +58,15 @@ namespace Resource osg::ref_ptr MultiObjectCache::takeFromObjectCache(const std::string& fileName) { std::lock_guard lock(_objectCacheMutex); + ++mGet; ObjectCacheMap::iterator found = _objectCache.find(fileName); if (found == _objectCache.end()) return osg::ref_ptr(); else { - osg::ref_ptr object = found->second; + osg::ref_ptr object = std::move(found->second); _objectCache.erase(found); + ++mHit; return object; } } @@ -79,10 +82,15 @@ namespace Resource } } - unsigned int MultiObjectCache::getCacheSize() const + CacheStats MultiObjectCache::getStats() const { std::lock_guard lock(_objectCacheMutex); - return _objectCache.size(); + return CacheStats{ + .mSize = _objectCache.size(), + .mGet = mGet, + .mHit = mHit, + .mExpired = mExpired, + }; } } diff --git a/components/resource/multiobjectcache.hpp b/components/resource/multiobjectcache.hpp index e1629f3197..654a88b524 100644 --- a/components/resource/multiobjectcache.hpp +++ b/components/resource/multiobjectcache.hpp @@ -8,6 +8,8 @@ #include #include +#include "cachestats.hpp" + namespace osg { class Object; @@ -37,13 +39,16 @@ namespace Resource /** call releaseGLObjects on all objects attached to the object cache.*/ void releaseGLObjects(osg::State* state); - unsigned int getCacheSize() const; + CacheStats getStats() const; protected: typedef std::multimap> ObjectCacheMap; ObjectCacheMap _objectCache; mutable std::mutex _objectCacheMutex; + std::size_t mGet = 0; + std::size_t mHit = 0; + std::size_t mExpired = 0; }; } diff --git a/components/resource/niffilemanager.cpp b/components/resource/niffilemanager.cpp index 352d367f9b..481126f304 100644 --- a/components/resource/niffilemanager.cpp +++ b/components/resource/niffilemanager.cpp @@ -3,7 +3,6 @@ #include #include -#include #include @@ -41,25 +40,25 @@ namespace Resource NifFileManager::~NifFileManager() = default; - Nif::NIFFilePtr NifFileManager::get(const std::string& name) + Nif::NIFFilePtr NifFileManager::get(VFS::Path::NormalizedView name) { osg::ref_ptr obj = mCache->getRefFromObjectCache(name); if (obj) return static_cast(obj.get())->mNifFile; else { - auto file = std::make_shared(name); + auto file = std::make_shared(name.value()); Nif::Reader reader(*file, mEncoder); reader.parse(mVFS->get(name)); obj = new NifFileHolder(file); - mCache->addEntryToObjectCache(name, obj); + mCache->addEntryToObjectCache(name.value(), obj); return file; } } void NifFileManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const { - stats->setAttribute(frameNumber, "Nif", mCache->getCacheSize()); + Resource::reportStats("Nif", frameNumber, mCache->getStats(), *stats); } } diff --git a/components/resource/niffilemanager.hpp b/components/resource/niffilemanager.hpp index dab4b70748..a5395fef7e 100644 --- a/components/resource/niffilemanager.hpp +++ b/components/resource/niffilemanager.hpp @@ -26,7 +26,7 @@ namespace Resource /// Retrieve a NIF file from the cache, or load it from the VFS if not cached yet. /// @note For performance reasons the NifFileManager does not handle case folding, needs /// to be done in advance by other managers accessing the NifFileManager. - Nif::NIFFilePtr get(const std::string& name); + Nif::NIFFilePtr get(VFS::Path::NormalizedView name); void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; }; diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp index dffa0e9fdb..e619b7102c 100644 --- a/components/resource/objectcache.hpp +++ b/components/resource/objectcache.hpp @@ -20,6 +20,8 @@ #ifndef OPENMW_COMPONENTS_RESOURCE_OBJECTCACHE #define OPENMW_COMPONENTS_RESOURCE_OBJECTCACHE +#include "cachestats.hpp" + #include #include #include @@ -29,26 +31,28 @@ #include #include #include +#include namespace osg { class Object; class State; class NodeVisitor; + class Stats; } namespace Resource { + struct GenericObjectCacheItem + { + osg::ref_ptr mValue; + double mLastUsage; + }; template class GenericObjectCache : public osg::Referenced { public: - GenericObjectCache() - : osg::Referenced(true) - { - } - // Update last usage timestamp using referenceTime for each cache time if they are not nullptr and referenced // from somewhere else. Remove items with last usage > expiryTime. Note: last usage might be updated from other // places so nullptr or not references elsewhere items are not always removed. @@ -64,6 +68,7 @@ namespace Resource item.mLastUsage = referenceTime; if (item.mLastUsage > expiryTime) return false; + ++mExpired; if (item.mValue != nullptr) objectsToRemove.push_back(std::move(item.mValue)); return true; @@ -105,34 +110,29 @@ namespace Resource osg::ref_ptr getRefFromObjectCache(const auto& key) { std::lock_guard lock(mMutex); - const auto itr = mItems.find(key); - if (itr != mItems.end()) - return itr->second.mValue; - else - return nullptr; + if (Item* const item = find(key)) + return item->mValue; + return nullptr; } std::optional> getRefFromObjectCacheOrNone(const auto& key) { const std::lock_guard lock(mMutex); - const auto it = mItems.find(key); - if (it == mItems.end()) - return std::nullopt; - return it->second.mValue; + if (Item* const item = find(key)) + return item->mValue; + return std::nullopt; } /** Check if an object is in the cache, and if it is, update its usage time stamp. */ bool checkInObjectCache(const auto& key, double timeStamp) { std::lock_guard lock(mMutex); - const auto itr = mItems.find(key); - if (itr != mItems.end()) + if (Item* const item = find(key)) { - itr->second.mLastUsage = timeStamp; + item->mLastUsage = timeStamp; return true; } - else - return false; + return false; } /** call releaseGLObjects on all objects attached to the object cache.*/ @@ -162,13 +162,6 @@ namespace Resource f(k, v.mValue.get()); } - /** Get the number of objects in the cache. */ - unsigned int getCacheSize() const - { - std::lock_guard lock(mMutex); - return mItems.size(); - } - template std::optional>> lowerBound(K&& key) { @@ -179,21 +172,36 @@ namespace Resource return std::pair(it->first, it->second.mValue); } - protected: - struct Item + CacheStats getStats() const { - osg::ref_ptr mValue; - double mLastUsage; - }; + const std::lock_guard lock(mMutex); + return CacheStats{ + .mSize = mItems.size(), + .mGet = mGet, + .mHit = mHit, + .mExpired = mExpired, + }; + } + + protected: + using Item = GenericObjectCacheItem; std::map> mItems; mutable std::mutex mMutex; - }; + std::size_t mGet = 0; + std::size_t mHit = 0; + std::size_t mExpired = 0; - class ObjectCache : public GenericObjectCache - { + Item* find(const auto& key) + { + ++mGet; + const auto it = mItems.find(key); + if (it == mItems.end()) + return nullptr; + ++mHit; + return &it->second; + } }; - } #endif diff --git a/components/resource/resourcemanager.hpp b/components/resource/resourcemanager.hpp index b2427c308a..63ec95de63 100644 --- a/components/resource/resourcemanager.hpp +++ b/components/resource/resourcemanager.hpp @@ -3,6 +3,8 @@ #include +#include + #include "objectcache.hpp" namespace VFS @@ -74,7 +76,7 @@ namespace Resource { public: explicit ResourceManager(const VFS::Manager* vfs, double expiryDelay) - : GenericResourceManager(vfs, expiryDelay) + : GenericResourceManager(vfs, expiryDelay) { } }; diff --git a/components/resource/resourcesystem.cpp b/components/resource/resourcesystem.cpp index 65a83a60ab..f012627efb 100644 --- a/components/resource/resourcesystem.cpp +++ b/components/resource/resourcesystem.cpp @@ -2,6 +2,7 @@ #include +#include "bgsmfilemanager.hpp" #include "imagemanager.hpp" #include "keyframemanager.hpp" #include "niffilemanager.hpp" @@ -15,11 +16,14 @@ namespace Resource : mVFS(vfs) { mNifFileManager = std::make_unique(vfs, encoder); + mBgsmFileManager = std::make_unique(vfs, expiryDelay); mImageManager = std::make_unique(vfs, expiryDelay); - mSceneManager = std::make_unique(vfs, mImageManager.get(), mNifFileManager.get(), expiryDelay); + mSceneManager = std::make_unique( + vfs, mImageManager.get(), mNifFileManager.get(), mBgsmFileManager.get(), expiryDelay); mKeyframeManager = std::make_unique(vfs, mSceneManager.get(), expiryDelay, encoder); addResourceManager(mNifFileManager.get()); + addResourceManager(mBgsmFileManager.get()); addResourceManager(mKeyframeManager.get()); // note, scene references images so add images afterwards for correct implementation of updateCache() addResourceManager(mSceneManager.get()); @@ -43,6 +47,11 @@ namespace Resource return mImageManager.get(); } + BgsmFileManager* ResourceSystem::getBgsmFileManager() + { + return mBgsmFileManager.get(); + } + NifFileManager* ResourceSystem::getNifFileManager() { return mNifFileManager.get(); diff --git a/components/resource/resourcesystem.hpp b/components/resource/resourcesystem.hpp index f7f09b9277..5609176a89 100644 --- a/components/resource/resourcesystem.hpp +++ b/components/resource/resourcesystem.hpp @@ -25,6 +25,7 @@ namespace Resource class SceneManager; class ImageManager; + class BgsmFileManager; class NifFileManager; class KeyframeManager; class BaseResourceManager; @@ -41,6 +42,7 @@ namespace Resource SceneManager* getSceneManager(); ImageManager* getImageManager(); + BgsmFileManager* getBgsmFileManager(); NifFileManager* getNifFileManager(); KeyframeManager* getKeyframeManager(); @@ -74,6 +76,7 @@ namespace Resource private: std::unique_ptr mSceneManager; std::unique_ptr mImageManager; + std::unique_ptr mBgsmFileManager; std::unique_ptr mNifFileManager; std::unique_ptr mKeyframeManager; diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 787f2e8441..fbc6acb3cf 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -4,11 +4,15 @@ #include #include +#include #include #include #include +#include #include +#include +#include #include @@ -51,6 +55,7 @@ #include #include +#include "bgsmfilemanager.hpp" #include "errormarker.hpp" #include "imagemanager.hpp" #include "niffilemanager.hpp" @@ -267,6 +272,11 @@ namespace Resource void apply(osg::Node& node) override { + // If an osgAnimation bone/transform, ensure underscores in name are replaced with spaces + // this is for compatibility reasons + if (dynamic_cast(&node)) + node.setName(Misc::StringUtils::underscoresToSpaces(node.getName())); + if (osg::StateSet* stateset = node.getStateSet()) { if (stateset->getRenderingHint() == osg::StateSet::TRANSPARENT_BIN) @@ -352,8 +362,55 @@ namespace Resource std::vector> mRigGeometryHolders; }; + void updateVertexInfluenceMap(osgAnimation::RigGeometry& rig) + { + osgAnimation::VertexInfluenceMap* vertexInfluenceMap = rig.getInfluenceMap(); + if (!vertexInfluenceMap) + return; + + std::vector renameList; + for (const auto& [boneName, unused] : *vertexInfluenceMap) + { + if (boneName.find('_') != std::string::npos) + renameList.push_back(boneName); + } + + for (const std::string& oldName : renameList) + { + const std::string newName = Misc::StringUtils::underscoresToSpaces(oldName); + if (vertexInfluenceMap->find(newName) == vertexInfluenceMap->end()) + (*vertexInfluenceMap)[newName] = std::move((*vertexInfluenceMap)[oldName]); + vertexInfluenceMap->erase(oldName); + } + } + + class RenameAnimCallbacksVisitor : public osg::NodeVisitor + { + public: + RenameAnimCallbacksVisitor() + : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) + { + } + + void apply(osg::MatrixTransform& node) override + { + // osgAnimation update callback name must match bone name/channel targets + osg::Callback* cb = node.getUpdateCallback(); + while (cb) + { + auto animCb = dynamic_cast*>(cb); + if (animCb) + animCb->setName(Misc::StringUtils::underscoresToSpaces(animCb->getName())); + + cb = cb->getNestedCallback(); + } + + traverse(node); + } + }; + SceneManager::SceneManager(const VFS::Manager* vfs, Resource::ImageManager* imageManager, - Resource::NifFileManager* nifFileManager, double expiryDelay) + Resource::NifFileManager* nifFileManager, Resource::BgsmFileManager* bgsmFileManager, double expiryDelay) : ResourceManager(vfs, expiryDelay) , mShaderManager(new Shader::ShaderManager) , mForceShaders(false) @@ -368,6 +425,7 @@ namespace Resource , mSharedStateManager(new SharedStateManager) , mImageManager(imageManager) , mNifFileManager(nifFileManager) + , mBgsmFileManager(bgsmFileManager) , mMinFilter(osg::Texture::LINEAR_MIPMAP_LINEAR) , mMagFilter(osg::Texture::LINEAR) , mMaxAnisotropy(1) @@ -511,6 +569,13 @@ namespace Resource return mCache->checkInObjectCache(VFS::Path::normalizeFilename(name), timeStamp); } + void SceneManager::setUpNormalsRTForStateSet(osg::StateSet* stateset, bool enabled) + { + if (!getSupportsNormalsRT()) + return; + stateset->setAttributeAndModes(new osg::ColorMaski(1, enabled, enabled, enabled, enabled)); + } + /// @brief Callback to read image files from the VFS. class ImageReadCallback : public osgDB::ReadFileCallback { @@ -545,9 +610,10 @@ namespace Resource namespace { osg::ref_ptr loadNonNif( - const std::string& normalizedFilename, std::istream& model, Resource::ImageManager* imageManager) + VFS::Path::NormalizedView normalizedFilename, std::istream& model, Resource::ImageManager* imageManager) { - auto ext = Misc::getFileExtension(normalizedFilename); + const std::string_view ext = Misc::getFileExtension(normalizedFilename.value()); + const bool isColladaFile = ext == "dae"; osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(std::string(ext)); if (!reader) { @@ -563,10 +629,10 @@ namespace Resource // findFileCallback would be necessary. but findFileCallback does not support virtual files, so we can't // implement it. options->setReadFileCallback(new ImageReadCallback(imageManager)); - if (ext == "dae") + if (isColladaFile) options->setOptionString("daeUseSequencedTextureUnits"); - const std::array fileHash = Files::getHash(normalizedFilename, model); + const std::array fileHash = Files::getHash(normalizedFilename.value(), model); osgDB::ReaderWriter::ReadResult result = reader->readNode(model, options); if (!result.success()) @@ -591,9 +657,13 @@ namespace Resource node->accept(rigFinder); for (osg::Node* foundRigNode : rigFinder.mFoundNodes) { - if (foundRigNode->libraryName() == std::string("osgAnimation")) + if (foundRigNode->libraryName() == std::string_view("osgAnimation")) { osgAnimation::RigGeometry* foundRigGeometry = static_cast(foundRigNode); + + if (isColladaFile) + Resource::updateVertexInfluenceMap(*foundRigGeometry); + osg::ref_ptr newRig = new SceneUtil::RigGeometryHolder(*foundRigGeometry, osg::CopyOp::DEEP_COPY_ALL); @@ -608,13 +678,18 @@ namespace Resource } } - if (ext == "dae") + if (isColladaFile) { Resource::ColladaDescriptionVisitor colladaDescriptionVisitor; node->accept(colladaDescriptionVisitor); if (colladaDescriptionVisitor.mSkeleton) { + // Collada bones may have underscores in place of spaces due to a collada limitation + // we should rename the bones and update callbacks here at load time + Resource::RenameAnimCallbacksVisitor renameBoneVisitor; + node->accept(renameBoneVisitor); + if (osg::Group* group = dynamic_cast(node)) { group->removeChildren(0, group->getNumChildren()); @@ -721,12 +796,13 @@ namespace Resource } } - osg::ref_ptr load(const std::string& normalizedFilename, const VFS::Manager* vfs, - Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) + osg::ref_ptr load(VFS::Path::NormalizedView normalizedFilename, const VFS::Manager* vfs, + Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager, + Resource::BgsmFileManager* materialMgr) { - auto ext = Misc::getFileExtension(normalizedFilename); + const std::string_view ext = Misc::getFileExtension(normalizedFilename.value()); if (ext == "nif") - return NifOsg::Loader::load(*nifFileManager->get(normalizedFilename), imageManager); + return NifOsg::Loader::load(*nifFileManager->get(normalizedFilename), imageManager, materialMgr); else if (ext == "spt") { Log(Debug::Warning) << "Ignoring SpeedTree data file " << normalizedFilename; @@ -778,12 +854,12 @@ namespace Resource } }; - bool canOptimize(const std::string& filename) + static bool canOptimize(std::string_view filename) { - size_t slashpos = filename.find_last_of("\\/"); - if (slashpos != std::string::npos && slashpos + 1 < filename.size()) + const std::string_view::size_type slashpos = filename.find_last_of('/'); + if (slashpos != std::string_view::npos && slashpos + 1 < filename.size()) { - std::string basename = filename.substr(slashpos + 1); + const std::string_view basename = filename.substr(slashpos + 1); // xmesh.nif can not be optimized because there are keyframes added in post if (!basename.empty() && basename[0] == 'x') return false; @@ -796,7 +872,7 @@ namespace Resource // For spell VFX, DummyXX nodes must remain intact. Not adding those to reservedNames to avoid being overly // cautious - instead, decide on filename - if (filename.find("vfx_pattern") != std::string::npos) + if (filename.find("vfx_pattern") != std::string_view::npos) return false; return true; } @@ -843,11 +919,12 @@ namespace Resource { try { + VFS::Path::Normalized path("meshes/marker_error.****"); for (const auto meshType : { "nif", "osg", "osgt", "osgb", "osgx", "osg2", "dae" }) { - const std::string normalized = "meshes/marker_error." + std::string(meshType); - if (mVFS->exists(normalized)) - return load(normalized, mVFS, mImageManager, mNifFileManager); + path.changeExtension(meshType); + if (mVFS->exists(path)) + return load(path, mVFS, mImageManager, mNifFileManager, mBgsmFileManager); } } catch (const std::exception& e) @@ -856,7 +933,8 @@ namespace Resource << ", using embedded marker_error instead"; } Files::IMemStream file(ErrorMarker::sValue.data(), ErrorMarker::sValue.size()); - return loadNonNif("error_marker.osgt", file, mImageManager); + constexpr VFS::Path::NormalizedView errorMarker("error_marker.osgt"); + return loadNonNif(errorMarker, file, mImageManager); } osg::ref_ptr SceneManager::cloneErrorMarker() @@ -869,7 +947,7 @@ namespace Resource osg::ref_ptr SceneManager::getTemplate(std::string_view name, bool compile) { - std::string normalized = VFS::Path::normalizeFilename(name); + const VFS::Path::Normalized normalized(name); osg::ref_ptr obj = mCache->getRefFromObjectCache(normalized); if (obj) @@ -879,7 +957,7 @@ namespace Resource osg::ref_ptr loaded; try { - loaded = load(normalized, mVFS, mImageManager, mNifFileManager); + loaded = load(normalized, mVFS, mImageManager, mNifFileManager, mBgsmFileManager); SceneUtil::ProcessExtraDataVisitor extraDataVisitor(this); loaded->accept(extraDataVisitor); @@ -1116,7 +1194,7 @@ namespace Resource stats->setAttribute(frameNumber, "StateSet", mSharedStateManager->getNumSharedStateSets()); } - stats->setAttribute(frameNumber, "Node", mCache->getCacheSize()); + Resource::reportStats("Node", frameNumber, mCache->getStats(), *stats); } osg::ref_ptr SceneManager::createShaderVisitor(const std::string& shaderPrefix) diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 12900441de..31ad51694c 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -32,6 +32,7 @@ namespace Resource { class ImageManager; class NifFileManager; + class BgsmFileManager; class SharedStateManager; } @@ -90,7 +91,7 @@ namespace Resource { public: explicit SceneManager(const VFS::Manager* vfs, Resource::ImageManager* imageManager, - Resource::NifFileManager* nifFileManager, double expiryDelay); + Resource::NifFileManager* nifFileManager, Resource::BgsmFileManager* bgsmFileManager, double expiryDelay); ~SceneManager(); Shader::ShaderManager& getShaderManager(); @@ -224,6 +225,8 @@ namespace Resource void setSupportsNormalsRT(bool supports) { mSupportsNormalsRT = supports; } bool getSupportsNormalsRT() const { return mSupportsNormalsRT; } + void setUpNormalsRTForStateSet(osg::StateSet* stateset, bool enabled); + void setSoftParticles(bool enabled) { mSoftParticles = enabled; } bool getSoftParticles() const { return mSoftParticles; } @@ -257,6 +260,7 @@ namespace Resource Resource::ImageManager* mImageManager; Resource::NifFileManager* mNifFileManager; + Resource::BgsmFileManager* mBgsmFileManager; osg::Texture::FilterMode mMinFilter; osg::Texture::FilterMode mMagFilter; diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index 6ff2112381..6730ddb303 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -2,7 +2,11 @@ #include #include +#include #include +#include +#include +#include #include @@ -16,145 +20,267 @@ #include +#include "cachestats.hpp" + namespace Resource { + namespace + { + constexpr float statsWidth = 1280.0f; + constexpr float statsHeight = 1024.0f; + constexpr float characterSize = 17.0f; + constexpr float backgroundMargin = 5; + constexpr float backgroundSpacing = 3; + constexpr float maxStatsHeight = 420.0f; + constexpr std::size_t pageSize + = static_cast((maxStatsHeight - 2 * backgroundMargin) / characterSize); + constexpr int statsHandlerKey = osgGA::GUIEventAdapter::KEY_F4; + const VFS::Path::Normalized fontName("Fonts/DejaVuLGCSansMono.ttf"); + + bool collectStatRendering = false; + bool collectStatCameraObjects = false; + bool collectStatViewerObjects = false; + bool collectStatResource = false; + bool collectStatGPU = false; + bool collectStatEvent = false; + bool collectStatFrameRate = false; + bool collectStatUpdate = false; + bool collectStatEngine = false; + + std::vector generateAllStatNames() + { + constexpr std::size_t itemsPerPage = 24; - static bool collectStatRendering = false; - static bool collectStatCameraObjects = false; - static bool collectStatViewerObjects = false; - static bool collectStatResource = false; - static bool collectStatGPU = false; - static bool collectStatEvent = false; - static bool collectStatFrameRate = false; - static bool collectStatUpdate = false; - static bool collectStatEngine = false; + constexpr std::string_view firstPage[] = { + "FrameNumber", + "", + "Compiling", + "WorkQueue", + "WorkThread", + "UnrefQueue", + "", + "Texture", + "StateSet", + "Composite", + "", + "Mechanics Actors", + "Mechanics Objects", + "", + "Physics Actors", + "Physics Objects", + "Physics Projectiles", + "Physics HeightFields", + "", + "Lua UsedMemory", + "", + "", + "", + "", + }; - constexpr std::string_view sFontName = "Fonts/DejaVuLGCSansMono.ttf"; + static_assert(std::size(firstPage) == itemsPerPage); - static void setupStatCollection() - { - const char* envList = getenv("OPENMW_OSG_STATS_LIST"); - if (envList == nullptr) - return; + constexpr std::string_view caches[] = { + "Node", + "Shape", + "Shape Instance", + "Image", + "Nif", + "Keyframe", + "BSShader Material", + "Groundcover Chunk", + "Object Chunk", + "Terrain Chunk", + "Terrain Texture", + "Land", + }; - std::string_view kwList(envList); + constexpr std::string_view cellPreloader[] = { + "CellPreloader Count", + "CellPreloader Added", + "CellPreloader Evicted", + "CellPreloader Loaded", + "CellPreloader Expired", + }; - auto kwBegin = kwList.begin(); + constexpr std::string_view navMesh[] = { + "NavMesh Jobs", + "NavMesh Removing", + "NavMesh Updating", + "NavMesh Delayed", + "NavMesh Pushed", + "NavMesh Processing", + "NavMesh DbJobs Write", + "NavMesh DbJobs Read", + "NavMesh DbCache Get", + "NavMesh DbCache Hit", + "NavMesh CacheSize", + "NavMesh UsedTiles", + "NavMesh CachedTiles", + "NavMesh Cache Get", + "NavMesh Cache Hit", + }; - while (kwBegin != kwList.end()) - { - auto kwEnd = std::find(kwBegin, kwList.end(), ';'); - - const auto kw = kwList.substr(std::distance(kwList.begin(), kwBegin), std::distance(kwBegin, kwEnd)); - - if (kw == "gpu") - collectStatGPU = true; - else if (kw == "event") - collectStatEvent = true; - else if (kw == "frame_rate") - collectStatFrameRate = true; - else if (kw == "update") - collectStatUpdate = true; - else if (kw == "engine") - collectStatEngine = true; - else if (kw == "rendering") - collectStatRendering = true; - else if (kw == "cameraobjects") - collectStatCameraObjects = true; - else if (kw == "viewerobjects") - collectStatViewerObjects = true; - else if (kw == "resource") - collectStatResource = true; - else if (kw == "times") + std::vector statNames; + + for (std::string_view name : firstPage) + statNames.emplace_back(name); + + for (std::size_t i = 0; i < std::size(caches); ++i) { - collectStatGPU = true; - collectStatEvent = true; - collectStatFrameRate = true; - collectStatUpdate = true; - collectStatEngine = true; - collectStatRendering = true; + Resource::addCacheStatsAttibutes(caches[i], statNames); + if ((i + 1) % 5 != 0) + statNames.emplace_back(); } - if (kwEnd == kwList.end()) - break; + for (std::string_view name : cellPreloader) + statNames.emplace_back(name); - kwBegin = std::next(kwEnd); - } - } + while (statNames.size() % itemsPerPage != 0) + statNames.emplace_back(); - class SetFontVisitor : public osg::NodeVisitor - { - public: - SetFontVisitor(osgText::Font* font) - : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) - , mFont(font) - { + for (std::string_view name : navMesh) + statNames.emplace_back(name); + + return statNames; } - void apply(osg::Drawable& node) override + void setupStatCollection() { - if (osgText::Text* text = dynamic_cast(&node)) + const char* envList = getenv("OPENMW_OSG_STATS_LIST"); + if (envList == nullptr) + return; + + std::string_view kwList(envList); + + auto kwBegin = kwList.begin(); + + while (kwBegin != kwList.end()) { - text->setFont(mFont); + auto kwEnd = std::find(kwBegin, kwList.end(), ';'); + + const auto kw = kwList.substr(std::distance(kwList.begin(), kwBegin), std::distance(kwBegin, kwEnd)); + + if (kw == "gpu") + collectStatGPU = true; + else if (kw == "event") + collectStatEvent = true; + else if (kw == "frame_rate") + collectStatFrameRate = true; + else if (kw == "update") + collectStatUpdate = true; + else if (kw == "engine") + collectStatEngine = true; + else if (kw == "rendering") + collectStatRendering = true; + else if (kw == "cameraobjects") + collectStatCameraObjects = true; + else if (kw == "viewerobjects") + collectStatViewerObjects = true; + else if (kw == "resource") + collectStatResource = true; + else if (kw == "times") + { + collectStatGPU = true; + collectStatEvent = true; + collectStatFrameRate = true; + collectStatUpdate = true; + collectStatEngine = true; + collectStatRendering = true; + } + + if (kwEnd == kwList.end()) + break; + + kwBegin = std::next(kwEnd); } } - private: - osgText::Font* mFont; - }; - - osg::ref_ptr getMonoFont(VFS::Manager* vfs) - { - if (osgDB::Registry::instance()->getReaderWriterForExtension("ttf") && vfs->exists(sFontName)) + osg::ref_ptr createBackgroundRectangle( + const osg::Vec3& pos, const float width, const float height, const osg::Vec4& color) { - Files::IStreamPtr streamPtr = vfs->get(sFontName); - return osgText::readRefFontStream(*streamPtr.get()); + osg::ref_ptr geometry = new osg::Geometry; + + geometry->setUseDisplayList(false); + + osg::ref_ptr stateSet = new osg::StateSet; + geometry->setStateSet(stateSet); + + osg::ref_ptr vertices = new osg::Vec3Array; + vertices->push_back(osg::Vec3(pos.x(), pos.y(), 0)); + vertices->push_back(osg::Vec3(pos.x(), pos.y() - height, 0)); + vertices->push_back(osg::Vec3(pos.x() + width, pos.y() - height, 0)); + vertices->push_back(osg::Vec3(pos.x() + width, pos.y(), 0)); + geometry->setVertexArray(vertices); + + osg::ref_ptr colors = new osg::Vec4Array; + colors->push_back(color); + geometry->setColorArray(colors, osg::Array::BIND_OVERALL); + + osg::ref_ptr base + = new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLE_FAN, 0); + base->push_back(0); + base->push_back(1); + base->push_back(2); + base->push_back(3); + geometry->addPrimitiveSet(base); + + return geometry; } - return nullptr; - } + osg::ref_ptr getMonoFont(const VFS::Manager& vfs) + { + if (osgDB::Registry::instance()->getReaderWriterForExtension("ttf") && vfs.exists(fontName)) + { + const Files::IStreamPtr streamPtr = vfs.get(fontName); + return osgText::readRefFontStream(*streamPtr); + } - StatsHandler::StatsHandler(bool offlineCollect, VFS::Manager* vfs) - : _key(osgGA::GUIEventAdapter::KEY_F4) - , _initialized(false) - , _statsType(false) - , _offlineCollect(offlineCollect) - , _statsWidth(1280.0f) - , _statsHeight(1024.0f) - , _characterSize(18.0f) - { - _camera = new osg::Camera; - _camera->getOrCreateStateSet()->setGlobalDefaults(); - _camera->setRenderer(new osgViewer::Renderer(_camera.get())); - _camera->setProjectionResizePolicy(osg::Camera::FIXED); + return nullptr; + } + + class SetFontVisitor : public osg::NodeVisitor + { + public: + SetFontVisitor(osgText::Font* font) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mFont(font) + { + } - _resourceStatsChildNum = 0; + void apply(osg::Drawable& node) override + { + if (osgText::Text* text = dynamic_cast(&node)) + { + text->setFont(mFont); + } + } - _textFont = getMonoFont(vfs); + private: + osgText::Font* mFont; + }; } - Profiler::Profiler(bool offlineCollect, VFS::Manager* vfs) - : _offlineCollect(offlineCollect) - , _initFonts(false) + Profiler::Profiler(bool offlineCollect, const VFS::Manager& vfs) + : mOfflineCollect(offlineCollect) + , mTextFont(getMonoFont(vfs)) { - _characterSize = 18; + _characterSize = characterSize; _font.clear(); - _textFont = getMonoFont(vfs); - setKeyEventTogglesOnScreenStats(osgGA::GUIEventAdapter::KEY_F3); setupStatCollection(); } void Profiler::setUpFonts() { - if (_textFont != nullptr) + if (mTextFont != nullptr) { - SetFontVisitor visitor(_textFont); + SetFontVisitor visitor(mTextFont); _switch->accept(visitor); } - _initFonts = true; + mInitFonts = true; } bool Profiler::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa) @@ -162,24 +288,45 @@ namespace Resource osgViewer::ViewerBase* viewer = nullptr; bool handled = StatsHandler::handle(ea, aa); - if (_initialized && !_initFonts) + if (_initialized && !mInitFonts) setUpFonts(); auto* view = dynamic_cast(&aa); if (view) viewer = view->getViewerBase(); - if (viewer) + if (viewer != nullptr) { // Add/remove openmw stats to the osd as necessary viewer->getViewerStats()->collectStats("engine", _statsType >= StatsHandler::StatsType::VIEWER_STATS); - if (_offlineCollect) - CollectStatistics(viewer); + if (mOfflineCollect) + collectStatistics(*viewer); } return handled; } + StatsHandler::StatsHandler(bool offlineCollect, const VFS::Manager& vfs) + : mOfflineCollect(offlineCollect) + , mSwitch(new osg::Switch) + , mCamera(new osg::Camera) + , mTextFont(getMonoFont(vfs)) + , mStatNames(generateAllStatNames()) + { + osg::ref_ptr stateset = mSwitch->getOrCreateStateSet(); + stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + stateset->setMode(GL_BLEND, osg::StateAttribute::ON); + stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); +#ifdef OSG_GL1_AVAILABLE + stateset->setAttribute(new osg::PolygonMode(), osg::StateAttribute::PROTECTED); +#endif + + mCamera->getOrCreateStateSet()->setGlobalDefaults(); + mCamera->setRenderer(new osgViewer::Renderer(mCamera.get())); + mCamera->setProjectionResizePolicy(osg::Camera::FIXED); + mCamera->addChild(mSwitch); + } + bool StatsHandler::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa) { if (ea.getHandled()) @@ -189,18 +336,21 @@ namespace Resource { case (osgGA::GUIEventAdapter::KEYDOWN): { - if (ea.getKey() == _key) + if (ea.getKey() == statsHandlerKey) { - osgViewer::View* myview = dynamic_cast(&aa); - if (!myview) + osgViewer::View* const view = dynamic_cast(&aa); + if (view == nullptr) return false; - osgViewer::ViewerBase* viewer = myview->getViewerBase(); + osgViewer::ViewerBase* const viewer = view->getViewerBase(); + + if (viewer == nullptr) + return false; - toggle(viewer); + toggle(*viewer); - if (_offlineCollect) - CollectStatistics(viewer); + if (mOfflineCollect) + collectStatistics(*viewer); aa.requestRedraw(); return true; @@ -223,66 +373,69 @@ namespace Resource if (width <= 0 || height <= 0) return; - _camera->setViewport(0, 0, width, height); - if (fabs(height * _statsWidth) <= fabs(width * _statsHeight)) + mCamera->setViewport(0, 0, width, height); + if (std::abs(height * statsWidth) <= std::abs(width * statsHeight)) { - _camera->setProjectionMatrix( - osg::Matrix::ortho2D(_statsWidth - width * _statsHeight / height, _statsWidth, 0.0, _statsHeight)); + mCamera->setProjectionMatrix( + osg::Matrix::ortho2D(statsWidth - width * statsHeight / height, statsWidth, 0.0, statsHeight)); } else { - _camera->setProjectionMatrix( - osg::Matrix::ortho2D(0.0, _statsWidth, _statsHeight - height * _statsWidth / width, _statsHeight)); + mCamera->setProjectionMatrix( + osg::Matrix::ortho2D(0.0, statsWidth, statsHeight - height * statsWidth / width, statsHeight)); } } - void StatsHandler::toggle(osgViewer::ViewerBase* viewer) + void StatsHandler::toggle(osgViewer::ViewerBase& viewer) { - if (!_initialized) + if (!mInitialized) { setUpHUDCamera(viewer); setUpScene(viewer); + mInitialized = true; } - _statsType = !_statsType; - - if (!_statsType) + if (mPage == mSwitch->getNumChildren()) { - _camera->setNodeMask(0); - _switch->setAllChildrenOff(); + mPage = 0; + + mCamera->setNodeMask(0); + mSwitch->setAllChildrenOff(); - viewer->getViewerStats()->collectStats("resource", false); + viewer.getViewerStats()->collectStats("resource", false); } else { - _camera->setNodeMask(0xffffffff); - _switch->setSingleChildOn(_resourceStatsChildNum); + mCamera->setNodeMask(0xffffffff); + mSwitch->setSingleChildOn(mPage); - viewer->getViewerStats()->collectStats("resource", true); + viewer.getViewerStats()->collectStats("resource", true); + + ++mPage; } } - void StatsHandler::setUpHUDCamera(osgViewer::ViewerBase* viewer) + void StatsHandler::setUpHUDCamera(osgViewer::ViewerBase& viewer) { // Try GraphicsWindow first so we're likely to get the main viewer window - osg::GraphicsContext* context = dynamic_cast(_camera->getGraphicsContext()); + osg::GraphicsContext* context = dynamic_cast(mCamera->getGraphicsContext()); if (!context) { osgViewer::Viewer::Windows windows; - viewer->getWindows(windows); + viewer.getWindows(windows); if (!windows.empty()) context = windows.front(); else { // No GraphicsWindows were found, so let's try to find a GraphicsContext - context = _camera->getGraphicsContext(); + context = mCamera->getGraphicsContext(); if (!context) { osgViewer::Viewer::Contexts contexts; - viewer->getContexts(contexts); + viewer.getContexts(contexts); if (contexts.empty()) return; @@ -292,241 +445,151 @@ namespace Resource } } - _camera->setGraphicsContext(context); + mCamera->setGraphicsContext(context); - _camera->setRenderOrder(osg::Camera::POST_RENDER, 11); + mCamera->setRenderOrder(osg::Camera::POST_RENDER, 11); - _camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); - _camera->setViewMatrix(osg::Matrix::identity()); + mCamera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); + mCamera->setViewMatrix(osg::Matrix::identity()); setWindowSize(context->getTraits()->width, context->getTraits()->height); // only clear the depth buffer - _camera->setClearMask(0); - _camera->setAllowEventFocus(false); - - _camera->setRenderer(new osgViewer::Renderer(_camera.get())); - - _initialized = true; - } - - osg::Geometry* createBackgroundRectangle( - const osg::Vec3& pos, const float width, const float height, osg::Vec4& color) - { - osg::StateSet* ss = new osg::StateSet; - - osg::Geometry* geometry = new osg::Geometry; - - geometry->setUseDisplayList(false); - geometry->setStateSet(ss); - - osg::Vec3Array* vertices = new osg::Vec3Array; - geometry->setVertexArray(vertices); + mCamera->setClearMask(0); + mCamera->setAllowEventFocus(false); - vertices->push_back(osg::Vec3(pos.x(), pos.y(), 0)); - vertices->push_back(osg::Vec3(pos.x(), pos.y() - height, 0)); - vertices->push_back(osg::Vec3(pos.x() + width, pos.y() - height, 0)); - vertices->push_back(osg::Vec3(pos.x() + width, pos.y(), 0)); - - osg::Vec4Array* colors = new osg::Vec4Array; - colors->push_back(color); - geometry->setColorArray(colors, osg::Array::BIND_OVERALL); - - osg::DrawElementsUShort* base = new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLE_FAN, 0); - base->push_back(0); - base->push_back(1); - base->push_back(2); - base->push_back(3); - - geometry->addPrimitiveSet(base); - - return geometry; + mCamera->setRenderer(new osgViewer::Renderer(mCamera.get())); } - class ResourceStatsTextDrawCallback : public osg::Drawable::DrawCallback + namespace { - public: - ResourceStatsTextDrawCallback(osg::Stats* stats, const std::vector& statNames) - : mStats(stats) - , mStatNames(statNames) + class ResourceStatsTextDrawCallback : public osg::Drawable::DrawCallback { - } + public: + explicit ResourceStatsTextDrawCallback(osg::Stats* stats, std::span statNames) + : mStats(stats) + , mStatNames(statNames) + { + } - void drawImplementation(osg::RenderInfo& renderInfo, const osg::Drawable* drawable) const override - { - if (!mStats) - return; + void drawImplementation(osg::RenderInfo& renderInfo, const osg::Drawable* drawable) const override + { + if (mStats == nullptr) + return; - osgText::Text* text = (osgText::Text*)(drawable); + osgText::Text* text = (osgText::Text*)(drawable); - std::ostringstream viewStr; - viewStr.setf(std::ios::left, std::ios::adjustfield); - viewStr.width(14); - // Used fixed formatting, as scientific will switch to "...e+.." notation for - // large numbers of vertices/drawables/etc. - viewStr.setf(std::ios::fixed); - viewStr.precision(0); + std::ostringstream viewStr; + viewStr.setf(std::ios::left, std::ios::adjustfield); + viewStr.width(14); + // Used fixed formatting, as scientific will switch to "...e+.." notation for + // large numbers of vertices/drawables/etc. + viewStr.setf(std::ios::fixed); + viewStr.precision(0); - unsigned int frameNumber = renderInfo.getState()->getFrameStamp()->getFrameNumber() - 1; + const unsigned int frameNumber = renderInfo.getState()->getFrameStamp()->getFrameNumber() - 1; - for (const auto& statName : mStatNames.get()) - { - if (statName.empty()) - viewStr << std::endl; - else + for (const std::string& statName : mStatNames) { - double value = 0.0; - if (mStats->getAttribute(frameNumber, statName, value)) - viewStr << std::setw(8) << value << std::endl; + if (statName.empty()) + viewStr << std::endl; else - viewStr << std::setw(8) << "." << std::endl; + { + double value = 0.0; + if (mStats->getAttribute(frameNumber, statName, value)) + viewStr << std::setw(8) << value << std::endl; + else + viewStr << std::setw(8) << "." << std::endl; + } } - } - text->setText(viewStr.str()); + text->setText(viewStr.str()); - text->drawImplementation(renderInfo); - } + text->drawImplementation(renderInfo); + } - osg::ref_ptr mStats; - std::reference_wrapper> mStatNames; - }; + private: + osg::ref_ptr mStats; + std::span mStatNames; + }; + } - void StatsHandler::setUpScene(osgViewer::ViewerBase* viewer) + void StatsHandler::setUpScene(osgViewer::ViewerBase& viewer) { - _switch = new osg::Switch; - - _camera->addChild(_switch); - - osg::StateSet* stateset = _switch->getOrCreateStateSet(); - stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF); - stateset->setMode(GL_BLEND, osg::StateAttribute::ON); - stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); -#ifdef OSG_GL1_AVAILABLE - stateset->setAttribute(new osg::PolygonMode(), osg::StateAttribute::PROTECTED); -#endif - - osg::Vec4 backgroundColor(0.0, 0.0, 0.0f, 0.3); - osg::Vec4 staticTextColor(1.0, 1.0, 0.0f, 1.0); - osg::Vec4 dynamicTextColor(1.0, 1.0, 1.0f, 1.0); - float backgroundMargin = 5; - float backgroundSpacing = 3; - - // resource stats + const osg::Vec4 backgroundColor(0.0, 0.0, 0.0f, 0.3); + const osg::Vec4 staticTextColor(1.0, 1.0, 0.0f, 1.0); + const osg::Vec4 dynamicTextColor(1.0, 1.0, 1.0f, 1.0); + + const auto longest = std::max_element(mStatNames.begin(), mStatNames.end(), + [](const std::string& lhs, const std::string& rhs) { return lhs.size() < rhs.size(); }); + const std::size_t longestSize = longest->size(); + const float statNamesWidth = longestSize * characterSize * 0.6 + 2 * backgroundMargin; + const float statTextWidth = 7 * characterSize + 2 * backgroundMargin; + const float statHeight = pageSize * characterSize + 2 * backgroundMargin; + const float width = statNamesWidth + backgroundSpacing + statTextWidth; + + for (std::size_t offset = 0; offset < mStatNames.size(); offset += pageSize) { - osg::Group* group = new osg::Group; - group->setCullingActive(false); - _resourceStatsChildNum = _switch->getNumChildren(); - _switch->addChild(group, false); + osg::ref_ptr group = new osg::Group; - static const std::vector statNames({ - "FrameNumber", - "", - "Compiling", - "WorkQueue", - "WorkThread", - "UnrefQueue", - "", - "Texture", - "StateSet", - "Node", - "Shape", - "Shape Instance", - "Image", - "Nif", - "Keyframe", - "", - "Groundcover Chunk", - "Object Chunk", - "Terrain Chunk", - "Terrain Texture", - "Land", - "Composite", - "", - "NavMesh Jobs", - "NavMesh Waiting", - "NavMesh Pushed", - "NavMesh Processing", - "NavMesh DbJobs Write", - "NavMesh DbJobs Read", - "NavMesh DbCache Get", - "NavMesh DbCache Hit", - "NavMesh CacheSize", - "NavMesh UsedTiles", - "NavMesh CachedTiles", - "NavMesh Cache Get", - "NavMesh Cache Hit", - "", - "Mechanics Actors", - "Mechanics Objects", - "", - "Physics Actors", - "Physics Objects", - "Physics Projectiles", - "Physics HeightFields", - "", - "Lua UsedMemory", - }); + group->setCullingActive(false); - static const auto longest = std::max_element(statNames.begin(), statNames.end(), - [](const std::string& lhs, const std::string& rhs) { return lhs.size() < rhs.size(); }); - const float statNamesWidth = 13 * _characterSize + 2 * backgroundMargin; - const float statTextWidth = 7 * _characterSize + 2 * backgroundMargin; - const float statHeight = statNames.size() * _characterSize + 2 * backgroundMargin; - osg::Vec3 pos(_statsWidth - statNamesWidth - backgroundSpacing - statTextWidth, statHeight, 0.0f); + const std::size_t count = std::min(mStatNames.size() - offset, pageSize); + std::span currentStatNames(mStatNames.data() + offset, count); + osg::Vec3 pos(statsWidth - width, statHeight - characterSize, 0.0f); group->addChild( - createBackgroundRectangle(pos + osg::Vec3(-backgroundMargin, _characterSize + backgroundMargin, 0), + createBackgroundRectangle(pos + osg::Vec3(-backgroundMargin, backgroundMargin + characterSize, 0), statNamesWidth, statHeight, backgroundColor)); osg::ref_ptr staticText = new osgText::Text; group->addChild(staticText.get()); staticText->setColor(staticTextColor); - staticText->setCharacterSize(_characterSize); + staticText->setCharacterSize(characterSize); staticText->setPosition(pos); std::ostringstream viewStr; viewStr.clear(); viewStr.setf(std::ios::left, std::ios::adjustfield); - viewStr.width(longest->size()); - for (const auto& statName : statNames) - { + viewStr.width(longestSize); + for (const std::string& statName : currentStatNames) viewStr << statName << std::endl; - } staticText->setText(viewStr.str()); pos.x() += statNamesWidth + backgroundSpacing; group->addChild( - createBackgroundRectangle(pos + osg::Vec3(-backgroundMargin, _characterSize + backgroundMargin, 0), + createBackgroundRectangle(pos + osg::Vec3(-backgroundMargin, backgroundMargin + characterSize, 0), statTextWidth, statHeight, backgroundColor)); osg::ref_ptr statsText = new osgText::Text; group->addChild(statsText.get()); statsText->setColor(dynamicTextColor); - statsText->setCharacterSize(_characterSize); + statsText->setCharacterSize(characterSize); statsText->setPosition(pos); statsText->setText(""); - statsText->setDrawCallback(new ResourceStatsTextDrawCallback(viewer->getViewerStats(), statNames)); + statsText->setDrawCallback(new ResourceStatsTextDrawCallback(viewer.getViewerStats(), currentStatNames)); - if (_textFont) + if (mTextFont != nullptr) { - staticText->setFont(_textFont); - statsText->setFont(_textFont); + staticText->setFont(mTextFont); + statsText->setFont(mTextFont); } + + mSwitch->addChild(group, false); } } void StatsHandler::getUsage(osg::ApplicationUsage& usage) const { - usage.addKeyboardMouseBinding(_key, "On screen resource usage stats."); + usage.addKeyboardMouseBinding(statsHandlerKey, "On screen resource usage stats."); } - void CollectStatistics(osgViewer::ViewerBase* viewer) + void collectStatistics(osgViewer::ViewerBase& viewer) { osgViewer::Viewer::Cameras cameras; - viewer->getCameras(cameras); + viewer.getCameras(cameras); for (auto* camera : cameras) { if (collectStatGPU) @@ -537,17 +600,16 @@ namespace Resource camera->getStats()->collectStats("scene", true); } if (collectStatEvent) - viewer->getViewerStats()->collectStats("event", true); + viewer.getViewerStats()->collectStats("event", true); if (collectStatFrameRate) - viewer->getViewerStats()->collectStats("frame_rate", true); + viewer.getViewerStats()->collectStats("frame_rate", true); if (collectStatUpdate) - viewer->getViewerStats()->collectStats("update", true); + viewer.getViewerStats()->collectStats("update", true); if (collectStatResource) - viewer->getViewerStats()->collectStats("resource", true); + viewer.getViewerStats()->collectStats("resource", true); if (collectStatViewerObjects) - viewer->getViewerStats()->collectStats("scene", true); + viewer.getViewerStats()->collectStats("scene", true); if (collectStatEngine) - viewer->getViewerStats()->collectStats("engine", true); + viewer.getViewerStats()->collectStats("engine", true); } - } diff --git a/components/resource/stats.hpp b/components/resource/stats.hpp index fc2899d386..0ea421e83d 100644 --- a/components/resource/stats.hpp +++ b/components/resource/stats.hpp @@ -28,57 +28,47 @@ namespace Resource class Profiler : public osgViewer::StatsHandler { public: - Profiler(bool offlineCollect, VFS::Manager* vfs); + explicit Profiler(bool offlineCollect, const VFS::Manager& vfs); + bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa) override; private: void setUpFonts(); - bool _offlineCollect; - bool _initFonts; - osg::ref_ptr _textFont; + bool mInitFonts = false; + bool mOfflineCollect; + osg::ref_ptr mTextFont; }; class StatsHandler : public osgGA::GUIEventHandler { public: - StatsHandler(bool offlineCollect, VFS::Manager* vfs); - - void setKey(int key) { _key = key; } - int getKey() const { return _key; } + explicit StatsHandler(bool offlineCollect, const VFS::Manager& vfs); bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa) override; - void setWindowSize(int w, int h); - - void toggle(osgViewer::ViewerBase* viewer); - - void setUpHUDCamera(osgViewer::ViewerBase* viewer); - void setUpScene(osgViewer::ViewerBase* viewer); - /** Get the keyboard and mouse usage of this manipulator.*/ void getUsage(osg::ApplicationUsage& usage) const override; private: - osg::ref_ptr _switch; - int _key; - osg::ref_ptr _camera; - bool _initialized; - bool _statsType; - bool _offlineCollect; + unsigned mPage = 0; + bool mInitialized = false; + bool mOfflineCollect; + osg::ref_ptr mSwitch; + osg::ref_ptr mCamera; + osg::ref_ptr mTextFont; + std::vector mStatNames; - float _statsWidth; - float _statsHeight; + void setWindowSize(int w, int h); - float _characterSize; + void toggle(osgViewer::ViewerBase& viewer); - int _resourceStatsChildNum; + void setUpHUDCamera(osgViewer::ViewerBase& viewer); - osg::ref_ptr _textFont; + void setUpScene(osgViewer::ViewerBase& viewer); }; - void CollectStatistics(osgViewer::ViewerBase* viewer); - + void collectStatistics(osgViewer::ViewerBase& viewer); } #endif diff --git a/components/sceneutil/depth.cpp b/components/sceneutil/depth.cpp index 738fa93dd8..5232d321dc 100644 --- a/components/sceneutil/depth.cpp +++ b/components/sceneutil/depth.cpp @@ -4,6 +4,7 @@ #include #include +#include #include namespace SceneUtil @@ -116,8 +117,7 @@ namespace SceneUtil if (Settings::camera().mReverseZ) { - osg::ref_ptr exts = osg::GLExtensions::Get(0, false); - if (exts && exts->isClipControlSupported) + if (SceneUtil::getGLExtensions().isClipControlSupported) { enableReverseZ = true; Log(Debug::Info) << "Using reverse-z depth buffer"; diff --git a/components/sceneutil/extradata.cpp b/components/sceneutil/extradata.cpp index bd82e9abba..5e91830bba 100644 --- a/components/sceneutil/extradata.cpp +++ b/components/sceneutil/extradata.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include diff --git a/components/sceneutil/glextensions.cpp b/components/sceneutil/glextensions.cpp new file mode 100644 index 0000000000..3a14dab8ed --- /dev/null +++ b/components/sceneutil/glextensions.cpp @@ -0,0 +1,60 @@ +#include "glextensions.hpp" + +#include + +namespace SceneUtil +{ + namespace + { + std::set> sGLExtensions; + + class GLExtensionsObserver : public osg::Observer + { + public: + static GLExtensionsObserver sInstance; + + ~GLExtensionsObserver() override + { + for (auto& ptr : sGLExtensions) + { + osg::ref_ptr ref; + if (ptr.lock(ref)) + ref->removeObserver(this); + } + } + + void objectDeleted(void* referenced) override + { + sGLExtensions.erase(static_cast(referenced)); + } + }; + + // construct after sGLExtensions so this gets destroyed first. + GLExtensionsObserver GLExtensionsObserver::sInstance{}; + } + + bool glExtensionsReady() + { + return !sGLExtensions.empty(); + } + + osg::GLExtensions& getGLExtensions() + { + if (sGLExtensions.empty()) + throw std::runtime_error( + "GetGLExtensionsOperation was not used when the current context was created or there is no current " + "context"); + return **sGLExtensions.begin(); + } + + GetGLExtensionsOperation::GetGLExtensionsOperation() + : GraphicsOperation("GetGLExtensionsOperation", false) + { + } + + void GetGLExtensionsOperation::operator()(osg::GraphicsContext* graphicsContext) + { + auto [itr, _] = sGLExtensions.emplace(graphicsContext->getState()->get()); + (*itr)->addObserver(&GLExtensionsObserver::sInstance); + } +} diff --git a/components/sceneutil/glextensions.hpp b/components/sceneutil/glextensions.hpp new file mode 100644 index 0000000000..05e7f715c1 --- /dev/null +++ b/components/sceneutil/glextensions.hpp @@ -0,0 +1,21 @@ +#ifndef OPENMW_COMPONENTS_SCENEUTIL_GLEXTENSIONS_H +#define OPENMW_COMPONENTS_SCENEUTIL_GLEXTENSIONS_H + +#include +#include + +namespace SceneUtil +{ + bool glExtensionsReady(); + osg::GLExtensions& getGLExtensions(); + + class GetGLExtensionsOperation : public osg::GraphicsOperation + { + public: + GetGLExtensionsOperation(); + + void operator()(osg::GraphicsContext* graphicsContext) override; + }; +} + +#endif diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 8f7304416b..c76f0b6b5c 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -824,7 +825,7 @@ namespace SceneUtil , mPointLightFadeEnd(0.f) , mPointLightFadeStart(0.f) { - osg::GLExtensions* exts = osg::GLExtensions::Get(0, false); + osg::GLExtensions* exts = SceneUtil::glExtensionsReady() ? &SceneUtil::getGLExtensions() : nullptr; bool supportsUBO = exts && exts->isUniformBufferObjectSupported; bool supportsGPU4 = exts && exts->isGpuShader4Supported; diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index 06930ebe59..d1553cc8d8 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -30,6 +30,7 @@ #include #include +#include "glextensions.hpp" #include "shadowsbin.hpp" namespace { @@ -920,8 +921,7 @@ void SceneUtil::MWShadowTechnique::setupCastingShader(Shader::ShaderManager & sh // This can't be part of the constructor as OSG mandates that there be a trivial constructor available osg::ref_ptr castingVertexShader = shaderManager.getShader("shadowcasting.vert"); - osg::ref_ptr exts = osg::GLExtensions::Get(0, false); - std::string useGPUShader4 = exts && exts->isGpuShader4Supported ? "1" : "0"; + std::string useGPUShader4 = SceneUtil::getGLExtensions().isGpuShader4Supported ? "1" : "0"; for (int alphaFunc = GL_NEVER; alphaFunc <= GL_ALWAYS; ++alphaFunc) { auto& program = _castingPrograms[alphaFunc - GL_NEVER]; @@ -1025,7 +1025,6 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) { dummyState->setTextureAttribute(i, _fallbackShadowMapTexture, osg::StateAttribute::ON); dummyState->addUniform(new osg::Uniform(("shadowTexture" + std::to_string(i - baseUnit)).c_str(), i)); - dummyState->addUniform(new osg::Uniform(("shadowTextureUnit" + std::to_string(i - baseUnit)).c_str(), i)); } cv.pushStateSet(dummyState); @@ -1711,14 +1710,6 @@ void MWShadowTechnique::createShaders() for (auto& perFrameUniformList : _uniforms) perFrameUniformList.emplace_back(shadowTextureSampler.get()); } - - { - std::stringstream sstr; - sstr<<"shadowTextureUnit"< shadowTextureUnit = new osg::Uniform(sstr.str().c_str(),(int)(settings->getBaseShadowTextureUnit()+sm_i)); - for (auto& perFrameUniformList : _uniforms) - perFrameUniformList.emplace_back(shadowTextureUnit.get()); - } } switch(settings->getShaderHint()) diff --git a/components/sceneutil/osgacontroller.cpp b/components/sceneutil/osgacontroller.cpp index 06b693a6ca..d3268fef36 100644 --- a/components/sceneutil/osgacontroller.cpp +++ b/components/sceneutil/osgacontroller.cpp @@ -1,5 +1,6 @@ #include +#include #include #include #include @@ -9,6 +10,7 @@ #include #include +#include #include #include #include @@ -24,6 +26,10 @@ namespace SceneUtil void LinkVisitor::link(osgAnimation::UpdateMatrixTransform* umt) { + // If osgAnimation had underscores, we should update the umt name also + // otherwise the animation channel and updates wont be applied + umt->setName(Misc::StringUtils::underscoresToSpaces(umt->getName())); + const osgAnimation::ChannelList& channels = mAnimation->getChannels(); for (const auto& channel : channels) { @@ -85,9 +91,8 @@ namespace SceneUtil } } - osg::Vec3f OsgAnimationController::getTranslation(float time) const + osg::Matrixf OsgAnimationController::getTransformForNode(float time, const std::string_view name) const { - osg::Vec3f translationValue; std::string animationName; float newTime = time; @@ -98,10 +103,11 @@ namespace SceneUtil { newTime = time - emulatedAnimation.mStartTime; animationName = emulatedAnimation.mName; + break; } } - // Find the root transform track in animation + // Find the bone's transform track in animation for (const auto& mergedAnimationTrack : mMergedAnimationTracks) { if (mergedAnimationTrack->getName() != animationName) @@ -111,7 +117,7 @@ namespace SceneUtil for (const auto& channel : channels) { - if (channel->getTargetName() != "bip01" || channel->getName() != "transform") + if (!Misc::StringUtils::ciEqual(name, channel->getTargetName()) || channel->getName() != "transform") continue; if (osgAnimation::MatrixLinearSampler* templateSampler @@ -119,13 +125,17 @@ namespace SceneUtil { osg::Matrixf matrix; templateSampler->getValueAt(newTime, matrix); - translationValue = matrix.getTrans(); - return osg::Vec3f(translationValue[0], translationValue[1], translationValue[2]); + return matrix; } } } - return osg::Vec3f(); + return osg::Matrixf::identity(); + } + + osg::Vec3f OsgAnimationController::getTranslation(float time) const + { + return getTransformForNode(time, "bip01").getTrans(); } void OsgAnimationController::update(float time, const std::string& animationName) @@ -162,6 +172,12 @@ namespace SceneUtil update(time - emulatedAnimation.mStartTime, emulatedAnimation.mName); } } + + // Reset the transform of this node to whats in the animation + // we force this here because downstream some code relies on the bone having a non-modified transform + // as this is how the NIF controller behaves. RotationController is a good example of this. + // Without this here, it causes osgAnimation skeletons to spin wildly + static_cast(node)->setMatrix(getTransformForNode(time, node->getName())); } traverse(node, nv); diff --git a/components/sceneutil/osgacontroller.hpp b/components/sceneutil/osgacontroller.hpp index 8739d68b99..bb9a621760 100644 --- a/components/sceneutil/osgacontroller.hpp +++ b/components/sceneutil/osgacontroller.hpp @@ -59,6 +59,9 @@ namespace SceneUtil /// @brief Handles the location of the instance osg::Vec3f getTranslation(float time) const override; + /// @brief Handles finding bone position in the animation + osg::Matrixf getTransformForNode(float time, const std::string_view name) const; + /// @brief Calls animation track update() void update(float time, const std::string& animationName); diff --git a/components/sceneutil/serialize.cpp b/components/sceneutil/serialize.cpp index 8d8acacae4..fa239e692f 100644 --- a/components/sceneutil/serialize.cpp +++ b/components/sceneutil/serialize.cpp @@ -175,18 +175,19 @@ namespace SceneUtil mgr->addWrapper(new GeometrySerializer); // ignore the below for now to avoid warning spam - const char* ignore[] = { "MWRender::PtrHolder", "Resource::TemplateRef", "Resource::TemplateMultiRef", - "SceneUtil::CompositeStateSetUpdater", "SceneUtil::UBOManager", "SceneUtil::LightListCallback", - "SceneUtil::LightManagerUpdateCallback", "SceneUtil::FFPLightStateAttribute", - "SceneUtil::UpdateRigBounds", "SceneUtil::UpdateRigGeometry", "SceneUtil::LightSource", - "SceneUtil::DisableLight", "SceneUtil::MWShadowTechnique", "SceneUtil::TextKeyMapHolder", - "Shader::AddedState", "Shader::RemovedAlphaFunc", "NifOsg::FlipController", - "NifOsg::KeyframeController", "NifOsg::Emitter", "NifOsg::ParticleColorAffector", - "NifOsg::ParticleSystem", "NifOsg::GravityAffector", "NifOsg::ParticleBomb", "NifOsg::GrowFadeAffector", - "NifOsg::InverseWorldMatrix", "NifOsg::StaticBoundingBoxCallback", "NifOsg::GeomMorpherController", - "NifOsg::UpdateMorphGeometry", "NifOsg::UVController", "NifOsg::VisController", "osgMyGUI::Drawable", - "osg::DrawCallback", "osg::UniformBufferObject", "osgOQ::ClearQueriesCallback", - "osgOQ::RetrieveQueriesCallback", "osg::DummyObject" }; + const char* ignore[] + = { "Debug::DebugDrawer", "MWRender::PtrHolder", "Resource::TemplateRef", "Resource::TemplateMultiRef", + "SceneUtil::CompositeStateSetUpdater", "SceneUtil::UBOManager", "SceneUtil::LightListCallback", + "SceneUtil::LightManagerUpdateCallback", "SceneUtil::FFPLightStateAttribute", + "SceneUtil::UpdateRigBounds", "SceneUtil::UpdateRigGeometry", "SceneUtil::LightSource", + "SceneUtil::DisableLight", "SceneUtil::MWShadowTechnique", "SceneUtil::TextKeyMapHolder", + "Shader::AddedState", "Shader::RemovedAlphaFunc", "NifOsg::FlipController", + "NifOsg::KeyframeController", "NifOsg::Emitter", "NifOsg::ParticleColorAffector", + "NifOsg::ParticleSystem", "NifOsg::GravityAffector", "NifOsg::ParticleBomb", + "NifOsg::GrowFadeAffector", "NifOsg::InverseWorldMatrix", "NifOsg::StaticBoundingBoxCallback", + "NifOsg::GeomMorpherController", "NifOsg::UpdateMorphGeometry", "NifOsg::UVController", + "NifOsg::VisController", "osgMyGUI::Drawable", "osg::DrawCallback", "osg::UniformBufferObject", + "osgOQ::ClearQueriesCallback", "osgOQ::RetrieveQueriesCallback", "osg::DummyObject" }; for (size_t i = 0; i < sizeof(ignore) / sizeof(ignore[0]); ++i) { mgr->addWrapper(makeDummySerializer(ignore[i])); diff --git a/components/sceneutil/shadow.cpp b/components/sceneutil/shadow.cpp index 04f3b65edd..0d68ccaa0f 100644 --- a/components/sceneutil/shadow.cpp +++ b/components/sceneutil/shadow.cpp @@ -13,6 +13,16 @@ namespace SceneUtil { using namespace osgShadow; + ShadowManager* ShadowManager::sInstance = nullptr; + + const ShadowManager& ShadowManager::instance() + { + if (sInstance) + return *sInstance; + else + throw std::logic_error("No ShadowManager exists yet"); + } + void ShadowManager::setupShadowSettings( const Settings::ShadowsCategory& settings, Shader::ShaderManager& shaderManager) { @@ -75,15 +85,11 @@ namespace SceneUtil mShadowTechnique->disableDebugHUD(); } - void ShadowManager::disableShadowsForStateSet(const Settings::ShadowsCategory& settings, osg::StateSet& stateset) + void ShadowManager::disableShadowsForStateSet(osg::StateSet& stateset) const { - if (!settings.mEnableShadows) + if (!mEnableShadows) return; - const int numberOfShadowMapsPerLight = settings.mNumberOfShadowMaps; - - int baseShadowTextureUnit = 8 - numberOfShadowMapsPerLight; - osg::ref_ptr fakeShadowMapImage = new osg::Image(); fakeShadowMapImage->allocateImage(1, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT); *(float*)fakeShadowMapImage->data() = std::numeric_limits::infinity(); @@ -92,14 +98,14 @@ namespace SceneUtil fakeShadowMapTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); fakeShadowMapTexture->setShadowComparison(true); fakeShadowMapTexture->setShadowCompareFunc(osg::Texture::ShadowCompareFunc::ALWAYS); - for (int i = baseShadowTextureUnit; i < baseShadowTextureUnit + numberOfShadowMapsPerLight; ++i) + for (unsigned int i = mShadowSettings->getBaseShadowTextureUnit(); + i < mShadowSettings->getBaseShadowTextureUnit() + mShadowSettings->getNumShadowMapsPerLight(); ++i) { - stateset.setTextureAttributeAndModes(i, fakeShadowMapTexture, + stateset.setTextureAttribute(i, fakeShadowMapTexture, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED); - stateset.addUniform( - new osg::Uniform(("shadowTexture" + std::to_string(i - baseShadowTextureUnit)).c_str(), i)); - stateset.addUniform( - new osg::Uniform(("shadowTextureUnit" + std::to_string(i - baseShadowTextureUnit)).c_str(), i)); + stateset.addUniform(new osg::Uniform( + ("shadowTexture" + std::to_string(i - mShadowSettings->getBaseShadowTextureUnit())).c_str(), + static_cast(i))); } } @@ -111,6 +117,9 @@ namespace SceneUtil , mOutdoorShadowCastingMask(outdoorShadowCastingMask) , mIndoorShadowCastingMask(indoorShadowCastingMask) { + if (sInstance) + throw std::logic_error("A ShadowManager already exists"); + mShadowedScene->setShadowTechnique(mShadowTechnique); if (Stereo::getStereo()) @@ -127,6 +136,8 @@ namespace SceneUtil mShadowTechnique->setWorldMask(worldMask); enableOutdoorMode(); + + sInstance = this; } ShadowManager::~ShadowManager() @@ -135,7 +146,7 @@ namespace SceneUtil Stereo::Manager::instance().setShadowTechnique(nullptr); } - Shader::ShaderManager::DefineMap ShadowManager::getShadowDefines(const Settings::ShadowsCategory& settings) + Shader::ShaderManager::DefineMap ShadowManager::getShadowDefines(const Settings::ShadowsCategory& settings) const { if (!mEnableShadows) return getShadowsDisabledDefines(); diff --git a/components/sceneutil/shadow.hpp b/components/sceneutil/shadow.hpp index fd82e828b6..952d750051 100644 --- a/components/sceneutil/shadow.hpp +++ b/components/sceneutil/shadow.hpp @@ -26,10 +26,10 @@ namespace SceneUtil class ShadowManager { public: - static void disableShadowsForStateSet(const Settings::ShadowsCategory& settings, osg::StateSet& stateset); - static Shader::ShaderManager::DefineMap getShadowsDisabledDefines(); + static const ShadowManager& instance(); + explicit ShadowManager(osg::ref_ptr sceneRoot, osg::ref_ptr rootNode, unsigned int outdoorShadowCastingMask, unsigned int indoorShadowCastingMask, unsigned int worldMask, const Settings::ShadowsCategory& settings, Shader::ShaderManager& shaderManager); @@ -37,13 +37,17 @@ namespace SceneUtil void setupShadowSettings(const Settings::ShadowsCategory& settings, Shader::ShaderManager& shaderManager); - Shader::ShaderManager::DefineMap getShadowDefines(const Settings::ShadowsCategory& settings); + void disableShadowsForStateSet(osg::StateSet& stateset) const; + + Shader::ShaderManager::DefineMap getShadowDefines(const Settings::ShadowsCategory& settings) const; void enableIndoorMode(const Settings::ShadowsCategory& settings); void enableOutdoorMode(); protected: + static ShadowManager* sInstance; + bool mEnableShadows; osg::ref_ptr mShadowedScene; diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index ab600de11d..21a753df12 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -286,4 +286,125 @@ namespace SceneUtil mOperationQueue->add(operation); } + GLenum computeUnsizedPixelFormat(GLenum format) + { + switch (format) + { + // Try compressed formats first, they're more likely to be used + + // Generic + case GL_COMPRESSED_ALPHA_ARB: + return GL_ALPHA; + case GL_COMPRESSED_INTENSITY_ARB: + return GL_INTENSITY; + case GL_COMPRESSED_LUMINANCE_ALPHA_ARB: + return GL_LUMINANCE_ALPHA; + case GL_COMPRESSED_LUMINANCE_ARB: + return GL_LUMINANCE; + case GL_COMPRESSED_RGB_ARB: + return GL_RGB; + case GL_COMPRESSED_RGBA_ARB: + return GL_RGBA; + + // S3TC + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT: + return GL_RGB; + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT: + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: + case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT: + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: + return GL_RGBA; + + // RGTC + case GL_COMPRESSED_RED_RGTC1_EXT: + case GL_COMPRESSED_SIGNED_RED_RGTC1_EXT: + return GL_RED; + case GL_COMPRESSED_RED_GREEN_RGTC2_EXT: + case GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT: + return GL_RG; + + // PVRTC + case GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG: + case GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG: + return GL_RGB; + case GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG: + case GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG: + return GL_RGBA; + + // ETC + case GL_COMPRESSED_R11_EAC: + case GL_COMPRESSED_SIGNED_R11_EAC: + return GL_RED; + case GL_COMPRESSED_RG11_EAC: + case GL_COMPRESSED_SIGNED_RG11_EAC: + return GL_RG; + case GL_ETC1_RGB8_OES: + case GL_COMPRESSED_RGB8_ETC2: + case GL_COMPRESSED_SRGB8_ETC2: + return GL_RGB; + case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2: + case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2: + case GL_COMPRESSED_RGBA8_ETC2_EAC: + case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC: + return GL_RGBA; + + // ASTC + case GL_COMPRESSED_RGBA_ASTC_4x4_KHR: + case GL_COMPRESSED_RGBA_ASTC_5x4_KHR: + case GL_COMPRESSED_RGBA_ASTC_5x5_KHR: + case GL_COMPRESSED_RGBA_ASTC_6x5_KHR: + case GL_COMPRESSED_RGBA_ASTC_6x6_KHR: + case GL_COMPRESSED_RGBA_ASTC_8x5_KHR: + case GL_COMPRESSED_RGBA_ASTC_8x6_KHR: + case GL_COMPRESSED_RGBA_ASTC_8x8_KHR: + case GL_COMPRESSED_RGBA_ASTC_10x5_KHR: + case GL_COMPRESSED_RGBA_ASTC_10x6_KHR: + case GL_COMPRESSED_RGBA_ASTC_10x8_KHR: + case GL_COMPRESSED_RGBA_ASTC_10x10_KHR: + case GL_COMPRESSED_RGBA_ASTC_12x10_KHR: + case GL_COMPRESSED_RGBA_ASTC_12x12_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR: + return GL_RGBA; + + // Plug in some holes computePixelFormat has, you never know when these could come in handy + case GL_INTENSITY4: + case GL_INTENSITY8: + case GL_INTENSITY12: + case GL_INTENSITY16: + return GL_INTENSITY; + + case GL_LUMINANCE4: + case GL_LUMINANCE8: + case GL_LUMINANCE12: + case GL_LUMINANCE16: + return GL_LUMINANCE; + + case GL_LUMINANCE4_ALPHA4: + case GL_LUMINANCE6_ALPHA2: + case GL_LUMINANCE8_ALPHA8: + case GL_LUMINANCE12_ALPHA4: + case GL_LUMINANCE12_ALPHA12: + case GL_LUMINANCE16_ALPHA16: + return GL_LUMINANCE_ALPHA; + } + + return osg::Image::computePixelFormat(format); + } + } diff --git a/components/sceneutil/util.hpp b/components/sceneutil/util.hpp index 29fee09176..b76f46a688 100644 --- a/components/sceneutil/util.hpp +++ b/components/sceneutil/util.hpp @@ -112,6 +112,10 @@ namespace SceneUtil protected: osg::ref_ptr mOperationQueue; }; + + // Compute the unsized format equivalent to the given pixel format + // Unlike osg::Image::computePixelFormat, this also covers compressed formats + GLenum computeUnsizedPixelFormat(GLenum format); } #endif diff --git a/components/sceneutil/visitor.cpp b/components/sceneutil/visitor.cpp index aadbf35af8..8d1bf46322 100644 --- a/components/sceneutil/visitor.cpp +++ b/components/sceneutil/visitor.cpp @@ -5,6 +5,8 @@ #include +#include + #include #include @@ -13,7 +15,6 @@ namespace SceneUtil { - bool FindByNameVisitor::checkGroup(osg::Group& group) { if (Misc::StringUtils::ciEqual(group.getName(), mNameToFind)) @@ -21,6 +22,7 @@ namespace SceneUtil mFoundNode = &group; return true; } + return false; } @@ -46,24 +48,22 @@ namespace SceneUtil void FindByNameVisitor::apply(osg::Geometry&) {} - void NodeMapVisitor::apply(osg::MatrixTransform& trans) + void NodeMapVisitorBoneOnly::apply(osg::MatrixTransform& trans) { - // Choose first found node in file - - if (trans.libraryName() == std::string_view("osgAnimation")) - { - std::string nodeName = trans.getName(); - // Convert underscores to whitespaces as a workaround for Collada (OpenMW's animation system uses - // whitespace-separated names) - std::replace(nodeName.begin(), nodeName.end(), '_', ' '); - mMap.emplace(nodeName, &trans); - } - else + // Choose first found bone in file + if (dynamic_cast(&trans) != nullptr) mMap.emplace(trans.getName(), &trans); traverse(trans); } + void NodeMapVisitor::apply(osg::MatrixTransform& trans) + { + // Choose first found node in file + mMap.emplace(trans.getName(), &trans); + traverse(trans); + } + void RemoveVisitor::remove() { for (RemoveVec::iterator it = mToRemove.begin(); it != mToRemove.end(); ++it) diff --git a/components/sceneutil/visitor.hpp b/components/sceneutil/visitor.hpp index 3e3df4d4b3..a9a943423c 100644 --- a/components/sceneutil/visitor.hpp +++ b/components/sceneutil/visitor.hpp @@ -50,14 +50,14 @@ namespace SceneUtil std::vector mFoundNodes; }; + typedef std::unordered_map, Misc::StringUtils::CiHash, + Misc::StringUtils::CiEqual> + NodeMap; + /// Maps names to nodes class NodeMapVisitor : public osg::NodeVisitor { public: - typedef std::unordered_map, Misc::StringUtils::CiHash, - Misc::StringUtils::CiEqual> - NodeMap; - NodeMapVisitor(NodeMap& map) : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) , mMap(map) @@ -70,6 +70,22 @@ namespace SceneUtil NodeMap& mMap; }; + /// Maps names to bone nodes + class NodeMapVisitorBoneOnly : public osg::NodeVisitor + { + public: + NodeMapVisitorBoneOnly(NodeMap& map) + : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) + , mMap(map) + { + } + + void apply(osg::MatrixTransform& trans) override; + + private: + NodeMap& mMap; + }; + /// @brief Base class for visitors that remove nodes from a scene graph. /// Subclasses need to fill the mToRemove vector. /// To use, node->accept(removeVisitor); removeVisitor.remove(); diff --git a/components/sdlutil/sdlinputwrapper.cpp b/components/sdlutil/sdlinputwrapper.cpp index cc9706732e..43de84bb70 100644 --- a/components/sdlutil/sdlinputwrapper.cpp +++ b/components/sdlutil/sdlinputwrapper.cpp @@ -252,6 +252,11 @@ namespace SDLUtil SDL_GL_GetDrawableSize(mSDLWindow, &w, &h); int x, y; SDL_GetWindowPosition(mSDLWindow, &x, &y); + + // Happens when you Alt-Tab out of game + if (w == 0 && h == 0) + return; + mViewer->getCamera()->getGraphicsContext()->resized(x, y, w, h); mViewer->getEventQueue()->windowResize(x, y, w, h); diff --git a/components/sdlutil/sdlmappings.cpp b/components/sdlutil/sdlmappings.cpp index fe248e6f70..8a82206c33 100644 --- a/components/sdlutil/sdlmappings.cpp +++ b/components/sdlutil/sdlmappings.cpp @@ -83,7 +83,7 @@ namespace SDLUtil Uint8 myGuiMouseButtonToSdl(MyGUI::MouseButton button) { - Uint8 value = button.getValue() + 1; + Uint8 value = static_cast(button.getValue() + 1); if (value == SDL_BUTTON_RIGHT) value = SDL_BUTTON_MIDDLE; else if (value == SDL_BUTTON_MIDDLE) diff --git a/components/settings/categories/navigator.hpp b/components/settings/categories/navigator.hpp index d6d7adcd56..c65dd3392e 100644 --- a/components/settings/categories/navigator.hpp +++ b/components/settings/categories/navigator.hpp @@ -63,6 +63,7 @@ namespace Settings SettingValue mEnableNavMeshDiskCache{ mIndex, "Navigator", "enable nav mesh disk cache" }; SettingValue mWriteToNavmeshdb{ mIndex, "Navigator", "write to navmeshdb" }; SettingValue mMaxNavmeshdbFileSize{ mIndex, "Navigator", "max navmeshdb file size" }; + SettingValue mWaitForAllJobsOnExit{ mIndex, "Navigator", "wait for all jobs on exit" }; }; } diff --git a/components/settings/categories/sound.hpp b/components/settings/categories/sound.hpp index 995bce2a58..8398a38c55 100644 --- a/components/settings/categories/sound.hpp +++ b/components/settings/categories/sound.hpp @@ -23,6 +23,7 @@ namespace Settings SettingValue mBufferCacheMax{ mIndex, "Sound", "buffer cache max", makeMaxSanitizerInt(1) }; SettingValue mHrtfEnable{ mIndex, "Sound", "hrtf enable" }; SettingValue mHrtf{ mIndex, "Sound", "hrtf" }; + SettingValue mCameraListener{ mIndex, "Sound", "camera listener" }; }; } diff --git a/components/settings/categories/water.hpp b/components/settings/categories/water.hpp index 2e04114244..63adce4ee3 100644 --- a/components/settings/categories/water.hpp +++ b/components/settings/categories/water.hpp @@ -26,6 +26,8 @@ namespace Settings SettingValue mSmallFeatureCullingPixelSize{ mIndex, "Water", "small feature culling pixel size", makeMaxStrictSanitizerFloat(0) }; SettingValue mRefractionScale{ mIndex, "Water", "refraction scale", makeClampSanitizerFloat(0, 1) }; + SettingValue mSunlightScattering{ mIndex, "Water", "sunlight scattering" }; + SettingValue mWobblyShores{ mIndex, "Water", "wobbly shores" }; }; } diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index e281f64448..2676ea3168 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -22,9 +22,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -183,6 +185,7 @@ namespace Shader , mAdditiveBlending(false) , mDiffuseHeight(false) , mNormalHeight(false) + , mReconstructNormalZ(false) , mTexStageRequiringTangents(-1) , mSoftParticles(false) , mNode(nullptr) @@ -428,6 +431,7 @@ namespace Shader normalMapTex->setFilter(osg::Texture::MAG_FILTER, diffuseMap->getFilter(osg::Texture::MAG_FILTER)); normalMapTex->setMaxAnisotropy(diffuseMap->getMaxAnisotropy()); normalMapTex->setName("normalMap"); + normalMap = normalMapTex; int unit = texAttributes.size(); if (!writableStateSet) @@ -439,6 +443,21 @@ namespace Shader mRequirements.back().mNormalHeight = normalHeight; } } + + if (normalMap != nullptr && normalMap->getImage(0)) + { + // Special handling for red-green normal maps (e.g. BC5 or R8G8) + switch (SceneUtil::computeUnsizedPixelFormat(normalMap->getImage(0)->getPixelFormat())) + { + case GL_RG: + case GL_RG_INTEGER: + { + mRequirements.back().mReconstructNormalZ = true; + mRequirements.back().mNormalHeight = false; + } + } + } + if (mAutoUseSpecularMaps && diffuseMap != nullptr && specularMap == nullptr && diffuseMap->getImage(0)) { std::string specularMapFileName = diffuseMap->getImage(0)->getFileName(); @@ -628,6 +647,7 @@ namespace Shader defineMap["diffuseParallax"] = reqs.mDiffuseHeight ? "1" : "0"; defineMap["parallax"] = reqs.mNormalHeight ? "1" : "0"; + defineMap["reconstructNormalZ"] = reqs.mReconstructNormalZ ? "1" : "0"; writableStateSet->addUniform(new osg::Uniform("colorMode", reqs.mColorMode)); addedState->addUniform("colorMode"); @@ -676,8 +696,7 @@ namespace Shader defineMap["adjustCoverage"] = "1"; // Preventing alpha tested stuff shrinking as lower mip levels are used requires knowing the texture size - osg::ref_ptr exts = osg::GLExtensions::Get(0, false); - if (exts && exts->isGpuShader4Supported) + if (SceneUtil::getGLExtensions().isGpuShader4Supported) defineMap["useGPUShader4"] = "1"; // We could fall back to a texture size uniform if EXT_gpu_shader4 is missing } diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index a8e79ec995..9ce0819bd3 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -110,6 +110,7 @@ namespace Shader bool mDiffuseHeight; // true if diffuse map has height info in alpha channel bool mNormalHeight; // true if normal map has height info in alpha channel + bool mReconstructNormalZ; // used for red-green normal maps (e.g. BC5) // -1 == no tangents required int mTexStageRequiringTangents; diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 8df5dc3a77..7ccd89ac21 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -63,7 +63,7 @@ namespace Terrain void ChunkManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const { - stats->setAttribute(frameNumber, "Terrain Chunk", mCache->getCacheSize()); + Resource::reportStats("Terrain Chunk", frameNumber, mCache->getStats(), *stats); } void ChunkManager::clearCache() diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index fafe2dcb58..09d2680acd 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -271,18 +272,37 @@ namespace Terrain stateset->addUniform(UniformCollection::value().mBlendMap); } + bool parallax = it->mNormalMap && it->mParallax; + bool reconstructNormalZ = false; + if (it->mNormalMap) { stateset->setTextureAttributeAndModes(2, it->mNormalMap); stateset->addUniform(UniformCollection::value().mNormalMap); + + // Special handling for red-green normal maps (e.g. BC5 or R8G8). + const osg::Image* image = it->mNormalMap->getImage(0); + if (image) + { + switch (SceneUtil::computeUnsizedPixelFormat(image->getPixelFormat())) + { + case GL_RG: + case GL_RG_INTEGER: + { + reconstructNormalZ = true; + parallax = false; + } + } + } } Shader::ShaderManager::DefineMap defineMap; defineMap["normalMap"] = (it->mNormalMap) ? "1" : "0"; defineMap["blendMap"] = (!blendmaps.empty()) ? "1" : "0"; defineMap["specularMap"] = it->mSpecular ? "1" : "0"; - defineMap["parallax"] = (it->mNormalMap && it->mParallax) ? "1" : "0"; + defineMap["parallax"] = parallax ? "1" : "0"; defineMap["writeNormals"] = (it == layers.end() - 1) ? "1" : "0"; + defineMap["reconstructNormalZ"] = reconstructNormalZ ? "1" : "0"; Stereo::shaderStereoDefines(defineMap); stateset->setAttributeAndModes(shaderManager.getProgram("terrain", defineMap)); diff --git a/components/terrain/texturemanager.cpp b/components/terrain/texturemanager.cpp index 360d87bf48..6b388faf69 100644 --- a/components/terrain/texturemanager.cpp +++ b/components/terrain/texturemanager.cpp @@ -1,6 +1,5 @@ #include "texturemanager.hpp" -#include #include #include @@ -56,7 +55,7 @@ namespace Terrain void TextureManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const { - stats->setAttribute(frameNumber, "Terrain Texture", mCache->getCacheSize()); + Resource::reportStats("Terrain Texture", frameNumber, mCache->getStats(), *stats); } } diff --git a/components/version/version.cpp.in b/components/version/version.cpp.in index 312520acbb..12192785b7 100644 --- a/components/version/version.cpp.in +++ b/components/version/version.cpp.in @@ -52,4 +52,11 @@ namespace Version return getVersion() == version && getCommitHash() == commitHash && getTagHash() == tagHash; } + std::string_view getDocumentationUrl() + { + if constexpr (std::string_view("@OPENMW_VERSION_COMMITHASH@") == "@OPENMW_VERSION_TAGHASH@") + return OPENMW_DOC_BASEURL "openmw-@OPENMW_VERSION_MAJOR@.@OPENMW_VERSION_MINOR@.@OPENMW_VERSION_RELEASE@/"; + else + return OPENMW_DOC_BASEURL "latest/"; + } } diff --git a/components/version/version.hpp b/components/version/version.hpp index c05cf8a594..a8a8117dee 100644 --- a/components/version/version.hpp +++ b/components/version/version.hpp @@ -17,6 +17,8 @@ namespace Version std::string getOpenmwVersionDescription(); bool checkResourcesVersion(const std::filesystem::path& resourcePath); + + std::string_view getDocumentationUrl(); } #endif // VERSION_HPP diff --git a/components/vfs/archive.hpp b/components/vfs/archive.hpp index 42b88219d7..bd793b8523 100644 --- a/components/vfs/archive.hpp +++ b/components/vfs/archive.hpp @@ -2,9 +2,9 @@ #define OPENMW_COMPONENTS_VFS_ARCHIVE_H #include -#include #include "filemap.hpp" +#include "pathutil.hpp" namespace VFS { @@ -17,7 +17,7 @@ namespace VFS virtual void listResources(FileMap& out) = 0; /// True if this archive contains the provided normalized file. - virtual bool contains(std::string_view file) const = 0; + virtual bool contains(Path::NormalizedView file) const = 0; virtual std::string getDescription() const = 0; }; diff --git a/components/vfs/bsaarchive.hpp b/components/vfs/bsaarchive.hpp index 304fc438ad..664466fa40 100644 --- a/components/vfs/bsaarchive.hpp +++ b/components/vfs/bsaarchive.hpp @@ -10,6 +10,10 @@ #include #include +#include +#include +#include + namespace VFS { template @@ -44,30 +48,21 @@ namespace VFS for (Bsa::BSAFile::FileList::const_iterator it = filelist.begin(); it != filelist.end(); ++it) { mResources.emplace_back(&*it, mFile.get()); + mFiles.emplace_back(it->name()); } - } - virtual ~BsaArchive() {} + std::sort(mFiles.begin(), mFiles.end()); + } void listResources(FileMap& out) override { for (auto& resource : mResources) - { - std::string ent = resource.mInfo->name(); - Path::normalizeFilenameInPlace(ent); - - out[ent] = &resource; - } + out[VFS::Path::Normalized(resource.mInfo->name())] = &resource; } - bool contains(std::string_view file) const override + bool contains(Path::NormalizedView file) const override { - for (const auto& it : mResources) - { - if (Path::pathEqual(file, it.mInfo->name())) - return true; - } - return false; + return std::binary_search(mFiles.begin(), mFiles.end(), file); } std::string getDescription() const override { return std::string{ "BSA: " } + mFile->getFilename(); } @@ -75,36 +70,27 @@ namespace VFS private: std::unique_ptr mFile; std::vector> mResources; + std::vector mFiles; }; - template - struct ArchiveSelector - { - }; - - template <> - struct ArchiveSelector + inline std::unique_ptr makeBsaArchive(const std::filesystem::path& path) { - using type = BsaArchive; - }; - - template <> - struct ArchiveSelector - { - using type = BsaArchive; - }; - - template <> - struct ArchiveSelector - { - using type = BsaArchive; - }; + switch (Bsa::BSAFile::detectVersion(path)) + { + case Bsa::BsaVersion::Unknown: + break; + case Bsa::BsaVersion::Uncompressed: + return std::make_unique>(path); + case Bsa::BsaVersion::Compressed: + return std::make_unique>(path); + case Bsa::BsaVersion::BA2GNRL: + return std::make_unique>(path); + case Bsa::BsaVersion::BA2DX10: + return std::make_unique>(path); + } - template <> - struct ArchiveSelector - { - using type = BsaArchive; - }; + throw std::runtime_error("Unknown archive type '" + Files::pathToUnicodeString(path) + "'"); + } } #endif diff --git a/components/vfs/filesystemarchive.cpp b/components/vfs/filesystemarchive.cpp index 7d88dd9cc0..3303c6656c 100644 --- a/components/vfs/filesystemarchive.cpp +++ b/components/vfs/filesystemarchive.cpp @@ -12,51 +12,54 @@ namespace VFS { FileSystemArchive::FileSystemArchive(const std::filesystem::path& path) - : mBuiltIndex(false) - , mPath(path) + : mPath(path) { - } + const auto str = mPath.u8string(); + std::size_t prefix = str.size(); - void FileSystemArchive::listResources(FileMap& out) - { - if (!mBuiltIndex) - { - const auto str = mPath.u8string(); - size_t prefix = str.size(); + if (prefix > 0 && str[prefix - 1] != '\\' && str[prefix - 1] != '/') + ++prefix; - if (!mPath.empty() && str[prefix - 1] != '\\' && str[prefix - 1] != '/') - ++prefix; + std::filesystem::recursive_directory_iterator iterator(mPath); - for (const auto& i : std::filesystem::recursive_directory_iterator(mPath)) - { - if (std::filesystem::is_directory(i)) - continue; - - const auto& path = i.path(); - const std::string proper = Files::pathToUnicodeString(path); - - FileSystemArchiveFile file(path); + for (auto it = std::filesystem::begin(iterator), end = std::filesystem::end(iterator); it != end;) + { + const std::filesystem::directory_entry& entry = *it; - std::string searchable = Path::normalizeFilename(std::string_view{ proper }.substr(prefix)); + if (!entry.is_directory()) + { + const std::filesystem::path& filePath = entry.path(); + const std::string proper = Files::pathToUnicodeString(filePath); + VFS::Path::Normalized searchable(std::string_view{ proper }.substr(prefix)); + FileSystemArchiveFile file(filePath); - const auto inserted = mIndex.emplace(searchable, file); + const auto inserted = mIndex.emplace(std::move(searchable), std::move(file)); if (!inserted.second) Log(Debug::Warning) - << "Warning: found duplicate file for '" << proper + << "Found duplicate file for '" << proper << "', please check your file system for two files with the same name in different cases."; - else - out[inserted.first->first] = &inserted.first->second; } - mBuiltIndex = true; - } - else - { - for (auto& [k, v] : mIndex) - out[k] = &v; + + // Exception thrown by the operator++ may not contain the context of the error like what exact path caused + // the problem which makes it hard to understand what's going on when iteration happens over a directory + // with thousands of files and subdirectories. + const std::filesystem::path prevPath = entry.path(); + std::error_code ec; + it.increment(ec); + if (ec != std::error_code()) + throw std::runtime_error("Failed to recursively iterate over \"" + Files::pathToUnicodeString(mPath) + + "\" when incrementing to the next item from \"" + Files::pathToUnicodeString(prevPath) + + "\": " + ec.message()); } } - bool FileSystemArchive::contains(std::string_view file) const + void FileSystemArchive::listResources(FileMap& out) + { + for (auto& [k, v] : mIndex) + out[k] = &v; + } + + bool FileSystemArchive::contains(Path::NormalizedView file) const { return mIndex.find(file) != mIndex.end(); } diff --git a/components/vfs/filesystemarchive.hpp b/components/vfs/filesystemarchive.hpp index 00fe5ba971..215c443b58 100644 --- a/components/vfs/filesystemarchive.hpp +++ b/components/vfs/filesystemarchive.hpp @@ -30,13 +30,12 @@ namespace VFS void listResources(FileMap& out) override; - bool contains(std::string_view file) const override; + bool contains(Path::NormalizedView file) const override; std::string getDescription() const override; private: - std::map> mIndex; - bool mBuiltIndex; + std::map> mIndex; std::filesystem::path mPath; }; diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index a6add0861a..2d22a7a034 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -43,6 +43,11 @@ namespace VFS return getNormalized(name); } + Files::IStreamPtr Manager::get(Path::NormalizedView name) const + { + return getNormalized(name.value()); + } + Files::IStreamPtr Manager::getNormalized(std::string_view normalizedName) const { assert(Path::isNormalized(normalizedName)); @@ -57,6 +62,11 @@ namespace VFS return mIndex.find(name) != mIndex.end(); } + bool Manager::exists(Path::NormalizedView name) const + { + return mIndex.find(name) != mIndex.end(); + } + std::string Manager::getArchive(const Path::Normalized& name) const { for (auto it = mArchives.rbegin(); it != mArchives.rend(); ++it) @@ -89,4 +99,21 @@ namespace VFS ++normalized.back(); return { it, mIndex.lower_bound(normalized) }; } + + RecursiveDirectoryRange Manager::getRecursiveDirectoryIterator(VFS::Path::NormalizedView path) const + { + if (path.value().empty()) + return { mIndex.begin(), mIndex.end() }; + const auto it = mIndex.lower_bound(path); + if (it == mIndex.end() || !it->first.view().starts_with(path.value())) + return { it, it }; + std::string copy(path.value()); + ++copy.back(); + return { it, mIndex.lower_bound(copy) }; + } + + RecursiveDirectoryRange Manager::getRecursiveDirectoryIterator() const + { + return { mIndex.begin(), mIndex.end() }; + } } diff --git a/components/vfs/manager.hpp b/components/vfs/manager.hpp index 7598b77e68..d64be1d1d1 100644 --- a/components/vfs/manager.hpp +++ b/components/vfs/manager.hpp @@ -43,11 +43,15 @@ namespace VFS /// @note May be called from any thread once the index has been built. bool exists(const Path::Normalized& name) const; + bool exists(Path::NormalizedView name) const; + /// Retrieve a file by name. /// @note Throws an exception if the file can not be found. /// @note May be called from any thread once the index has been built. Files::IStreamPtr get(const Path::Normalized& name) const; + Files::IStreamPtr get(Path::NormalizedView name) const; + /// Retrieve a file by name (name is already normalized). /// @note Throws an exception if the file can not be found. /// @note May be called from any thread once the index has been built. @@ -61,6 +65,10 @@ namespace VFS /// @note May be called from any thread once the index has been built. RecursiveDirectoryRange getRecursiveDirectoryIterator(std::string_view path) const; + RecursiveDirectoryRange getRecursiveDirectoryIterator(VFS::Path::NormalizedView path) const; + + RecursiveDirectoryRange getRecursiveDirectoryIterator() const; + /// Retrieve the absolute path to the file /// @note Throws an exception if the file can not be found. /// @note May be called from any thread once the index has been built. diff --git a/components/vfs/pathutil.hpp b/components/vfs/pathutil.hpp index 0856bfffa2..50f16d1804 100644 --- a/components/vfs/pathutil.hpp +++ b/components/vfs/pathutil.hpp @@ -5,14 +5,18 @@ #include #include +#include #include #include namespace VFS::Path { + inline constexpr char separator = '/'; + inline constexpr char extensionSeparator = '.'; + inline constexpr char normalize(char c) { - return c == '\\' ? '/' : Misc::StringUtils::toLower(c); + return c == '\\' ? separator : Misc::StringUtils::toLower(c); } inline constexpr bool isNormalized(std::string_view name) @@ -20,9 +24,14 @@ namespace VFS::Path return std::all_of(name.begin(), name.end(), [](char v) { return v == normalize(v); }); } + inline void normalizeFilenameInPlace(auto begin, auto end) + { + std::transform(begin, end, begin, normalize); + } + inline void normalizeFilenameInPlace(std::string& name) { - std::transform(name.begin(), name.end(), name.begin(), normalize); + normalizeFilenameInPlace(name.begin(), name.end()); } /// Normalize the given filename, making slashes/backslashes consistent, and lower-casing. @@ -58,17 +67,75 @@ namespace VFS::Path bool operator()(std::string_view left, std::string_view right) const { return pathLess(left, right); } }; + inline constexpr auto findSeparatorOrExtensionSeparator(auto begin, auto end) + { + return std::find_if(begin, end, [](char v) { return v == extensionSeparator || v == separator; }); + } + + class Normalized; + + class NormalizedView + { + public: + constexpr NormalizedView() noexcept = default; + + constexpr explicit NormalizedView(const char* value) + : mValue(value) + { + if (!isNormalized(mValue)) + throw std::invalid_argument("NormalizedView value is not normalized: \"" + std::string(mValue) + "\""); + } + + NormalizedView(const Normalized& value) noexcept; + + constexpr std::string_view value() const noexcept { return mValue; } + + friend constexpr bool operator==(const NormalizedView& lhs, const NormalizedView& rhs) = default; + + friend constexpr bool operator==(const NormalizedView& lhs, const auto& rhs) { return lhs.mValue == rhs; } + +#if defined(_MSC_VER) && _MSC_VER <= 1935 + friend constexpr bool operator==(const auto& lhs, const NormalizedView& rhs) + { + return lhs == rhs.mValue; + } +#endif + + friend constexpr bool operator<(const NormalizedView& lhs, const NormalizedView& rhs) + { + return lhs.mValue < rhs.mValue; + } + + friend constexpr bool operator<(const NormalizedView& lhs, const auto& rhs) + { + return lhs.mValue < rhs; + } + + friend constexpr bool operator<(const auto& lhs, const NormalizedView& rhs) + { + return lhs < rhs.mValue; + } + + friend std::ostream& operator<<(std::ostream& stream, const NormalizedView& value) + { + return stream << value.mValue; + } + + private: + std::string_view mValue; + }; + class Normalized { public: Normalized() = default; - Normalized(std::string_view value) + explicit Normalized(std::string_view value) : mValue(normalizeFilename(value)) { } - Normalized(const char* value) + explicit Normalized(const char* value) : Normalized(std::string_view(value)) { } @@ -84,6 +151,11 @@ namespace VFS::Path normalizeFilenameInPlace(mValue); } + explicit Normalized(NormalizedView value) + : mValue(value.value()) + { + } + const std::string& value() const& { return mValue; } std::string value() && { return std::move(mValue); } @@ -94,28 +166,84 @@ namespace VFS::Path operator const std::string&() const { return mValue; } + bool changeExtension(std::string_view extension) + { + if (findSeparatorOrExtensionSeparator(extension.begin(), extension.end()) != extension.end()) + throw std::invalid_argument("Invalid extension: " + std::string(extension)); + const auto it = findSeparatorOrExtensionSeparator(mValue.rbegin(), mValue.rend()); + if (it == mValue.rend() || *it == separator) + return false; + const std::string::difference_type pos = mValue.rend() - it; + mValue.replace(pos, mValue.size(), extension); + normalizeFilenameInPlace(mValue.begin() + pos, mValue.end()); + return true; + } + + Normalized& operator=(NormalizedView value) + { + mValue = value.value(); + return *this; + } + + Normalized& operator/=(NormalizedView value) + { + mValue.reserve(mValue.size() + value.value().size() + 1); + mValue += separator; + mValue += value.value(); + return *this; + } + + Normalized& operator/=(std::string_view value) + { + mValue.reserve(mValue.size() + value.size() + 1); + mValue += separator; + const std::size_t offset = mValue.size(); + mValue += value; + normalizeFilenameInPlace(mValue.begin() + offset, mValue.end()); + return *this; + } + friend bool operator==(const Normalized& lhs, const Normalized& rhs) = default; - template - friend bool operator==(const Normalized& lhs, const T& rhs) + friend bool operator==(const Normalized& lhs, const auto& rhs) { return lhs.mValue == rhs; } + +#if defined(_MSC_VER) && _MSC_VER <= 1935 + friend bool operator==(const auto& lhs, const Normalized& rhs) + { + return lhs == rhs.mValue; + } +#endif + + friend bool operator==(const Normalized& lhs, const NormalizedView& rhs) { - return lhs.mValue == rhs; + return lhs.mValue == rhs.value(); } - friend bool operator<(const Normalized& lhs, const Normalized& rhs) { return lhs.mValue < rhs.mValue; } + friend bool operator<(const Normalized& lhs, const Normalized& rhs) + { + return lhs.mValue < rhs.mValue; + } - template - friend bool operator<(const Normalized& lhs, const T& rhs) + friend bool operator<(const Normalized& lhs, const auto& rhs) { return lhs.mValue < rhs; } - template - friend bool operator<(const T& lhs, const Normalized& rhs) + friend bool operator<(const auto& lhs, const Normalized& rhs) { return lhs < rhs.mValue; } + friend bool operator<(const Normalized& lhs, const NormalizedView& rhs) + { + return lhs.mValue < rhs.value(); + } + + friend bool operator<(const NormalizedView& lhs, const Normalized& rhs) + { + return lhs.value() < rhs.mValue; + } + friend std::ostream& operator<<(std::ostream& stream, const Normalized& value) { return stream << value.mValue; @@ -124,6 +252,34 @@ namespace VFS::Path private: std::string mValue; }; + + inline NormalizedView::NormalizedView(const Normalized& value) noexcept + : mValue(value.view()) + { + } + + inline Normalized operator/(NormalizedView lhs, NormalizedView rhs) + { + Normalized result(lhs); + result /= rhs; + return result; + } + + struct Hash + { + using is_transparent = void; + + [[nodiscard]] std::size_t operator()(std::string_view sv) const { return std::hash{}(sv); } + + [[nodiscard]] std::size_t operator()(const std::string& s) const { return std::hash{}(s); } + + [[nodiscard]] std::size_t operator()(const Normalized& s) const { return std::hash{}(s.value()); } + + [[nodiscard]] std::size_t operator()(NormalizedView s) const + { + return std::hash{}(s.value()); + } + }; } #endif diff --git a/components/vfs/registerarchives.cpp b/components/vfs/registerarchives.cpp index 9dbe878bca..f017b5f73c 100644 --- a/components/vfs/registerarchives.cpp +++ b/components/vfs/registerarchives.cpp @@ -25,18 +25,7 @@ namespace VFS // Last BSA has the highest priority const auto archivePath = collections.getPath(*archive); Log(Debug::Info) << "Adding BSA archive " << archivePath; - Bsa::BsaVersion bsaVersion = Bsa::BSAFile::detectVersion(archivePath); - - if (bsaVersion == Bsa::BSAVER_COMPRESSED) - vfs->addArchive(std::make_unique::type>(archivePath)); - else if (bsaVersion == Bsa::BSAVER_BA2_GNRL) - vfs->addArchive(std::make_unique::type>(archivePath)); - else if (bsaVersion == Bsa::BSAVER_BA2_DX10) - vfs->addArchive(std::make_unique::type>(archivePath)); - else if (bsaVersion == Bsa::BSAVER_UNCOMPRESSED) - vfs->addArchive(std::make_unique::type>(archivePath)); - else - throw std::runtime_error("Unknown archive type '" + *archive + "'"); + vfs->addArchive(makeBsaArchive(archivePath)); } else { diff --git a/docs/source/conf.py b/docs/source/conf.py index 902e84c393..1dca7374e5 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -143,7 +143,9 @@ html_theme = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +html_theme_options = { + 'navigation_with_keys': True +} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] diff --git a/docs/source/luadoc_data_paths.sh b/docs/source/luadoc_data_paths.sh index 83937a82a5..7f32348b38 100755 --- a/docs/source/luadoc_data_paths.sh +++ b/docs/source/luadoc_data_paths.sh @@ -9,5 +9,6 @@ paths=( scripts/omw/settings/player.lua scripts/omw/ui.lua scripts/omw/usehandlers.lua + scripts/omw/skillhandlers.lua ) printf '%s\n' "${paths[@]}" \ No newline at end of file diff --git a/docs/source/manuals/installation/install-game-files.rst b/docs/source/manuals/installation/install-game-files.rst index 6da5d3d55a..8a7a594691 100644 --- a/docs/source/manuals/installation/install-game-files.rst +++ b/docs/source/manuals/installation/install-game-files.rst @@ -101,18 +101,7 @@ Assuming you used the filepath above, your ``.esm`` files will be located in ``~ You can now run the OpenMW launcher, and from there run the installation wizard. Point it to your ``Morrowind.esm`` in the folder you extracted it to, and follow the instructions. ------ -Steam ------ - -Windows -------- - -Windows users can download Morrowind through Steam. -Afterwards, you can point OpenMW to the Steam install location at -``C:\Program Files\Steam\SteamApps\common\Morrowind\Data Files\`` -and find ``Morrowind.esm`` there. - +--------------------- XBox Game Pass for PC --------------------- @@ -125,8 +114,21 @@ option and the app will prompt you to move the Morrowind files to a new folder. Once done you can find ``Morrowind.esm`` in the folder you chose. -macOS ----- +Steam +----- + +Windows +^^^^^^^ + +Windows users can download Morrowind through Steam. +Afterwards, you can point OpenMW to the Steam install location at +``C:\Program Files\Steam\SteamApps\common\Morrowind\Data Files\`` +and find ``Morrowind.esm`` there. + + +macOS +^^^^^ If you are running macOS, you can also download Morrowind through Steam: @@ -151,9 +153,9 @@ If you are running macOS, you can also download Morrowind through Steam: ``~/Library/Application Support/Steam/steamapps/common/The Elder Scrolls III - Morrowind/Data Files/`` Linux ------ +^^^^^ Debian/Ubuntu - using "Steam Proton" & "OpenMW launcher". ---------------------------------------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #. Install Steam from "Ubuntu Software" Center #. Enable Proton (basically WINE under the hood). This is done in the Steam client menu drop down. Select, "Steam | Settings" then in the "SteamPlay" section check the box next to "enable steam play for all other titles" #. Now Morrowind should be selectable in your game list (as long as you own it). You can install it like any other game, choose to install it and remember the directory path of the location you pick. @@ -161,10 +163,10 @@ Debian/Ubuntu - using "Steam Proton" & "OpenMW launcher". #. Launch "OpenMW launcher" and follow the setup wizard, when asked, point it at the location you installed Morrowind to, we will be looking for the directory that contains the Morrowing.esm file, for example '/steam library/steamapps/common/Morrowind/Data Files/'. #. Everything should now be in place, click that big "PLAY" button and fire up OpenMW. -Nb. Bloodmoon.esm needs to be below Tribunal.esm in your datafiles list, if you dont have the right order a red "!" will apear next to the filename in the datafiles section of the OpenMW launcher, just drag bloodmoon below tribunal to fix it. +Note, Bloodmoon.esm needs to be below Tribunal.esm in your datafiles list, if you don't have the right order a red "!" will apear next to the filename in the datafiles section of the OpenMW launcher, just drag bloodmoon below tribunal to fix it. Wine ----- +~~~~ Users of other platforms running Wine can run Steam within it and find ``Morrowind.esm`` at diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index 47807ab793..fb354a10a7 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -19,6 +19,7 @@ Lua API reference openmw_animation openmw_async openmw_vfs + openmw_markup openmw_world openmw_self openmw_nearby @@ -41,6 +42,7 @@ Lua API reference interface_item_usage interface_mwui interface_settings + interface_skill_progression interface_ui iterables diff --git a/docs/source/reference/lua-scripting/engine_handlers.rst b/docs/source/reference/lua-scripting/engine_handlers.rst index 754a63b314..2b5e99e6ae 100644 --- a/docs/source/reference/lua-scripting/engine_handlers.rst +++ b/docs/source/reference/lua-scripting/engine_handlers.rst @@ -124,6 +124,15 @@ Engine handler is a function defined by a script, that can be called by the engi * - onTouchMove(touchEvent) - | A finger moved on a touch device. | `Touch event `_. + * - onMouseButtonPress(button) + - | A mouse button was pressed + | Button id + * - onMouseButtonRelease(button) + - | A mouse button was released + | Button id + * - onMouseWheel(vertical, horizontal) + - | Mouse wheel was scrolled + | vertical and horizontal mouse wheel change * - | onConsoleCommand( | mode, command, selectedObject) - | User entered `command` in in-game console. Called if either diff --git a/docs/source/reference/lua-scripting/interface_controls.rst b/docs/source/reference/lua-scripting/interface_controls.rst index c4b1709f59..f8e8fd0d4e 100644 --- a/docs/source/reference/lua-scripting/interface_controls.rst +++ b/docs/source/reference/lua-scripting/interface_controls.rst @@ -4,5 +4,5 @@ Interface Controls .. include:: version.rst .. raw:: html - :file: generated_html/scripts_omw_playercontrols.html + :file: generated_html/scripts_omw_input_playercontrols.html diff --git a/docs/source/reference/lua-scripting/interface_skill_progression.rst b/docs/source/reference/lua-scripting/interface_skill_progression.rst new file mode 100644 index 0000000000..f27f03d556 --- /dev/null +++ b/docs/source/reference/lua-scripting/interface_skill_progression.rst @@ -0,0 +1,6 @@ +Interface SkillProgression +========================== + +.. raw:: html + :file: generated_html/scripts_omw_skillhandlers.html + diff --git a/docs/source/reference/lua-scripting/openmw_markup.rst b/docs/source/reference/lua-scripting/openmw_markup.rst new file mode 100644 index 0000000000..b37afec88f --- /dev/null +++ b/docs/source/reference/lua-scripting/openmw_markup.rst @@ -0,0 +1,7 @@ +Package openmw.markup +===================== + +.. include:: version.rst + +.. raw:: html + :file: generated_html/openmw_markup.html diff --git a/docs/source/reference/lua-scripting/setting_renderers.rst b/docs/source/reference/lua-scripting/setting_renderers.rst index 7f40eb08bd..b85c7fbaab 100644 --- a/docs/source/reference/lua-scripting/setting_renderers.rst +++ b/docs/source/reference/lua-scripting/setting_renderers.rst @@ -128,7 +128,7 @@ Table with the following optional fields: - Disables changing the setting from the UI inputBinding ------ +------------ Allows the user to bind inputs to an action or trigger @@ -143,10 +143,9 @@ Table with the following fields: * - name - type (default) - description - * - type - - 'keyboardPress', 'keyboardHold' - - The type of input that's allowed to be bound * - key - #string - Key of the action or trigger to which the input is bound - + * - type + - 'action', 'trigger' + - Type of the key diff --git a/docs/source/reference/lua-scripting/tables/interfaces.rst b/docs/source/reference/lua-scripting/tables/interfaces.rst index 5029baf0a3..f2e5921b02 100644 --- a/docs/source/reference/lua-scripting/tables/interfaces.rst +++ b/docs/source/reference/lua-scripting/tables/interfaces.rst @@ -25,6 +25,10 @@ - by global scripts - | Allows to extend or override built-in item usage | mechanics. + * - :ref:`SkillProgression ` + - by local scripts + - | Control, extend, and override skill progression of the + | player. * - :ref:`Settings ` - by player and global scripts - Save, display and track changes of setting values. diff --git a/docs/source/reference/lua-scripting/tables/packages.rst b/docs/source/reference/lua-scripting/tables/packages.rst index 247bd7eacc..fd82608aed 100644 --- a/docs/source/reference/lua-scripting/tables/packages.rst +++ b/docs/source/reference/lua-scripting/tables/packages.rst @@ -19,6 +19,8 @@ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.vfs ` | everywhere | | Read-only access to data directories via VFS. | +------------------------------------------------------------+--------------------+---------------------------------------------------------------+ +|:ref:`openmw.markup ` | everywhere | | API to work with markup languages. | ++------------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.world ` | by global scripts | | Read-write access to the game world. | +------------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.self ` | by local scripts | | Full access to the object the script is attached to. | diff --git a/docs/source/reference/modding/custom-shader-effects.rst b/docs/source/reference/modding/custom-shader-effects.rst index 60a306a97a..f0a517f9f1 100644 --- a/docs/source/reference/modding/custom-shader-effects.rst +++ b/docs/source/reference/modding/custom-shader-effects.rst @@ -61,7 +61,7 @@ This effect is used to imitate effects such as refraction and heat distortion. A diffuse slot to a material and add uv scrolling. The red and green channels of the texture are used to offset the final scene texture. Blue and alpha channels are ignored. -To use this feature the :ref:`post processing` setting must be enabled. +To use this feature the :ref:`post processing ` setting must be enabled. This setting can either be activated in the OpenMW launcher, in-game, or changed in `settings.cfg`: :: diff --git a/docs/source/reference/modding/settings/navigator.rst b/docs/source/reference/modding/settings/navigator.rst index 55b9e19b19..6fafdcdfd2 100644 --- a/docs/source/reference/modding/settings/navigator.rst +++ b/docs/source/reference/modding/settings/navigator.rst @@ -245,6 +245,16 @@ Absent pieces usually mean a bug in recast mesh tiles building. Allows to do in-game debug. Potentially decreases performance. +wait for all jobs on exit +------------------------- + +:Type: boolean +:Range: True/False +:Default: False + +Wait until all queued async navmesh jobs are processed before exiting the engine. +Useful when a benchmark generates jobs to write into navmeshdb faster than they are processed. + Expert settings *************** diff --git a/docs/source/reference/modding/settings/postprocessing.rst b/docs/source/reference/modding/settings/postprocessing.rst index b4b2f5a8d4..ed876c88d6 100644 --- a/docs/source/reference/modding/settings/postprocessing.rst +++ b/docs/source/reference/modding/settings/postprocessing.rst @@ -1,6 +1,8 @@ Post Processing Settings ######################## +.. _Post Processing: + enabled ------- diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index 9ee1cbfaa5..22ceb34f44 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -8,11 +8,15 @@ force shaders :Range: True/False :Default: False -Force rendering with shaders. By default, only bump-mapped objects will use shaders. -Enabling this option may cause slightly different visuals if the "clamp lighting" option is set to false. +Force rendering with shaders, even for objects that don't strictly need them. +By default, only objects with certain effects, such as bump or normal maps will use shaders. +With enhancements enabled, such as :ref:`enable shadows` and :ref:`reverse z`, shaders must be used for all objects, as if this setting is true. +Typically, one or more of these enhancements will be enabled, and shaders will be needed for everything anyway, meaning toggling this setting will have no effect. + +Some settings, such as :ref:`clamp lighting` only apply to objects using shaders, so enabling this option may cause slightly different visuals when used at the same time. Otherwise, there should not be a visual difference. -Please note enabling shaders has a significant performance impact on most systems. +Please note enabling shaders may have a significant performance impact on some systems, and a mild impact on many others. force per pixel lighting ------------------------ @@ -21,10 +25,10 @@ force per pixel lighting :Range: True/False :Default: False -Force the use of per pixel lighting. By default, only bump mapped objects use per-pixel lighting. -Has no effect if the 'force shaders' option is false. +Force the use of per pixel lighting. By default, only bump and normal mapped objects use per-pixel lighting. +Only affects objects drawn with shaders (see :ref:`force shaders` option). Enabling per-pixel lighting results in visual differences to the original MW engine as certain lights in Morrowind rely on vertex lighting to look as intended. -Note that groundcover shaders ignore this setting. +Note that groundcover shaders and particle effects ignore this setting. clamp lighting -------------- @@ -34,8 +38,8 @@ clamp lighting :Default: True Restrict the amount of lighting that an object can receive to a maximum of (1,1,1). -Only affects objects that render with shaders (see 'force shaders' option). -Always affects terrain. +Only affects objects drawn with shaders (see :ref:`force shaders` option) as objects drawn without shaders always have clamped lighting. +When disabled, terrain is always drawn with shaders to prevent seams between tiles that are and that aren't. Leaving this option at its default makes the lighting compatible with Morrowind's fixed-function method, but the lighting may appear dull and there might be colour shifts. @@ -49,9 +53,9 @@ auto use object normal maps :Default: False If this option is enabled, normal maps are automatically recognized and used if they are named appropriately -(see 'normal map pattern', e.g. for a base texture foo.dds, the normal map texture would have to be named foo_n.dds). +(see :ref:`normal map pattern`, e.g. for a base texture ``foo.dds``, the normal map texture would have to be named ``foo_n.dds``). If this option is disabled, -normal maps are only used if they are explicitly listed within the mesh file (.nif or .osg file). Affects objects. +normal maps are only used if they are explicitly listed within the mesh file (``.nif`` or ``.osg`` file). Affects objects. auto use object specular maps ----------------------------- @@ -61,10 +65,10 @@ auto use object specular maps :Default: False If this option is enabled, specular maps are automatically recognized and used if they are named appropriately -(see 'specular map pattern', e.g. for a base texture foo.dds, -the specular map texture would have to be named foo_spec.dds). +(see :ref:`specular map pattern`, e.g. for a base texture ``foo.dds``, +the specular map texture would have to be named ``foo_spec.dds``). If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file -(.osg file, not supported in .nif files). Affects objects. +(``.osg`` file, not supported in ``.nif`` files). Affects objects. auto use terrain normal maps ---------------------------- @@ -73,7 +77,7 @@ auto use terrain normal maps :Range: True/False :Default: False -See 'auto use object normal maps'. Affects terrain. +See :ref:`auto use object normal maps`. Affects terrain. auto use terrain specular maps ------------------------------ @@ -82,7 +86,7 @@ auto use terrain specular maps :Range: True/False :Default: False -If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. +If a file with pattern :ref:`terrain specular map pattern` exists, use that file as a 'diffuse specular' map. The texture must contain the layer colour in the RGB channel (as usual), and a specular multiplier in the alpha channel. normal map pattern @@ -93,7 +97,7 @@ normal map pattern :Default: _n The filename pattern to probe for when detecting normal maps -(see 'auto use object normal maps', 'auto use terrain normal maps') +(see :ref:`auto use object normal maps`, :ref:`auto use terrain normal maps`) normal height map pattern ------------------------- @@ -113,7 +117,7 @@ specular map pattern :Range: :Default: _spec -The filename pattern to probe for when detecting object specular maps (see 'auto use object specular maps') +The filename pattern to probe for when detecting object specular maps (see :ref:`auto use object specular maps`) terrain specular map pattern ---------------------------- @@ -122,7 +126,7 @@ terrain specular map pattern :Range: :Default: _diffusespec -The filename pattern to probe for when detecting terrain specular maps (see 'auto use terrain specular maps') +The filename pattern to probe for when detecting terrain specular maps (see :ref:`auto use terrain specular maps`) apply lighting to environment maps ---------------------------------- @@ -166,7 +170,7 @@ normal maps are provided. This is due to some groundcover mods using the Z-Up normals technique to avoid some common issues with shading. As a consequence, per pixel lighting would give undesirable results. -Note that the rendering will act as if you have 'force shaders' option enabled +Note that the rendering will act as if you have :ref:`force shaders` option enabled when not set to 'legacy'. This means that shaders will be used to render all objects and the terrain. @@ -283,7 +287,7 @@ between them. Note, this relies on overriding specific properties of particle systems that potentially differ from the source content, this setting may change the look of some particle systems. -Note that the rendering will act as if you have 'force shaders' option enabled. +Note that the rendering will act as if you have :ref:`force shaders` option enabled. This means that shaders will be used to render all objects and the terrain. weather particle occlusion diff --git a/docs/source/reference/modding/settings/sound.rst b/docs/source/reference/modding/settings/sound.rst index 7a5718735c..dbcad65d0d 100644 --- a/docs/source/reference/modding/settings/sound.rst +++ b/docs/source/reference/modding/settings/sound.rst @@ -127,3 +127,16 @@ Allowed values for this field are enumerated in openmw.log file is an HRTF enabl The default value is empty, which uses the default profile. This setting can be controlled in the Settings tab of the launcher. + +camera listener +--------------- + +:Type: boolean +:Range: True/False +:Default: False + +When true, uses the camera position and direction for audio instead of the player position. +This makes audio in third person sound relative to camera instead of the player. +False is vanilla Morrowind behaviour. + +This setting can be controlled in the Settings tab of the launcher. \ No newline at end of file diff --git a/docs/source/reference/modding/settings/water.rst b/docs/source/reference/modding/settings/water.rst index fe407071f2..b04b92de94 100644 --- a/docs/source/reference/modding/settings/water.rst +++ b/docs/source/reference/modding/settings/water.rst @@ -58,6 +58,34 @@ This setting has no effect if the shader setting is false. This setting can be toggled with the 'Refraction' button in the Water tab of the Video panel of the Options menu. +sunlight scattering +------------------- + +:Type: boolean +:Range: True/False +:Default: True + +This setting enables sunlight scattering. +This makes incident sunlight seemingly spread through water, simulating the optical property. + +This setting has no effect if refraction is turned off. + +This setting can be toggled with the 'Sunlight Scattering' button in the Water tab of the Video panel of the Options menu. + +wobbly shores +------------- + +:Type: boolean +:Range: True/False +:Default: True + +This setting makes shores wobbly. +The water surface will smoothly fade into the shoreline and wobble based on water normal-mapping, which avoids harsh transitions. + +This setting has no effect if refraction is turned off. + +This setting can be toggled with the 'Wobbly Shores' button in the Water tab of the Video panel of the Options menu. + reflection detail ----------------- diff --git a/docs/source/reference/modding/texture-modding/texture-basics.rst b/docs/source/reference/modding/texture-modding/texture-basics.rst index 78ae007704..8bbf018fba 100644 --- a/docs/source/reference/modding/texture-modding/texture-basics.rst +++ b/docs/source/reference/modding/texture-modding/texture-basics.rst @@ -25,6 +25,19 @@ Content creators need to know that OpenMW uses the DX format for normal maps, an See the section `Automatic use`_ further down below for detailed information. +The RGB channels of the normal map are used to store XYZ components of tangent space normals and the alpha channel of the normal map may be used to store a height map used for parallax. + +This is different from the setup used in Bethesda games that use the traditional pipeline, which may store specular information in the alpha channel. + +Special pixel formats that only store two color channels exist and are used by Bethesda games that employ a PBR-based pipeline. Compressed red-green formats are optimized for use with normal maps and suffer from far less quality degradation than S3TC-compressed normal maps of equivalent size. + +OpenMW supports the use of such pixel formats. When a red-green normal map is provided, the Z component of the normal will be reconstructed based on XY components it stores. +Naturally, since these formats cannot provide an alpha channel, they do not support parallax. + +Keep in mind, however, that while the necessary hardware support is widespread for compressed red-green formats, it is less ubiquitous than the support for S3TC family of compressed formats. +Should you run into the consequences of this, you might want to convert such textures into an uncompressed red-green format such as R8G8. +Be careful not to try and convert such textures into a full-color format as the previously non-existent blue channel would then be used. + Specular Mapping ################ diff --git a/extern/Base64/CMakeLists.txt b/extern/Base64/CMakeLists.txt index 94992a22b5..fc750823c7 100644 --- a/extern/Base64/CMakeLists.txt +++ b/extern/Base64/CMakeLists.txt @@ -1,6 +1,6 @@ add_library(Base64 INTERFACE) target_include_directories(Base64 INTERFACE .) -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(Base64 INTERFACE ) endif() diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index 10d75c1057..1a6fcf2625 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -212,8 +212,8 @@ if (BUILD_BENCHMARKS AND NOT OPENMW_USE_SYSTEM_BENCHMARK) include(FetchContent) FetchContent_Declare(benchmark - URL https://github.com/google/benchmark/archive/refs/tags/v1.7.1.zip - URL_HASH SHA512=bec4016263587a57648e02b094c69e838c0a21e16c3dcfc6f03100397ab8f95d5fab1f5fd0d7e0e8adbb8212fff1eb574581158fdda1fa7fd6ff12762154b0cc + URL https://github.com/google/benchmark/archive/refs/tags/v1.8.3.zip + URL_HASH SHA512=d73587ad9c49338749e1d117a6f8c7ff9c603a91a2ffa91a7355c7df7dea82710b9a810d34ddfef20973ecdc77092ec10fb2b4e4cc8d2e7810cbed79617b3828 SOURCE_DIR fetched/benchmark ) FetchContent_MakeAvailableExcludeFromAll(benchmark) @@ -234,7 +234,7 @@ if (NOT OPENMW_USE_SYSTEM_YAML_CPP) ) FetchContent_MakeAvailableExcludeFromAll(yaml-cpp) - if (MSVC) + if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(yaml-cpp PRIVATE ) endif() endif() diff --git a/extern/oics/CMakeLists.txt b/extern/oics/CMakeLists.txt index 4bd3bc51ad..2d34f3f3e6 100644 --- a/extern/oics/CMakeLists.txt +++ b/extern/oics/CMakeLists.txt @@ -22,6 +22,6 @@ endif() target_link_libraries(oics SDL2::SDL2) -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(oics PUBLIC ) endif() diff --git a/extern/oics/ICSInputControlSystem.cpp b/extern/oics/ICSInputControlSystem.cpp index 9f03ec121e..7d92d9ee07 100644 --- a/extern/oics/ICSInputControlSystem.cpp +++ b/extern/oics/ICSInputControlSystem.cpp @@ -42,6 +42,7 @@ namespace ICS , mXmouseAxisBinded(false), mYmouseAxisBinded(false) , mClientWidth(1) , mClientHeight(1) + , mMouseAxisBindingInitialValues{0} { ICS_LOG(" - Creating InputControlSystem - "); @@ -802,11 +803,7 @@ namespace ICS std::string InputControlSystem::scancodeToString(SDL_Scancode key) { - SDL_Keycode code = SDL_GetKeyFromScancode(key); - if (code == SDLK_UNKNOWN) - return std::string(SDL_GetScancodeName(key)); - else - return std::string(SDL_GetKeyName(code)); + return std::string(SDL_GetScancodeName(key)); } void InputControlSystem::adjustMouseRegion(Uint16 width, Uint16 height) diff --git a/extern/osg-ffmpeg-videoplayer/CMakeLists.txt b/extern/osg-ffmpeg-videoplayer/CMakeLists.txt index 10c8d356a0..8ff608bf04 100644 --- a/extern/osg-ffmpeg-videoplayer/CMakeLists.txt +++ b/extern/osg-ffmpeg-videoplayer/CMakeLists.txt @@ -18,7 +18,7 @@ target_link_libraries(${OSG_FFMPEG_VIDEOPLAYER_LIBRARY} SDL2::SDL2) link_directories(${CMAKE_CURRENT_BINARY_DIR}) -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(${OSG_FFMPEG_VIDEOPLAYER_LIBRARY} PUBLIC diff --git a/files/data-mw/l10n/Calendar/de.yaml b/files/data-mw/l10n/Calendar/de.yaml index b3ee621402..3c6b672dd2 100644 --- a/files/data-mw/l10n/Calendar/de.yaml +++ b/files/data-mw/l10n/Calendar/de.yaml @@ -1,4 +1,5 @@ -# source: https://en.uesp.net/wiki/Lore:Calendar +# Source for month and weekday names: German Morrowind GOTY version +# For reference: https://www.elderscrollsportal.de/almanach/Tamriel-Almanach:%C3%9Cbersetzungskompendium month1: "Morgenstern" month2: "Morgenröte" @@ -13,8 +14,26 @@ month10: "Eisherbst" month11: "Abenddämmerung" month12: "Abendstern" -# The variant of month names in the context "day X of month Y". -# In English it is the same, but some languages require a different form. +# In German, there are two different options to generate the genitive form of a month name: +# +# (1) Apply standard rules for genitive (too complicated to elaborate here, but you usually add an "s"/"es" at the end of the word). +# (2) Use the nominative version. +# +# Nowadays, option (2) is more commonly used, so let's apply that here as well. +# Let me add the names for option (1) in case we want to switch in the future: +# +# monthInGenitive1: "des Morgensterns" +# monthInGenitive2: "der Morgenröte" +# monthInGenitive3: "der Erstsaat" +# monthInGenitive4: "der Regenhand" +# monthInGenitive5: "der Zweitsaat" +# monthInGenitive6: "des Mittjahres" +# monthInGenitive7: "der Sonnenhöhe" +# monthInGenitive8: "der Herbstsaat" +# monthInGenitive9: "des Herdfeuers" +# monthInGenitive10: "des Eisherbstes" +# monthInGenitive11: "der Abenddämmerung" +# monthInGenitive12: "des Abendsterns" monthInGenitive1: "Morgenstern" monthInGenitive2: "Morgenröte" monthInGenitive3: "Erstsaat" @@ -28,7 +47,9 @@ monthInGenitive10: "Eisherbst" monthInGenitive11: "Abenddämmerung" monthInGenitive12: "Abendstern" -dateFormat: "tag {day} im {monthInGenitive} {year, number, :: group-off}" +# Standard German date format: d. MMMM YYYY +# Modified example for TES lore: "16. Herbstsaat 3E 427" +dateFormat: "{day}. {month} {year, number, :: group-off}" weekday1: "Sundas" weekday2: "Morndas" @@ -36,4 +57,4 @@ weekday3: "Tirdas" weekday4: "Middas" weekday5: "Turdas" weekday6: "Fredas" -weekday7: "Loredas" +weekday7: "Loredas" \ No newline at end of file diff --git a/files/data/CMakeLists.txt b/files/data/CMakeLists.txt index 055154f555..0addb6d1ae 100644 --- a/files/data/CMakeLists.txt +++ b/files/data/CMakeLists.txt @@ -90,6 +90,7 @@ set(BUILTIN_DATA_FILES scripts/omw/mwui/textEdit.lua scripts/omw/mwui/space.lua scripts/omw/mwui/init.lua + scripts/omw/skillhandlers.lua scripts/omw/ui.lua scripts/omw/usehandlers.lua scripts/omw/worldeventhandlers.lua diff --git a/files/data/builtin.omwscripts b/files/data/builtin.omwscripts index f0f988c749..81fb76f023 100644 --- a/files/data/builtin.omwscripts +++ b/files/data/builtin.omwscripts @@ -12,6 +12,7 @@ GLOBAL: scripts/omw/cellhandlers.lua GLOBAL: scripts/omw/usehandlers.lua GLOBAL: scripts/omw/worldeventhandlers.lua CREATURE, NPC, PLAYER: scripts/omw/mechanics/animationcontroller.lua +PLAYER: scripts/omw/skillhandlers.lua PLAYER: scripts/omw/mechanics/playercontroller.lua MENU: scripts/omw/camera/settings.lua MENU: scripts/omw/input/settings.lua diff --git a/files/data/l10n/Calendar/de.yaml b/files/data/l10n/Calendar/de.yaml new file mode 100644 index 0000000000..c02765c5c2 --- /dev/null +++ b/files/data/l10n/Calendar/de.yaml @@ -0,0 +1,49 @@ +month1: "Januar" +month2: "Februar" +month3: "März" +month4: "April" +month5: "Mai" +month6: "Juni" +month7: "Juli" +month8: "August" +month9: "September" +month10: "Oktober" +month11: "November" +month12: "Dezember" + +# In German, there are two different options to generate the genitive form of a month name: +# +# (1) Apply standard rules for genitive (too complicated to elaborate here, but you usually add an "s"/"es" at the end of the word). +# (2) Use the nominative version. +# +# Nowadays, option (2) is more commonly used, so let's apply that here as well. +monthInGenitive1: "Januar" +monthInGenitive2: "Februar" +monthInGenitive3: "März" +monthInGenitive4: "April" +monthInGenitive5: "Mai" +monthInGenitive6: "Juni" +monthInGenitive7: "Juli" +monthInGenitive8: "August" +monthInGenitive9: "September" +monthInGenitive10: "Oktober" +monthInGenitive11: "November" +monthInGenitive12: "Dezember" + +# Standard German date format: d. MMMM YYYY +# Example: "23. Februar 1337" +dateFormat: "{day}. {month} {year, number, :: group-off}" + +weekday1: "Sonntag" +weekday2: "Montag" +weekday3: "Dienstag" +weekday4: "Mittwoch" +weekday5: "Donnerstag" +weekday6: "Freitag" +weekday7: "Samstag" + +# In German, there are usually no "a.m."/"p.m." shenanigans going on. +# In case of ambiguity, "vormittags" ("mornings") and "nachmittags" ("in the afternoon") are used. +am: "vormittags" +pm: "nachmittags" +day: "Tag" diff --git a/files/data/l10n/Calendar/sv.yaml b/files/data/l10n/Calendar/sv.yaml new file mode 100644 index 0000000000..858e586412 --- /dev/null +++ b/files/data/l10n/Calendar/sv.yaml @@ -0,0 +1,50 @@ +# Swedish does not actually upper-case first letter on months and weekday names, so I'm doing them lower case right now. + +month1: "januari" +month2: "februari" +month3: "mars" +month4: "april" +month5: "maj" +month6: "juni" +month7: "juli" +month8: "augusti" +month9: "september" +month10: "oktober" +month11: "november" +month12: "december" + +# There are no different grammatical forms of the months in Swedish + +monthInGenitive1: "januari" +monthInGenitive2: "februari" +monthInGenitive3: "mars" +monthInGenitive4: "april" +monthInGenitive5: "maj" +monthInGenitive6: "juni" +monthInGenitive7: "juli" +monthInGenitive8: "augusti" +monthInGenitive9: "september" +monthInGenitive10: "oktober" +monthInGenitive11: "november" +monthInGenitive12: "december" + +# Standard Swedish date format: d MMMM YYYY +# Source: http://www4.sprakochfolkminnen.se/cgi-bin/srfl/visasvar.py?sok=datum&svar=26089 +# Example: "23 februari 1337" +dateFormat: "{day} {month} {year, number, :: group-off}" + +# The Swedish week starts with monday actually, but whatever. + +weekday1: "söndag" +weekday2: "måndag" +weekday3: "tisdag" +weekday4: "onsdag" +weekday5: "torsdag" +weekday6: "fredag" +weekday7: "lördag" + +# In Swedish, as with German, we don't use AM/PM but instead a 24h clock. +# But instead of that, we could use "förmiddag" and "eftermiddag", which is basically "morning" and "afternoon" +am: "förmiddag" +pm: "eftermiddag" +day: "dag" diff --git a/files/data/l10n/Interface/de.yaml b/files/data/l10n/Interface/de.yaml index ac1a95a0ea..8457797422 100644 --- a/files/data/l10n/Interface/de.yaml +++ b/files/data/l10n/Interface/de.yaml @@ -1,28 +1,34 @@ +DurationDay: "{days} d " +DurationHour: "{hours} h " +DurationMinute: "{minutes} min " +# There is no abbreviation for "Monat" ("month") in German, so full terms are used instead. +DurationMonth: |- + {months, plural, + one{{months} Monat } + other{{months} Monate } + } +DurationSecond: "{seconds} s " +# In German, "J."/"Jr." exist as abbreviations for "Jahr" ("year") but are seldomly used. +# A plural version of these does not exist (at least, to my knowledge). +# +# To avoid confusion, use full terms instead. +DurationYear: |- + {years, plural, + one{{years} Jahr } + other{{years} Jahre } + } No: "Nein" NotAvailableShort: "N/A" Reset: "Zurücksetzen" Yes: "Ja" - -# To be translated: - -#DurationDay: "{days} d " -#DurationHour: "{hours} h " -#DurationMinute: "{minutes} min " -#DurationMonth: |- -# {months, plural, -# one{{months} mo } -# other{{months} mos } -# } -#DurationSecond: "{seconds} s " -#DurationYear: |- -# {years, plural, -# one{{years} yr } -# other{{years} yrs } -# } -#Cancel: "Cancel" -#Close: "Close" -#None: "None" -#OK: "OK" -#Off: "Off" -#On: "On" -#Copy: "Copy" +Cancel: "Abbrechen" +Close: "Schließen" +# This one is a bit tricky since it can be translated to +# "keiner"/"keine"/"keines", "nichts", or "leer" ("empty") depending on context. +# +# Using the "keine" option for now. +None: "Keine" +OK: "OK" +Off: "Aus" +On: "Ein" +Copy: "Kopieren" \ No newline at end of file diff --git a/files/data/l10n/Interface/fr.yaml b/files/data/l10n/Interface/fr.yaml index bac4346364..0a078a1676 100644 --- a/files/data/l10n/Interface/fr.yaml +++ b/files/data/l10n/Interface/fr.yaml @@ -22,4 +22,4 @@ None: "Aucun" OK: "Valider" Cancel: "Annuler" Close: "Fermer" -#Copy: "Copy" +Copy: "Copier" diff --git a/files/data/l10n/Interface/sv.yaml b/files/data/l10n/Interface/sv.yaml index aae63a1941..10a6c50b58 100644 --- a/files/data/l10n/Interface/sv.yaml +++ b/files/data/l10n/Interface/sv.yaml @@ -1,17 +1,25 @@ -Cancel: "Avbryt" -Close: "Stäng" DurationDay: "{days} d " DurationHour: "{hours} tim " DurationMinute: "{minutes} min " -DurationMonth: "{months} må " +DurationMonth: |- + {months, plural, + one{{months} må } + other{{months} må } + } DurationSecond: "{seconds} sek " -DurationYear: "{years} år " +DurationYear: |- + {years, plural, + one{{years} år } + other{{years} år } + } No: "Nej" -None: "Inget" NotAvailableShort: "N/A" -OK: "Ok" -Off: "Av" -On: "På" -Reset: "Återställ" +Reset: "Reset" Yes: "Ja" -#Copy: "Copy" +On: "På" +Off: "Av" +None: "Inget" +OK: "Ok" +Cancel: "Avbryt" +Close: "Stäng" +Copy: "Kopiera" \ No newline at end of file diff --git a/files/data/l10n/OMWCamera/de.yaml b/files/data/l10n/OMWCamera/de.yaml index 62b50c5862..76ff7bece3 100644 --- a/files/data/l10n/OMWCamera/de.yaml +++ b/files/data/l10n/OMWCamera/de.yaml @@ -1,72 +1,70 @@ -Camera: "OpenMW Kamera" -settingsPageDescription: "OpenMW Kameraeinstellungen" +Camera: "OpenMW: Kamera" +settingsPageDescription: "OpenMW-Kameraeinstellungen" -thirdPersonSettings: "Dritte-Person-Modus" +thirdPersonSettings: "Verfolgerperspektive" -viewOverShoulder: "Blick über die Schulter" +viewOverShoulder: "Über-die-Schulter-Ansicht" viewOverShoulderDescription: | - Steuert den Dritte-Person-Ansichtsmodus. - Nein: Die Ansicht ist auf den Kopf vom Spielercharakter zentriert. Fadenkreuz ist ausgeblendet. - Ja: Während die Kamera mit ungezogener Waffe hinter der Schulter des Spielercharakter positioniert ist, ist das Fadenkreuz immer sichtbar. + Beeinflusst die Verfolgerperspektive. + Nein: Die Ansicht ist auf den Kopf des Spielercharakters zentriert; das Fadenkreuz ist ausgeblendet. + Ja: Die Kamera ist bei weggesteckter Waffe hinter der Schulter des Spielercharakters positioniert; das Fadenkreuz ist immer sichtbar. -shoulderOffsetX: "Schulteransicht horizontaler Versatz" +shoulderOffsetX: "Schulteransicht: horizontaler Offset" shoulderOffsetXDescription: > - Horizontaler Versatz der Über-die-Schulter-Ansicht. - Verwenden Sie für die linke Schulter einen negativen Wert. + Horizontaler Offset der „Über-die-Schulter-Ansicht“. + Größere Werte verschieben die Ansicht nach rechts; negative Werte verschieben die Ansicht über die linke Schulter. -shoulderOffsetY: "Schulteransicht vertikaler Versatz" +shoulderOffsetY: "Schulteransicht: vertikaler Offset" shoulderOffsetYDescription: > - Vertikaler Versatz der Über-die-Schulter-Ansicht. + Vertikaler Offset der „Über-die-Schulter-Ansicht“. -autoSwitchShoulder: "Automatischer Schulteransicht wechsel" +autoSwitchShoulder: "Schulter automatisch wechseln" autoSwitchShoulderDescription: > - Bei Hindernissen welche die Kamera in die Nähe des Spielercharakter bringen würde, - hat diese Einstellung den Effekt dass die Kamera automatisch auf Schulteransicht wechselt. + Falls aktiviert, wechselt die Ansicht temporär auf die andere Schulter, solange ein Hindernis die Kamera zu nah an den Spielercharakter bewegen würde. -zoomOutWhenMoveCoef: "Kamera-Zoom bei Bewegung" +zoomOutWhenMoveCoef: "Kamera-Zoomfaktor bei Bewegung" zoomOutWhenMoveCoefDescription: > - Bewegt die Kamera vom Spielercharakter weg (positiver Wert) oder auf sie zu (negativer Wert), während sich der Charakter bewegt. - Funktioniert nur, wenn "Blick über die Schulter" aktiviert ist. Der Wert 0 deaktiviert den Zoom (Standard: 20.0). + Bewegt die Kamera vom Spielercharakter weg (positiver Wert) oder auf ihn zu (negativer Wert), solange sich dieser bewegt. Der Wert 0 deaktiviert den Zoom (Standard: 20,0). + Funktioniert nur, wenn „Über-die-Schulter-Ansicht“ aktiviert ist. previewIfStandStill: "Vorschau bei Stillstand" previewIfStandStillDescription: > - Verhindert dass sich der Spielercharakter in die Kamerarichtung dreht, während er untätig ist und seine Waffe weggesteckt hat. + Falls aktiviert, dreht sich der Spielercharakter nicht in Blickrichtung der Kamera, solange er untätig ist und seine Waffe weggesteckt ist. -deferredPreviewRotation: "Verzögerte Drehung der Vorschau" +deferredPreviewRotation: "Sanfter Übergang aus der Vorschau" deferredPreviewRotationDescription: | - Wenn diese Einstellung aktiv ist, dreht sich die Figur nach dem Verlassen des Vorschau- oder Vanity-Modus sanft in die Blickrichtung. - Wenn deaktiviert, dreht sich die Kamera und nicht der Spielercharakter. + Falls aktiviert, dreht sich der Spielercharakter nach dem Verlassen des Vorschau- oder Vanity-Modus sanft in die Blickrichtung der Kamera. + Falls deaktiviert, dreht sich die Kamera ruckartig in Richtung des Spielercharakters. -ignoreNC: "Ignoriere „Keine Kollision“-Variable" +ignoreNC: "Ignoriere „No Collision“-Flag" ignoreNCDescription: > - Verhindert dass die Kamera sich durch Objekte bewegt bei denen das NC-Flag (No Collision) im NIF-Modell aktiviert ist. + Falls aktiviert, bewegt sich die Kamera nicht durch Objekte, deren NC-Flag („No Collision“-Flag) im NIF-Modell aktiviert ist. move360: "360°-Bewegung" move360Description: > - Macht die Bewegungsrichtung unabhängig von der Kamerarichtung, während die Waffe des Spielercharakters nicht gezogen ist. - Zum Beispiel schaut der Spielercharakter in die Kamera, während er rückwärts läuft. + Falls aktiviert, ist die Bewegungsrichtung des Spielercharakters bei weggesteckter Waffe unabhängig von der Kamerarichtung. + Beispiel: Läuft der Spielercharakter in diesem Modus rückwärts, dreht er sich stattdessen mit dem Gesicht zur Kamera. -move360TurnSpeed: "360°-Bewegungs-Drehgeschwindigkeit" -move360TurnSpeedDescription: "Drehgeschwindigkeitsmultiplikator (Standard: 5.0)." +move360TurnSpeed: "Multiplikator für 360°-Bewegung-Drehgeschwindigkeit" +move360TurnSpeedDescription: "Multiplikator für die Drehgeschwindigkeit bei aktivierter „360°-Bewegung“ (Standard: 5,0)." slowViewChange: "Sanfter Ansichtswechsel" -slowViewChangeDescription: "Macht den Übergang von der Ego-Perspektive zur Dritte-Person-Perspektive nicht augenblicklich." +slowViewChangeDescription: "Falls aktiviert, wechselt die Kamera nicht mehr ruckartig von der Ego- in die Verfolgerperspektive." -povAutoSwitch: "Automatischer Wechsel in Ego-Perspektive" -povAutoSwitchDescription: "Wechselt automatisch in die Ego-Perspektive wenn sich direkt hinter dem Spielercharakter ein Hindernis befindet." +povAutoSwitch: "Automatischer Wechsel zur Egoperspektive" +povAutoSwitchDescription: "Falls aktiviert, wechselt die Kamera automatisch in die Egoperspektive, sobald sich sich ein Hindernis direkt hinter dem Spielercharakter befindet." -headBobbingSettings: "Kopfbewegungen in der Ego-Perspektive" +headBobbingSettings: "Kopfwippen in der Egoperspektive" headBobbing_enabled: "Eingeschaltet" -headBobbing_enabledDescription: "" +headBobbing_enabledDescription: "Falls aktiviert, wippt die Kamera beim Laufen in der Egoperspektive auf und ab." -headBobbing_step: "Schrittweite" -headBobbing_stepDescription: "Die Länge jedes Schritts (Standard: 90.0)." +headBobbing_step: "Schrittlänge" +headBobbing_stepDescription: "Definiert die Länge eines Schritts, also die Frequenz des Kopfwippens (Standard: 90,0)." headBobbing_height: "Schritthöhe" -headBobbing_heightDescription: "Die Amplitude der Kopfbewegung (Standard: 3.0)." +headBobbing_heightDescription: "Definiert die Höhe eines Schritts, also die Amplitude des Kopfwippens (Standard: 3,0)." headBobbing_roll: "Maximaler Rollwinkel" -headBobbing_rollDescription: "Der maximale Rollwinkel in Grad (Standard: 0.2)." - +headBobbing_rollDescription: "Der maximale Rollwinkel der Kamera beim Kopfwippen in Grad (Standard: 0,2)." \ No newline at end of file diff --git a/files/data/l10n/OMWCamera/en.yaml b/files/data/l10n/OMWCamera/en.yaml index d030bd22ec..609b028167 100644 --- a/files/data/l10n/OMWCamera/en.yaml +++ b/files/data/l10n/OMWCamera/en.yaml @@ -1,43 +1,43 @@ Camera: "OpenMW Camera" -settingsPageDescription: "OpenMW Camera settings" +settingsPageDescription: "OpenMW camera settings." -thirdPersonSettings: "Third person mode" +thirdPersonSettings: "Third Person Mode" -viewOverShoulder: "View over the shoulder" +viewOverShoulder: "View Over the Shoulder" viewOverShoulderDescription: | Controls third person view mode. No: view is centered on the character's head. Crosshair is hidden. Yes: while weapon sheathed the camera is positioned behind the character's shoulder, crosshair is always visible. -shoulderOffsetX: "Shoulder view horizontal offset" +shoulderOffsetX: "Shoulder View Horizontal Offset" shoulderOffsetXDescription: > Horizontal offset of the over-the-shoulder view. For the left shoulder use a negative value. -shoulderOffsetY: "Shoulder view vertical offset" +shoulderOffsetY: "Shoulder View Vertical Offset" shoulderOffsetYDescription: > Vertical offset of the over-the-shoulder view. -autoSwitchShoulder: "Auto switch shoulder" +autoSwitchShoulder: "Auto Switch Shoulder" autoSwitchShoulderDescription: > When there are obstacles that would push the camera close to the player character, this setting makes the camera automatically switch to the shoulder farther away from the obstacles. -zoomOutWhenMoveCoef: "Zoom out when move coef" +zoomOutWhenMoveCoef: "Zoom Out When Move Coef" zoomOutWhenMoveCoefDescription: > Moves the camera away (positive value) or towards (negative value) the player character while the character is moving. Works only if "view over the shoulder" is enabled. Set this to zero to disable (default: 20.0). -previewIfStandStill: "Preview if stand still" +previewIfStandStill: "Preview if Stand Still" previewIfStandStillDescription: > Prevents the player character from turning towards the camera direction while they're idle and have their weapon sheathed. -deferredPreviewRotation: "Deferred preview rotation" +deferredPreviewRotation: "Deferred Preview Rotation" deferredPreviewRotationDescription: | If enabled then the character smoothly rotates to the view direction after exiting preview or vanity mode. If disabled then the camera rotates rather than the character. -ignoreNC: "Ignore 'No Collision' flag" +ignoreNC: "Ignore 'No Collision' Flag" ignoreNCDescription: > Prevents the camera from clipping through objects that have NC (No Collision) flag turned on in the NIF model. @@ -46,27 +46,27 @@ move360Description: > Makes the movement direction independent from the camera direction while the player character's weapon is sheathed. For example, the player character will look at the camera while running backwards. -move360TurnSpeed: "Move 360 turning speed" +move360TurnSpeed: "Move 360 Turning Speed" move360TurnSpeedDescription: "Turning speed multiplier (default: 5.0)." -slowViewChange: "Smooth view change" +slowViewChange: "Smooth View Change" slowViewChangeDescription: "Makes the transition from 1st person to 3rd person view non-instantaneous." -povAutoSwitch: "First person auto switch" +povAutoSwitch: "First Person Auto Switch" povAutoSwitchDescription: "Auto switch to the first person view if there is an obstacle right behind the player." -headBobbingSettings: "Head bobbing in first person view" +headBobbingSettings: "Head Bobbing in First Person View" headBobbing_enabled: "Enabled" headBobbing_enabledDescription: "" -headBobbing_step: "Base step length" +headBobbing_step: "Base Step Length" headBobbing_stepDescription: "The length of each step (default: 90.0)." -headBobbing_height: "Step height" +headBobbing_height: "Step Height" headBobbing_heightDescription: "The amplitude of the head bobbing (default: 3.0)." -headBobbing_roll: "Max roll angle" +headBobbing_roll: "Max Roll Angle" headBobbing_rollDescription: "The maximum roll angle in degrees (default: 0.2)." diff --git a/files/data/l10n/OMWCamera/ru.yaml b/files/data/l10n/OMWCamera/ru.yaml index 2b41ef0ee7..49821157cc 100644 --- a/files/data/l10n/OMWCamera/ru.yaml +++ b/files/data/l10n/OMWCamera/ru.yaml @@ -1,5 +1,5 @@ Camera: "Камера OpenMW" -settingsPageDescription: "Настройки камеры для OpenMW" +settingsPageDescription: "Настройки камеры OpenMW." thirdPersonSettings: "Режим от третьего лица" diff --git a/files/data/l10n/OMWControls/de.yaml b/files/data/l10n/OMWControls/de.yaml new file mode 100644 index 0000000000..db757e9165 --- /dev/null +++ b/files/data/l10n/OMWControls/de.yaml @@ -0,0 +1,86 @@ +# Source for most of the setting names: German Morrowind GOTY version + +ControlsPage: "OpenMW-Steuerung" +ControlsPageDescription: "Zusätzliche Einstellungen für Spielereingaben" + +MovementSettings: "Fortbewegung" + +alwaysRun: "Immer rennen" +alwaysRunDescription: | + Falls aktiviert, rennt der Spielercharakter standardmäßig; falls deaktiviert, geht er standardmäßig. + Gedrückthalten der Umschalttaste („Shift“) invertiert das aktuelle Verhalten vorübergehend, Drücken der Feststelltaste („Caps Lock“) schaltet das aktuelle Verhalten um. + +toggleSneak: "Schleichen umschalten" +toggleSneakDescription: | + Falls aktiviert, schaltet die „Schleichen“-Taste den Schleichen-Modus dauerhaft ein bzw. aus; falls deaktiviert, muss die „Schleichen“-Taste gedrückt gehalten werden, um im Schleichen-Modus zu bleiben. + Diese Option ist vor allem für Spieler mit schleichlastigen Spielercharakteren zu empfehlen. + +smoothControllerMovement: "Sanfte Controller-Bewegung" +smoothControllerMovementDescription: | + Aktiviert die sanfte Controller-Steuerung mit den Analog-Sticks. Dies macht den Übergang zwischen gehen und rennen weniger abrupt. + +TogglePOV_name: "Perspektive umschalten" +TogglePOV_description: "Zwischen Ego- und Verfolgerperspektive umschalten. Gedrückt halten, um den Vorschau-Modus zu aktivieren." + +Zoom3rdPerson_name: "Hinein-/Herauszoomen" +Zoom3rdPerson_description: "Bewegt die Kamera in Verfolgerperspektive näher an den Spielercharakter oder weiter von ihm weg." + +MoveForward_name: "Vorwärts" +MoveForward_description: "Kann sich mit „Rückwärts“-Eingabe aufheben." + +MoveBackward_name: "Rückwärts" +MoveBackward_description: "Kann sich mit „Vorwärts“-Eingabe aufheben." + +MoveLeft_name: "Links" +MoveLeft_description: "Kann sich mit „Rechts“-Eingabe aufheben." + +MoveRight_name: "Rechts" +MoveRight_description: "Kann sich mit „Links“-Eingabe aufheben." + +Use_name: "Benutzen" +Use_description: "Abhängig von der Haltung des Spielercharakters mit einer Waffe angreifen, einen Zauber wirken oder ein Objekt verwenden bzw. aktivieren." + +Run_name: "Rennen" +Run_description: "Gedrückt halten, um den Spielercharakter abhängig vom „Immer rennen“-Status gehen bzw. rennen zu lassen." + +AlwaysRun_name: "Immer rennen" +AlwaysRun_description: "„Immer rennen“-Status umschalten." + +Jump_name: "Sprung" +Jump_description: "Springen, solange sich der Spielercharakter auf dem Boden befindet." + +AutoMove_name: "Automatische Vorwärts-Bewegung" +AutoMove_description: "„Automatische Bewegung“-Status umschalten. Falls aktiviert, bewegt sich der Spielercharakter kontinuierlich vorwärts." + +Sneak_name: "Schleichen" +Sneak_description: "Gedrückt halten, um bei ausgeschalteter „Schleichen umschalten“-Option zu schleichen." + +ToggleSneak_name: "Schleichen umschalten" +ToggleSneak_description: "Bei eingeschalteter „Schleichen umschalten“-Option den Schleichen-Modus umschalten." + +ToggleWeapon_name: "Waffe ziehen" +ToggleWeapon_description: "Kampfhaltung einnehmen bzw. verlassen." + +ToggleSpell_name: "Zauber vorbereiten" +ToggleSpell_description: "Zauberhaltung einnehmen bzw. verlassen." + +Inventory_name: "Menü-Modus" +Inventory_description: "Inventar-Modus öffnen bzw. schließen." + +Journal_name: "Tagebuch" +Journal_description: "Tagebuch öffnen bzw. schließen." + +QuickKeysMenu_name: "Kurzmenü" +QuickKeysMenu_description: "Kurzmenü für Schnellauswahl von Gegenständen und Zaubern öffnen bzw. schließen." + +SmoothMoveForward_name: "Sanfte Vorwärts-Bewegung" +SmoothMoveForward_description: "Angepasste Vorwärts-Bewegung für aktivierte „Sanfte Controller-Bewegung“-Option" + +SmoothMoveBackward_name: "Sanfte Rückwärts-Bewegung" +SmoothMoveBackward_description: "Angepasste Rückwärts-Bewegung für aktivierte „Sanfte Controller-Bewegung“-Option" + +SmoothMoveLeft_name: "Sanfte Bewegung nach links" +SmoothMoveLeft_description: "Angepasste Bewegung nach links für aktivierte „Sanfte Controller-Bewegung“-Option" + +SmoothMoveRight_name: "Sanfte Bewegung nach rechts" +SmoothMoveRight_description: "Angepasste Bewegung nach rechts für aktivierte „Sanfte Controller-Bewegung“-Option" \ No newline at end of file diff --git a/files/data/l10n/OMWControls/en.yaml b/files/data/l10n/OMWControls/en.yaml index 9c45c1d1e5..d4df56436b 100644 --- a/files/data/l10n/OMWControls/en.yaml +++ b/files/data/l10n/OMWControls/en.yaml @@ -1,22 +1,21 @@ ControlsPage: "OpenMW Controls" -ControlsPageDescription: "Additional settings related to player controls" +ControlsPageDescription: "Additional settings related to player controls." MovementSettings: "Movement" -alwaysRun: "Always run" +alwaysRun: "Always Run" alwaysRunDescription: | - If this setting is true, the character is running by default, otherwise the character is walking by default. - The shift key will temporarily invert this setting, and the caps lock key will invert this setting while it's "locked". + If this setting is true, the character will run by default, otherwise the character will walk by default. + The Shift key will temporarily invert this setting, and the Caps Lock key will invert this setting while it's "locked". -toggleSneak: "Toggle sneak" +toggleSneak: "Toggle Sneak" toggleSneakDescription: | - This setting causes the sneak key (bound to Ctrl by default) to toggle sneaking on and off - rather than requiring the key to be held down while sneaking. + This setting makes the Sneak key (bound to Ctrl by default) toggle sneaking instead of having to be held down to sneak. Players that spend significant time sneaking may find the character easier to control with this option enabled. -smoothControllerMovement: "Smooth controller movement" +smoothControllerMovement: "Smooth Controller Movement" smoothControllerMovementDescription: | - Enables smooth movement with controller stick, with no abrupt switch from walking to running. + Enables smooth controller stick movement. This makes the transition from walking to running less abrupt. TogglePOV_name: "Toggle POV" TogglePOV_description: "Toggle between first and third person view. Hold to enter preview mode." @@ -25,61 +24,61 @@ Zoom3rdPerson_name: "Zoom In/Out" Zoom3rdPerson_description: "Moves the camera closer / further away when in third person view." MoveForward_name: "Move Forward" -MoveForward_description: "Can cancel out with Move Backward" +MoveForward_description: "Can cancel out with Move Backward." MoveBackward_name: "Move Backward" -MoveBackward_description: "Can cancel out with Move Forward" +MoveBackward_description: "Can cancel out with Move Forward." MoveLeft_name: "Move Left" -MoveLeft_description: "Can cancel out with Move Right" +MoveLeft_description: "Can cancel out with Move Right." MoveRight_name: "Move Right" -MoveRight_description: "Can cancel out with Move Left" +MoveRight_description: "Can cancel out with Move Left." Use_name: "Use" -Use_description: "Attack with a weapon or cast a spell depending on current stance" +Use_description: "Attack with a weapon or cast a spell depending on the current stance." Run_name: "Run" -Run_description: "Hold to run/walk, depending on the Always Run setting" +Run_description: "Hold to run/walk depending on the Always Run setting." AlwaysRun_name: "Always Run" -AlwaysRun_description: "Toggle the Always Run setting" +AlwaysRun_description: "Toggle the Always Run setting." Jump_name: "Jump" -Jump_description: "Jump whenever you are on the ground" +Jump_description: "Jump whenever you are on the ground." AutoMove_name: "Auto Run" -AutoMove_description: "Toggle continous forward movement" +AutoMove_description: "Toggle continuous forward movement." Sneak_name: "Sneak" -Sneak_description: "Hold to sneak, if the Toggle Sneak setting is off" +Sneak_description: "Hold to sneak if the Toggle Sneak setting is off." ToggleSneak_name: "Toggle Sneak" -ToggleSneak_description: "Toggle sneak, if the Toggle Sneak setting is on" +ToggleSneak_description: "Toggle sneak if the Toggle Sneak setting is on." ToggleWeapon_name: "Ready Weapon" -ToggleWeapon_description: "Enter or leave the weapon stance" +ToggleWeapon_description: "Enter or leave the weapon stance." ToggleSpell_name: "Ready Magic" -ToggleSpell_description: "Enter or leave the magic stance" +ToggleSpell_description: "Enter or leave the magic stance." Inventory_name: "Inventory" -Inventory_description: "Open the inventory" +Inventory_description: "Open the inventory." Journal_name: "Journal" -Journal_description: "Open the journal" +Journal_description: "Open the journal." -QuickKeysMenu_name: "QuickKeysMenu" -QuickKeysMenu_description: "Open the quick keys menu" +QuickKeysMenu_name: "Quick Menu" +QuickKeysMenu_description: "Open the quick keys menu." SmoothMoveForward_name: "Smooth Move Forward" -SmoothMoveForward_description: "Forward movement adjusted for smooth Walk-Run transitions" +SmoothMoveForward_description: "Forward movement adjusted for smooth Walk-Run transitions." SmoothMoveBackward_name: "Smooth Move Backward" -SmoothMoveBackward_description: "Backward movement adjusted for smooth Walk-Run transitions" +SmoothMoveBackward_description: "Backward movement adjusted for smooth Walk-Run transitions." SmoothMoveLeft_name: "Smooth Move Left" -SmoothMoveLeft_description: "Left movement adjusted for smooth Walk-Run transitions" +SmoothMoveLeft_description: "Left movement adjusted for smooth Walk-Run transitions." -SkmoothMoveRight_name: "SmoothMove Right" -SkmoothMoveRight_description: "Right movement adjusted for smooth Walk-Run transitions" +SmoothMoveRight_name: "Smooth Move Right" +SmoothMoveRight_description: "Right movement adjusted for smooth Walk-Run transitions." diff --git a/files/data/l10n/OMWControls/fr.yaml b/files/data/l10n/OMWControls/fr.yaml index dab9ddb8fc..95fa88a6ac 100644 --- a/files/data/l10n/OMWControls/fr.yaml +++ b/files/data/l10n/OMWControls/fr.yaml @@ -9,11 +9,79 @@ alwaysRunDescription: | Inactif : Le personnage se déplace par défaut en marchant.\n\n La touche Maj. inverse temporairement ce paramètre.\n\n La touche Verr Maj inverse ce paramètre lorsqu'elle est verrouillée. + toggleSneak: "Mode discrétion maintenu" toggleSneakDescription: | Une simple pression de la touche associée (Ctrl par défaut) active le mode discrétion, une seconde pression désactive le mode discrétion.\n\n Il n'est plus nécessaire de maintenir une touche appuyée pour que le mode discrétion soit actif.\n\n Certains joueurs ayant une utilisation intensive du mode discrétion considèrent qu'il est plus aisé de contrôler leur personnage ainsi. -# smoothControllerMovement -# smoothControllerMovementDescription +smoothControllerMovement: "Mouvements à la manette adoucis" +smoothControllerMovementDescription: | + Active le lissage des mouvements du stick analogique. Ceci permet une transition entre marche et course moins abrupte. + +TogglePOV_name: "Changer de vue" +TogglePOV_description: "Change entre la vue à la première et la troisième personne. Maintenir la touche pour activer le mode attente." + +Zoom3rdPerson_name: "Zoom Avant/Arrière" +Zoom3rdPerson_description: "Approche / éloigne la caméra lorsque la caméra est à la troisième personne." + +MoveForward_name: "En avant" +MoveForward_description: "Annulé par En arrière." + +MoveBackward_name: "En arrière" +MoveBackward_description: "Annulé par En avant." + +MoveLeft_name: "À gauche" +MoveLeft_description: "Annulé par Droite." + +MoveRight_name: "À droite" +MoveRight_description: "Annulé par Gauche." + +Use_name: "Utiliser" +Use_description: "Attaque avec une arme ou lance une magie, dépend de la préparation du joueur." + +Run_name: "Courir" +Run_description: "Maintenir pour courir/marcher, en fonction du paramètre Toujours Courir." + +AlwaysRun_name: "Toujours courir" +AlwaysRun_description: "Active le paramètre Toujours courir." + +Jump_name: "Sauter" +Jump_description: "Saute lorsque le joueur touche le sol." + +AutoMove_name: "Course automatique" +AutoMove_description: "Active un mouvement continu vers l'avant." + +Sneak_name: "Discrétion" +Sneak_description: "Maintenez la touche si le paramètre Mode discrétion maintenu est désactivé." + +ToggleSneak_name: "Maintenir la discrétion" +ToggleSneak_description: "Entre en mode discrétion si le paramètre Mode discrétion maintenu est activé." + +ToggleWeapon_name: "Préparer arme" +ToggleWeapon_description: "Entre ou sort de la position armée." + +ToggleSpell_name: "Préparer sort" +ToggleSpell_description: "Entre ou sort de la position lanceur de sort." + +Inventory_name: "Inventaire" +Inventory_description: "Ouvre l'inventaire." + +Journal_name: "Journal" +Journal_description: "Ouvre le journal." + +QuickKeysMenu_name: "Menu Touches rapides" +QuickKeysMenu_description: "Ouvre le menu des touches rapides." + +SmoothMoveForward_name: "En avant, doux" +SmoothMoveForward_description: "Déplace le joueur en avant, avec une transition douce entre la marche et la course." + +SmoothMoveBackward_name: "En arrière, doux" +SmoothMoveBackward_description: "Déplace le joueur en arrière, avec une transition douce entre la marche et la course." + +SmoothMoveLeft_name: "À gauche, doux" +SmoothMoveLeft_description: "Déplace le joueur à gauche, avec une transition douce entre la marche et la course.." + +SmoothMoveRight_name: "À droite, doux" +SmoothMoveRight_description: "Déplace le joueur à droite, avec une transition douce entre la marche et la course." diff --git a/files/data/l10n/OMWControls/ru.yaml b/files/data/l10n/OMWControls/ru.yaml index 0ce3609e16..4675321969 100644 --- a/files/data/l10n/OMWControls/ru.yaml +++ b/files/data/l10n/OMWControls/ru.yaml @@ -1,5 +1,5 @@ ControlsPage: "Управление OpenMW" -ControlsPageDescription: "Дополнительные настройки, связанные с управлением игроком" +ControlsPageDescription: "Дополнительные настройки, связанные с управлением игроком." MovementSettings: "Движение" @@ -14,5 +14,72 @@ toggleSneakDescription: | чтобы красться, её достаточно нажать единожды для переключения положения, а не зажимать. Игрокам, которые много времени крадутся, может быть проще управлять персонажем, когда опция включена. -# smoothControllerMovement -# smoothControllerMovementDescription +smoothControllerMovement: "Плавное движение на геймпаде" +smoothControllerMovementDescription: | + Эта настройка сглаживает движение при использовании стика. Это делает переход от ходьбы к бегу не таким резким. + +TogglePOV_name: "Изменить вид" +TogglePOV_description: "Переключиться между видами от первого и третьего лица. При зажатии будет включен режим предпросмотра." + +Zoom3rdPerson_name: "Приблизить/отдалить камеру" +Zoom3rdPerson_description: "Приблизить или отдалить от персонажа камеру в виде от третьего лица." + +MoveForward_name: "Двигаться вперед" +MoveForward_description: "Одновременное зажатие кнопки движения назад отменит движение." + +MoveBackward_name: "Двигаться назад" +MoveBackward_description: "Одновременное зажатие кнопки движения вперед отменит движение." + +MoveLeft_name: "Двигаться влево" +MoveLeft_description: "Одновременное зажатие кнопки движения вправо отменит движение." + +MoveRight_name: "Двигаться вправо" +MoveRight_description: "Одновременное зажатие кнопки движения влево отменит движение." + +Use_name: "Использовать" +Use_description: "Совершить атаку оружием или использовать заклинание в зависимости от текущей стойки." + +Run_name: "Бежать" +Run_description: "Бежать или идти при зажатии (в зависимости от значения настройки постоянного бега)." + +AlwaysRun_name: "Постоянный бег" +AlwaysRun_description: "Переключить настройку постоянного бега." + +Jump_name: "Прыгать" +Jump_description: "Прыгнуть, если вы находитесь на земле." + +AutoMove_name: "Автоматический бег" +AutoMove_description: "Переключить непрерывное движение вперед." + +Sneak_name: "Красться" +Sneak_description: "Красться при зажатии, если настройка переключения движения крадучись выключена." + +ToggleSneak_name: "Переключить движение крадучись" +ToggleSneak_description: "Переключить движение крадучись, если соответствующая настройка включена." + +ToggleWeapon_name: "Приготовить оружие" +ToggleWeapon_description: "Принять стойку оружия или покинуть её." + +ToggleSpell_name: "Приготовить магию" +ToggleSpell_description: "Принять стойку магии или покинуть её." + +Inventory_name: "Инвентарь" +Inventory_description: "Открыть инвентарь." + +Journal_name: "Дневник" +Journal_description: "Открыть дневник." + +QuickKeysMenu_name: "Меню быстрых клавиш" +QuickKeysMenu_description: "Открыть меню быстрых клавиш." + +SmoothMoveForward_name: "Плавно двигаться вперед" +SmoothMoveForward_description: "Движение вперед с плавными переходами от ходьбы к бегу." + +SmoothMoveBackward_name: "Плавно двигаться назад" +SmoothMoveBackward_description: "Движение назад с плавными переходами от ходьбы к бегу." + +SmoothMoveLeft_name: "Плавно двигаться влево" +SmoothMoveLeft_description: "Движение влево с плавными переходами от ходьбы к бегу." + +SmoothMoveRight_name: "Плавно двигаться вправо" +SmoothMoveRight_description: "Движение вправо с плавными переходами от ходьбы к бегу." diff --git a/files/data/l10n/OMWControls/sv.yaml b/files/data/l10n/OMWControls/sv.yaml index 73fc5e18dc..59fecd1f35 100644 --- a/files/data/l10n/OMWControls/sv.yaml +++ b/files/data/l10n/OMWControls/sv.yaml @@ -1,7 +1,7 @@ ControlsPage: "OpenMW Kontroller" ControlsPageDescription: "Ytterligare inställningar relaterade till spelarkontroller" -MovementSettings: "Rörelse" +MovementSettings: "Rörelser" alwaysRun: "Spring alltid" alwaysRunDescription: | @@ -14,5 +14,72 @@ toggleSneakDescription: | istället för att att kräva att knappen hålls nedtryckt för att smyga. Spelare som spenderar mycket tid med att smyga lär ha lättare att kontrollera rollfiguren med denna funktion aktiverad. -# smoothControllerMovement -# smoothControllerMovementDescription +smoothControllerMovement: "Mjuka handkontrollsrörelser" +smoothControllerMovementDescription: | + Aktiverar mjuka styrspaksrörelser för handkontroller. Gör övergången från gående till springande mindre abrubt. + +TogglePOV_name: "Växla perspektiv" +TogglePOV_description: "Växlar mellan första- och tredjepersonsperspektiv. Håll in för att aktivera omloppskamera." + +Zoom3rdPerson_name: "Zooma in/ut" +Zoom3rdPerson_description: "Flyttar kameran närmare / längre bort i tredjepersonsperspektivet." + +MoveForward_name: "Förflyttning framåt" +MoveForward_description: "Kan avbrytas med Förflyttning bakåt." + +MoveBackward_name: "Förflyttning bakåt" +MoveBackward_description: "Kan avbrytas med Förflyttning framåt." + +MoveLeft_name: "Förflyttning vänster" +MoveLeft_description: "Kan avbrytas med Förflyttning höger." + +MoveRight_name: "Förflyttning höger" +MoveRight_description: "Kan avbrytas med Förflyttning vänster." + +Use_name: "Använd" +Use_description: "Attackera med ett vapen eller kasta en besvärjelse beroende på nuvarande hållning." + +Run_name: "Spring" +Run_description: "Håll in för att springa/gå, beroende på Spring alltid-inställningen." + +AlwaysRun_name: "Spring alltid" +AlwaysRun_description: "Aktiverar Spring alltid-funktionen." + +Jump_name: "Hoppa" +Jump_description: "Hoppar när du är på marken." + +AutoMove_name: "Förflytta automatiskt" +AutoMove_description: "Aktiverar konstant förflyttning framåt." + +Sneak_name: "Smyg" +Sneak_description: "Håll in för att smyga, om Växla till smyg-inställningen är av." + +ToggleSneak_name: "Växla till smygläge" +ToggleSneak_description: "Knappen för smyg växlar till smygläge med ett tryck om denna är på." + +ToggleWeapon_name: "Redo vapen" +ToggleWeapon_description: "Gå in i eller lämna vapenposition." + +ToggleSpell_name: "Redo magi" +ToggleSpell_description: "Gå in i eller lämna magiposition." + +Inventory_name: "Lager" +Inventory_description: "Öppna lagret." + +Journal_name: "Dagbok" +Journal_description: "Öppna dagboken." + +QuickKeysMenu_name: "Snabbknappsmeny" +QuickKeysMenu_description: "Öppnar snabbknappsmenyn." + +SmoothMoveForward_name: "Mjuk förflyttning framåt" +SmoothMoveForward_description: "Framåtförflyttning anpassad för mjuka övergångar från gång till spring." + +SmoothMoveBackward_name: "Mjuk förflyttning bakåt" +SmoothMoveBackward_description: "Bakåtförflyttning anpassad för mjuka övergångar från gång till spring." + +SmoothMoveLeft_name: "Mjuk förflyttning vänster" +SmoothMoveLeft_description: "Vänsterförflyttning anpassad för mjuka övergångar från gång till spring." + +SmoothMoveRight_name: "Mjuk förflyttning höger" +SmoothMoveRight_description: "Högerförflyttning anpassad för mjuka övergångar från gång till spring." diff --git a/files/data/l10n/OMWEngine/de.yaml b/files/data/l10n/OMWEngine/de.yaml index aab58fb30c..3b583bfb21 100644 --- a/files/data/l10n/OMWEngine/de.yaml +++ b/files/data/l10n/OMWEngine/de.yaml @@ -1,114 +1,142 @@ # Console -#-- To be translated -#ConsoleWindow: "Console" +ConsoleWindow: "Kommandokonsole" # Debug window -DebugWindow: "Debug" -LogViewer: "Protokollansicht" +DebugWindow: "Debug-Fenster" +LogViewer: "Log-Ansicht" LuaProfiler: "Lua-Profiler" PhysicsProfiler: "Physik-Profiler" # Messages -BuildingNavigationMesh: "Baue Navigationsgitter" - -#-- To be translated -#AskLoadLastSave: "The most recent save is '%s'. Do you want to load it?" -#InitializingData: "Initializing Data..." -#LoadingExterior: "Loading Area" -#LoadingFailed: "Failed to load saved game" -#LoadingInterior: "Loading Area" -#LoadingInProgress: "Loading Save Game" -#LoadingRequiresNewVersionError: |- -# This save file was created using a newer version of OpenMW and is thus not supported. -# Please upgrade to the newest OpenMW version to load this file. -# LoadingRequiresOldVersionError: |- -# This save file was created using an older version of OpenMW in a format that is no longer supported. -# Load and save this file using {version} to upgrade it. -#NewGameConfirmation: "Do you want to start a new game and lose the current one?" -#QuitGameConfirmation: "Quit the game?" -#SaveGameDenied: "The game cannot be saved right now." -#SavingInProgress: "Saving..." -#ScreenshotFailed: "Failed to save screenshot" -#ScreenshotMade: "%s has been saved" +AskLoadLastSave: "Der aktuellste Spielstand ist '%s'. Soll er geladen werden?" +BuildingNavigationMesh: "Erstelle Navigationsgitter..." +InitializingData: "Initialisiere Daten..." +LoadingExterior: "Lade Bereich..." +LoadingFailed: "Laden des Spielstandes fehlgeschlagen!" +LoadingInterior: "Lade Bereich..." +LoadingInProgress: "Lade Spielstand..." +LoadingRequiresNewVersionError: |- + Der Spielstand wurde mit einer neueren Version von OpenMW erstellt und wird daher nicht unterstützt. + Bitte aktualisieren Sie Ihre OpenMW-Installation, um den Spielstand laden zu können. +LoadingRequiresOldVersionError: |- + Der Spielstand wurde mit einer älteren Version von OpenMW erstellt, deren Speicherformat nicht mehr unterstützt wird. + Laden und speichern Sie den Spielstand mit Version {version}, um das Format anzupassen. +NewGameConfirmation: "Neues Spiel starten und aktuellen Fortschritt verwerfen?" +QuitGameConfirmation: "Spiel verlassen?" +SaveGameDenied: "Das Spiel kann gerade nicht gespeichert werden!" +SavingInProgress: "Speichere Spiel..." +ScreenshotFailed: "Speichern des Screenshots fehlgeschlagen!" +ScreenshotMade: "%s wurde gespeichert." # Save game menu -SelectCharacter: "Charakterauswahl..." +DeleteGame: "Spielstand löschen" +DeleteGameConfirmation: "Möchten Sie den Spielstand wirklich löschen?" +EmptySaveNameError: "Der Spielstand kann ohne Name nicht gespeichert werden!" +LoadGameConfirmation: "Spielstand laden und aktuellen Fortschritt verwerfen?" +MissingContentFilesConfirmation: |- + Die aktuell ausgewählten Spieledateien stimmen nicht mit den im Spielstand verwendeten überein, + was zu Fehlern beim Laden und während des Spielens führen kann. + Möchten Sie wirklich fortfahren? +MissingContentFilesList: |- + {files, plural, + one{\n\nEine fehlende Datei gefunden: } + few{\n\n{files} fehlende Dateien gefunden:\n} + other{\n\n{files} fehlende Dateien gefunden:\n} + } +MissingContentFilesListCopy: |- + {files, plural, + one{\n\nDrücken Sie „Kopieren“, um den Namen in die Zwischenablage zu kopieren.} + few{\n\nDrücken Sie „Kopieren“, um alle Namen in die Zwischenablage zu kopieren.} + other{\n\nDrücken Sie „Kopieren“, um alle Namen in die Zwischenablage zu kopieren.} + } +OverwriteGameConfirmation: "Sind Sie sicher, dass Sie den Spielstand überschreiben wollen?" +SelectCharacter: "Charakter auswählen..." TimePlayed: "Spielzeit" -#-- To be translated -#DeleteGame: "Delete Game" -#DeleteGameConfirmation: "Are you sure you want to delete this saved game?" -#EmptySaveNameError: "Game can not be saved without a name!" -#LoadGameConfirmation: "Do you want to load a saved game and lose the current one?" -#MissingContentFilesConfirmation: |- -# The currently selected content files do not match the ones used by this save game. -# Errors may occur during load or game play. -# Do you wish to continue? -#MissingContentFilesList: |- -# {files, plural, -# one{\n\nFound missing file: } -# few{\n\nFound {files} missing files:\n} -# other{\n\nFound {files} missing files:\n} -# } -#MissingContentFilesListCopy: |- -# {files, plural, -# one{\n\nPress Copy to place its name to the clipboard.} -# few{\n\nPress Copy to place their names to the clipboard.} -# other{\n\nPress Copy to place their names to the clipboard.} -# } -#OverwriteGameConfirmation: "Are you sure you want to overwrite this saved game?" - # Settings menu ActorsProcessingRange: "Akteur-Verarbeitungsreichweite" Anisotropy: "Anisotropie" +Audio: "Audio" +AudioMaster: "Master-Lautstärke" +AudioVoice: "Stimmen" +AudioEffects: "Effekte" +AudioFootsteps: "Schritte" +AudioMusic: "Musik" CameraSensitivity: "Kameraempfindlichkeit" CameraZoomIn: "Kamera hineinzoomen" CameraZoomOut: "Kamera herauszoomen" -ChangeRequiresRestart: "Diese Änderung erfordert einen Neustart um wirksam zu werden." +ChangeRequiresRestart: "Diese Änderung erfordert einen Neustart, um wirksam zu werden." +ConfirmResetBindings: "Alle Tastenbelegungen zurücksetzen?" +ConfirmResolution: "Neue Auflösung wird sofort angewendet. Fortsetzen?" Controller: "Controller" +Controls: "Steuerung" +DelayLow: "Schnell" +DelayHigh: "Langsam" +DetailLevel: "Detailgrad" +Difficulty: "Schwierigkeitsgrad" +DifficultyEasy: "Leicht" +DifficultyHard: "Schwer" +DistanceHigh: "Weit" +DistanceLow: "Kurz" +EnableController: "Controller aktivieren" FieldOfView: "Sichtfeld" -FrameRateHint: "Hinweis: Drücken Sie F3, um die aktuelle Bildrate\nanzuzeigen." -InvertXAxis: "X-Achse umkehren" +FieldOfViewLow: "Niedrig" +FieldOfViewHigh: "Hoch" +FrameRateHint: "Hinweis: Drücken Sie F3,\num die aktuelle Bildrate anzuzeigen." +GammaCorrection: "Gamma-Korrektur" +GammaDark: "Dunkel" +GammaLight: "Hell" +GmstOverridesL10n: "Texte aus ESM-Dateien haben Vorrang" +InvertXAxis: "X-Achse invertieren" +InvertYAxis: "Y-Achse invertieren" Language: "Sprache" -LanguageNote: "Hinweis: Diese Einstellungen wirkt sich nicht auf Text von ESM-Dateien aus." +LanguageNote: "Hinweis: Diese Einstellungen wirken sich nicht auf Texte aus ESM-Dateien aus." LightingMethod: "Beleuchtungsmethode" LightingMethodLegacy: "Veraltet" LightingMethodShaders: "Shader" -LightingMethodShadersCompatibility: "Shader (Kompatibilität)" -LightingResetToDefaults: "Setzt auf Standardwerte zurück; möchten Sie fortfahren? Änderungen an der Beleuchtungsmethode erfordern einen Neustart." +LightingMethodShadersCompatibility: "Shader (Kompatibilitätsmodus)" +LightingResetToDefaults: "Einstellungen wirklich zurücksetzen? Das Ändern der „Beleuchtungsmethode“ erfordert einen Neustart." Lights: "Beleuchtung" -LightsBoundingSphereMultiplier: "Bounding-Sphere-Multiplikator" -LightsBoundingSphereMultiplierTooltip: "Standard: 1.65\nMultiplikator für Begrenzungskugel.\nHöhere Zahlen ermöglichen einen sanften Abfall, erfordern jedoch eine Erhöhung der Anzahl der maximalen Lichter.\n\nBeeinflusst nicht die Beleuchtung oder Lichtstärke." -LightsFadeStartMultiplier: "Licht Verblassungs-Start-Multiplikator" -LightsFadeStartMultiplierTooltip: "Standard: 0.85\nBruchteil der maximalen Entfernung, bei der die Lichter zu verblassen beginnen.\n\nStellen Sie hier einen niedrigen Wert für langsamere Übergänge oder einen hohen Wert für schnellere Übergänge ein." -#LightsLightingMethodTooltip: "Set the internal handling of light sources.\n\n -# \"Legacy\" always uses 8 lights per object and provides a lighting closest to an original game.\n\n -# \"Shaders (compatibility)\" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.\n\n -# \"Shaders\" carries all of the benefits that \"Shaders (compatibility)\" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware." +LightsBoundingSphereMultiplier: "Multiplikator für Begrenzungssphäre" +LightsBoundingSphereMultiplierTooltip: "Standard: 1,65\nMultiplikator für Größe der Begrenzungssphäre.\nGrößere Werte ermöglichen einen sanfteren Abfall, erfordern jedoch eine Erhöhung der maximalen Anzahl von Lichtquellen.\n\nBeeinflusst nicht die Beleuchtung oder Lichtintensität." +LightsFadeStartMultiplier: "Multiplikator für Startwert der Lichtabblendung" +LightsFadeStartMultiplierTooltip: "Standard: 0,85\nBruchteil der maximalen Lichtreichweite, bei der Lichtquellen langsam zu verblassen beginnen.\n\nKleinere Werte führen zu einem sanfteren Übergang, der allerdings bereits bei geringerer Entfernung startet; größere Werte machen den Übergang abrupter, betreffen aber nur weiter entfernte Lichtquellen." +LightsLightingMethodTooltip: "Legt die interne Behandlung von Lichtquellen fest.\n\n + „Veraltet“ verwendet immer bis zu 8 Lichtquellen pro Objekt und führt zu Ergebnissen, die denen der Original-Engine am ähnlichsten sind.\n\n + „Shader (Kompatibilitätsmodus)“ entfernt das Maximum von 8 Lichtquellen pro Objekt; Bodenvegetation wird von Lichtquellen beleuchtet und das sanfte Abblenden von Lichtquellen wird aktiviert. Es wird empfohlen, diese Option für ältere Hardware und Maximalwerte für Lichtquellen nahe 8 zu verwenden.\n\n + „Shader“ bietet alle Vorteile von „Shader (Kompatibilitätsmodus)“, nutzt aber einen moderneren Ansatz, der größere Maximalwerte für Lichtquellen bei geringen bis keinen Leistungseinbußen ermöglicht. Funktioniert möglicherweise nicht auf älterer Hardware." LightsMaximumDistance: "Maximale Lichtreichweite" -LightsMaximumDistanceTooltip: "Standard: 8192\nMaximale Entfernung, bei der Lichter erscheinen (gemessen in Einheiten).\n\nSetzen Sie dies auf 0, um eine unbegrenzte Entfernung zu verwenden." -LightsMinimumInteriorBrightness: "Minimale Innenhelligkeit" -LightsMinimumInteriorBrightnessTooltip: "Standard: 0.08\nMinimale Umgebungshelligkeit im Innenraum.\n\nErhöhen Sie dies, wenn Sie das Gefühl haben, dass Innenräume zu dunkel sind." -MaxLights: "Maximale Anzahl an Lichtern" -MaxLightsTooltip: "Standard: 8\nMaximale Anzahl von Lichtern pro Objekt.\n\nEine niedrige Zahl in der Nähe des Standardwerts führt zu Lichtsprüngen, ähnlich wie bei klassischer Beleuchtung." +LightsMaximumDistanceTooltip: "Standard: 8192 (1 Zelle)\nMaximale Entfernung, bis zu der Lichtquellen noch dargestellt werden (gemessen in In-Game-Einheiten).\n\nEin Wert von 0 entspricht einer unbegrenzten Reichweite." +LightsMinimumInteriorBrightness: "Minimale Helligkeit in Innenräumen" +LightsMinimumInteriorBrightnessTooltip: "Standard: 0,08\nMinimale Umgebungshelligkeit in Innenräumen.\n\nKann erhöht werden, falls Innenräume (v.a. bei Shader-Beleuchtungsmethoden) zu dunkel dargestellt werden." +MaxLights: "Maximale Anzahl von Lichtquellen pro Objekt" +MaxLightsTooltip: "Standard: 8\nMaximale Anzahl von Lichtquellen, die ein Objekt beleuchten können.\n\nKleine Werte können gerade an Orten mit vielen Lichtquellen zum Aufploppen und zum schnellen Wechsel von Lichtern führen, wie es aus der Original-Engine und anderen The-Elder-Scrolls-Titeln bekannt ist." +MenuHelpDelay: "Verzögerung des Hilfe-Menüs" +MenuTransparency: "Menü-Transparenz" MouseAndKeyboard: "Maus/Tastatur" -PostProcessing: "Nachbearbeitung" -PostProcessingTooltip: "Optimiert über Nachbearbeitungs-HUD, siehe Eingabeeinstellungen." +PostProcessing: "Post-Processing" +PostProcessingIsNotEnabled: "Post-Processing ist nicht aktiviert!" +PostProcessingTooltip: "Wird über Post-Processing-HUD konfiguriert (siehe Tastenbelegung)." +Preferences: "Einstellungen" PrimaryLanguage: "Hauptsprache" PrimaryLanguageTooltip: "Lokalisierungsdateien für diese Sprache haben die höchste Priorität." -RainRippleDetail: "Detail der Regenkräuselung" -RainRippleDetailDense: "Dicht" -RainRippleDetailSimple: "Einfach" -RainRippleDetailSparse: "Spärlich" -ReflectionShaderDetail: "Reflexions-Shader-Detail" +QualityHigh: "Hoch" +QualityLow: "Niedrig" +QualityMedium: "Mittel" +RainRippleDetail: "Detailgrad der Regentropfen-Wellen" +RainRippleDetailDense: "Dicht (mit Normal-Maps)" +RainRippleDetailSimple: "Einfach (keine Normal-Maps)" +RainRippleDetailSparse: "Spärlich (mit Normal-Maps)" +RebindAction: "Drücken Sie eine Taste oder einen Knopf, um diese Tastenbelegung zu ändern." +ReflectionShaderDetail: "Detailgrad des Wasserreflexions-Shaders" ReflectionShaderDetailActors: "Akteure" ReflectionShaderDetailGroundcover: "Bodenvegetation" ReflectionShaderDetailObjects: "Objekte" @@ -116,69 +144,40 @@ ReflectionShaderDetailSky: "Himmel" ReflectionShaderDetailTerrain: "Terrain" ReflectionShaderDetailWorld: "Welt" Refraction: "Lichtbrechung" -Screenshot: "Bildschirmfoto" +ResetControls: "Tastenbelegungen zurücksetzen" +Screenshot: "Screenshot" Scripts: "Skripte" +ScriptsDisabled: "Laden Sie einen Spielstand, um auf die Skripteinstellungen zugreifen zu können." SecondaryLanguage: "Sekundäre Sprache" -SecondaryLanguageTooltip: "Lokalisierungsdateien für diese Sprache können verwendet werden, wenn bei der primären Sprachdateien die erforderlichen Zeilen fehlen." -TextureFiltering: "Texturfilterung" +SecondaryLanguageTooltip: "Lokalisierungsdateien für diese Sprache werden als Rückfalloption verwendet, falls in den Dateien der Primärsprache die erforderlichen Zeilen fehlen." +SensitivityHigh: "Hoch" +SensitivityLow: "Niedrig" +SettingsWindow: "Optionen" +Subtitles: "Untertitel" +SunlightScattering: "Sonnenlicht-Streuung" +TestingExteriorCells: "Außenzellen testen" +TestingInteriorCells: "Innenzellen testen" +TextureFiltering: "Texturfilter" TextureFilteringBilinear: "Bilinear" -TextureFilteringDisabled: "Nichts" +TextureFilteringDisabled: "Keiner" TextureFilteringOther: "Sonstiges" TextureFilteringTrilinear: "Trilinear" ToggleHUD: "HUD umschalten" -TogglePostProcessorHUD: "Nachbearbeitungs-HUD umschalten" +TogglePostProcessorHUD: "Post-Processing-HUD umschalten" +TransparencyFull: "Vollständig" +TransparencyNone: "Keine" +Video: "Video" +ViewDistance: "Sichtweite" VSync: "VSync" +VSyncAdaptive: "Adaptiv" Water: "Wasser" -WaterShader: "Wasser Shader" +WaterShader: "Wasser-Shader" WaterShaderTextureQuality: "Texturqualität" -WindowBorder: "Fensterrand" +WindowBorder: "Fensterrahmen darstellen" WindowMode: "Fenstermodus" WindowModeFullscreen: "Vollbild" +WindowModeHint: "Hinweis: Die Option „Fenster in Vollbildgröße“\nverwendet automatisch die native Bildschirmauflösung." WindowModeWindowed: "Fenster" -WindowModeWindowedFullscreen: "Fenster Vollbild" - -#-- To be translated -#Audio: "Audio" -#AudioMaster: "Master" -#AudioVoice: "Voice" -#AudioEffects: "Effects" -#AudioFootsteps: "Footsteps" -#AudioMusic: "Music" -#ConfirmResetBindings: "Reset all controls to the default?" -#ConfirmResolution: "New resolution will be applied immediately. Do you want to continue?" -#Controls: "Controls" -#DelayLow: "Fast" -#DelayHigh: "Slow" -#DetailLevel: "Detail Level" -#Difficulty: "Difficulty" -#DifficultyEasy: "Easy" -#DifficultyHard: "Hard" -#DistanceHigh: "Far" -#DistanceLow: "Near" -#EnableController: "Enable Controller" -#FieldOfViewLow: "Low" -#FieldOfViewHigh: "High" -#GammaCorrection: "Gamma Correction" -#GammaDark: "Dark" -#GammaLight: "Light" -#GmstOverridesL10n: "Strings from ESM files have priority" -#InvertYAxis: "Invert Y Axis" -#MenuHelpDelay: "Menu Help Delay" -#MenuTransparency: "Menu Transparency" -#Preferences: "Prefs" -#PostProcessingIsNotEnabled -#QualityHigh: "High" -#QualityLow: "Low" -#QualityMedium: "Medium" -#RebindAction: "Press a key or button to rebind this control." -#ResetControls: "Reset Controls" -#SensitivityHigh: "High" -#SensitivityLow: "Low" -#SettingsWindow: "Options" -#Subtitles: "Subtitles" -#TestingExteriorCells: "Testing exterior cells" -#TestingInteriorCells: "Testing interior cells" -#TransparencyFull: "Full" -#TransparencyNone: "None" -#Video: "Video" -#ViewDistance: "View Distance" +WindowModeWindowedFullscreen: "Fenster in Vollbildgröße" +# More fitting translations of "wobbly" are welcome +WobblyShores: "Wabbelige Uferlinien" \ No newline at end of file diff --git a/files/data/l10n/OMWEngine/en.yaml b/files/data/l10n/OMWEngine/en.yaml index f6ad237394..37f9486083 100644 --- a/files/data/l10n/OMWEngine/en.yaml +++ b/files/data/l10n/OMWEngine/en.yaml @@ -13,7 +13,7 @@ PhysicsProfiler: "Physics Profiler" # Messages AskLoadLastSave: "The most recent save is '%s'. Do you want to load it?" -BuildingNavigationMesh: "Building navigation mesh" +BuildingNavigationMesh: "Building Navigation Mesh" InitializingData: "Initializing Data..." LoadingExterior: "Loading Area" LoadingFailed: "Failed to load saved game" @@ -57,7 +57,7 @@ MissingContentFilesListCopy: |- } OverwriteGameConfirmation: "Are you sure you want to overwrite this saved game?" SelectCharacter: "Select Character..." -TimePlayed: "Time played" +TimePlayed: "Time Played" # Settings menu @@ -94,7 +94,7 @@ FrameRateHint: "Hint: press F3 to show\nthe current frame rate." GammaCorrection: "Gamma Correction" GammaDark: "Dark" GammaLight: "Light" -GmstOverridesL10n: "Strings from ESM files have priority" +GmstOverridesL10n: "Strings From ESM Files Have Priority" InvertXAxis: "Invert X Axis" InvertYAxis: "Invert Y Axis" Language: "Language" @@ -131,12 +131,12 @@ PrimaryLanguageTooltip: "Localization files for this language have the highest p QualityHigh: "High" QualityLow: "Low" QualityMedium: "Medium" -RainRippleDetail: "Rain ripple detail" +RainRippleDetail: "Rain Ripple Detail" RainRippleDetailDense: "Dense" RainRippleDetailSimple: "Simple" RainRippleDetailSparse: "Sparse" RebindAction: "Press a key or button to rebind this control." -ReflectionShaderDetail: "Reflection shader detail" +ReflectionShaderDetail: "Reflection Shader Detail" ReflectionShaderDetailActors: "Actors" ReflectionShaderDetailGroundcover: "Groundcover" ReflectionShaderDetailObjects: "Objects" @@ -154,8 +154,9 @@ SensitivityHigh: "High" SensitivityLow: "Low" SettingsWindow: "Options" Subtitles: "Subtitles" -TestingExteriorCells: "Testing exterior cells" -TestingInteriorCells: "Testing interior cells" +SunlightScattering: "Sunlight Scattering" +TestingExteriorCells: "Testing Exterior Cells" +TestingInteriorCells: "Testing Interior Cells" TextureFiltering: "Texture Filtering" TextureFilteringBilinear: "Bilinear" TextureFilteringDisabled: "None" @@ -170,11 +171,12 @@ ViewDistance: "View Distance" VSync: "VSync" VSyncAdaptive: "Adaptive" Water: "Water" -WaterShader: "Water shader" -WaterShaderTextureQuality: "Texture quality" +WaterShader: "Water Shader" +WaterShaderTextureQuality: "Texture Quality" WindowBorder: "Window Border" WindowMode: "Window Mode" WindowModeFullscreen: "Fullscreen" WindowModeHint: "Hint: Windowed Fullscreen mode\nalways uses the native display resolution." WindowModeWindowed: "Windowed" WindowModeWindowedFullscreen: "Windowed Fullscreen" +WobblyShores: "Wobbly Shores" diff --git a/files/data/l10n/OMWEngine/fr.yaml b/files/data/l10n/OMWEngine/fr.yaml index 990ecfce9d..814314ff5a 100644 --- a/files/data/l10n/OMWEngine/fr.yaml +++ b/files/data/l10n/OMWEngine/fr.yaml @@ -2,11 +2,11 @@ ConsoleWindow: "Console" - # Debug window DebugWindow: "Fenêtre de débogage" LogViewer: "Journal" +LuaProfiler: "Profileur Lua" PhysicsProfiler: "Profileur des performances de la physique" @@ -22,22 +22,19 @@ LoadingInProgress: "Chargement de la sauvegarde" LoadingRequiresNewVersionError: |- Ce fichier de sauvegarde provient d'une version plus récente d'OpenMW, il n'est par consequent pas supporté. Mettez à jour votre version d'OpenMW afin de pouvoir charger cette sauvegarde. -# LoadingRequiresOldVersionError: |- -# This save file was created using an older version of OpenMW in a format that is no longer supported. -# Load and save this file using {version} to upgrade it. +LoadingRequiresOldVersionError: |- + Ce fichier de sauvegarde provient d'une version trop ancienne d'OpenMW, il n'est par consequent pas supporté. + Ouvrez et enregistez cette sauvegarde avec {version} pour la mettre à jour. NewGameConfirmation: "Voulez-vous démarrer une nouvelle partie ? Toute progression non sauvegardée sera perdue." QuitGameConfirmation: "Quitter la partie ?" SaveGameDenied: "Sauvegarde impossible" SavingInProgress: "Sauvegarde en cours..." -#ScreenshotFailed: "Failed to save screenshot" -#ScreenshotMade: "%s has been saved" +ScreenshotFailed: "Échec de la sauvegarde de la capture d'écran" +ScreenshotMade: "Capture d'écran %s sauvegardée" # Save game menu -SelectCharacter: "Sélection du personnage..." -TimePlayed: "Temps de jeu" - DeleteGame: "Supprimer la partie" DeleteGameConfirmation: "Voulez-vous réellement supprimer cette partie sauvegardée ?" EmptySaveNameError: "Impossible de sauvegarder une partie lui donner un nom !" @@ -46,19 +43,21 @@ MissingContentFilesConfirmation: |- Les données de jeu actuellement sélectionnées ne correspondent pas à celle indiquée dans cette sauvegarde. Cela peut entraîner des erreurs lors du chargement, mais aussi lors de votre partie. Voulez-vous continuer ? -#MissingContentFilesList: |- -# {files, plural, -# one{\n\nFound missing file: } -# few{\n\nFound {files} missing files:\n} -# other{\n\nFound {files} missing files:\n} -# } -#MissingContentFilesListCopy: |- -# {files, plural, -# one{\n\nPress Copy to place its name to the clipboard.} -# few{\n\nPress Copy to place their names to the clipboard.} -# other{\n\nPress Copy to place their names to the clipboard.} -# } +MissingContentFilesList: |- + {files, plural, + one{\n\nUn fichier manquant trouvé : } + few{\n\n{files} fichiers manquant trouvés :\n} + other{\n\n{files} fichiers manquant trouvés :\n} + } +MissingContentFilesListCopy: |- + {files, plural, + one{\n\nCliquez sur Copier pour placer ce nom dans le presse-papier.} + few{\n\nCliquez sur Copier pour placer ces noms dans le presse-papier.} + other{\n\nCliquez sur Copier pour placer ces noms dans le presse-papier.} + } OverwriteGameConfirmation: "Écraser la sauvegarde précédente ?" +SelectCharacter: "Sélection du personnage..." +TimePlayed: "Temps de jeu" # Settings menu @@ -100,31 +99,31 @@ InvertXAxis: "Inverser l'axe X" InvertYAxis: "Inverser l'axe Y" Language: "Localisation" LanguageNote: "Note: Ce paramètre n'affecte pas les textes des fichiers ESM." -LightingMethodLegacy: "Traditionnelle" LightingMethod: "Méthode d'affichage des lumières" -LightingMethodShadersCompatibility: "Shaders (mode de compatibilité)" +LightingMethodLegacy: "Traditionnelle" LightingMethodShaders: "Shaders" +LightingMethodShadersCompatibility: "Shaders (mode de compatibilité)" LightingResetToDefaults: "Voulez-vous réinitialiser les paramètres d'affichage des lumières à leur valeur par défaut ? Ces changements requièrent un redémarrage de l'application." +Lights: "Sources lumineuses" LightsBoundingSphereMultiplier: "Multiplicateur de sphère englobante" LightsBoundingSphereMultiplierTooltip: "valeur par défaut: 1.65\nMultiplicateur pour le rayon de la sphère incluant les sources lumineuses.\nUn multiplicateur plus élevé permet une extinction plus douce, mais applique un plus grand nombre de sources lumineuses sur chaque objet.\n\nCe paramètre ne modifie ni l'intensité ni la luminance des lumières." LightsFadeStartMultiplier: "Seuil de perte d'éclat lumineux" LightsFadeStartMultiplierTooltip: "valeur par défaut: 0.85\nFraction de la distance maximale d'une source à partir de laquelle l'intensité lumineuse commence à décroître.\n\nSélectionnez une valeur basse pour une transition douce ou une valeur plus élevée pour une transition plus abrupte." -#LightsLightingMethodTooltip: "Set the internal handling of light sources.\n\n -# \"Legacy\" always uses 8 lights per object and provides a lighting closest to an original game.\n\n -# \"Shaders (compatibility)\" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.\n\n -# \"Shaders\" carries all of the benefits that \"Shaders (compatibility)\" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware." +LightsLightingMethodTooltip: "Définit la gestion des sources lumineuses :\n\n + \"Traditionnelle\" Chaque objet est éclairé par 8 sources lumineuses. Cet méthode est la plus proche du jeu original.\n\n + \"Shaders (mode de compatibilité)\" supprime la limite des 8 sources lumineuses. Cette méthode permet d'éclairer la végétation au sol, mais aussi de configurer à quel distance une source lumineuse s'estompe. Ce choix est recommandé pour les ordinateurs plus anciens avec un nombre de sources lumineuses proche de 8.\n\n + \"Shaders\" offre tous les bénéfices apportés par \"Shaders (mode de compatibilité)\", mais utilise une approche moderne. Celle-ci permet, sur du matériel moderne, d'augmenter le nombre de sources lumineuses par objet sans perte de performance." LightsMaximumDistance: "Distance maximale des sources lumineuses" LightsMaximumDistanceTooltip: "valeur par défaut: 8192\nDistance maximale d'affichage des sources lumineuses (en unité de distance).\n\nMettez cette valeur à 0 pour une distance d'affichage infinie." LightsMinimumInteriorBrightness: "Luminosité intérieure minimale" LightsMinimumInteriorBrightnessTooltip: "valeur par défaut: 0.08\nLuminosité ambiante minimum en intérieur.\n\nAugmentez cette valeur si les intérieurs vous semblent trop sombres." -Lights: "Sources lumineuses" MaxLights: "Maximum de sources lumineuses" MaxLightsTooltip: "valeur par défaut: 8\nNombre maximum de sources lumineuses par objet.\n\nUne valeur faible mène à des apparitions tardives des sources lumineuses similaires à celles obtenues avec la méthode d'éclairage traditionnelle." MenuHelpDelay: "Délai d'affichage du menu d'aide" MenuTransparency: "Transparence des menus" MouseAndKeyboard: "Souris/Clavier" -PostProcessingIsNotEnabled: "Le post-traitement est désactivé" PostProcessing: "Post-traitement" +PostProcessingIsNotEnabled: "Le post-traitement est désactivé" PostProcessingTooltip: "Modifiable dans le HUD de post-traitement, accessible à partir les paramètres de contrôle." Preferences: "Préférences" PrimaryLanguage: "Localisation principale" @@ -147,19 +146,20 @@ ReflectionShaderDetailWorld: "Monde" Refraction: "Réfraction" ResetControls: "Réinitialiser les contrôles" Screenshot: "Capture d'écran" -ScriptsDisabled: "Chargez une sauvegarde pour accéder aux paramètres des scripts." Scripts: "Scripts" +ScriptsDisabled: "Chargez une sauvegarde pour accéder aux paramètres des scripts." SecondaryLanguage: "Localisation secondaire" SecondaryLanguageTooltip: "Localisation utilisée si le texte est absent de la localisation principale." SensitivityHigh: "Haute" SensitivityLow: "Faible" SettingsWindow: "Options" Subtitles: "Sous-titres" +SunlightScattering: "Diffusion des rayons solaires" TestingExteriorCells: "Vérification des espaces (cells) extérieurs" TestingInteriorCells: "Vérification des espaces (cells) intérieurs" +TextureFiltering: "Filtre appliqué aux textures" TextureFilteringBilinear: "Bilinéaire" TextureFilteringDisabled: "Aucun" -TextureFiltering: "Filtre appliqué aux textures" TextureFilteringOther: "Autre" TextureFilteringTrilinear: "Trilinéaire" ToggleHUD: "Afficher/masquer le HUD" @@ -169,11 +169,14 @@ TransparencyNone: "Basse" Video: "Graphisme" ViewDistance: "Distance d'affichage" VSync: "VSync" +VSyncAdaptive: "Adaptif" Water: "Eau" WaterShader: "Shader pour l'eau" WaterShaderTextureQuality: "Qualité des textures" WindowBorder: "Bordure de fenêtre" -WindowModeFullscreen: "Plein écran" WindowMode: "Mode d'affichage" +WindowModeFullscreen: "Plein écran" +WindowModeHint: "Info : Le mode \"Fenêtré plein écran\" utilise toujours la résolution native de l'écran." WindowModeWindowed: "Fenêtré" WindowModeWindowedFullscreen: "Fenêtré plein écran" +WobblyShores: "Rivages vacillants" diff --git a/files/data/l10n/OMWEngine/ru.yaml b/files/data/l10n/OMWEngine/ru.yaml index a9f396f73c..1edecbf8b0 100644 --- a/files/data/l10n/OMWEngine/ru.yaml +++ b/files/data/l10n/OMWEngine/ru.yaml @@ -135,6 +135,7 @@ RainRippleDetail: "Капли дождя на воде" RainRippleDetailDense: "Плотные" RainRippleDetailSimple: "Упрощенные" RainRippleDetailSparse: "Редкие" +RebindAction: "Нажмите клавишу, которую нужно назначить на это действие." ReflectionShaderDetail: "Детализация отражений" ReflectionShaderDetailActors: "Персонажи" ReflectionShaderDetailGroundcover: "Трава" @@ -143,7 +144,6 @@ ReflectionShaderDetailSky: "Небо" ReflectionShaderDetailTerrain: "Ландшафт" ReflectionShaderDetailWorld: "Мир" Refraction: "Рефракция" -RebindAction: "Нажмите клавишу, которую нужно назначить на это действие." ResetControls: "Сбросить" Screenshot: "Снимок экрана" Scripts: "Скрипты" @@ -154,17 +154,18 @@ SensitivityHigh: "Высокая" SensitivityLow: "Низкая" SettingsWindow: "Настройки" Subtitles: "Субтитры" -TestingExteriorCells: "Проверка наружних ячеек" +SunlightScattering: "Рассеяние солнечного света" +TestingExteriorCells: "Проверка наружных ячеек" TestingInteriorCells: "Проверка ячеек-помещений" TextureFiltering: "Фильтрация текстур" TextureFilteringBilinear: "Билинейная" TextureFilteringDisabled: "Отключена" TextureFilteringOther: "Другая" TextureFilteringTrilinear: "Трилинейная" -TransparencyFull: "Прозрачное" -TransparencyNone: "Непрозрачное" ToggleHUD: "Переключить HUD" TogglePostProcessorHUD: "Меню настроек постобработки" +TransparencyFull: "Прозрачное" +TransparencyNone: "Непрозрачное" ViewDistance: "Дальность обзора" Video: "Видео" VSync: "Вертикальная синхронизация" @@ -178,3 +179,4 @@ WindowModeFullscreen: "Полный экран" WindowModeHint: "Подсказка: режим Оконный без полей\nвсегда использует родное разрешение экрана." WindowModeWindowed: "Оконный" WindowModeWindowedFullscreen: "Оконный без полей" +WobblyShores: "Колеблющиеся берега" diff --git a/files/data/l10n/OMWEngine/sv.yaml b/files/data/l10n/OMWEngine/sv.yaml index bbc6132f55..791efb660d 100644 --- a/files/data/l10n/OMWEngine/sv.yaml +++ b/files/data/l10n/OMWEngine/sv.yaml @@ -22,15 +22,15 @@ LoadingInProgress: "Laddar sparat spel" LoadingRequiresNewVersionError: |- Denna sparfil skapades i en nyare version av OpenMW och stöds därför inte. Uppgradera till den senaste versionen av OpenMW för att ladda filen. -# LoadingRequiresOldVersionError: |- -# This save file was created using an older version of OpenMW in a format that is no longer supported. -# Load and save this file using {version} to upgrade it. + LoadingRequiresOldVersionError: |- + Denna sparfil skapades i en äldre version av OpenMW i ett format som inte längre stöds. + Ladda och spara denna sparfil med {version} för att uppgradera den. NewGameConfirmation: "Vill du starta ett nytt spel och förlora det pågående spelet?" QuitGameConfirmation: "Avsluta spelet?" SaveGameDenied: "Spelet kan inte sparas just nu." SavingInProgress: "Sparar..." -#ScreenshotFailed: "Failed to save screenshot" -#ScreenshotMade: "%s has been saved" +ScreenshotFailed: "Misslyckades att spara skärmdump" +ScreenshotMade: "%s har sparats" # Save game menu @@ -44,18 +44,18 @@ MissingContentFilesConfirmation: |- De valda innehållsfilerna matchar inte filerna som används av denna sparfil. Fel kan uppstå vid laddning eller under spel. Vill du fortsätta? -#MissingContentFilesList: |- -# {files, plural, -# one{\n\nFound missing file: } -# few{\n\nFound {files} missing files:\n} -# other{\n\nFound {files} missing files:\n} -# } -#MissingContentFilesListCopy: |- -# {files, plural, -# one{\n\nPress Copy to place its name to the clipboard.} -# few{\n\nPress Copy to place their names to the clipboard.} -# other{\n\nPress Copy to place their names to the clipboard.} -# } +MissingContentFilesList: |- + {files, plural, + one{\n\nHittade saknad fil: } + few{\n\nHittade {files} saknade filer:\n} + other{\n\nHittade {files} saknade filer:\n} + } +MissingContentFilesListCopy: |- + {files, plural, + one{\n\nPress Kopiera för att placera namnet i urklipp.} + few{\n\nPress Kopiera för att placera deras namn i urklipp.} + other{\n\nPress Kopiera för att placera deras namn i urklipp.} + } OverwriteGameConfirmation: "Är du säker på att du vill skriva över det här sparade spelet?" SelectCharacter: "Välj spelfigur..." @@ -109,10 +109,10 @@ LightsBoundingSphereMultiplier: "Gränssfärsmultiplikator" LightsBoundingSphereMultiplierTooltip: "Förvalt: 1.65\nMultiplikator för ljusens gränssfär.\nHögre värden ger mjukare minskning av gränssfären, men kräver högre värde i Max antal ljuskällor.\n\nPåverkar inte ljusstyrkan." LightsFadeStartMultiplier: "Blekningsstartmultiplikator" LightsFadeStartMultiplierTooltip: "Förvalt: 0.85\nFraktion av det maximala avståndet från vilket ljuskällor börjar blekna.\n\nVälj lågt värde för långsammare övergång eller högre värde för snabbare övergång." -#LightsLightingMethodTooltip: "Set the internal handling of light sources.\n\n -# \"Legacy\" always uses 8 lights per object and provides a lighting closest to an original game.\n\n -# \"Shaders (compatibility)\" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.\n\n -# \"Shaders\" carries all of the benefits that \"Shaders (compatibility)\" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware." +LightsLightingMethodTooltip: "Välj intern hantering av ljuskällor.\n\n + \"Gammaldags\" använder alltid max 8 ljuskällor per objekt och ger ljussättning likt ett gammaldags spel.\n\n + \"Shader (kompatibilitet)\" tar bort begränsningen med max 8 ljuskällor per objekt. Detta läge aktiverar också ljus på marktäckning och ett konfigurerbart ljusbleknande. Rekommenderas för äldre hårdvara tillsammans med en ljusbegränsning nära 8.\n\n + \"Shader\" har alla fördelar som \"Shader (kompatibilitet)\" har, med med ett modernt förhållningssätt som möjliggör fler maximalt antal ljuskällor med liten eller ingen prestandaförlust på modern hårdvara." LightsMaximumDistance: "Maximalt ljusavstånd" LightsMaximumDistanceTooltip: "Förvalt: 8192\nMaximala avståndet där ljuskällor syns (mätt i enheter).\n\nVärdet 0 ger oändligt avstånd." LightsMinimumInteriorBrightness: "Minsta ljusstyrka i interiörer" @@ -154,6 +154,7 @@ SensitivityHigh: "Hög" SensitivityLow: "Låg" SettingsWindow: "Inställningar" Subtitles: "Undertexter" +SunlightScattering: "Solljusspridning" TestingExteriorCells: "Testar exteriöra celler" TestingInteriorCells: "Testar interiöra celler" TextureFiltering: "Texturfiltrering" @@ -166,8 +167,9 @@ TogglePostProcessorHUD: "Visa/dölj Postprocess-HUD" TransparencyFull: "Full" TransparencyNone: "Ingen" Video: "Bild" -VSync: "VSynk" ViewDistance: "Siktavstånd" +VSync: "VSynk" +VSyncAdaptive: "Adaptiv" Water: "Vatten" WaterShader: "Vattenshader" WaterShaderTextureQuality: "Texturkvalitet" @@ -177,3 +179,4 @@ WindowModeFullscreen: "Fullskärm" WindowModeHint: "Notera: Fullskärm i fönsterläge\nanvänder alltid skärmens nativa upplösning." WindowModeWindowed: "Fönster" WindowModeWindowedFullscreen: "Fullskärm i fönsterläge" +WobblyShores: "Vaggande stränder" diff --git a/files/data/l10n/OMWShaders/de.yaml b/files/data/l10n/OMWShaders/de.yaml index 7d17199dd9..7ea5eb59e3 100644 --- a/files/data/l10n/OMWShaders/de.yaml +++ b/files/data/l10n/OMWShaders/de.yaml @@ -1,6 +1,6 @@ # Post-processing HUD -Abovewater: "Überwasser" +Abovewater: "Über Wasser" ActiveShaders: "Aktive Shader" Author: "Autor" Description: "Beschreibung" @@ -8,27 +8,66 @@ InactiveShaders: "Inaktive Shader" InExteriors: "Außenbereich" InInteriors: "Innenbereich" KeyboardControls: | - Tastatursteuerung:: + Tastatursteuerung: Shift+Pfeil-Rechts > Aktiviere Shader Shift+Pfeil-Links > Deaktiviere Shader - Shift+Pfeil-Hoch > Verschiebe Shader nach oben - Shift+Pfeil-Runter > Verschiebe Shader nach unten -PostProcessHUD: "Nachbearbeitungs HUD" -ResetShader: "Setze Shader auf Standardzustand zurück" -ShaderLocked: "Verschlossen" -ShaderLockedDescription: "Kann nicht umgeschaltet oder verschoben werden, gesteuert durch externes Lua-Skript" + Shift+Pfeil-Hoch > Verschiebe Shader in Liste nach oben + Shift+Pfeil-Runter > Verschiebe Shader in Liste nach unten +# Better avoid the German translation "Nachbearbeitung" as it might lead to confusion. +PostProcessHUD: "Post-Processing-HUD" +ResetShader: "Shader zurücksetzen" +ShaderLocked: "Gesperrt" +ShaderLockedDescription: "Shader kann nicht umgeschaltet oder verschoben werden, sondern wird durch externes Lua-Skript gesteuert." ShaderResetUniform: "r" -Underwater: "Unterwasser" +Underwater: "Unter Wasser" Version: "Version" # Built-in post-processing shaders -DisplayDepthName: "Visualisiert den Tiefenpuffer." -DisplayDepthFactorDescription: "Bestimmt die Korrelation zwischen dem Pixeltiefenwert und seiner Ausgabefarbe. Hohe Werte führen zu einem helleren Bild." -DisplayDepthFactorName: "Farbfaktor" -ContrastLevelDescription: "Kontraststufe" +# Adjustments +# +AdjustmentsDescription: "Farbanpassungen" +# "Contrast Level" +ContrastLevelDescription: "Kontrast-Wert" ContrastLevelName: "Kontrast" -GammaLevelDescription: "Gamma-Level" +# "Gamma Level" GammaLevelName: "Gamma" +GammaLevelDescription: "Gamma-Wert" + +# Bloom +# +BloomDescription: "Bloom-Shader, der seine Berechnungen bei ungefähr linearem Licht durchführt." +# "Bloom Clamp Level" +BloomClampLevelName: "Oberer Grenzwert" +BloomClampLevelDescription: "Begrenze die Helligkeit einzelner Pixel auf diesen Wert, bevor Bloom in die Szene gemischt wird." +# "Bloom Threshold Level" +BloomThresholdLevelName: "Unterer Schwellenwert" +BloomThresholdLevelDescription: "Ignoriere Pixel, deren Helligkeit geringer als dieser Wert ist, bei der Berechnung der Bloom-Unschärfe. Dies wirkt sich nicht auf den Himmel aus." +# "Gamma Level" +# (see above) +# "Radius Level" +RadiusLevelName: "Radius" +RadiusLevelDescription: "Radius des Effektes" +# "Sky Factor Level" +SkyFactorLevelName: "Himmel-Faktor" +SkyFactorLevelDescription: "Multiplikator für Licht, das direkt vom Himmel kommt." +# "Strength Level" +StrengthLevelName: "Strength" +StrengthLevelDescription: "Stärke des Effektes" + + +# Debug +# +DebugDescription: "Debug-Shader" +# "Display Depth" +DebugHeaderDepth: "Tiefenpuffer (Z-Buffer)" +DisplayDepthName: "Visualisiere den Tiefenpuffer (Z-Puffer)" +# "Depth Factor" +DisplayDepthFactorName: "Faktor für Tiefenfärbung" +DisplayDepthFactorDescription: "Verknüpfungsfaktor zwischen dem Tiefenwert eines Pixels und der ausgegebenen Farbe. Größere Werte führen zu einem helleren Bild." +# "Display Normals" +DebugHeaderNormals: "Normalenvektoren" +DisplayNormalsName: "Visualisiere Normalenvektoren" +NormalsInWorldSpace: "Zeige Normalenvektoren in Spielwelt-Koordinaten" \ No newline at end of file diff --git a/files/data/l10n/OMWShaders/en.yaml b/files/data/l10n/OMWShaders/en.yaml index 6588591f00..a8c13da34b 100644 --- a/files/data/l10n/OMWShaders/en.yaml +++ b/files/data/l10n/OMWShaders/en.yaml @@ -14,7 +14,7 @@ KeyboardControls: | Shift+Left-Arrow > Deactive shader Shift+Up-Arrow > Move shader up Shift+Down-Arrow > Move shader down -PostProcessHUD: "Postprocess HUD" +PostProcessHUD: "Post Processor HUD" ResetShader: "Reset shader to default state" ShaderLocked: "Locked" ShaderLockedDescription: "Cannot be toggled or moved, controlled by external Lua script" @@ -30,11 +30,11 @@ BloomDescription: "Bloom shader performing its calculations in (approximately) l DebugDescription: "Debug shader." DebugHeaderDepth: "Depth Buffer" DebugHeaderNormals: "Normals" -DisplayDepthFactorName: "Depth colour factor" +DisplayDepthFactorName: "Depth Colour Factor" DisplayDepthFactorDescription: "Determines correlation between pixel depth value and its output colour. High values lead to brighter image." -DisplayDepthName: "Visualize depth buffer" -DisplayNormalsName: "Visualize pass normals" -NormalsInWorldSpace: "Show normals in world space" +DisplayDepthName: "Visualize Depth Buffer" +DisplayNormalsName: "Visualize Pass Normals" +NormalsInWorldSpace: "Show Normals in World Space" ContrastLevelDescription: "Constrast level." ContrastLevelName: "Constrast" GammaLevelDescription: "Gamma level." diff --git a/files/data/l10n/OMWShaders/fr.yaml b/files/data/l10n/OMWShaders/fr.yaml index 3b4e47370e..45513589eb 100644 --- a/files/data/l10n/OMWShaders/fr.yaml +++ b/files/data/l10n/OMWShaders/fr.yaml @@ -34,6 +34,7 @@ DisplayDepthFactorName: "Rapport couleur/profondeur" DisplayDepthFactorDescription: "Détermine la corrélation entre la valeur de profondeur d'un pixel et sa couleur de sortie. Une valeur élevée mène à une image plus lumineuse." DisplayDepthName: "Visualise le Z-buffer (tampon de profondeur)." DisplayNormalsName: "Visualise le G-buffer de normales (normals pass)" +NormalsInWorldSpace: "Affiche la normale de la surface de chaque objet" ContrastLevelDescription: "Niveau de contraste" ContrastLevelName: "Contraste" GammaLevelDescription: "Correction Gamma" diff --git a/files/data/mygui/openmw_layers.xml b/files/data/mygui/openmw_layers.xml index 045fb1cdc2..459db3fcb9 100644 --- a/files/data/mygui/openmw_layers.xml +++ b/files/data/mygui/openmw_layers.xml @@ -13,6 +13,7 @@ + diff --git a/files/data/mygui/openmw_settings_window.layout b/files/data/mygui/openmw_settings_window.layout index 27298b9756..5a25a61936 100644 --- a/files/data/mygui/openmw_settings_window.layout +++ b/files/data/mygui/openmw_settings_window.layout @@ -1,6 +1,6 @@  - + @@ -457,7 +457,7 @@ - + @@ -467,6 +467,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/data/mygui/openmw_windows.skin.xml b/files/data/mygui/openmw_windows.skin.xml index 14f6930b3c..9491862b34 100644 --- a/files/data/mygui/openmw_windows.skin.xml +++ b/files/data/mygui/openmw_windows.skin.xml @@ -457,7 +457,6 @@ - @@ -592,7 +591,6 @@ - @@ -729,7 +727,6 @@ - diff --git a/files/data/openmw_aux/ui.lua b/files/data/openmw_aux/ui.lua index 0abd3f2f6d..cadae2a033 100644 --- a/files/data/openmw_aux/ui.lua +++ b/files/data/openmw_aux/ui.lua @@ -24,7 +24,7 @@ function aux_ui.deepLayoutCopy(layout) for k, v in pairs(layout) do if k == 'content' then result[k] = deepContentCopy(v) - elseif type(v) == 'table' then + elseif type(v) == 'table' and getmetatable(v) == nil then result[k] = aux_ui.deepLayoutCopy(v) else result[k] = v diff --git a/files/data/scripts/omw/activationhandlers.lua b/files/data/scripts/omw/activationhandlers.lua index edadc30f79..a70696824f 100644 --- a/files/data/scripts/omw/activationhandlers.lua +++ b/files/data/scripts/omw/activationhandlers.lua @@ -33,6 +33,9 @@ local function onActivate(obj, actor) if world.isWorldPaused() then return end + if obj.parentContainer then + return + end local handlers = handlersPerObject[obj.id] if handlers then for i = #handlers, 1, -1 do diff --git a/files/data/scripts/omw/console/global.lua b/files/data/scripts/omw/console/global.lua index bba0cbc7b3..d1d5ae423a 100644 --- a/files/data/scripts/omw/console/global.lua +++ b/files/data/scripts/omw/console/global.lua @@ -23,6 +23,7 @@ local env = { core = require('openmw.core'), types = require('openmw.types'), vfs = require('openmw.vfs'), + markup = require('openmw.markup'), async = require('openmw.async'), world = require('openmw.world'), aux_util = require('openmw_aux.util'), diff --git a/files/data/scripts/omw/console/local.lua b/files/data/scripts/omw/console/local.lua index 6962b9e798..1acd18df0c 100644 --- a/files/data/scripts/omw/console/local.lua +++ b/files/data/scripts/omw/console/local.lua @@ -25,6 +25,7 @@ local env = { core = require('openmw.core'), types = require('openmw.types'), vfs = require('openmw.vfs'), + markup = require('openmw.markup'), async = require('openmw.async'), nearby = require('openmw.nearby'), self = require('openmw.self'), diff --git a/files/data/scripts/omw/console/menu.lua b/files/data/scripts/omw/console/menu.lua index 9d6dbaf1d7..b6851bc646 100644 --- a/files/data/scripts/omw/console/menu.lua +++ b/files/data/scripts/omw/console/menu.lua @@ -47,6 +47,7 @@ local env = { core = require('openmw.core'), storage = require('openmw.storage'), vfs = require('openmw.vfs'), + markup = require('openmw.markup'), ambient = require('openmw.ambient'), async = require('openmw.async'), ui = require('openmw.ui'), diff --git a/files/data/scripts/omw/console/player.lua b/files/data/scripts/omw/console/player.lua index 6d0ee790a9..9d2e372a93 100644 --- a/files/data/scripts/omw/console/player.lua +++ b/files/data/scripts/omw/console/player.lua @@ -72,6 +72,7 @@ local env = { core = require('openmw.core'), types = require('openmw.types'), vfs = require('openmw.vfs'), + markup = require('openmw.markup'), ambient = require('openmw.ambient'), async = require('openmw.async'), nearby = require('openmw.nearby'), diff --git a/files/data/scripts/omw/input/actionbindings.lua b/files/data/scripts/omw/input/actionbindings.lua index 35a467fb52..bc871a3934 100644 --- a/files/data/scripts/omw/input/actionbindings.lua +++ b/files/data/scripts/omw/input/actionbindings.lua @@ -1,11 +1,7 @@ -local core = require('openmw.core') local input = require('openmw.input') local util = require('openmw.util') local async = require('openmw.async') local storage = require('openmw.storage') -local ui = require('openmw.ui') - -local I = require('openmw.interfaces') local actionPressHandlers = {} local function onActionPress(id, handler) @@ -89,48 +85,87 @@ end local bindingSection = storage.playerSection('OMWInputBindings') -local keyboardPresses = {} -local keybordHolds = {} -local boundActions = {} +local devices = { + keyboard = true, + mouse = true, + controller = true +} + +local function invalidBinding(binding) + if not binding.key then + return 'has no key' + elseif binding.type ~= 'action' and binding.type ~= 'trigger' then + return string.format('has invalid type', binding.type) + elseif binding.type == 'action' and not input.actions[binding.key] then + return string.format("action %s doesn't exist", binding.key) + elseif binding.type == 'trigger' and not input.triggers[binding.key] then + return string.format("trigger %s doesn't exist", binding.key) + elseif not binding.device or not devices[binding.device] then + return string.format("invalid device %s", binding.device) + elseif not binding.button then + return 'has no button' + end +end -local function bindAction(action) - if boundActions[action] then return end - boundActions[action] = true - input.bindAction(action, async:callback(function() - if keybordHolds[action] then - for _, binding in pairs(keybordHolds[action]) do - if input.isKeyPressed(binding.code) then return true end +local boundActions = {} +local actionBindings = {} + +local function bindAction(binding, id) + local action = binding.key + actionBindings[action] = actionBindings[action] or {} + actionBindings[action][id] = binding + if not boundActions[action] then + boundActions[binding.key] = true + input.bindAction(action, async:callback(function() + for _, binding in pairs(actionBindings[action] or {}) do + if binding.device == 'keyboard' then + if input.isKeyPressed(binding.button) then + return true + end + elseif binding.device == 'mouse' then + if input.isMouseButtonPressed(binding.button) then + return true + end + elseif binding.device == 'controller' then + if input.isControllerButtonPressed(binding.button) then + return true + end + end end - end - return false - end), {}) + return false + end), {}) + end +end + +local triggerBindings = {} +for device in pairs(devices) do triggerBindings[device] = {} end + +local function bindTrigger(binding, id) + local deviceBindings = triggerBindings[binding.device] + deviceBindings[binding.button] = deviceBindings[binding.button] or {} + deviceBindings[binding.button][id] = binding end local function registerBinding(binding, id) - if not input.actions[binding.key] and not input.triggers[binding.key] then - print(string.format('Skipping binding for unknown action or trigger: "%s"', binding.key)) - return - end - if binding.type == 'keyboardPress' then - local bindings = keyboardPresses[binding.code] or {} - bindings[id] = binding - keyboardPresses[binding.code] = bindings - elseif binding.type == 'keyboardHold' then - local bindings = keybordHolds[binding.key] or {} - bindings[id] = binding - keybordHolds[binding.key] = bindings - bindAction(binding.key) - else - error('Unknown binding type "' .. binding.type .. '"') + local invalid = invalidBinding(binding) + if invalid then + print(string.format('Skipping invalid binding %s: %s', id, invalid)) + elseif binding.type == 'action' then + bindAction(binding, id) + elseif binding.type == 'trigger' then + bindTrigger(binding, id) end end function clearBinding(id) - for _, boundTriggers in pairs(keyboardPresses) do - boundTriggers[id] = nil + for _, deviceBindings in pairs(triggerBindings) do + for _, buttonBindings in pairs(deviceBindings) do + buttonBindings[id] = nil + end end - for _, boundKeys in pairs(keybordHolds) do - boundKeys[id] = nil + + for _, bindings in pairs(actionBindings) do + bindings[id] = nil end end @@ -170,11 +205,24 @@ return { end end, onKeyPress = function(e) - local bindings = keyboardPresses[e.code] - if bindings then - for _, binding in pairs(bindings) do - input.activateTrigger(binding.key) - end + local buttonTriggers = triggerBindings.keyboard[e.code] + if not buttonTriggers then return end + for _, binding in pairs(buttonTriggers) do + input.activateTrigger(binding.key) + end + end, + onMouseButtonPress = function(button) + local buttonTriggers = triggerBindings.mouse[button] + if not buttonTriggers then return end + for _, binding in pairs(buttonTriggers) do + input.activateTrigger(binding.key) + end + end, + onControllerButtonPress = function(id) + local buttonTriggers = triggerBindings.controller[id] + if not buttonTriggers then return end + for _, binding in pairs(buttonTriggers) do + input.activateTrigger(binding.key) end end, } diff --git a/files/data/scripts/omw/input/playercontrols.lua b/files/data/scripts/omw/input/playercontrols.lua index 311b5a16a9..202e604087 100644 --- a/files/data/scripts/omw/input/playercontrols.lua +++ b/files/data/scripts/omw/input/playercontrols.lua @@ -83,6 +83,7 @@ end local movementControlsOverridden = false local autoMove = false +local attemptToJump = false local function processMovement() local movement = input.getRangeActionValue('MoveForward') - input.getRangeActionValue('MoveBackward') local sideMovement = input.getRangeActionValue('MoveRight') - input.getRangeActionValue('MoveLeft') @@ -97,6 +98,7 @@ local function processMovement() self.controls.movement = movement self.controls.sideMovement = sideMovement self.controls.run = run + self.controls.jump = attemptToJump if not settings:get('toggleSneak') then self.controls.sneak = input.getBooleanActionValue('Sneak') @@ -115,7 +117,7 @@ end input.registerTriggerHandler('Jump', async:callback(function() if not movementAllowed() then return end - self.controls.jump = Player.getControlSwitch(self, Player.CONTROL_SWITCH.Jumping) + attemptToJump = Player.getControlSwitch(self, Player.CONTROL_SWITCH.Jumping) end)) input.registerTriggerHandler('ToggleSneak', async:callback(function() @@ -223,6 +225,7 @@ local function onFrame(_) if combatAllowed() then processAttacking() end + attemptToJump = false end local function onSave() diff --git a/files/data/scripts/omw/input/settings.lua b/files/data/scripts/omw/input/settings.lua index 6c1b857131..83a862b2d2 100644 --- a/files/data/scripts/omw/input/settings.lua +++ b/files/data/scripts/omw/input/settings.lua @@ -44,16 +44,65 @@ local bindingSection = storage.playerSection('OMWInputBindings') local recording = nil +local mouseButtonNames = { + [1] = 'Left', + [2] = 'Middle', + [3] = 'Right', + [4] = '4', + [5] = '5', +} + +-- TODO: support different controllers, use icons to render controller buttons +local controllerButtonNames = { + [-1] = 'Invalid', + [input.CONTROLLER_BUTTON.A] = "A", + [input.CONTROLLER_BUTTON.B] = "B", + [input.CONTROLLER_BUTTON.X] = "X", + [input.CONTROLLER_BUTTON.Y] = "Y", + [input.CONTROLLER_BUTTON.Back] = "Back", + [input.CONTROLLER_BUTTON.Guide] = "Guide", + [input.CONTROLLER_BUTTON.Start] = "Start", + [input.CONTROLLER_BUTTON.LeftStick] = "Left Stick", + [input.CONTROLLER_BUTTON.RightStick] = "Right Stick", + [input.CONTROLLER_BUTTON.LeftShoulder] = "LB", + [input.CONTROLLER_BUTTON.RightShoulder] = "RB", + [input.CONTROLLER_BUTTON.DPadUp] = "D-pad Up", + [input.CONTROLLER_BUTTON.DPadDown] = "D-pad Down", + [input.CONTROLLER_BUTTON.DPadLeft] = "D-pad Left", + [input.CONTROLLER_BUTTON.DPadRight] = "D-pad Right", +} + +local function bindingLabel(recording, binding) + if recording then + return interfaceL10n('N/A') + elseif not binding or not binding.button then + return interfaceL10n('None') + elseif binding.device == 'keyboard' then + return input.getKeyName(binding.button) + elseif binding.device == 'mouse' then + return string.format('Mouse %s', mouseButtonNames[binding.button] or 'Unknown') + elseif binding.device == 'controller' then + return string.format('Controller %s', controllerButtonNames[binding.button] or 'Unknown') + else + return 'Unknown' + end +end + +local inputTypes = { + action = input.actions, + trigger = input.triggers, +} I.Settings.registerRenderer('inputBinding', function(id, set, arg) if type(id) ~= 'string' then error('inputBinding: must have a string default value') end if not arg then error('inputBinding: argument with "key" and "type" is required') end if not arg.type then error('inputBinding: type argument is required') end + if not inputTypes[arg.type] then error('inputBinding: type must be "action" or "trigger"') end if not arg.key then error('inputBinding: key argument is required') end - local info = input.actions[arg.key] or input.triggers[arg.key] - if not info then return {} end + local info = inputTypes[arg.type][arg.key] + if not info then print(string.format('inputBinding: %s %s not found', arg.type, arg.key)) return end - local l10n = core.l10n(info.key) + local l10n = core.l10n(info.l10n) local name = { template = I.MWUI.templates.textNormal, @@ -70,9 +119,7 @@ I.Settings.registerRenderer('inputBinding', function(id, set, arg) } local binding = bindingSection:get(id) - local label = interfaceL10n('None') - if binding then label = input.getKeyName(binding.code) end - if recording and recording.id == id then label = interfaceL10n('N/A') end + local label = bindingLabel(recording and recording.id == id, binding) local recorder = { template = I.MWUI.templates.textNormal, @@ -115,22 +162,30 @@ I.Settings.registerRenderer('inputBinding', function(id, set, arg) return column end) +local function bindButton(device, button) + if recording == nil then return end + local binding = { + device = device, + button = button, + type = recording.arg.type, + key = recording.arg.key, + } + bindingSection:set(recording.id, binding) + local refresh = recording.refresh + recording = nil + refresh() +end + return { engineHandlers = { onKeyPress = function(key) - if recording == nil then return end - local binding = { - code = key.code, - type = recording.arg.type, - key = recording.arg.key, - } - if key.code == input.KEY.Escape then -- TODO: prevent settings modal from closing - binding.code = nil - end - bindingSection:set(recording.id, binding) - local refresh = recording.refresh - recording = nil - refresh() + bindButton(key.code ~= input.KEY.Escape and 'keyboard' or nil, key.code) + end, + onMouseButtonPress = function(button) + bindButton('mouse', button) + end, + onControllerButtonPress = function(id) + bindButton('controller', id) end, } } diff --git a/files/data/scripts/omw/mechanics/playercontroller.lua b/files/data/scripts/omw/mechanics/playercontroller.lua index 19e62d02c7..333e097404 100644 --- a/files/data/scripts/omw/mechanics/playercontroller.lua +++ b/files/data/scripts/omw/mechanics/playercontroller.lua @@ -1,7 +1,13 @@ +local ambient = require('openmw.ambient') local core = require('openmw.core') +local Skill = core.stats.Skill +local I = require('openmw.interfaces') local nearby = require('openmw.nearby') local self = require('openmw.self') local types = require('openmw.types') +local NPC = types.NPC +local Actor = types.Actor +local ui = require('openmw.ui') local cell = nil local autodoors = {} @@ -31,6 +37,65 @@ local function processAutomaticDoors() end end +local function skillLevelUpHandler(skillid, source, params) + local skillStat = NPC.stats.skills[skillid](self) + if skillStat.base >= 100 then + return false + end + + if params.skillIncreaseValue then + skillStat.base = skillStat.base + params.skillIncreaseValue + end + + local levelStat = Actor.stats.level(self) + if params.levelUpProgress then + levelStat.progress = levelStat.progress + params.levelUpProgress + end + + if params.levelUpAttribute and params.levelUpAttributeIncreaseValue then + levelStat.skillIncreasesForAttribute[params.levelUpAttribute] + = levelStat.skillIncreasesForAttribute[params.levelUpAttribute] + params.levelUpAttributeIncreaseValue + end + + if params.levelUpSpecialization and params.levelUpSpecializationIncreaseValue then + levelStat.skillIncreasesForSpecialization[params.levelUpSpecialization] + = levelStat.skillIncreasesForSpecialization[params.levelUpSpecialization] + params.levelUpSpecializationIncreaseValue; + end + + local skillRecord = Skill.record(skillid) + local npcRecord = NPC.record(self) + local class = NPC.classes.record(npcRecord.class) + + ambient.playSound("skillraise") + + local message = string.format(core.getGMST('sNotifyMessage39'),skillRecord.name,skillStat.base) + + if source == I.SkillProgression.SKILL_INCREASE_SOURCES.Book then + message = '#{sBookSkillMessage}\n'..message + end + + ui.showMessage(message) + + if levelStat.progress >= core.getGMST('iLevelUpTotal') then + ui.showMessage('#{sLevelUpMsg}') + end + + if not source or source == I.SkillProgression.SKILL_INCREASE_SOURCES.Usage then skillStat.progress = 0 end +end + +local function skillUsedHandler(skillid, params) + if NPC.isWerewolf(self) then + return false + end + + local skillStat = NPC.stats.skills[skillid](self) + skillStat.progress = skillStat.progress + params.skillGain / I.SkillProgression.getSkillProgressRequirement(skillid) + + if skillStat.progress >= 1 then + I.SkillProgression.skillLevelUp(skillid, I.SkillProgression.SKILL_INCREASE_SOURCES.Usage) + end +end + local function onUpdate() if self.cell ~= cell then cell = self.cell @@ -39,6 +104,9 @@ local function onUpdate() processAutomaticDoors() end +I.SkillProgression.addSkillUsedHandler(skillUsedHandler) +I.SkillProgression.addSkillLevelUpHandler(skillLevelUpHandler) + return { engineHandlers = { onUpdate = onUpdate, diff --git a/files/data/scripts/omw/settings/global.lua b/files/data/scripts/omw/settings/global.lua index f7356d15c4..15a9614636 100644 --- a/files/data/scripts/omw/settings/global.lua +++ b/files/data/scripts/omw/settings/global.lua @@ -1,7 +1,7 @@ local storage = require('openmw.storage') local common = require('scripts.omw.settings.common') -common.getSection(true, common.groupSectionKey):removeOnExit() +common.getSection(true, common.groupSectionKey):setLifeTime(storage.LIFE_TIME.Temporary) return { interfaceName = 'Settings', diff --git a/files/data/scripts/omw/settings/menu.lua b/files/data/scripts/omw/settings/menu.lua index 88913143e8..4e6971a516 100644 --- a/files/data/scripts/omw/settings/menu.lua +++ b/files/data/scripts/omw/settings/menu.lua @@ -6,8 +6,11 @@ local core = require('openmw.core') local storage = require('openmw.storage') local I = require('openmw.interfaces') +local auxUi = require('openmw_aux.ui') + local common = require('scripts.omw.settings.common') --- :reset on startup instead of :removeOnExit +common.getSection(false, common.groupSectionKey):setLifeTime(storage.LIFE_TIME.GameSession) +-- need to :reset() on reloadlua as well as game session end common.getSection(false, common.groupSectionKey):reset() local renderers = {} @@ -21,6 +24,7 @@ local interfaceL10n = core.l10n('Interface') local pages = {} local groups = {} local pageOptions = {} +local groupElements = {} local interval = { template = I.MWUI.templates.interval } local growingIntreval = { @@ -116,6 +120,11 @@ local function renderSetting(group, setting, value, global) } end local argument = common.getArgumentSection(global, group.key):get(setting.key) + local ok, rendererResult = pcall(renderFunction, value, set, argument) + if not ok then + print(string.format('Setting %s renderer "%s" error: %s', setting.key, setting.renderer, rendererResult)) + end + return { name = setting.key, type = ui.TYPE.Flex, @@ -129,7 +138,7 @@ local function renderSetting(group, setting, value, global) content = ui.content { titleLayout, growingIntreval, - renderFunction(value, set, argument), + ok and rendererResult or {}, -- TODO: display error? }, } end @@ -245,10 +254,12 @@ end local function generateSearchHints(page) local hints = {} - local l10n = core.l10n(page.l10n) - table.insert(hints, l10n(page.name)) - if page.description then - table.insert(hints, l10n(page.description)) + do + local l10n = core.l10n(page.l10n) + table.insert(hints, l10n(page.name)) + if page.description then + table.insert(hints, l10n(page.description)) + end end local pageGroups = groups[page.key] for _, pageGroup in pairs(pageGroups) do @@ -268,7 +279,7 @@ local function generateSearchHints(page) return table.concat(hints, ' ') end -local function renderPage(page) +local function renderPage(page, options) local l10n = core.l10n(page.l10n) local sortedGroups = {} for _, group in pairs(groups[page.key]) do @@ -281,7 +292,15 @@ local function renderPage(page) if not group then error(string.format('%s group "%s" was not found', pageGroup.global and 'Global' or 'Player', pageGroup.key)) end - table.insert(groupLayouts, renderGroup(group, pageGroup.global)) + local groupElement = groupElements[page.key][group.key] + if not groupElement or not groupElement.layout then + groupElement = ui.create(renderGroup(group, pageGroup.global)) + end + if groupElement.layout == nil then + error(string.format('Destroyed group element for %s %s', page.key, group.key)) + end + groupElements[page.key][group.key] = groupElement + table.insert(groupLayouts, groupElement) end local groupsLayout = { name = 'groups', @@ -329,11 +348,14 @@ local function renderPage(page) bigSpacer, }, } - return { - name = l10n(page.name), - element = ui.create(layout), - searchHints = generateSearchHints(page), - } + options.name = l10n(page.name) + options.searchHints = generateSearchHints(page) + if options.element then + options.element.layout = layout + options.element:update() + else + options.element = ui.create(layout) + end end local function onSettingChanged(global) @@ -341,14 +363,23 @@ local function onSettingChanged(global) local group = common.getSection(global, common.groupSectionKey):get(groupKey) if not group or not pageOptions[group.page] then return end - local value = common.getSection(global, group.key):get(settingKey) + local groupElement = groupElements[group.page][group.key] + + if not settingKey then + if groupElement then + groupElement.layout = renderGroup(group) + groupElement:update() + else + renderPage(pages[group.page], pageOptions[group.page]) + end + return + end - local element = pageOptions[group.page].element - local groupsLayout = element.layout.content.groups - local groupLayout = groupsLayout.content[groupLayoutName(group.key, global)] - local settingsContent = groupLayout.content.settings.content + local value = common.getSection(global, group.key):get(settingKey) + local settingsContent = groupElement.layout.content.settings.content + auxUi.deepDestroy(settingsContent[settingKey]) -- support setting renderers which return UI elements settingsContent[settingKey] = renderSetting(group, group.settings[settingKey], value, global) - element:update() + groupElement:update() end) end @@ -357,6 +388,8 @@ local function onGroupRegistered(global, key) if not group then return end groups[group.page] = groups[group.page] or {} + groupElements[group.page] = groupElements[group.page] or {} + local pageGroup = { key = group.key, global = global, @@ -373,10 +406,8 @@ local function onGroupRegistered(global, key) local value = common.getSection(global, group.key):get(settingKey) - local element = pageOptions[group.page].element - local groupsLayout = element.layout.content.groups - local groupLayout = groupsLayout.content[groupLayoutName(group.key, global)] - local settingsContent = groupLayout.content.settings.content + local element = groupElements[group.page][group.key] + local settingsContent = element.layout.content.settings.content settingsContent[settingKey] = renderSetting(group, group.settings[settingKey], value, global) element:update() end)) @@ -385,15 +416,8 @@ local function onGroupRegistered(global, key) groups[group.page][pageGroup.key] = pageGroup if not pages[group.page] then return end - if pageOptions[group.page] then - pageOptions[group.page].element:destroy() - else - pageOptions[group.page] = {} - end - local renderedOptions = renderPage(pages[group.page]) - for k, v in pairs(renderedOptions) do - pageOptions[group.page][k] = v - end + pageOptions[group.page] = pageOptions[group.page] or {} + renderPage(pages[group.page], pageOptions[group.page]) end local function updateGroups(global) @@ -411,10 +435,7 @@ local function updateGroups(global) end end)) end - local updatePlayerGroups = function() updateGroups(false) end -updatePlayerGroups() - local updateGlobalGroups = function() updateGroups(true) end local menuGroups = {} @@ -423,22 +444,28 @@ local menuPages = {} local function resetPlayerGroups() local playerGroupsSection = storage.playerSection(common.groupSectionKey) for pageKey, page in pairs(groups) do - for groupKey, group in pairs(page) do - if not menuGroups[groupKey] and not group.global then + for groupKey in pairs(page) do + if not menuGroups[groupKey] then + if groupElements[pageKey][groupKey] then + groupElements[pageKey][groupKey]:destroy() + print(string.format('destroyed group element %s %s', pageKey, groupKey)) + groupElements[pageKey][groupKey] = nil + end page[groupKey] = nil playerGroupsSection:set(groupKey, nil) end end - if pageOptions[pageKey] then - pageOptions[pageKey].element:destroy() + local options = pageOptions[pageKey] + if options then if not menuPages[pageKey] then - ui.removeSettingsPage(pageOptions[pageKey]) + if options.element then + auxUi.deepDestroy(options.element) + options.element = nil + end + ui.removeSettingsPage(options) pageOptions[pageKey] = nil else - local renderedOptions = renderPage(pages[pageKey]) - for k, v in pairs(renderedOptions) do - pageOptions[pageKey][k] = v - end + renderPage(pages[pageKey], options) end end end @@ -468,17 +495,16 @@ local function registerPage(options) } pages[page.key] = page groups[page.key] = groups[page.key] or {} - if pageOptions[page.key] then - pageOptions[page.key].element:destroy() - end pageOptions[page.key] = pageOptions[page.key] or {} - local renderedOptions = renderPage(page) - for k, v in pairs(renderedOptions) do - pageOptions[page.key][k] = v - end + renderPage(page, pageOptions[page.key]) ui.registerSettingsPage(pageOptions[page.key]) end +updatePlayerGroups() +if menu.getState() == menu.STATE.Running then -- handle reloadlua correctly + updateGlobalGroups() +end + return { interfaceName = 'Settings', interface = { diff --git a/files/data/scripts/omw/skillhandlers.lua b/files/data/scripts/omw/skillhandlers.lua new file mode 100644 index 0000000000..e3ca24f9d0 --- /dev/null +++ b/files/data/scripts/omw/skillhandlers.lua @@ -0,0 +1,290 @@ +local self = require('openmw.self') +local I = require('openmw.interfaces') +local types = require('openmw.types') +local core = require('openmw.core') +local NPC = require('openmw.types').NPC +local Skill = core.stats.Skill + +--- +-- Table of skill use types defined by morrowind. +-- Each entry corresponds to an index into the available skill gain values +-- of a @{openmw.core#SkillRecord} +-- @type SkillUseType +-- @field #number Armor_HitByOpponent 0 +-- @field #number Block_Success 0 +-- @field #number Spellcast_Success 0 +-- @field #number Weapon_SuccessfulHit 0 +-- @field #number Alchemy_CreatePotion 0 +-- @field #number Alchemy_UseIngredient 1 +-- @field #number Enchant_Recharge 0 +-- @field #number Enchant_UseMagicItem 1 +-- @field #number Enchant_CreateMagicItem 2 +-- @field #number Enchant_CastOnStrike 3 +-- @field #number Acrobatics_Jump 0 +-- @field #number Acrobatics_Fall 1 +-- @field #number Mercantile_Success 0 +-- @field #number Mercantile_Bribe 1 +-- @field #number Security_DisarmTrap 0 +-- @field #number Security_PickLock 1 +-- @field #number Sneak_AvoidNotice 0 +-- @field #number Sneak_PickPocket 1 +-- @field #number Speechcraft_Success 0 +-- @field #number Speechcraft_Fail 1 +-- @field #number Armorer_Repair 0 +-- @field #number Athletics_RunOneSecond 0 +-- @field #number Athletics_SwimOneSecond 1 + +--- +-- Table of all existing sources for skill increases. Any sources not listed below will be treated as equal to Trainer. +-- @type SkillLevelUpSource +-- @field #string Book book +-- @field #string Trainer trainer +-- @field #string Usage usage + + +local skillUsedHandlers = {} +local skillLevelUpHandlers = {} + +local function tableHasValue(table, value) + for _, v in pairs(table) do + if v == value then return true end + end + return false +end + +local function shallowCopy(t1) + local t2 = {} + for key, value in pairs(t1) do t2[key] = value end + return t2 +end + +local function getSkillProgressRequirement(skillid) + local npcRecord = NPC.record(self) + local class = NPC.classes.record(npcRecord.class) + local skillStat = NPC.stats.skills[skillid](self) + local skillRecord = Skill.record(skillid) + + local factor = core.getGMST('fMiscSkillBonus') + if tableHasValue(class.majorSkills, skillid) then + factor = core.getGMST('fMajorSkillBonus') + elseif tableHasValue(class.minorSkills, skillid) then + factor = core.getGMST('fMinorSkillBonus') + end + + if skillRecord.specialization == class.specialization then + factor = factor * core.getGMST('fSpecialSkillBonus') + end + + return (skillStat.base + 1) * factor +end + + +local function skillUsed(skillid, options) + if #skillUsedHandlers == 0 then + -- If there are no handlers, then there won't be any effect, so skip calculations + return + end + + -- Make a copy so we don't change the caller's table + options = shallowCopy(options) + + -- Compute use value if it was not supplied directly + if not options.skillGain then + if not options.useType or options.useType > 3 or options.useType < 0 then + print('Error: Unknown useType: '..tostring(options.useType)) + return + end + local skillStat = NPC.stats.skills[skillid](self) + local skillRecord = Skill.record(skillid) + options.skillGain = skillRecord.skillGain[options.useType + 1] + + if options.scale then + options.skillGain = options.skillGain * options.scale + end + end + + for i = #skillUsedHandlers, 1, -1 do + if skillUsedHandlers[i](skillid, options) == false then + return + end + end +end + +local function skillLevelUp(skillid, source) + if #skillLevelUpHandlers == 0 then + -- If there are no handlers, then there won't be any effect, so skip calculations + return + end + + local skillRecord = Skill.record(skillid) + local npcRecord = NPC.record(self) + local class = NPC.classes.record(npcRecord.class) + + local levelUpProgress = 0 + local levelUpAttributeIncreaseValue = core.getGMST('iLevelupMiscMultAttriubte') + + if tableHasValue(class.minorSkills, skillid) then + levelUpProgress = core.getGMST('iLevelUpMinorMult') + levelUpAttributeIncreaseValue = core.getGMST('iLevelUpMinorMultAttribute') + elseif tableHasValue(class.majorSkills, skillid) then + levelUpProgress = core.getGMST('iLevelUpMajorMult') + levelUpAttributeIncreaseValue = core.getGMST('iLevelUpMajorMultAttribute') + end + + local options = + { + skillIncreaseValue = 1, + levelUpProgress = levelUpProgress, + levelUpAttribute = skillRecord.attribute, + levelUpAttributeIncreaseValue = levelUpAttributeIncreaseValue, + levelUpSpecialization = skillRecord.specialization, + levelUpSpecializationIncreaseValue = core.getGMST('iLevelupSpecialization'), + } + + for i = #skillLevelUpHandlers, 1, -1 do + if skillLevelUpHandlers[i](skillid, source, options) == false then + return + end + end +end + +return { + interfaceName = 'SkillProgression', + --- + -- Allows to extend or override built-in skill progression mechanics. + -- @module SkillProgression + -- @usage local I = require('openmw.interfaces') + -- + -- -- Forbid increasing destruction skill past 50 + -- I.SkillProgression.addSkillLevelUpHandler(function(skillid, options) + -- if skillid == 'destruction' and types.NPC.stats.skills.destruction(self).base >= 50 then + -- return false + -- end + -- end) + -- + -- -- Scale sneak skill progression based on active invisibility effects + -- I.SkillProgression.addSkillUsedHandler(function(skillid, params) + -- if skillid == 'sneak' and params.useType == I.SkillProgression.SKILL_USE_TYPES.Sneak_AvoidNotice then + -- local activeEffects = Actor.activeEffects(self) + -- local visibility = activeEffects:getEffect(core.magic.EFFECT_TYPE.Chameleon).magnitude / 100 + -- visibility = visibility + activeEffects:getEffect(core.magic.EFFECT_TYPE.Invisibility).magnitude + -- visibility = 1 - math.min(1, math.max(0, visibility)) + -- local oldSkillGain = params.skillGain + -- params.skillGain = oldSkillGain * visibility + -- end + -- end + -- + interface = { + --- Interface version + -- @field [parent=#SkillProgression] #number version + version = 1, + + --- Add new skill level up handler for this actor. + -- For load order consistency, handlers should be added in the body if your script. + -- If `handler(skillid, source, options)` returns false, other handlers (including the default skill level up handler) + -- will be skipped. Where skillid and source are the parameters passed to @{#SkillProgression.skillLevelUp}, and options is + -- a modifiable table of skill level up values, and can be modified to change the behavior of later handlers. + -- These values are calculated based on vanilla mechanics. Setting any value to nil will cause that mechanic to be skipped. By default contains these values: + -- + -- * `skillIncreaseValue` - The numeric amount of skill levels gained. + -- * `levelUpProgress` - The numeric amount of level up progress gained. + -- * `levelUpAttribute` - The string identifying the attribute that should receive points from this skill level up. + -- * `levelUpAttributeIncreaseValue` - The numeric amount of attribute increase points received. This contributes to the amount of each attribute the character receives during a vanilla level up. + -- * `levelUpSpecialization` - The string identifying the specialization that should receive points from this skill level up. + -- * `levelUpSpecializationIncreaseValue` - The numeric amount of specialization increase points received. This contributes to the icon displayed at the level up screen during a vanilla level up. + -- + -- @function [parent=#SkillProgression] addSkillLevelUpHandler + -- @param #function handler The handler. + addSkillLevelUpHandler = function(handler) + skillLevelUpHandlers[#skillLevelUpHandlers + 1] = handler + end, + + --- Add new skillUsed handler for this actor. + -- For load order consistency, handlers should be added in the body of your script. + -- If `handler(skillid, options)` returns false, other handlers (including the default skill progress handler) + -- will be skipped. Where options is a modifiable table of skill progression values, and can be modified to change the behavior of later handlers. + -- Contains a `skillGain` value as well as a shallow copy of the options passed to @{#SkillProgression.skillUsed}. + -- @function [parent=#SkillProgression] addSkillUsedHandler + -- @param #function handler The handler. + addSkillUsedHandler = function(handler) + skillUsedHandlers[#skillUsedHandlers + 1] = handler + end, + + --- Trigger a skill use, activating relevant handlers + -- @function [parent=#SkillProgression] skillUsed + -- @param #string skillid The if of the skill that was used + -- @param options A table of parameters. Must contain one of `skillGain` or `useType`. It's best to always include `useType` if applicable, even if you set `skillGain`, as it may be used + -- by handlers to make decisions. See the addSkillUsedHandler example at the top of this page. + -- + -- * `skillGain` - The numeric amount of skill to be gained. + -- * `useType` - #SkillUseType, A number from 0 to 3 (inclusive) representing the way the skill was used, with each use type having a different skill progression rate. Available use types and its effect is skill specific. See @{#SkillUseType} + -- + -- And may contain the following optional parameter: + -- + -- * `scale` - A numeric value used to scale the skill gain. Ignored if the `skillGain` parameter is set. + -- + -- Note that a copy of this table is passed to skill used handlers, so any parameters passed to this method will also be passed to the handlers. This can be used to provide additional information to + -- custom handlers when making custom skill progressions. + -- + skillUsed = skillUsed, + + --- @{#SkillUseType} + -- @field [parent=#SkillProgression] #SkillUseType SKILL_USE_TYPES Available skill usage types + SKILL_USE_TYPES = { + -- These are shared by multiple skills + Armor_HitByOpponent = 0, + Block_Success = 0, + Spellcast_Success = 0, + Weapon_SuccessfulHit = 0, + + -- Skill-specific use types + Alchemy_CreatePotion = 0, + Alchemy_UseIngredient = 1, + Enchant_Recharge = 0, + Enchant_UseMagicItem = 1, + Enchant_CreateMagicItem = 2, + Enchant_CastOnStrike = 3, + Acrobatics_Jump = 0, + Acrobatics_Fall = 1, + Mercantile_Success = 0, + Mercantile_Bribe = 1, -- Note: This is bugged in vanilla and is not actually in use. + Security_DisarmTrap = 0, + Security_PickLock = 1, + Sneak_AvoidNotice = 0, + Sneak_PickPocket = 1, + Speechcraft_Success = 0, + Speechcraft_Fail = 1, + Armorer_Repair = 0, + Athletics_RunOneSecond = 0, + Athletics_SwimOneSecond = 1, + }, + + --- Trigger a skill level up, activating relevant handlers + -- @function [parent=#SkillProgression] skillLevelUp + -- @param #string skillid The id of the skill to level up. + -- @param #SkillLevelUpSource source The source of the skill increase. + skillLevelUp = skillLevelUp, + + --- @{#SkillLevelUpSource} + -- @field [parent=#SkillProgression] #SkillLevelUpSource SKILL_INCREASE_SOURCES + SKILL_INCREASE_SOURCES = { + Book = 'book', + Usage = 'usage', + Trainer = 'trainer', + }, + + --- Compute the total skill gain required to level up a skill based on its current level, and other modifying factors such as major skills and specialization. + -- @function [parent=#SkillProgression] getSkillProgressRequirement + -- @param #string skillid The id of the skill to compute skill progress requirement for + getSkillProgressRequirement = getSkillProgressRequirement + }, + engineHandlers = { + -- Use the interface in these handlers so any overrides will receive the calls. + _onSkillUse = function (skillid, useType, scale) + I.SkillProgression.skillUsed(skillid, {useType = useType, scale = scale}) + end, + _onSkillLevelUp = function (skillid, source) + I.SkillProgression.skillLevelUp(skillid, source) + end, + }, +} diff --git a/files/lang/components_de.ts b/files/lang/components_de.ts index 59ba558328..76b90229fc 100644 --- a/files/lang/components_de.ts +++ b/files/lang/components_de.ts @@ -29,6 +29,14 @@ <b>Author:</b> %1<br/><b>Format version:</b> %2<br/><b>Modified:</b> %3<br/><b>Path:</b><br/>%4<br/><br/><b>Description:</b><br/>%5<br/><br/><b>Dependencies: </b>%6<br/> + + <br/><b>This content file cannot be disabled because it is part of OpenMW.</b><br/> + + + + <br/><b>This content file cannot be disabled because it is enabled in a config file other than the user one.</b><br/> + + ContentSelectorView::ContentSelector diff --git a/files/lang/components_fr.ts b/files/lang/components_fr.ts index c1c70ba277..706dc1c988 100644 --- a/files/lang/components_fr.ts +++ b/files/lang/components_fr.ts @@ -5,81 +5,91 @@ ContentSelector Select language used by ESM/ESP content files to allow OpenMW to detect their encoding. - + Sélectionnez la langue utilisée dans les fichiers ESM/ESP afin qu'OpenMW détecte leur encodage. ContentSelectorModel::ContentModel Unable to find dependent file: %1 - + Impossible de trouver le fichier de dépendance : %1 Dependent file needs to be active: %1 - + Le fichier de dépendance doit être activé : %1 This file needs to load after %1 - + Ce fichier doit être chargé après %1 ContentSelectorModel::EsmFile <b>Author:</b> %1<br/><b>Format version:</b> %2<br/><b>Modified:</b> %3<br/><b>Path:</b><br/>%4<br/><br/><b>Description:</b><br/>%5<br/><br/><b>Dependencies: </b>%6<br/> - + <b>Auteur :</b> %1<br/><b>Version du format :</b> %2<br/><b>Modifié le :</b> %3<br/><b>Emplacement :</b><br/>%4<br/><br/><b>Description :</b><br/>%5<br/><br/><b>Dépendences : </b>%6<br/> + + + <br/><b>This content file cannot be disabled because it is part of OpenMW.</b><br/> + <br/><b>Ce fichier de contenu ne peut être désactivé, car il fait partie intégrante d'OpenMW.</b><br/> + + + <br/><b>This content file cannot be disabled because it is enabled in a config file other than the user one.</b><br/> + <br/><b>Ce fichier de contenu ne peut être désactivé, car il est activé par un fichier de configuration non contrôlé par l'utilisateur.</b><br/> ContentSelectorView::ContentSelector &Check Selected - + &Activer la sélection &Uncheck Selected - + &Désactiver la sélection &Copy Path(s) to Clipboard - + &Copier l'emplacement dans le presse papier <No game file> - + <Pas de fichier de jeu> Process::ProcessInvoker Error starting executable - + Erreur au démarrage de l'application Error running executable - + Erreur lors de l'exécution de l'application Arguments: - + +Arguments : + <html><head/><body><p><b>Could not find %1</b></p><p>The application is not found.</p><p>Please make sure OpenMW is installed correctly and try again.</p></body></html> - + <html><head/><body><p><b>Impossible de trouver %1</b></p><p>L'application est manquante.</p><p>Assurez-vous qu'OpenMW est installé correctement puis réessayez.</p></body></html> <html><head/><body><p><b>Could not start %1</b></p><p>The application is not executable.</p><p>Please make sure you have the right permissions and try again.</p></body></html> - + <html><head/><body><p><b>Impossible de démarrer %1</b></p><p>L'application n'est pas exécutable.</p><p>Assurez-vous d'avoir les bons droits d'accès puis réessayez.</p></body></html> <html><head/><body><p><b>Could not start %1</b></p><p>An error occurred while starting %1.</p><p>Press "Show Details..." for more information.</p></body></html> - + <html><head/><body><p><b>Impossible de démarrer %1</b></p><p>Une erreur est apparue au lancement de %1.</p><p>Cliquez sur "Montrer les détails..." pour plus d'informations.</p></body></html> <html><head/><body><p><b>Executable %1 returned an error</b></p><p>An error occurred while running %1.</p><p>Press "Show Details..." for more information.</p></body></html> - + <html><head/><body><p><b>Le programme %1 a envoyé une erreur.</b></p><p>Une erreur est apparue durant l'exécution de %1.</p><p>Cliquez sur "Montrer les détails..." pour plus d'informations</p></body></html> diff --git a/files/lang/components_ru.ts b/files/lang/components_ru.ts index cca6591afe..d70ebe320a 100644 --- a/files/lang/components_ru.ts +++ b/files/lang/components_ru.ts @@ -29,6 +29,14 @@ <b>Author:</b> %1<br/><b>Format version:</b> %2<br/><b>Modified:</b> %3<br/><b>Path:</b><br/>%4<br/><br/><b>Description:</b><br/>%5<br/><br/><b>Dependencies: </b>%6<br/> <b>Автор:</b> %1<br/><b>Версия формата данных:</b> %2<br/><b>Дата изменения:</b> %3<br/><b>Путь к файлу:</b><br/>%4<br/><br/><b>Описание:</b><br/>%5<br/><br/><b>Зависимости: </b>%6<br/> + + <br/><b>This content file cannot be disabled because it is part of OpenMW.</b><br/> + <br/><b>Этот контентный файл не может быть отключен, потому что он является частью OpenMW.</b><br/> + + + <br/><b>This content file cannot be disabled because it is enabled in a config file other than the user one.</b><br/> + <br/><b>Этот контентный файл не может быть отключен, потому что он включен в конфигурационном файле, не являющемся пользовательским.</b><br/> + ContentSelectorView::ContentSelector @@ -65,7 +73,7 @@ Arguments: Параметры: - + <html><head/><body><p><b>Could not find %1</b></p><p>The application is not found.</p><p>Please make sure OpenMW is installed correctly and try again.</p></body></html> diff --git a/files/lang/components_sv.ts b/files/lang/components_sv.ts new file mode 100644 index 0000000000..8682a569bd --- /dev/null +++ b/files/lang/components_sv.ts @@ -0,0 +1,95 @@ + + + + + ContentSelector + + Select language used by ESM/ESP content files to allow OpenMW to detect their encoding. + Välj språk som används av ESM/ESP-innehållsfiler så att OpenMW kan hitta deras kodning. + + + + ContentSelectorModel::ContentModel + + Unable to find dependent file: %1 + Kunde inte hitta beroende fil: %1 + + + Dependent file needs to be active: %1 + Beroende fil måste vara aktiv: %1 + + + This file needs to load after %1 + Denna fil måste laddas efter %1 + + + + ContentSelectorModel::EsmFile + + <b>Author:</b> %1<br/><b>Format version:</b> %2<br/><b>Modified:</b> %3<br/><b>Path:</b><br/>%4<br/><br/><b>Description:</b><br/>%5<br/><br/><b>Dependencies: </b>%6<br/> + <b>Skapare:</b> %1<br/><b>Formatversion:</b> %2<br/><b>Ändrad:</b> %3<br/><b>Sökväg:</b><br/>%4<br/><br/><b>Beskrivning:</b><br/>%5<br/><br/><b>Beroenden: </b>%6<br/> + + + <br/><b>This content file cannot be disabled because it is part of OpenMW.</b><br/> + <br/><b>Denna innehållsfil kan inte inaktiveras då den är en del av OpenMW.</b><br/> + + + <br/><b>This content file cannot be disabled because it is enabled in a config file other than the user one.</b><br/> + <br/><b>Denna innehållsfil kan inte inaktiveras då den är en aktiverad i en annan konfigurationsfil än användarens.</b><br/> + + + + ContentSelectorView::ContentSelector + + &Check Selected + &Bocka i markerade + + + &Uncheck Selected + &Bocka ur markerade + + + &Copy Path(s) to Clipboard + &Kopiera sökväg(ar) till klippbordet + + + <No game file> + <Ingen spelfil> + + + + Process::ProcessInvoker + + Error starting executable + Kunde inte starta körbara filen + + + Error running executable + Fel när körbara filen skulle köras + + + +Arguments: + + +Argument: + + + + <html><head/><body><p><b>Could not find %1</b></p><p>The application is not found.</p><p>Please make sure OpenMW is installed correctly and try again.</p></body></html> + <html><head/><body><p><b>Kunde inte hitta %1</b></p><p>Applikationen hittas inte.</p><p>Se till att OpenMW är korrekt installerat och försök igen.</p></body></html> + + + <html><head/><body><p><b>Could not start %1</b></p><p>The application is not executable.</p><p>Please make sure you have the right permissions and try again.</p></body></html> + <html><head/><body><p><b>Kunde inte starta %1</b></p><p>Applikationen är inte körbar.</p><p>Se till att du har rätt behörigheter och försök igen.</p></body></html> + + + <html><head/><body><p><b>Could not start %1</b></p><p>An error occurred while starting %1.</p><p>Press "Show Details..." for more information.</p></body></html> + <html><head/><body><p><b>Kunde inte starta %1</b></p><p>Ett fel uppstod när %1 skulle startas.</p><p>Tryck på "Visa detaljer..." för mer information.</p></body></html> + + + <html><head/><body><p><b>Executable %1 returned an error</b></p><p>An error occurred while running %1.</p><p>Press "Show Details..." for more information.</p></body></html> + <html><head/><body><p><b>Körbara filen %1 gav ett felmeddelande</b></p><p>Ett fel uppstod när %1 kördes.</p><p>Tryck på "Visa detaljer..." för mer information.</p></body></html> + + + diff --git a/files/lang/launcher_de.ts b/files/lang/launcher_de.ts index 297a2c944c..579fe91662 100644 --- a/files/lang/launcher_de.ts +++ b/files/lang/launcher_de.ts @@ -7,18 +7,10 @@ Content Files - - <html><head/><body><p>note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> - - Data Directories - - <html><head/><body><p>note: directories that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> - - Scan directories for likely data directories and append them at the end of the list. @@ -67,10 +59,6 @@ Move selected archive one position up - - <html><head/><body><p>note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> - - Move selected archive one position down @@ -95,14 +83,6 @@ Cancel - - Remove unused tiles - - - - Max size - - MiB @@ -159,6 +139,26 @@ Ctrl+R + + <html><head/><body><p>Note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + + + + <html><head/><body><p>Note: directories that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + + + + <html><head/><body><p>Note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + + + + Remove Unused Tiles + + + + Max Size + + GraphicsPage @@ -227,27 +227,27 @@ - Window mode + Resolution - Framerate limit + Window Mode - Window border + Framerate Limit - Resolution + Window Border - Anti-aliasing + Anti-Aliasing - Vertical synchronization + Vertical Synchronization @@ -270,29 +270,29 @@ - File to import settings from: + Browse... - Browse... + Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, +so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar +to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. - Import add-on and plugin selection (creates a new Content List) + Run &Settings Importer - Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, -so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar -to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. + File to Import Settings From: - Import bitmap fonts setup + Import Bitmap Fonts - Run &Settings Importer + Import Add-on and Plugin Selection @@ -370,6 +370,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov &Uncheck Selected + + Resolved as %1 + + + + This is the data-local directory and cannot be disabled + + + + This directory is part of OpenMW and cannot be disabled + + + + This directory is enabled in an openmw.cfg other than the user one + + + + This archive is enabled in an openmw.cfg other than the user one + + Launcher::GraphicsPage @@ -607,844 +627,848 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov - Permanent barter disposition changes + <html><head/><body><p>Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.</p></body></html> - <html><head/><body><p>Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.</p></body></html> + <html><head/><body><p>Make Damage Fatigue magic effect uncapped like Drain Fatigue effect.</p><p>This means that unlike Morrowind you will be able to knock down actors using this effect.</p></body></html> - Followers defend immediately + Uncapped Damage Fatigue - <html><head/><body><p>Make Damage Fatigue magic effect uncapped like Drain Fatigue effect.</p><p>This means that unlike Morrowind you will be able to knock down actors using this effect.</p></body></html> + <html><head/><body><p>Stops combat with NPCs affected by Calm spells every frame -- like in Morrowind without the MCP.</p></body></html> - Uncapped Damage Fatigue + <html><head/><body><p>Make the value of filled soul gems dependent only on soul magnitude.</p></body></html> - <html><head/><body><p>Stops combat with NPCs affected by Calm spells every frame -- like in Morrowind without the MCP.</p></body></html> + <html><head/><body><p>Makes player swim a bit upward from the line of sight. Applies only in third person mode. Intended to make simpler swimming without diving.</p></body></html> - Classic Calm spells behavior + <html><head/><body><p>Make stealing items from NPCs that were knocked down possible during combat.</p></body></html> - <html><head/><body><p>Make the value of filled soul gems dependent only on soul magnitude.</p></body></html> + <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>When enabled, a navigation mesh is built in the background for world geometry to be used for pathfinding. When disabled only the path grid is used to build paths. Single-core CPU systems may have a big performance impact on existing interior location and moving across the exterior world. May slightly affect performance on multi-core CPU systems. Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. Moving across external world, entering/exiting location produce nav mesh update. NPC and creatures may not be able to find path before nav mesh is built around them. Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and cast a firebolt.</p></body></html> - Soulgem values rebalance + <html><head/><body><p>If enabled NPCs apply evasion maneuver to avoid collisions with others.</p></body></html> - <html><head/><body><p>Makes player swim a bit upward from the line of sight. Applies only in third person mode. Intended to make simpler swimming without diving.</p></body></html> + <html><head/><body><p>Don't use race weight in NPC movement speed calculations.</p></body></html> - Swim upward correction + <html><head/><body><p>If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. In this case we have to increment death counter and run disposed actor's script instantly.</p><p>If this setting is false, player has to wait until end of death animation in all cases. Makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation.</p></body></html> - Enchanted weapons are magical + <html><head/><body><p>Makes unarmed creature attacks able to reduce armor condition, just as attacks from NPCs and armed creatures.</p></body></html> - <html><head/><body><p>Make stealing items from NPCs that were knocked down possible during combat.</p></body></html> + Off - Classic reflected Absorb spells behavior + <html><head/><body><p>How many threads will be spawned to compute physics update in the background. A value of 0 means that the update will be performed in the main thread.</p><p>A value greater than 1 requires the Bullet library be compiled with multithreading support.</p></body></html> - <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>When enabled, a navigation mesh is built in the background for world geometry to be used for pathfinding. When disabled only the path grid is used to build paths. Single-core CPU systems may have a big performance impact on existing interior location and moving across the exterior world. May slightly affect performance on multi-core CPU systems. Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. Moving across external world, entering/exiting location produce nav mesh update. NPC and creatures may not be able to find path before nav mesh is built around them. Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and cast a firebolt.</p></body></html> + Cylinder - Use navigation mesh for pathfinding + Visuals - <html><head/><body><p>If enabled NPCs apply evasion maneuver to avoid collisions with others.</p></body></html> + Animations - NPCs avoid collisions + <html><head/><body><p>Use casting animations for magic items, just as for spells.</p></body></html> - <html><head/><body><p>Don't use race weight in NPC movement speed calculations.</p></body></html> + <html><head/><body><p>Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.</p></body></html> - Racial variation in speed fix + <html><head/><body><p>Load per-group KF-files and skeleton files from Animations folder</p></body></html> - <html><head/><body><p>If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. In this case we have to increment death counter and run disposed actor's script instantly.</p><p>If this setting is false, player has to wait until end of death animation in all cases. Makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation.</p></body></html> + <html><head/><body><p>Affects side and diagonal movement. Enabling this setting makes movement more realistic.</p><p>If disabled then the whole character's body is pointed to the direction of view. Diagonal movement has no special animation and causes sliding.</p><p>If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it changes straight right and straight left movement as well. Also turns the whole body up or down when swimming according to the movement direction.</p></body></html> - Can loot during death animation + <html><head/><body><p>Render holstered weapons (with quivers and scabbards), requires modded assets.</p></body></html> - <html><head/><body><p>Makes unarmed creature attacks able to reduce armor condition, just as attacks from NPCs and armed creatures.</p></body></html> + <html><head/><body><p>Render holstered shield, requires modded assets.</p></body></html> - Unarmed creature attacks damage armor + <html><head/><body><p>In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.</p></body></html> - Off + Shaders - Affect werewolves + <html><head/><body><p>If this option is enabled, normal maps are automatically recognized and used if they are named appropriately + (see 'normal map pattern', e.g. for a base texture foo.dds, the normal map texture would have to be named foo_n.dds). + If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file (.nif or .osg file). Affects objects.</p></body></html> - Do not affect werewolves + <html><head/><body><p>See 'auto use object normal maps'. Affects terrain.</p></body></html> - <html><head/><body><p>How many threads will be spawned to compute physics update in the background. A value of 0 means that the update will be performed in the main thread.</p><p>A value greater than 1 requires the Bullet library be compiled with multithreading support.</p></body></html> + <html><head/><body><p>If this option is enabled, specular maps are automatically recognized and used if they are named appropriately + (see 'specular map pattern', e.g. for a base texture foo.dds, + the specular map texture would have to be named foo_spec.dds). + If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file + (.osg file, not supported in .nif files). Affects objects.</p></body></html> - Axis-aligned bounding box + <html><head/><body><p>If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. The texture must contain the layer colour in the RGB channel (as usual), and a specular multiplier in the alpha channel.</p></body></html> - Rotating box + <html><head/><body><p>Normally environment map reflections aren't affected by lighting, which makes environment-mapped (and thus bump-mapped objects) glow in the dark. + Morrowind Code Patch includes an option to remedy that by doing environment-mapping before applying lighting, this is the equivalent of that option. + Affected objects will use shaders. + </p></body></html> - Cylinder + <html><head/><body><p>Allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. Can negatively impact performance.</p></body></html> - Visuals + <html><head/><body><p>Enables soft particles for particle effects. This technique softens the intersection between individual particles and other opaque geometry by blending between them.</p></body></html> - Animations + <html><head/><body><p>Simulate coverage-preserving mipmaps to prevent alpha-tested meshes shrinking as they get further away. Will cause meshes whose textures have coverage-preserving mipmaps to grow, though, so refer to mod installation instructions for how to set this.</p></body></html> - <html><head/><body><p>Use casting animations for magic items, just as for spells.</p></body></html> + <html><head/><body><p>EXPERIMENTAL: Stop rain and snow from falling through overhangs and roofs.</p></body></html> - Use magic item animation + Fog - <html><head/><body><p>Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.</p></body></html> + <html><head/><body><p>By default, the fog becomes thicker proportionally to your distance from the clipping plane set at the clipping distance, which causes distortion at the edges of the screen. + This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV.</p></body></html> - Smooth movement + <html><head/><body><p>Use exponential fog formula. By default, linear fog is used.</p></body></html> - <html><head/><body><p>Load per-group KF-files and skeleton files from Animations folder</p></body></html> + <html><head/><body><p>Reduce visibility of clipping plane by blending objects with the sky.</p></body></html> - Use additional animation sources + <html><head/><body><p>The fraction of the maximum distance at which blending with the sky starts.</p></body></html> - <html><head/><body><p>Affects side and diagonal movement. Enabling this setting makes movement more realistic.</p><p>If disabled then the whole character's body is pointed to the direction of view. Diagonal movement has no special animation and causes sliding.</p><p>If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it changes straight right and straight left movement as well. Also turns the whole body up or down when swimming according to the movement direction.</p></body></html> + Terrain - Turn to movement direction + <html><head/><body><p>Controls how large an object must be to be visible in the scene. The object’s size is divided by its distance to the camera and the result of the division is compared with this value. The smaller this value is, the more objects you will see in the scene.</p></body></html> - <html><head/><body><p>Render holstered weapons (with quivers and scabbards), requires modded assets.</p></body></html> + <html><head/><body><p>If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.</p></body></html> - Weapon sheathing + <html><head/><body><p>Use object paging for active cells grid.</p></body></html> - <html><head/><body><p>Render holstered shield, requires modded assets.</p></body></html> + <html><head/><body><p>If this setting is true, supporting models will make use of day night switch nodes.</p></body></html> - Shield sheathing + Post Processing - <html><head/><body><p>In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.</p></body></html> + <html><head/><body><p>If this setting is true, post processing will be enabled.</p></body></html> - Player movement ignores animation + <html><head/><body><p>Re-render transparent objects with forced alpha clipping.</p></body></html> - Shaders + <html><head/><body><p>Controls how much eye adaptation can change from frame to frame. Smaller values makes for slower transitions.</p></body></html> - <html><head/><body><p>If this option is enabled, normal maps are automatically recognized and used if they are named appropriately - (see 'normal map pattern', e.g. for a base texture foo.dds, the normal map texture would have to be named foo_n.dds). - If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file (.nif or .osg file). Affects objects.</p></body></html> + Audio - Auto use object normal maps + Select your preferred audio device. - <html><head/><body><p>See 'auto use object normal maps'. Affects terrain.</p></body></html> + Default - Auto use terrain normal maps + This setting controls HRTF, which simulates 3D sound on stereo systems. - <html><head/><body><p>If this option is enabled, specular maps are automatically recognized and used if they are named appropriately - (see 'specular map pattern', e.g. for a base texture foo.dds, - the specular map texture would have to be named foo_spec.dds). - If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file - (.osg file, not supported in .nif files). Affects objects.</p></body></html> + HRTF - Auto use object specular maps + Automatic - <html><head/><body><p>If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. The texture must contain the layer colour in the RGB channel (as usual), and a specular multiplier in the alpha channel.</p></body></html> + On - Auto use terrain specular maps + Select your preferred HRTF profile. - <html><head/><body><p>Normally environment map reflections aren't affected by lighting, which makes environment-mapped (and thus bump-mapped objects) glow in the dark. - Morrowind Code Patch includes an option to remedy that by doing environment-mapping before applying lighting, this is the equivalent of that option. - Affected objects will use shaders. - </p></body></html> + Interface - Bump/reflect map local lighting + <html><head/><body><p>This setting scales GUI windows. A value of 1.0 results in the normal scale.</p></body></html> - <html><head/><body><p>Allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. Can negatively impact performance.</p></body></html> + <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> - Use anti-alias alpha testing + <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> - <html><head/><body><p>Enables soft particles for particle effects. This technique softens the intersection between individual particles and other opaque geometry by blending between them.</p></body></html> + Size of characters in game texts. - <html><head/><body><p>Simulate coverage-preserving mipmaps to prevent alpha-tested meshes shrinking as they get further away. Will cause meshes whose textures have coverage-preserving mipmaps to grow, though, so refer to mod installation instructions for how to set this.</p></body></html> + <html><head/><body><p>Enable zooming on local and global maps.</p></body></html> - Adjust coverage for alpha test + <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> - <html><head/><body><p>EXPERIMENTAL: Stop rain and snow from falling through overhangs and roofs.</p></body></html> + <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - Fog + <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - <html><head/><body><p>By default, the fog becomes thicker proportionally to your distance from the clipping plane set at the clipping distance, which causes distortion at the edges of the screen. - This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV.</p></body></html> + <html><head/><body><p>Stretch menus, load screens, etc. to the window aspect ratio.</p></body></html> - Radial fog + <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> - <html><head/><body><p>Use exponential fog formula. By default, linear fog is used.</p></body></html> + <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> - Exponential fog + <html><head/><body><p>Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.</p></body></html> - <html><head/><body><p>Reduce visibility of clipping plane by blending objects with the sky.</p></body></html> + Miscellaneous - Sky blending + Saves - <html><head/><body><p>The fraction of the maximum distance at which blending with the sky starts.</p></body></html> + <html><head/><body><p>This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.</p></body></html> - Sky blending start + JPG - Terrain + PNG - Viewing distance + TGA - <html><head/><body><p>Controls how large an object must be to be visible in the scene. The object’s size is divided by its distance to the camera and the result of the division is compared with this value. The smaller this value is, the more objects you will see in the scene.</p></body></html> + Testing - Object paging min size + These settings are intended for testing mods and will cause issues if used for normal gameplay. - <html><head/><body><p>If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.</p></body></html> + <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> - Distant land + Browse… - <html><head/><body><p>Use object paging for active cells grid.</p></body></html> + Collision is used for both physics simulation and navigation mesh generation for pathfinding. Cylinder gives the best consistency between available navigation paths and ability to move by them. Changing this value affects navigation mesh generation therefore navigation mesh disk cache generated for one value will not be useful with another. - Active grid object paging + Gameplay - <html><head/><body><p>If this setting is true, supporting models will make use of day night switch nodes.</p></body></html> + <html><head/><body><p>If enabled, a magical ammunition is required to bypass normal weapon resistance or weakness. If disabled, a magical ranged weapon or a magical ammunition is required.</p></body></html> - Day night switch nodes + <html><head/><body><p>Make enchanted weapons without Magical flag bypass normal weapons resistance, like in Morrowind.</p></body></html> - Post Processing + cells - <html><head/><body><p>If this setting is true, post processing will be enabled.</p></body></html> + Shadows - Enable post processing + <html><head/><body><p>Type of "compute scene bounds" computation method to be used. Bounds (default) for good balance between performance and shadow quality, primitives for better looking shadows or none for no computation.</p></body></html> - <html><head/><body><p>Re-render transparent objects with forced alpha clipping.</p></body></html> + <html><head/><body><p>64 game units is 1 real life yard or about 0.9 m</p></body></html> - Transparent postpass + unit(s) - <html><head/><body><p>Controls how much eye adaptation can change from frame to frame. Smaller values makes for slower transitions.</p></body></html> + <html><head/><body><p>Enable shadows for NPCs and creatures besides the player character. May have a minor performance impact.</p></body></html> - Auto exposure speed + 512 - Audio + 1024 - Select your preferred audio device. + 2048 - Default + 4096 - This setting controls HRTF, which simulates 3D sound on stereo systems. + <html><head/><body><p>The fraction of the limit above at which shadows begin to gradually fade away.</p></body></html> - HRTF + <html><head/><body><p>Enable shadows exclusively for the player character. May have a very minor performance impact.</p></body></html> - Automatic + <html><head/><body><p>The resolution of each individual shadow map. Increasing it significantly improves shadow quality but may have a minor performance impact.</p></body></html> - On + <html><head/><body><p>The distance from the camera at which shadows completely disappear.</p></body></html> - Select your preferred HRTF profile. + <html><head/><body><p>Enable shadows for primarily inanimate objects. May have a significant performance impact.</p></body></html> - Interface + <html><head/><body><p>Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting.</p><p>Has no effect if actor/player shadows are not enabled.</p></body></html> - <html><head/><body><p>This setting scales GUI windows. A value of 1.0 results in the normal scale.</p></body></html> + <html><head/><body><p>Enable shadows for the terrain including distant terrain. May have a significant performance and shadow quality impact.</p></body></html> - GUI scaling factor + Lighting - <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> + Tooltip - Show effect duration + Crosshair - <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> + Screenshots - Change dialogue topic color + <html><head/><body><p>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.</p></body></html> - Size of characters in game texts. + <html><head/><body><p>Effects of reflected Absorb spells are not mirrored - like in Morrowind.</p></body></html> - Font size + <html><head/><body><p>Maximum distance at which lights will appear (measured in units).</p><p>Set this to 0 to use an unlimited distance.</p></body></html> - <html><head/><body><p>Enable zooming on local and global maps.</p></body></html> + <html><head/><body><p>Maximum number of lights per object.</p><p>A low number near default will cause light popping similar to what you would see with legacy lighting.</p></body></html> - Can zoom on maps + <html><head/><body><p>Fraction of maximum distance at which lights will start to fade.</p><p>Set this to a low value for slower transitions or a high value for quicker transitions.</p></body></html> - <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> + <html><head/><body><p>Set the internal handling of light sources.</p> +<p> "Legacy" always uses 8 lights per object and provides a lighting closest to an original game.</p> +<p>"Shaders (compatibility)" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.</p> +<p> "Shaders" carries all of the benefits that "Shaders (compatibility)" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware.</p></body></html> - <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> + Legacy - Show projectile damage + Shaders (compatibility) - <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> + <html><head/><body><p>Multipler for bounding sphere of lights.</p><p>Higher numbers allows for smooth falloff but require an increase in number of max lights.</p><p>Does not effect the illumination or strength of lights.</p></body></html> - Show melee info + <html><head/><body><p>Minimum ambient interior brightness.</p><p>Increase this if you feel interiors are too dark.</p></body></html> - <html><head/><body><p>Stretch menus, load screens, etc. to the window aspect ratio.</p></body></html> + In third-person view, use the camera as the sound listener instead of the player character. - Stretch menu background + Permanent Barter Disposition Changes - Show owned objects + Classic Calm Spells Behavior - <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> + NPCs Avoid Collisions - Show enchant chance + Soulgem Values Rebalance - <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> + Day Night Switch Nodes - Merchant equipping fix + Followers Defend Immediately - <html><head/><body><p>Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.</p></body></html> + Only Magical Ammo Bypass Resistance - Miscellaneous + Graphic Herbalism - Saves + Swim Upward Correction - <html><head/><body><p>This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.</p></body></html> + Enchanted Weapons Are Magical - Add "Time Played" to saves + Merchant Equipping Fix - JPG + Can Loot During Death Animation - PNG + Classic Reflected Absorb Spells Behavior - TGA + Unarmed Creature Attacks Damage Armor - Notify on saved screenshot + Affect Werewolves - Testing + Do Not Affect Werewolves - These settings are intended for testing mods and will cause issues if used for normal gameplay. + Background Physics Threads - <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> + Actor Collision Shape Type - Grab cursor + Axis-Aligned Bounding Box - Skip menu and generate default character + Rotating Box - Start default character at + Smooth Movement - default cell + Use Additional Animation Sources - Run script after startup: + Weapon Sheathing - Browse… + Shield Sheathing - Collision is used for both physics simulation and navigation mesh generation for pathfinding. Cylinder gives the best consistency between available navigation paths and ability to move by them. Changing this value affects navigation mesh generation therefore navigation mesh disk cache generated for one value will not be useful with another. + Player Movement Ignores Animation - Gameplay + Use Magic Item Animation - Always allow actors to follow over water + Auto Use Object Normal Maps - <html><head/><body><p>If enabled, a magical ammunition is required to bypass normal weapon resistance or weakness. If disabled, a magical ranged weapon or a magical ammunition is required.</p></body></html> + Soft Particles - Only magical ammo bypass resistance + Auto Use Object Specular Maps - Graphic herbalism + Auto Use Terrain Normal Maps - <html><head/><body><p>Make enchanted weapons without Magical flag bypass normal weapons resistance, like in Morrowind.</p></body></html> + Auto Use Terrain Specular Maps - Trainers choose offered skills by base value + Use Anti-Aliased Alpha Testing - Steal from knocked out actors in combat + Bump/Reflect Map Local Lighting - Factor strength into hand-to-hand combat + Weather Particle Occlusion - Background physics threads + Exponential Fog - Actor collision shape type + Radial Fog - Soft particles + Sky Blending Start - Weather particle occlusion + Sky Blending - cells + Object Paging Min Size - Shadows + Viewing Distance - bounds + Distant Land - primitives + Active Grid Object Paging - none + Transparent Postpass - <html><head/><body><p>Type of "compute scene bounds" computation method to be used. Bounds (default) for good balance between performance and shadow quality, primitives for better looking shadows or none for no computation.</p></body></html> + Auto Exposure Speed - Shadow planes computation method + Enable Post Processing - <html><head/><body><p>64 game units is 1 real life yard or about 0.9 m</p></body></html> + Shadow Planes Computation Method - unit(s) + Enable Actor Shadows - <html><head/><body><p>Enable shadows for NPCs and creatures besides the player character. May have a minor performance impact.</p></body></html> + Fade Start Multiplier - Enable actor shadows + Enable Player Shadows - 512 + Shadow Map Resolution - 1024 + Shadow Distance Limit: - 2048 + Enable Object Shadows - 4096 + Enable Indoor Shadows - <html><head/><body><p>The fraction of the limit above at which shadows begin to gradually fade away.</p></body></html> + Enable Terrain Shadows - Fade start multiplier + Maximum Light Distance - <html><head/><body><p>Enable shadows exclusively for the player character. May have a very minor performance impact.</p></body></html> + Max Lights - Enable player shadows + Lighting Method - <html><head/><body><p>The resolution of each individual shadow map. Increasing it significantly improves shadow quality but may have a minor performance impact.</p></body></html> + Bounding Sphere Multiplier - Shadow map resolution + Minimum Interior Brightness - <html><head/><body><p>The distance from the camera at which shadows completely disappear.</p></body></html> + Audio Device - Shadow distance limit: + HRTF Profile - <html><head/><body><p>Enable shadows for primarily inanimate objects. May have a significant performance impact.</p></body></html> + Tooltip and Crosshair - Enable object shadows + GUI Scaling Factor - <html><head/><body><p>Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting.</p><p>Has no effect if actor/player shadows are not enabled.</p></body></html> + Show Effect Duration - Enable indoor shadows + Change Dialogue Topic Color - <html><head/><body><p>Enable shadows for the terrain including distant terrain. May have a significant performance and shadow quality impact.</p></body></html> + Font Size - Enable terrain shadows + Show Projectile Damage - Lighting + Show Melee Info - Lighting method + Stretch Menu Background - Audio device + Show Owned Objects - HRTF profile + Show Enchant Chance - Tooltip + Maximum Quicksaves - Crosshair + Screenshot Format - Tooltip and crosshair + Grab Cursor - Maximum quicksaves + Default Cell - Screenshots + Bounds - Screenshot format + Primitives - <html><head/><body><p>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.</p></body></html> + None - <html><head/><body><p>Effects of reflected Absorb spells are not mirrored - like in Morrowind.</p></body></html> + Always Allow Actors to Follow over Water - <html><head/><body><p>Maximum distance at which lights will appear (measured in units).</p><p>Set this to 0 to use an unlimited distance.</p></body></html> + Racial Variation in Speed Fix - Lights maximum distance + Use Navigation Mesh for Pathfinding - <html><head/><body><p>Maximum number of lights per object.</p><p>A low number near default will cause light popping similar to what you would see with legacy lighting.</p></body></html> + Trainers Choose Offered Skills by Base Value - Max light sources + Steal from Knocked out Actors in Combat - <html><head/><body><p>Fraction of maximum distance at which lights will start to fade.</p><p>Set this to a low value for slower transitions or a high value for quicker transitions.</p></body></html> + Factor Strength into Hand-to-Hand Combat - Lights fade multiplier + Turn to Movement Direction - <html><head/><body><p>Set the internal handling of light sources.</p> -<p> "Legacy" always uses 8 lights per object and provides a lighting closest to an original game.</p> -<p>"Shaders (compatibility)" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.</p> -<p> "Shaders" carries all of the benefits that "Shaders (compatibility)" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware.</p></body></html> + Adjust Coverage for Alpha Test - Legacy + Use the Camera as the Sound Listener - Shaders (compatibility) + Can Zoom on Maps - <html><head/><body><p>Multipler for bounding sphere of lights.</p><p>Higher numbers allows for smooth falloff but require an increase in number of max lights.</p><p>Does not effect the illumination or strength of lights.</p></body></html> + Add "Time Played" to Saves - Lights bounding sphere multiplier + Notify on Saved Screenshot - <html><head/><body><p>Minimum ambient interior brightness.</p><p>Increase this if you feel interiors are too dark.</p></body></html> + Skip Menu and Generate Default Character + + + + Start Default Character at - Lights minimum interior brightness + Run Script After Startup: diff --git a/files/lang/launcher_fr.ts b/files/lang/launcher_fr.ts index 956d38b579..06940ca0d3 100644 --- a/files/lang/launcher_fr.ts +++ b/files/lang/launcher_fr.ts @@ -5,1447 +5,1474 @@ DataFilesPage Content Files - - - - <html><head/><body><p>note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> - + Fichiers de données Data Directories - - - - <html><head/><body><p>note: directories that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> - + Dossiers de données Scan directories for likely data directories and append them at the end of the list. - + Parcours les dossiers afin de trouver des <em>dossiers de données</em> et ajoute ceux-ci à la fin de la liste. Append - + Ajouter Scan directories for likely data directories and insert them above the selected position - + Parcours les dossiers afin de trouver des <em>dossiers de données</em> et ajoute ceux-ci au dessus de la position actuellement sélectionnée. Insert Above - + Insérer au dessus Move selected directory one position up - + Déplace le(s) dossier(s) sélectionné(s) d'une position vers le haut. Move Up - + Monter Move selected directory one position down - + Déplace le(s) dossier(s) sélectionné(s) d'une position vers le bas. Move Down - + Descendre Remove selected directory - + Supprime le(s) dossier(s) sélectionné(s) Remove - + Supprimer Archive Files - + Fichiers archive Move selected archive one position up - - - - <html><head/><body><p>note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> - + Déplace les archives sélectionnées d'une position vers le haut Move selected archive one position down - + Déplace les archives sélectionnées d'une position vers le bas Navigation Mesh Cache - + Mise en cache des mesh de navigation Generate navigation mesh cache for all content. Will be used by the engine to make cell loading faster. - + Génère un cache de mesh de navigation pour tout le contenu du jeu. Celui-ci sera utilisé par le moteur de jeu afin d'accélérer la transition entre les différentes zones de jeu. Update - + Mettre à jour Cancel navigation mesh generation. Already processed data will be saved. - + Annule la génération de mesh de navigation. Les données déjà sauvegardées seront sauvegardées. Cancel - - - - Remove unused tiles - - - - Max size - + Annuler MiB - + Mio Content List - + Liste de contenu Select a content list - + Sélectionne une Liste de contenu. New Content List - + Nouvelle Liste de contenu &New Content List - + &Nouvelle Liste de contenu Clone Content List - + Cloner une Liste de contenu Delete Content List - + Supprimer une Liste de contenu Ctrl+N - + Ctrl+N Ctrl+G - + Ctrl+G Ctrl+D - + Ctrl+D Check Selection - + Activer la sélection Uncheck Selection - + Désactiver la sélection Refresh Data Files - + Rafraîchir la liste des fichiers Ctrl+R - + Ctrl+R + + + <html><head/><body><p>Note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + <html><head/><body><p>Note : Les fichiers de contenu qui ne font pas partie de la <em>Liste de contenu</em> actuelle sont <span style=" font-style:italic;font-weight: bold">surlignés</span>.</p></body></html> + + + <html><head/><body><p>Note: directories that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + <html><head/><body><p>Note : Les dossiers de données qui ne font pas partie de la <em>Liste de contenu</em> actuelle sont <span style=" font-style:italic;font-weight: bold">surlignés</span>.</p></body></html> + + + <html><head/><body><p>Note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + <html><head/><body><p>Note : Les archives qui ne font pas partie de la <em>Liste de contenu</em> actuelle sont <span style=" font-style:italic;font-weight: bold">surlignées</span>.</p></body></html> + + + Remove Unused Tiles + Ignorer les tuiles inutilisées + + + Max Size + Taille maximale GraphicsPage 0 - + 0 2 - + 2 4 - + 4 8 - + 8 16 - + 16 Custom: - + Personnalisé : Standard: - + Standard : Fullscreen - + Plein écran Windowed Fullscreen - + Fenêtré plein écran Windowed - + Fenêtré Disabled - + Désactivé Enabled - + Activé Adaptive - + Adaptatif FPS - + FPS × - + × Screen - + Écran - Window mode - + Resolution + Résolution - Framerate limit - + Window Mode + Mode d'affichage - Window border - + Framerate Limit + Limite de framerate - Resolution - + Window Border + Bordure de fenêtre - Anti-aliasing - + Anti-Aliasing + Antialiasing - Vertical synchronization - + Vertical Synchronization + Synchronisation verticale ImportPage Form - + Form Morrowind Installation Wizard - + Assistant d'installation de Morrowind Run &Installation Wizard - + Lancer &l'Assistant d'installation Morrowind Settings Importer - - - - File to import settings from: - + Import des paramètres de Morrowind Browse... - - - - Import add-on and plugin selection (creates a new Content List) - + Parcourir... Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. - + Les polices fournies avec le moteur de jeu original sont floues lorsque l'interface utilisateur est agrandie. De plus, elles ne supportent qu'un faible nombre de caractères. Afin d'éviter ces désagréments, OpenMW propose son propre ensemble de polices. Ces polices sont encodées au format TrueType et ressemblent aux polices originales. Sélectionnez cette option si vous préférez utiliser les polices originales - à la place de celles fournies par OpenMW - ou si vous utilisez vos propres polices au format bitmap. - Import bitmap fonts setup - + Run &Settings Importer + Lancer &l'import des paramètres - Run &Settings Importer - + File to Import Settings From: + Ficher depuis lequel les paramètres seront importés : + + + Import Bitmap Fonts + Importer les paramètres des polices en bitmap + + + Import Add-on and Plugin Selection + Importer les extensions et la sélection des modules complémentaires (crée une nouvelle Liste de contenu) Launcher::DataFilesPage English - + Anglais French - + Français German - + Allemand Italian - + Italien Polish - + Polonais Russian - + Russe Spanish - + Espagnol New Content List - + Créer une Liste de contenu Content List name: - + Nom de la Liste de contenu : Clone Content List - + Cloner la Liste de contenu Select Directory - + Sélectionnez un dossier Delete Content List - + Supprimer la Liste de contenu Are you sure you want to delete <b>%1</b>? - + Êtes-vous sûre de vouloir supprimer <b>%1</b> ? Delete - + Supprimer Contains content file(s) - + Contient les fichiers de contenu Will be added to the current profile - + Sera ajouté au profil actuel &Check Selected - + &Active la sélection &Uncheck Selected - + &Désctive la sélection + + + Resolved as %1 + Localisation : %1 + + + This is the data-local directory and cannot be disabled + Ceci est le dossier data-local, il ne peut être désactivé. + + + This directory is part of OpenMW and cannot be disabled + Ce dossier ne peut être désactivé, car il fait partie intégrante d'OpenMW. + + + This directory is enabled in an openmw.cfg other than the user one + Ce dossier est activé dans un fichier openmw.cfg qui n'est pas celui de l'utilisateur. + + + This archive is enabled in an openmw.cfg other than the user one + Cette archive est activée dans un fichier openmw.cfg qui n'est pas celui de l'utilisateur. Launcher::GraphicsPage Error receiving number of screens - + Erreur lors de l'obtention du nombre d'écrans <br><b>SDL_GetNumVideoDisplays failed:</b><br><br> - + <br><b>SDL_GetNumVideoDisplays failed:</b><br><br> Screen - + Écran Error receiving resolutions - + Erreur lors de l'obtention des résolutions <br><b>SDL_GetNumDisplayModes failed:</b><br><br> - + <br><b>SDL_GetNumDisplayModes failed:</b><br><br> <br><b>SDL_GetDisplayMode failed:</b><br><br> - + <br><b>SDL_GetDisplayMode failed:</b><br><br> Launcher::ImportPage Error writing OpenMW configuration file - + Erreur lors de l'écriture du fichier de configuration Morrowind configuration file (*.ini) - + Fichier de configuration de Morrowind (*.ini) Importer finished - + Importation terminée Failed to import settings from INI file. - + Échec lors de l'importation du fichier INI. <html><head/><body><p><b>Could not open or create %1 for writing </b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - + <html><head/><body><p><b>Impossible d'ouvrir ou de créer %1 en écriture </b></p><p>Veuillez vous assurer que vous avez les bons droits d'accès puis réessayez.</p></body></html> Launcher::MainDialog Close - + Fermer Launch OpenMW - + Lancer OpenMW Help - + Aide Error opening OpenMW configuration file - + Erreur lors de l'ouverture du fichier de configuration d'OpenMW First run - + Premier lancement Run &Installation Wizard - + Lancer l'Assistant d'installation Skip - + Passer OpenMW %1 release - + OpenMW %1 release OpenMW development (%1) - + OpenMW development (%1) Compiled on %1 %2 - + Compilé le %1 %2 Error detecting Morrowind installation - + Erreur lors de la détection de l'installation de Morrowind Run &Installation Wizard... - + Lancer l'Assistant d'installation Error reading OpenMW configuration files - + Erreur lors de la lecture du fichier de configuration d'OpenMW Error writing OpenMW configuration file - + Erreur lors de l'écriture dans le fichier de configuration d'OpenMW Error writing user settings file - + Erreur lors de l'écriture du fichier de paramètres utilisateur Error writing Launcher configuration file - + Erreur lors de l'écriture dans le fichier de paramètres du Lanceur No game file selected - + Aucun fichier de jeu sélectionné <html><head/><body><p><b>Welcome to OpenMW!</b></p><p>It is recommended to run the Installation Wizard.</p><p>The Wizard will let you select an existing Morrowind installation, or install Morrowind for OpenMW to use.</p></body></html> - + <html><head/><body><p><b>Bienvenue sur OpenMW !</b></p><p>Il est recommandé de lancer l'Assistant d'installation .</p><p>L'assistant vous permettra de sélectionner une installation de Morrowind existante, ou d'installer Morrowind pour son utilisation dans OpenMW.</p></body></html> <br><b>Could not open %0 for reading:</b><br><br>%1<br><br>Please make sure you have the right permissions and try again.<br> - + <br><b>Impossible d'ouvrir %0 en lecture :</b><br><br>%1<br><br>Assurez vous d'avoir les bons droits d'accès puis réessayez.<br> <br><b>Could not open %0 for reading</b><br><br>Please make sure you have the right permissions and try again.<br> - + <br><b>Impossible d'ouvrir %0 en lecture :</b><br><br>%1<br><br>Assurez vous d'avoir les bons droits d'accès puis réessayez.<br> <br><b>Could not find the Data Files location</b><br><br>The directory containing the data files was not found. - + <br><b>Impossible de trouver l'emmplacement de Data Files</b><br><br>Le dossier contenant les fichiers de données n'a pas été trouvé. <br>The problem may be due to an incomplete installation of OpenMW.<br>Reinstalling OpenMW may resolve the problem.<br> - + <br>Ce problème peut survenir lors d'une installation incomplète d'OpenMW.<br>Réinstaller OpenMW pourrait résoudre le problème.<br> <br><b>Could not open or create %0 for writing</b><br><br>Please make sure you have the right permissions and try again.<br> - + <br><b>Impossible d'ouvrir ou de créer %0 en écriture :</b><br><br>%1<br><br>Assurez vous d'avoir les bons droits d'accès puis réessayez.<br> <br><b>You do not have a game file selected.</b><br><br>OpenMW will not start without a game file selected.<br> - + <br><b>Vous n'avez pas sélectionné de fichier de jeu.</b><br><br>OpenMW ne peut démarrer sans qu'un fichier de jeu soit sélectionné.<br> Error creating OpenMW configuration directory: code %0 - + Erreur lors de la création du dossier de configuration d'OpenMW : code %0 <br><b>Could not create directory %0</b><br><br>%1<br> - + <br><b>Impossible de créer le dossier %0</b><br><br>%1<br> Launcher::SettingsPage Text file (*.txt) - + Fichier texte (*.txt) MainWindow OpenMW Launcher - + Lanceur d'OpenMW OpenMW version - + Version d'OpenMW toolBar - + toolBar Data Files - + Données de jeu Allows to setup data files and directories - + Configurez et sélectionnez les fichiers, dossiers et archives de jeu Settings - + Options Allows to tweak engine settings - + Modifiez les options du moteur de jeu Import - + Importer Allows to import data from original engine - + Importez des données depuis le moteur de jeu original Display - + Affichage Allows to change display settings - + Modifiez les paramètres d'affichage QObject Select configuration file - + Sélectionnez un fichier de configuration Select script file - + Sélectionnez un fichier de script SelectSubdirs Select directories you wish to add - + Sélectionnez le dossier que vous désirez ajouter SettingsPage <html><head/><body><p>Make disposition change of merchants caused by trading permanent.</p></body></html> - - - - Permanent barter disposition changes - + <html><head/><body><p>Rends le changement de disposition des marchands due au Marchandage permanent.</p></body></html> <html><head/><body><p>Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.</p></body></html> - - - - Followers defend immediately - + <html><head/><body><p>Permets aux compagnons et aux escortes de démarrer le combat avec des ennemis entrés en combat avec eux ou avec le joueur. Si l'option est désactivée, ils attendent que les ennemis les attaquent en premier.</p></body></html> <html><head/><body><p>Make Damage Fatigue magic effect uncapped like Drain Fatigue effect.</p><p>This means that unlike Morrowind you will be able to knock down actors using this effect.</p></body></html> - + <html><head/><body><p>Cette option retire le plafond sur l'effet magique Atténuation de Fatigue, comme c'est le cas avec Absorption de Fatigue. Cette option vous permet d'assommer des personnages en utilisant cet effet magique.</p></body></html> Uncapped Damage Fatigue - + Domages de fatique déplafonnés <html><head/><body><p>Stops combat with NPCs affected by Calm spells every frame -- like in Morrowind without the MCP.</p></body></html> - - - - Classic Calm spells behavior - + <html><head/><body><p>Si cette option est activée, le jeu interrompt le combat avec les PNJ affectés par l'effet magique Apaisement à chaque frame. Ce comportement similaire à celui du moteur d'origine, sans MCP.</p></body></html> <html><head/><body><p>Make the value of filled soul gems dependent only on soul magnitude.</p></body></html> - - - - Soulgem values rebalance - + <html><head/><body><p>Si l'option est cochée, la valeur des gemmes sprirituelles dépend uniquement de la puissance de l'âme contenue.</p></body></html> <html><head/><body><p>Makes player swim a bit upward from the line of sight. Applies only in third person mode. Intended to make simpler swimming without diving.</p></body></html> - + <html><head/><body><p>Cette option fait que le joueur nage légèrement au-dessus de la ligne de visée. S'applique uniquement dans le mode Vue à la troisième personne. Ceci facilite la nage, hors plongée.</p></body></html> - Swim upward correction - + <html><head/><body><p>Make stealing items from NPCs that were knocked down possible during combat.</p></body></html> + <html><head/><body><p>Cette option rend possible le vol d'objets à un PNJ tombé à terre durant un combat.</p></body></html> - Enchanted weapons are magical - + <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>When enabled, a navigation mesh is built in the background for world geometry to be used for pathfinding. When disabled only the path grid is used to build paths. Single-core CPU systems may have a big performance impact on existing interior location and moving across the exterior world. May slightly affect performance on multi-core CPU systems. Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. Moving across external world, entering/exiting location produce nav mesh update. NPC and creatures may not be able to find path before nav mesh is built around them. Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and cast a firebolt.</p></body></html> + <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>Lorsque cette option est activée, un mesh de navigation est construit en arrière-plan à partir de la géométrie de l'environnement. Celui-ci est alors utilisé pour la recherche d'itinéraire.</p><p>Lorsqu'elle est désactivée, seule la grille des chemins est utilisée pour tracer les itinéraires.</p><p>Cette option peut affecter de façon significative les systèmes à simple cœur, lorsque le joueur passe d'un environnement intérieur à l'environnement extérieur. Ce paramètre peut aussi affecter, dans une moindre mesure, les systèmes multicœurs. La lenteur due à la génération de mesh de navigation des systèmes multicœurs peut dépendre des autres options ainsi que de la puissance du système. Se déplacer au sein de l'environnement extérieur, entrer et sortir d'un lieu génère un mesh de navigation. Les PNJ et les créatures peuvent ne pas trouver les mesh de navigation générés autour d'eux. Désactivez cette option si vous désirez avoir une intelligence artificielle à l'ancienne où les ennemis ne savent pas quoi faire lorsque le joueur se tient derrière un rocher et lance des boules de feu.</p></body></html> - <html><head/><body><p>Make stealing items from NPCs that were knocked down possible during combat.</p></body></html> - + <html><head/><body><p>If enabled NPCs apply evasion maneuver to avoid collisions with others.</p></body></html> + <html><head/><body><p>Si l'option est activée, les PNJ tentent des manœuvres d'évitement afin d'éviter d'entrer en collision les uns avec les autres.</p></body></html> - Classic reflected Absorb spells behavior - + <html><head/><body><p>Don't use race weight in NPC movement speed calculations.</p></body></html> + <html><head/><body><p>N'utilise pas le poids de la race du PNJ pour calculer sa vitesse de mouvement.</p></body></html> - <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>When enabled, a navigation mesh is built in the background for world geometry to be used for pathfinding. When disabled only the path grid is used to build paths. Single-core CPU systems may have a big performance impact on existing interior location and moving across the exterior world. May slightly affect performance on multi-core CPU systems. Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. Moving across external world, entering/exiting location produce nav mesh update. NPC and creatures may not be able to find path before nav mesh is built around them. Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and cast a firebolt.</p></body></html> - + <html><head/><body><p>If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. In this case we have to increment death counter and run disposed actor's script instantly.</p><p>If this setting is false, player has to wait until end of death animation in all cases. Makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation.</p></body></html> + <html><head/><body><p>Lorsque cette option est activée, le joueur est autorisé à piller créatures et PNJ (p. ex. les créatures invoquées) durant leur animation de mort, si elles ne sont pas en combat. Dans ce cas, le jeu incrémente le conteur de mort et lance son script instantanément.</p><p>Lorsque cette option est désactivée, le joueur doit attendre la fin de l'animation de mort. Dans ce cas, l'utilisation de l'exploit des créatures invoquées (piller des créatures invoquées telles que des Drémoras ou des Saintes Dorées afin d'obtenir des armes de grandes valeurs) est rendu beaucoup plus ardu. Cette option entre en confit avec les Mods de mannequin. En effet, ceux-ci utilisent SkipAnim afin d'éviter la fin de l'animation de mort.</p></body></html> - Use navigation mesh for pathfinding - + <html><head/><body><p>Makes unarmed creature attacks able to reduce armor condition, just as attacks from NPCs and armed creatures.</p></body></html> + <html><head/><body><p>L'option donne aux créatures non armées la capacité d'endommager les pièces d'armure, comme le font les PNJ et les créatures armées.</p></body></html> - <html><head/><body><p>If enabled NPCs apply evasion maneuver to avoid collisions with others.</p></body></html> - + Off + Inactif - NPCs avoid collisions - + <html><head/><body><p>How many threads will be spawned to compute physics update in the background. A value of 0 means that the update will be performed in the main thread.</p><p>A value greater than 1 requires the Bullet library be compiled with multithreading support.</p></body></html> + <html><head/><body><p>Détermine le nombre de thread qui seront générés en arrière-plan pour mettre à jour la physique du jeu. La valeur 0 signifie que ces mises à jour seront calculées par le thread principal.</p><p>Une valeur supérieure à 1 requière que la librairie Bullet soit compilée avec le support multithread.</p></body></html> - <html><head/><body><p>Don't use race weight in NPC movement speed calculations.</p></body></html> - + Cylinder + Cylindrique - Racial variation in speed fix - + Visuals + Visuels - <html><head/><body><p>If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. In this case we have to increment death counter and run disposed actor's script instantly.</p><p>If this setting is false, player has to wait until end of death animation in all cases. Makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation.</p></body></html> - + Animations + Animations - Can loot during death animation - + <html><head/><body><p>Use casting animations for magic items, just as for spells.</p></body></html> + <html><head/><body><p>Anime l'utilisation d'objet magique, de façon similaire à l'utilisation des sorts.</p></body></html> - <html><head/><body><p>Makes unarmed creature attacks able to reduce armor condition, just as attacks from NPCs and armed creatures.</p></body></html> - + <html><head/><body><p>Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.</p></body></html> + <html><head/><body><p>Cette option rend les mouvements des PNJ et du joueur plus souple. Recommandé si l'option "Se tourner en direction du mouvement" est activée.</p></body></html> - Unarmed creature attacks damage armor - + <html><head/><body><p>Load per-group KF-files and skeleton files from Animations folder</p></body></html> + <html><head/><body><p>Charge les fichiers d'animation groupée KF et les fichiers de structure de squelettes depuis le dossier Animation.</p></body></html> - Off - + <html><head/><body><p>Affects side and diagonal movement. Enabling this setting makes movement more realistic.</p><p>If disabled then the whole character's body is pointed to the direction of view. Diagonal movement has no special animation and causes sliding.</p><p>If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it changes straight right and straight left movement as well. Also turns the whole body up or down when swimming according to the movement direction.</p></body></html> + <html><head/><body><p>Affecte les mouvements latéraux et diagonaux. Activer cette option rend les mouvements plus réalistes.</p><p>Si elle est désactivée, le corps entier du personnage joueur est pointé dans la direction de visée. Les mouvements diagonaux n'ont pas d'animation spécifique et glissent sur le sol.</p><p>Si elle est activée, le bas du corps du personnage joueur est orienté suivant la direction du mouvement, le haut du corps est orienté partiellement, et la tête est alignée avec la direction de visée. En mode combat, cet effet n'est appliqué qu'aux mouvements diagonaux. Hors mode combat, cet effet est aussi appliqué aux mouvements purement latéraux. Cette option affecte aussi la nage, le moteur de jeu oriente alors tout le corps dans la direction du mouvement.</p></body></html> - Affect werewolves - + <html><head/><body><p>Render holstered weapons (with quivers and scabbards), requires modded assets.</p></body></html> + <html><head/><body><p>Cette option permet d'afficher les armes rengainées dans leur étui (p. ex. carquois et fourreau). Elle nécessite des modèles 3D fournis pas un mod.</p></body></html> - Do not affect werewolves - + <html><head/><body><p>Render holstered shield, requires modded assets.</p></body></html> + <html><head/><body><p>Cette option permet d'afficher les boucliers rengainés. Elle nécessite des modèles 3D fournis pas un mod.</p></body></html> - <html><head/><body><p>How many threads will be spawned to compute physics update in the background. A value of 0 means that the update will be performed in the main thread.</p><p>A value greater than 1 requires the Bullet library be compiled with multithreading support.</p></body></html> - + <html><head/><body><p>In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.</p></body></html> + <html><head/><body><p>En vue à la troisième personne, la caméra se balance avec mouvement d'animation du joueur. Activer cette option désactive ce balancement de sorte que le personnage joueur se déplace indépendamment de son animation. Ce comportement était activé par défaut dans les versions d'OpenMW 0.48 et antérieures.</p></body></html> - Axis-aligned bounding box - + Shaders + Shaders - Rotating box - + <html><head/><body><p>If this option is enabled, normal maps are automatically recognized and used if they are named appropriately + (see 'normal map pattern', e.g. for a base texture foo.dds, the normal map texture would have to be named foo_n.dds). + If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file (.nif or .osg file). Affects objects.</p></body></html> + <html><body><p>Lorsque cette option est activée, les normal maps (textures d'orientation de la surface) sont automatiquement reconnues et utilisées, lorsque le fichier est nommé correctement.</p><p>Par exemple, si une texture de base est nommée foo.dds, la texture de normal maps devrait être nommée foo_n.dds (voir 'normal map pattern' dans la documentation pour plus d'informations).</p><p>Si l'option est désactivée, les normal maps sont utilisées uniquement si elles sont listées explicitement dans le fichier de mesh (fichiers .nif ou .osg). Cette option affecte les objets.</p></body></html> - Cylinder - + <html><head/><body><p>See 'auto use object normal maps'. Affects terrain.</p></body></html> + <html><head/><body><p>Voir "Utilisation auto des normal maps pour les objets". Affecte le terrain.</p></body></html> - Visuals - + <html><head/><body><p>If this option is enabled, specular maps are automatically recognized and used if they are named appropriately + (see 'specular map pattern', e.g. for a base texture foo.dds, + the specular map texture would have to be named foo_spec.dds). + If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file + (.osg file, not supported in .nif files). Affects objects.</p></body></html> + <html><body><p>Si cette option est activée, les specular maps (textures indiquant si un objet est brillant ou mat) sont automatiquement reconnues et utilisées, lorsque le fichier est nommé correctement.</p><p>Par exemple, si une texture de base est nommée foo.dds, la texture de specular maps devrait être nommée foo_spec.dds (voir 'specular map pattern' dans la documentation pour plus d'informations, en anglais).</p><p>Si le paramètre est désactivé, les specular maps sont utilisées uniquement si elles sont listées explicitement dans le fichier de mesh (fichiers .osg, non supporté par le format nif). Cette option affecte les objets.</p></body></html> - Animations - + <html><head/><body><p>If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. The texture must contain the layer colour in the RGB channel (as usual), and a specular multiplier in the alpha channel.</p></body></html> + <html><head/><body><p>Si l'option est activée et qu'un fichier dont le nom respecte la structure des specular maps (voir Utilisation auto des specular maps pour les objets), le moteur de jeu utilise cette texture comme "diffuse specular map" : Cette texture doit contenir une couche (habituelle) de couleur RVB et un facteur de spécularité (≃brillance) dans le canal alpha.</p></body></html> - <html><head/><body><p>Use casting animations for magic items, just as for spells.</p></body></html> - + <html><head/><body><p>Normally environment map reflections aren't affected by lighting, which makes environment-mapped (and thus bump-mapped objects) glow in the dark. + Morrowind Code Patch includes an option to remedy that by doing environment-mapping before applying lighting, this is the equivalent of that option. + Affected objects will use shaders. + </p></body></html> + <html><head/><body><p>En temps normal, les réflexions dues au placage d'environnement (environment map) ne sont pas affectées par l'éclairage de la scène. En conséquence, les objets affectés par le placage d'environnement (et de relief) ont tendance à briller dans le noir. Le "Morrowind Code Patch" a, pour remédier à ça, inclus une option qui applique le placage d'environnement (et de relief) avant d'appliquer l'éclairage. Cette option est son équivalent. Les objets affectés utilisent toujours les shaders.</p></body></html> - Use magic item animation - + <html><head/><body><p>Allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. Can negatively impact performance.</p></body></html> + <html><body><p>Cette option permet à l'antialiasing à multiéchantillonnage (MSAA) de fonctionner avec le test alpha des mesh. Ceci produit un meilleur rendu des bords, sans pixélisation. Cette option peut affecter négativement les performances.</p></body></html> - <html><head/><body><p>Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.</p></body></html> - + <html><head/><body><p>Enables soft particles for particle effects. This technique softens the intersection between individual particles and other opaque geometry by blending between them.</p></body></html> + <html><body><p>Cette option active la génération douce des effets de particules. Cette technique adoucit l'intersection entre chaque particule et les éléments de géométrie opaque à l'aide d'un fondu progressif entre les deux.</p></body></html> - Smooth movement - + <html><head/><body><p>Simulate coverage-preserving mipmaps to prevent alpha-tested meshes shrinking as they get further away. Will cause meshes whose textures have coverage-preserving mipmaps to grow, though, so refer to mod installation instructions for how to set this.</p></body></html> + <html><body><p>Lorsque cette option est activée, le moteur réalise une simulation de couverture préservée des mipmaps (coverage-preserving mipmaps). Ceci empêche les mesh testés en transparence (alpha test) de diminuer en taille lorsqu'ils s'éloignent. En contrepartie, cette option tend à agrandir les mesh ayant déjà une couverture préservée des mipmaps. Référez-vous à l'installation du contenu additionnel (mod) pour savoir quelle option est la mieux adaptée à celui-ci.</p></body></html> - <html><head/><body><p>Load per-group KF-files and skeleton files from Animations folder</p></body></html> - + <html><head/><body><p>EXPERIMENTAL: Stop rain and snow from falling through overhangs and roofs.</p></body></html> + <html><body><p>Expérimental ! Cette option empêche la pluie et la neige de tomber au travers des promontoires et des toits.</p></body></html> - Use additional animation sources - + Fog + Brouillard - <html><head/><body><p>Affects side and diagonal movement. Enabling this setting makes movement more realistic.</p><p>If disabled then the whole character's body is pointed to the direction of view. Diagonal movement has no special animation and causes sliding.</p><p>If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it changes straight right and straight left movement as well. Also turns the whole body up or down when swimming according to the movement direction.</p></body></html> - + <html><head/><body><p>By default, the fog becomes thicker proportionally to your distance from the clipping plane set at the clipping distance, which causes distortion at the edges of the screen. + This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV.</p></body></html> + <html><body><p>Si cette option est désactivée, le brouillard s'épaissit linéairement avec la distance jusqu'à atteindre le plan de coupure perpendiculaire à la caméra (clipping plane) à la distance de coupure. Ceci cause des distorsions sur les bords de l'écran.</p><p>Si elle est activée, le brouillard utilise la distance de chaque objet avec la caméra (appelée distance euclidienne) pour calculer la densité de brouillard. Cette option rend le brouillard moins artificiel, particulièrement pour les larges champs de vision.</p></body></html> - Turn to movement direction - + <html><head/><body><p>Use exponential fog formula. By default, linear fog is used.</p></body></html> + <html><body><p>Si l'option est activée, le moteur de jeu utilise une décroissance exponentielle de la visibilité due au brouillard.</p><p>Si elle est désactivée, il utilise une décroissance linéaire de la visibilité due au brouillard.</p></body></html> - <html><head/><body><p>Render holstered weapons (with quivers and scabbards), requires modded assets.</p></body></html> - + <html><head/><body><p>Reduce visibility of clipping plane by blending objects with the sky.</p></body></html> + <html><head/><body><p>Si l'option est activée, le moteur de jeu réduit la visibilité du plan de coupure du brouillard en effectuant un fondu avec le ciel.</p></body></html> - Weapon sheathing - + <html><head/><body><p>The fraction of the maximum distance at which blending with the sky starts.</p></body></html> + <html><head/><body><p>Fraction de la distance d'affichage maximale à partir de laquelle le fondu avec le ciel commence.</p></body></html> - <html><head/><body><p>Render holstered shield, requires modded assets.</p></body></html> - + Terrain + Terrain - Shield sheathing - + <html><head/><body><p>Controls how large an object must be to be visible in the scene. The object’s size is divided by its distance to the camera and the result of the division is compared with this value. The smaller this value is, the more objects you will see in the scene.</p></body></html> + <html><body><p>Cette option contrôle la taille apparente minimale que les objets doivent avoir pour être visibles à l'écran. Le rapport entre la taille de l'objet et sa distance à la caméra est comparé à cette valeur seuil. Plus cette valeur est faible, plus le nombre d'objets visibles à l'écran sera élevé.</p></body></html> - <html><head/><body><p>In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.</p></body></html> - + <html><head/><body><p>If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.</p></body></html> + <html><body><p>Si l'option est activée, le moteur utilise la pagination et différents niveau de détails (LOD) afin d'afficher le terrain entier.</p><p>Si elle est désactivée, seul le terrain de jeu actif est affiché.</p></body></html> - Player movement ignores animation - + <html><head/><body><p>Use object paging for active cells grid.</p></body></html> + <html><body><p>Si l'option est activée, utilise la pagination des objets pour rendre l'espace de jeu actif.</p></body></html> - Shaders - + <html><head/><body><p>If this setting is true, supporting models will make use of day night switch nodes.</p></body></html> + <html><body><p>Si cette option est activée, le moteur prend en charge les "day night switch nodes" des modèles 3D. Ceci permet de sélectionner entre jour et nuit l'apparence de ces modèles.</p></body></html> - <html><head/><body><p>If this option is enabled, normal maps are automatically recognized and used if they are named appropriately - (see 'normal map pattern', e.g. for a base texture foo.dds, the normal map texture would have to be named foo_n.dds). - If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file (.nif or .osg file). Affects objects.</p></body></html> - + Post Processing + Post-processing - Auto use object normal maps - + <html><head/><body><p>If this setting is true, post processing will be enabled.</p></body></html> + <html><body><p>Si cette option est activée, le post-traitement est lui aussi activé (les shaders de post-traitement sont configurables en jeu).</p></body></html> - <html><head/><body><p>See 'auto use object normal maps'. Affects terrain.</p></body></html> - + <html><head/><body><p>Re-render transparent objects with forced alpha clipping.</p></body></html> + <html><head/><body><p>Si l'option est activée, le moteur réalise une seconde passe de rendu des objets transparents, en forçant leur découpe par le canal alpha de leurs textures.</p></body></html> - Auto use terrain normal maps - + <html><head/><body><p>Controls how much eye adaptation can change from frame to frame. Smaller values makes for slower transitions.</p></body></html> + <html><head/><body><p>Cette option contrôle la vitesse selon laquelle l'adaptation visuelle change d'une image à la suivante. Une valeur faible correspond à une adaptation lente.</p></body></html> - <html><head/><body><p>If this option is enabled, specular maps are automatically recognized and used if they are named appropriately - (see 'specular map pattern', e.g. for a base texture foo.dds, - the specular map texture would have to be named foo_spec.dds). - If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file - (.osg file, not supported in .nif files). Affects objects.</p></body></html> - + Audio + Audio - Auto use object specular maps - + Select your preferred audio device. + Sélectionnez votre périphérique audio principal. - <html><head/><body><p>If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. The texture must contain the layer colour in the RGB channel (as usual), and a specular multiplier in the alpha channel.</p></body></html> - + Default + Par défaut - Auto use terrain specular maps - + This setting controls HRTF, which simulates 3D sound on stereo systems. + Cette option contrôle l'HRTF, qui émule la spatialisation 3D du son pour un système stéréo. - <html><head/><body><p>Normally environment map reflections aren't affected by lighting, which makes environment-mapped (and thus bump-mapped objects) glow in the dark. - Morrowind Code Patch includes an option to remedy that by doing environment-mapping before applying lighting, this is the equivalent of that option. - Affected objects will use shaders. - </p></body></html> - + HRTF + HRTF - Bump/reflect map local lighting - + Automatic + Automatique - <html><head/><body><p>Allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. Can negatively impact performance.</p></body></html> - + On + Actif - Use anti-alias alpha testing - + Select your preferred HRTF profile. + Sélectionnez le profil HRTF qui vous convient le mieux. - <html><head/><body><p>Enables soft particles for particle effects. This technique softens the intersection between individual particles and other opaque geometry by blending between them.</p></body></html> - + Interface + Interface - <html><head/><body><p>Simulate coverage-preserving mipmaps to prevent alpha-tested meshes shrinking as they get further away. Will cause meshes whose textures have coverage-preserving mipmaps to grow, though, so refer to mod installation instructions for how to set this.</p></body></html> - + <html><head/><body><p>This setting scales GUI windows. A value of 1.0 results in the normal scale.</p></body></html> + <html><body><p>Cette option agrandit ou diminue l'interface utilisateur. Une valeur de 1 correspond à sa taille nominale.</p></body></html> - Adjust coverage for alpha test - + <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> + <html><body><p>Cette option permet d'activer la durée de vie restante des effets magiques et des lampes. Cette durée restante s'affiche en passant la souris au-dessus de l'icône de l'effet magique.</p></body></html> - <html><head/><body><p>EXPERIMENTAL: Stop rain and snow from falling through overhangs and roofs.</p></body></html> - + <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> + <html><body><p>Lorsque cette option est activée, les sujets de conversation de la fenêtre de dialogue auront des couleurs différentes si ceux-ci sont spécifiques au PNJ ou qu'ils ont déjà été lus autre part. Ces couleurs peuvent être changées dans le fichier settings.cfg.</p></body></html> - Fog - + Size of characters in game texts. + <html><body><p>Taille des caractères des textes du jeu.</p></body></html> - <html><head/><body><p>By default, the fog becomes thicker proportionally to your distance from the clipping plane set at the clipping distance, which causes distortion at the edges of the screen. - This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV.</p></body></html> - + <html><head/><body><p>Enable zooming on local and global maps.</p></body></html> + <html><body><p>Cette option permet de zoomer en jeu sur la carte (carte locale et carte du monde).</p></body></html> - Radial fog - + <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> + <html><body><p>Si cette option est activée, les contenants compatibles avec "Graphic Herbalism" l'utilisent, au lieu d'ouvrir le menu de son contenu.</p></body></html> - <html><head/><body><p>Use exponential fog formula. By default, linear fog is used.</p></body></html> - + <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> + <html><body><p>Lorsque cette option est activée, les bonus de dommages des flèches et des carreaux apparaissent dans l'infobulle de l'objet.</p></body></html> - Exponential fog - + <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> + <html><body><p>Lorsque cette option est activée, la portée et la rapidité d'attaque des armes de mêlées apparaissent dans l'infobulle de l'objet.</p></body></html> - <html><head/><body><p>Reduce visibility of clipping plane by blending objects with the sky.</p></body></html> - + <html><head/><body><p>Stretch menus, load screens, etc. to the window aspect ratio.</p></body></html> + <html><body><p>Étends les menus, les écrans de chargement et autres éléments d'interface aux formats d'image du jeu.</p></body></html> - Sky blending - + <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> + <html><body><p>Lorsque l'option est activée, affiche les chances de succès d'un enchantement dans le menu des enchantements.</body></html> - <html><head/><body><p>The fraction of the maximum distance at which blending with the sky starts.</p></body></html> - + <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> + <html><body><p>Empêche les marchands d'équiper les objets qui leur sont vendus.</p></body></html> - Sky blending start - + <html><head/><body><p>Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.</p></body></html> + <html><body><p>Lorsque l'option est activée, les entraîneurs choisissent les compétences à entraîner en utilisant uniquement les points de compétences de base. Ceci permet d'améliorer les compétences de marchandages de l'entraîneur, sans que le marchandage devienne une compétence proposée.</p></body></html> - Terrain - + Miscellaneous + Divers - Viewing distance - + Saves + Sauvegardes - <html><head/><body><p>Controls how large an object must be to be visible in the scene. The object’s size is divided by its distance to the camera and the result of the division is compared with this value. The smaller this value is, the more objects you will see in the scene.</p></body></html> - + <html><head/><body><p>This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.</p></body></html> + <html><body><p>Cette option affiche le temps de jeu de chaque sauvegarde dans leur menu de sélection.</p></body></html> - Object paging min size - + JPG + JPG - <html><head/><body><p>If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.</p></body></html> - + PNG + PNG - Distant land - + TGA + TGA - <html><head/><body><p>Use object paging for active cells grid.</p></body></html> - + Testing + Testeurs - Active grid object paging - + These settings are intended for testing mods and will cause issues if used for normal gameplay. + Ces options sont destinées aux testeurs de contenus additionnels (mods). Ils poseront problème lors d'une partie normale. - <html><head/><body><p>If this setting is true, supporting models will make use of day night switch nodes.</p></body></html> - + <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> + <html><body><p>Lorsque cette option est activée, OpenMW capture le curseur de la souris.</p><p>Lorsque le jeu est en mode "Regard/Déplacement", OpenMW centre le curseur au milieu de l'écran; sans tenir compte de cette option (le curseur/viseur est toujours au centre de la fenêtre d'OpenMW). Néanmoins, lorsqu'un menu est ouvert, cette option permet de récupérer le contrôle de la souris. En effet, lorsque l'option est active, le mouvement du curseur s'arrête aux bords de la fenêtre et empêche d'accéder à d'autres applications. Tandis que, lorsque l'option est désactivée, le curseur peut sortir librement de la fenêtre et accéder au bureau.</p><p>Cette option ne s'applique pas à l'écran lorsque la touche Échap a été enfoncée, dans ce cas, le curseur n'est jamais capturé. Cette option n'agit pas non plus sur "Atl-Tab" ou toute autre séquence de touches utilisée par le système d'exploitation pour récupérer le contrôle du curseur de la souris. </p><p>Cette option interagit avec l'option de minimisation de la fenêtre lors de la perte de focus en modifiant ce qui est considéré comme une perte de focus. Par exemple, avec une configuration à doubles écrans, il peut être plus agréable d'accéder au second écran avec cette option désactivée.</p><p>Note aux développeurs : Il est préférable de désactiver cette option lorsque le jeu tourne dans un débogueur, ceci afin d'empêcher le curseur de la souris de devenir inutilisable lorsque le jeu s'arrête sur un breakpoint.</p></body></html> - Day night switch nodes - + Browse… + Parcourir... - Post Processing - + Collision is used for both physics simulation and navigation mesh generation for pathfinding. Cylinder gives the best consistency between available navigation paths and ability to move by them. Changing this value affects navigation mesh generation therefore navigation mesh disk cache generated for one value will not be useful with another. + Les volumes de collision sont utilisés à la fois par la simulation de la physique du jeu et par la génération de mesh de navigation utilisé pour les choix d'itinéraires. Les collisions cylindriques donnent la meilleure correspondance entre l'itinéraire choisi et la capacité à les emprunter. Changer la valeur de cette option affecte la génération de mesh de navigation, dès lors les mesh de navigation précédemment mis en cache ne seront plus compatibles. - <html><head/><body><p>If this setting is true, post processing will be enabled.</p></body></html> - + Gameplay + Jouabilité - Enable post processing - + <html><head/><body><p>If enabled, a magical ammunition is required to bypass normal weapon resistance or weakness. If disabled, a magical ranged weapon or a magical ammunition is required.</p></body></html> + <html><body><p>Si l'option est activée, une munition magique est requise pour contourner la résistance ou la faiblesse normale d'une arme.</p><p>Si l'option est désactivée, une arme à distance magique ou une munition magique est requise.</p></body></html> - <html><head/><body><p>Re-render transparent objects with forced alpha clipping.</p></body></html> - + <html><head/><body><p>Make enchanted weapons without Magical flag bypass normal weapons resistance, like in Morrowind.</p></body></html> + <html><body><p>Lorsque cette option est activée, elle permet aussi aux armes enchantées, non indiquées comme étant magique, de contourner la résistance de l'arme, comme c'est le cas dans le moteur original.</p></body></html> - Transparent postpass - + cells + cellules - <html><head/><body><p>Controls how much eye adaptation can change from frame to frame. Smaller values makes for slower transitions.</p></body></html> - + Shadows + Ombres - Auto exposure speed - + <html><head/><body><p>Type of "compute scene bounds" computation method to be used. Bounds (default) for good balance between performance and shadow quality, primitives for better looking shadows or none for no computation.</p></body></html> + <html><body><p>Type de méthode utilisée pour calculer de "compute scene bound" (limite de scène). Le choix "bordures" (par défaut) donne un bon équilibre entre qualité des ombres et performances, "primitives" pour un meilleur rendu ou "aucun" pour éviter ce calcul.</p></body></html> - Audio - + <html><head/><body><p>64 game units is 1 real life yard or about 0.9 m</p></body></html> + <html><body><p>64 unités de jeu correspondent à 1 yard, c'est-à-dire environ 0.9 m.</p></body></html> - Select your preferred audio device. - + unit(s) + unité(s) - Default - + <html><head/><body><p>Enable shadows for NPCs and creatures besides the player character. May have a minor performance impact.</p></body></html> + <html><head/><body><p>Cette option affiche l'ombre des PNJ et des créatures. Elle peut avoir un impact faible sur les performances.</p></body></html> - This setting controls HRTF, which simulates 3D sound on stereo systems. - + 512 + 512 - HRTF - + 1024 + 1024 - Automatic - + 2048 + 2048 - On - + 4096 + 4096 - Select your preferred HRTF profile. - + <html><head/><body><p>The fraction of the limit above at which shadows begin to gradually fade away.</p></body></html> + <html><body><p>Fraction de la limite ci-dessus à partir de laquelle les ombres commencent à disparaître.</p></body></html> - Interface - + <html><head/><body><p>Enable shadows exclusively for the player character. May have a very minor performance impact.</p></body></html> + <html><body><p>Cette option affiche l'ombre du personnage joueur. Elle pourrait avoir un impact très faible sur les performances.</p></body></html> - <html><head/><body><p>This setting scales GUI windows. A value of 1.0 results in the normal scale.</p></body></html> - + <html><head/><body><p>The resolution of each individual shadow map. Increasing it significantly improves shadow quality but may have a minor performance impact.</p></body></html> + <html><body><p>La résolution de chaque carte d'ombre. Augmenter cette valeur, de façon importante, augmente la qualité des ombres, mais peut impliquer un impact mineur sur les performances.</p></body></html> - GUI scaling factor - + <html><head/><body><p>The distance from the camera at which shadows completely disappear.</p></body></html> + <html><body><p>Distance à la caméra à partir de laquelle les ombres disparaissent entièrement.</p></body></html> - <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> - + <html><head/><body><p>Enable shadows for primarily inanimate objects. May have a significant performance impact.</p></body></html> + <html><head/><body><p>Cette option affiche l'ombre des objets. Elle peut avoir un impact significative sur les performances.</p></body></html> - Show effect duration - + <html><head/><body><p>Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting.</p><p>Has no effect if actor/player shadows are not enabled.</p></body></html> + <html><body><p>À cause de certaines limitations avec les données de Morrowind, seuls les personnages et les créatures peuvent projeter une ombre en intérieur, ceci peut déconcerter certains joueurs.</p><p>Cette option n'a aucun effet si les ombres des PNJ/créature et du personnage joueur n'ont pas été activées.</p></body></html> - <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> - + <html><head/><body><p>Enable shadows for the terrain including distant terrain. May have a significant performance and shadow quality impact.</p></body></html> + <html><head/><body><p>Cette option affiche l'ombre des terrains. Elle peut avoir un impact faible sur les performances et la qualité des ombres.</p></body></html> - Change dialogue topic color - + Lighting + Éclairage - Size of characters in game texts. - + Tooltip + Infobulles - Font size - + Crosshair + Réticule de visée - <html><head/><body><p>Enable zooming on local and global maps.</p></body></html> - + Screenshots + Captures d'écran - Can zoom on maps - + <html><head/><body><p>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.</p></body></html> + <html><body><p>Cette option donne aux PNJ/créatures la capacité de nager en surface lorsqu'ils suivent un autre acteur, indépendamment de leur capacité à nager. Elle fonctionne uniquement lorsque l'utilisation des mesh de navigation pour le choix d'itinéraire est activée.</p></body></html> - <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> - + <html><head/><body><p>Effects of reflected Absorb spells are not mirrored - like in Morrowind.</p></body></html> + <html><body><p>L'effet réfléchi des sorts d'absorption n'est pas appliqué, comme dans le moteur original.</p></body></html> - <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - + <html><head/><body><p>Maximum distance at which lights will appear (measured in units).</p><p>Set this to 0 to use an unlimited distance.</p></body></html> + <html><body><p>Distance maximale d'affichage des sources lumineuses (en unité de distance).</p><p>Mettez cette valeur à 0 pour une distance d'affichage infinie.</p></body></html> - Show projectile damage - + <html><head/><body><p>Maximum number of lights per object.</p><p>A low number near default will cause light popping similar to what you would see with legacy lighting.</p></body></html> + <html><body><p>Nombre maximum de sources lumineuses par objet.</p><p>Une valeur faible mène à des apparitions tardives des sources lumineuses similaires à celles obtenues avec la méthode d'éclairage traditionnelle.</p></body></html> - <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - + <html><head/><body><p>Fraction of maximum distance at which lights will start to fade.</p><p>Set this to a low value for slower transitions or a high value for quicker transitions.</p></body></html> + <html><body><p>Fraction de la distance maximale d'une source à partir de laquelle l'intensité lumineuse commence à décroître.</p><p>Sélectionnez une valeur basse pour une transition douce ou une valeur plus élevée pour une transition plus abrupte.</p></body></html> - Show melee info - + <html><head/><body><p>Set the internal handling of light sources.</p> +<p> "Legacy" always uses 8 lights per object and provides a lighting closest to an original game.</p> +<p>"Shaders (compatibility)" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.</p> +<p> "Shaders" carries all of the benefits that "Shaders (compatibility)" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware.</p></body></html> + <html><body><p>Définit la gestion des sources lumineuses :</p> +<p>"Traditionnelle" Chaque objet est éclairé par 8 sources lumineuses. Cet méthode est la plus proche du jeu original.</p> +<p>"Shaders (mode de compatibilité)" supprime la limite des 8 sources lumineuses. Cette méthode permet d'éclairer la végétation au sol, mais aussi de configurer à quel distance une source lumineuse s'estompe. Ce choix est recommandé pour les ordinateurs plus anciens avec un nombre de sources lumineuses proche de 8.</p> +<p>"Shaders" offre tous les bénéfices apportés par "Shaders (mode de compatibilité)", mais utilise une approche moderne. Celle-ci permet, sur du matériel moderne, d'augmenter le nombre de sources lumineuses par objet sans perte de performance."</p></body></html> - <html><head/><body><p>Stretch menus, load screens, etc. to the window aspect ratio.</p></body></html> - + Legacy + Traditionnelle - Stretch menu background - + Shaders (compatibility) + Shaders (mode de compatibilité) - Show owned objects - + <html><head/><body><p>Multipler for bounding sphere of lights.</p><p>Higher numbers allows for smooth falloff but require an increase in number of max lights.</p><p>Does not effect the illumination or strength of lights.</p></body></html> + <html><body><p>Multiplicateur pour le rayon de la sphère incluant les sources lumineuses.</p><p>Un multiplicateur plus élevé permet une extinction plus douce, mais applique un plus grand nombre de sources lumineuses sur chaque objet.</p><p>Ce paramètre ne modifie ni l'intensité ni la luminance des lumières.</body></html> - <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> - + <html><head/><body><p>Minimum ambient interior brightness.</p><p>Increase this if you feel interiors are too dark.</p></body></html> + <html><head/><body><p>Luminosité ambiante minimale en intérieur.</p><p>Augmentez cette valeur si les intérieurs vous semblent trop sombres.</p></body></html> - Show enchant chance - + In third-person view, use the camera as the sound listener instead of the player character. + En vue à la troisième personne, le jeu reproduit le son reçu par la caméra au lieu de celui reçu par tête du personnage joueur. - <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> - + Permanent Barter Disposition Changes + Disposition au Marchandage permanente - Merchant equipping fix - + Classic Calm Spells Behavior + Comportement d'origine pour le sort d'Apaisement - <html><head/><body><p>Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.</p></body></html> - + NPCs Avoid Collisions + Les PNJ évitent les collisions - Miscellaneous - + Soulgem Values Rebalance + Rééquilibrage de la valeur des gemmes sprirituelles - Saves - + Day Night Switch Nodes + Support des noeuds jour/nuit - <html><head/><body><p>This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.</p></body></html> - + Followers Defend Immediately + Les compagnons se défendent immédiatement - Add "Time Played" to saves - + Only Magical Ammo Bypass Resistance + Seules les munitions contournent la résistance - JPG - + Graphic Herbalism + Support de Graphic herbalism - PNG - + Swim Upward Correction + Correction verticale de la nage - TGA - + Enchanted Weapons Are Magical + Rendre magiques les armes enchantées - Notify on saved screenshot - + Merchant Equipping Fix + Correctif : équipement des marchands - Testing - + Can Loot During Death Animation + Pillage possible durant l'animation de mort - These settings are intended for testing mods and will cause issues if used for normal gameplay. - + Classic Reflected Absorb Spells Behavior + Comportement traditionnel de la réflexion des sorts d'absorbtion - <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> - + Unarmed Creature Attacks Damage Armor + L'attaque des créatures non armées endomage les armures - Grab cursor - + Affect Werewolves + S'applique aux loups garoux - Skip menu and generate default character - + Do Not Affect Werewolves + Ne s'applique pas aux loups garoux - Start default character at - + Background Physics Threads + Thread(s) d'arrière plan dédié(s) à la physique - default cell - + Actor Collision Shape Type + Volume de collison pour les personnages - Run script after startup: - + Axis-Aligned Bounding Box + Volume englobant aligné à l'axe - Browse… - + Rotating Box + Boite tournante - Collision is used for both physics simulation and navigation mesh generation for pathfinding. Cylinder gives the best consistency between available navigation paths and ability to move by them. Changing this value affects navigation mesh generation therefore navigation mesh disk cache generated for one value will not be useful with another. - + Smooth Movement + Mouvements lissés - Gameplay - + Use Additional Animation Sources + Utiliser des sources d'animations additionnelles - Always allow actors to follow over water - + Weapon Sheathing + Arme rengainée - <html><head/><body><p>If enabled, a magical ammunition is required to bypass normal weapon resistance or weakness. If disabled, a magical ranged weapon or a magical ammunition is required.</p></body></html> - + Shield Sheathing + Bouclier rengainé - Only magical ammo bypass resistance - + Player Movement Ignores Animation + Le mouvement du personnage joueur ignore son animation - Graphic herbalism - + Use Magic Item Animation + Anime l'utilisation d'objets magique - <html><head/><body><p>Make enchanted weapons without Magical flag bypass normal weapons resistance, like in Morrowind.</p></body></html> - + Auto Use Object Normal Maps + Utilisation auto des normal maps pour les objets - Trainers choose offered skills by base value - + Soft Particles + Apparition douce des particules - Steal from knocked out actors in combat - + Auto Use Object Specular Maps + Utilisation auto des specular maps pour les objets - Factor strength into hand-to-hand combat - + Auto Use Terrain Normal Maps + Utilisation auto des normal maps pour le terrain - Background physics threads - + Auto Use Terrain Specular Maps + Utilisation auto des specular maps pour le terrain - Actor collision shape type - + Use Anti-Aliased Alpha Testing + Utiliser l'alpha testing pour l'anti-aliasing - Soft particles - + Bump/Reflect Map Local Lighting + Plaquage de rugosité/environnement sous lumière locale - Weather particle occlusion - + Weather Particle Occlusion + Occlusion des particules liées à la météo - cells - + Exponential Fog + Brouillard exponentiel - Shadows - + Radial Fog + Brouillard radial - bounds - + Sky Blending Start + Début du fondu du ciel - primitives - + Sky Blending + Fondu du ciel - none - + Object Paging Min Size + Taille min des objets pour leur pagination - <html><head/><body><p>Type of "compute scene bounds" computation method to be used. Bounds (default) for good balance between performance and shadow quality, primitives for better looking shadows or none for no computation.</p></body></html> - + Viewing Distance + Distance d'affichage - Shadow planes computation method - + Distant Land + Terrain distant - <html><head/><body><p>64 game units is 1 real life yard or about 0.9 m</p></body></html> - + Active Grid Object Paging + Pagination pour l'espace actif - unit(s) - + Transparent Postpass + Passe de transparence différée - <html><head/><body><p>Enable shadows for NPCs and creatures besides the player character. May have a minor performance impact.</p></body></html> - + Auto Exposure Speed + Vitesse d'auto-exposition - Enable actor shadows - + Enable Post Processing + Activer le post-traitement - 512 - + Shadow Planes Computation Method + Méthode de calcul pour les plans d'ombre - 1024 - + Enable Actor Shadows + Ombre des PNJ/créatures - 2048 - + Fade Start Multiplier + Début de l'atténuation (multiplicateur) - 4096 - + Enable Player Shadows + Ombre du personnage joueur - <html><head/><body><p>The fraction of the limit above at which shadows begin to gradually fade away.</p></body></html> - + Shadow Map Resolution + Résolution des crates d'ombres - Fade start multiplier - + Shadow Distance Limit: + Limite de distance des ombres - <html><head/><body><p>Enable shadows exclusively for the player character. May have a very minor performance impact.</p></body></html> - + Enable Object Shadows + Ombre des objets - Enable player shadows - + Enable Indoor Shadows + Ombres en intérieur - <html><head/><body><p>The resolution of each individual shadow map. Increasing it significantly improves shadow quality but may have a minor performance impact.</p></body></html> - + Enable Terrain Shadows + Ombre des terrains - Shadow map resolution - + Maximum Light Distance + Portée maximale des sources lumineuse - <html><head/><body><p>The distance from the camera at which shadows completely disappear.</p></body></html> - + Max Lights + Nombre maximum de sources lumineuses - Shadow distance limit: - + Lighting Method + Méthode d'illumination - <html><head/><body><p>Enable shadows for primarily inanimate objects. May have a significant performance impact.</p></body></html> - + Bounding Sphere Multiplier + Multiplicateur de portée des sphères lumineuses - Enable object shadows - + Minimum Interior Brightness + Luminosité intérieure minimale - <html><head/><body><p>Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting.</p><p>Has no effect if actor/player shadows are not enabled.</p></body></html> - + Audio Device + Périphérique audio - Enable indoor shadows - + HRTF Profile + Profil HRTF - <html><head/><body><p>Enable shadows for the terrain including distant terrain. May have a significant performance and shadow quality impact.</p></body></html> - + Tooltip and Crosshair + Infobulles et réticule de visée - Enable terrain shadows - + GUI Scaling Factor + Échelle de l'interface - Lighting - + Show Effect Duration + Afficher la durée des effets - Lighting method - + Change Dialogue Topic Color + Changer la couleur des sujets de conversation - Audio device - + Font Size + Taille des polices - HRTF profile - + Show Projectile Damage + Afficher les dommages des projectiles - Tooltip - + Show Melee Info + Afficher les infos de mêlée - Crosshair - + Stretch Menu Background + Étendre les arrière-plans - Tooltip and crosshair - + Show Owned Objects + Afficher la possession des objets - Maximum quicksaves - + Show Enchant Chance + Afficher les chances d'enchantement - Screenshots - + Maximum Quicksaves + Nombre maximum de sauvegardes rapides - Screenshot format - + Screenshot Format + Format des captures - <html><head/><body><p>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.</p></body></html> - + Grab Cursor + Capturer le curseur - <html><head/><body><p>Effects of reflected Absorb spells are not mirrored - like in Morrowind.</p></body></html> - + Default Cell + la cellule par défaut - <html><head/><body><p>Maximum distance at which lights will appear (measured in units).</p><p>Set this to 0 to use an unlimited distance.</p></body></html> - + Bounds + bordures - Lights maximum distance - + Primitives + primitives - <html><head/><body><p>Maximum number of lights per object.</p><p>A low number near default will cause light popping similar to what you would see with legacy lighting.</p></body></html> - + None + aucune - Max light sources - + Always Allow Actors to Follow over Water + Toujours permettre aux PNJ/créatures à vous suivre sur l'eau - <html><head/><body><p>Fraction of maximum distance at which lights will start to fade.</p><p>Set this to a low value for slower transitions or a high value for quicker transitions.</p></body></html> - + Racial Variation in Speed Fix + Correction de la variation raciale pour la vitesse - Lights fade multiplier - + Use Navigation Mesh for Pathfinding + Utiliser les mesh de navigation pour la recherche d'itinéraires - <html><head/><body><p>Set the internal handling of light sources.</p> -<p> "Legacy" always uses 8 lights per object and provides a lighting closest to an original game.</p> -<p>"Shaders (compatibility)" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.</p> -<p> "Shaders" carries all of the benefits that "Shaders (compatibility)" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware.</p></body></html> - + Trainers Choose Offered Skills by Base Value + Entraîneurs : choix de compétences à partir des valeurs de base - Legacy - + Steal from Knocked out Actors in Combat + Vol possible aux PNJ/créatures évanouies en combat - Shaders (compatibility) - + Factor Strength into Hand-to-Hand Combat + Multiplicateur de force pour le combat à mains nues - <html><head/><body><p>Multipler for bounding sphere of lights.</p><p>Higher numbers allows for smooth falloff but require an increase in number of max lights.</p><p>Does not effect the illumination or strength of lights.</p></body></html> - + Turn to Movement Direction + Se tourner en direction du mouvement - Lights bounding sphere multiplier - + Adjust Coverage for Alpha Test + Ajuster la couverture pour l'alpha test - <html><head/><body><p>Minimum ambient interior brightness.</p><p>Increase this if you feel interiors are too dark.</p></body></html> - + Use the Camera as the Sound Listener + Utiliser le son arrivant à la caméra - Lights minimum interior brightness - + Can Zoom on Maps + Permettre le zoom sur la carte + + + Add "Time Played" to Saves + Ajoute le temps de jeu aux sauvegardes + + + Notify on Saved Screenshot + Notifier l'enregistrement des captures d'écran + + + Skip Menu and Generate Default Character + Passer le menu principal et générer un personnage standard + + + Start Default Character at + Placer le personnage par défaut dans + + + Run Script After Startup: + Script à lancer après démarrage : <html><head/><body><p>Force the use of per pixel lighting. By default, only bump and normal mapped objects use per-pixel lighting. Only affects objects drawn with shaders. Enabling per-pixel lighting results in visual differences to the original MW engine as certain lights in Morrowind rely on vertex lighting to look as intended. Note that groundcover shaders and particle effects ignore this setting.</p></body></html> diff --git a/files/lang/launcher_ru.ts b/files/lang/launcher_ru.ts index 99949370a4..cf913cc1d7 100644 --- a/files/lang/launcher_ru.ts +++ b/files/lang/launcher_ru.ts @@ -84,11 +84,11 @@ Отменить - Remove unused tiles + Remove Unused Tiles Удалить неиспользуемые тайлы - Max size + Max Size Максимальный размер @@ -109,7 +109,7 @@ &New Content List - Новый список плагинов + &Новый список плагинов Clone Content List @@ -148,16 +148,16 @@ Ctrl+R - <html><head/><body><p>note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> - <html><head/><body><p>подсказка: файлы данных, не включенные в текущий список, <span style=" font-style:italic;font-weight: bold">выделены</span></p></body></html> + <html><head/><body><p>Note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + <html><head/><body><p>Подсказка: файлы данных, не включенные в текущий список, <span style=" font-style:italic;font-weight: bold">выделены</span></p></body></html> - <html><head/><body><p>note: directories that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> - <html><head/><body><p>подсказка: директории, не включенные в текущий список, <span style=" font-style:italic;font-weight: bold">выделены</span></p></body></html> + <html><head/><body><p>Note: directories that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + <html><head/><body><p>Подсказка: директории, не включенные в текущий список, <span style=" font-style:italic;font-weight: bold">выделены</span></p></body></html> - <html><head/><body><p>note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> - <html><head/><body><p>подсказка: архивы, не включенные в текущий список, <span style=" font-style:italic;font-weight: bold">выделены</span></p></body></html> + <html><head/><body><p>Note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + <html><head/><body><p>Подсказка: архивы, не включенные в текущий список, <span style=" font-style:italic;font-weight: bold">выделены</span></p></body></html> @@ -227,15 +227,15 @@ Экран - Window mode + Window Mode Режим окна - Framerate limit + Framerate Limit Максимум кадров в секунду - Window border + Window Border Рамка окна @@ -243,11 +243,11 @@ Разрешение экрана - Anti-aliasing + Anti-Aliasing Сглаживание - Vertical synchronization + Vertical Synchronization Вертикальная синхронизация @@ -270,7 +270,7 @@ Импорт настроек из Morrowind - File to import settings from: + File to Import Settings From: Импортировать настройки из файла: @@ -278,8 +278,8 @@ Выбрать... - Import add-on and plugin selection (creates a new Content List) - Импортировать подключенные плагины (создает новый список плагинов) + Import Add-on and Plugin Selection + Импортировать список подключенных аддонов и плагинов Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, @@ -290,7 +290,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Включите эту опцию, если вы все равно хотите использовать оригинальные шрифты вместо шрифтов OpenMW, или если вы используете сторонние растровые шрифты. - Import bitmap fonts setup + Import Bitmap Fonts Импортировать растровые шрифты @@ -372,6 +372,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov &Uncheck Selected &Отключить выбранные + + Resolved as %1 + Путь разрешен как %1 + + + This is the data-local directory and cannot be disabled + Это директория data-loсal, и она не может быть отключена + + + This directory is part of OpenMW and cannot be disabled + Это директория является частью OpenMW и не может быть отключена + + + This directory is enabled in an openmw.cfg other than the user one + Это директория включена в openmw.cfg, не являющемся пользовательским + + + This archive is enabled in an openmw.cfg other than the user one + Этот архив включен в openmw.cfg, не являющемся пользовательским + Launcher::GraphicsPage @@ -527,7 +547,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <br><b>Could not create directory %0</b><br><br>%1<br> - <br><b>Не удалось создать директорию %0</b><br><br> + <br><b>Не удалось создать директорию %0</b><br><br>%1<br> @@ -609,7 +629,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Делает изменение отношения торговцев к персонажу игрока из-за торговли постоянным.</p></body></html> - Permanent barter disposition changes + Permanent Barter Disposition Changes Постоянная смена отношения торговцев @@ -617,7 +637,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Немедленно вводить спутников персонажа игрока в бой с противниками, начавшими бой с ними или персонажем игрока. Если настройка отключена, они не вступят в бой до первой атаки от противников или игрока.</p></body></html> - Followers defend immediately + Followers Defend Immediately Спутники защищаются сразу @@ -633,7 +653,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Предотвращает вступление в бой персонажей, подвергнутых эффектам Усмирения, каждый кадр - как в Morrowind без MCP.</p></body></html> - Classic Calm spells behavior + Classic Calm Spells Behavior Классическое поведение Усмирения @@ -641,7 +661,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Делает стоимость заполненных камней душ зависимой только от силы души.</p></body></html> - Soulgem values rebalance + Soulgem Values Rebalance Ребаланс стоимости камней душ @@ -649,11 +669,11 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Делает так, чтобы персонаж плыл немного вверх относительно камеры в режиме от третьего лица. Предназначено для того, чтобы было проще плыть по поверхности воды без погружения в нее.</p></body></html> - Swim upward correction + Swim Upward Correction Коррекция при плавании вверх - Enchanted weapons are magical + Enchanted Weapons Are Magical Зачарованное оружие - магическое @@ -665,7 +685,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Эффекты отраженных заклинаний Поглощения не отзеркаливаются - как в Morrowind.</p></body></html> - Classic reflected Absorb spells behavior + Classic Reflected Absorb Spells Behavior Классическое поведение Поглощения @@ -673,7 +693,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Когда включено, NPC пытаются избегать столкновения с другими персонажами.</p></body></html> - NPCs avoid collisions + NPCs Avoid Collisions Персонажи избегают столкновения @@ -681,7 +701,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Не учитывать множитель веса расы при вычислении скорости перемещения.</p></body></html> - Racial variation in speed fix + Racial Variation in Speed Fix Фикс влияния веса рас на Скорость @@ -689,7 +709,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Когда эта настройка включена, игрок может обыскивать тела персонажей (например, призванных существ) во время анимации их смерти, если они не находятся в бою. Если при этом игрок убрал тело, то нам приходится сразу увеличивать счетчик количества погибших и запускать скрипт удаленного персонажа.</p><p>Когда эта настройка отключена, игроку всегда придется ждать завершения анимации смерти. В основном предотвращает эксплойт с призванными существами (сбор дорогого оружия с дремор и золотых святых). Конфликтует с модами на манекены, которые используют команду SkipAnim для предотвращения завершения анимации смерти.</p></body></html> - Can loot during death animation + Can Loot During Death Animation Обыск тел во время анимации смерти @@ -701,7 +721,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Позволяет атакам существ без оружия повреждать броню цели, по аналогии с атаками оружием.</p></body></html> - Unarmed creature attacks damage armor + Unarmed Creature Attacks Damage Armor Атаки существ повреждают броню @@ -709,11 +729,11 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Отключено - Affect werewolves + Affect Werewolves Включая оборотней - Do not affect werewolves + Do Not Affect Werewolves Не включая оборотней @@ -725,11 +745,11 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Форма объектов столкновений, используемых для физики и поиска путей с помощью навигационной сетки. Цилиндры дают лучшую совместимость между поиском путей и возможностью двигаться по ним. Изменение значения этой настройки влияет на создание навигационной сетки, поэтому сетка, сгенерированная с одним значением, не может быть использована для другого. - Axis-aligned bounding box + Axis-Aligned Bounding Box Параллелепипед - Rotating box + Rotating Box Вращающийся параллелепипед @@ -749,7 +769,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Использовать анимации сотворения заклинаний для магических предметов, по аналогии с заклинаниями.</p></body></html> - Use magic item animation + Use Magic Item Animation Анимации магических предметов @@ -757,7 +777,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Делает перемещение персонажей более плавным. Рекомендуется использовать совместно с настройкой "Поворот в направлении движения".</p></body></html> - Smooth movement + Smooth Movement Плавное перемещение @@ -765,7 +785,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Загружать KF-файлы и файлы скелетов из директории Animations</p></body></html> - Use additional animation sources + Use Additional Animation Sources Использовать источники анимаций @@ -773,7 +793,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Влияет на перемещение по диагонали и в сторону. Включение этой настройки делает перемещение более реалистичным.</p><p>При ее выключении тело персонажа всегда целиком направлено в направлении движения. Так как в игре нет анимаций для движения по диагонали, из-за этого получается скольжение.</p><p>Когда настройка включена, только ноги персонажа поворачиваются в направлении движения. Верхняя часть тела поворачивается частично. Голова всегда смотрит в одном направлении с камерой. В бою этот режим работает только для движения по диагонали, вне боя он также влияет на движение влево и вправо. Также во время плавания тело персонажа поворачивается в направлении движения.</p></body></html> - Turn to movement direction + Turn to Movement Direction Поворот в направлении движения @@ -781,7 +801,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Отображать зачехленное оружие (с колчанами и ножнами). Требует ресурсы из модов.</p></body></html> - Weapon sheathing + Weapon Sheathing Зачехление оружия @@ -789,7 +809,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Отображать щиты на спине. Требует ресурсы из модов.</p></body></html> - Shield sheathing + Shield Sheathing Зачехление щитов @@ -805,7 +825,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Когда настройка отключена, карты используются, только если они явно заданы в модели (.nif или .osg). Влияет на объекты.</p></body></html> - Auto use object normal maps + Auto Use Object Normal Maps Карты нормалей объектов @@ -813,7 +833,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>См. 'Карты нормалей объектов'. Влияет на ландшафт.</p></body></html> - Auto use terrain normal maps + Auto Use Terrain Normal Maps Карты нормалей ландшафта @@ -829,7 +849,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov (.osg-моделях, они не поддерживаются в .nif-моделях). Влияет на объекты.</p></body></html> - Auto use object specular maps + Auto Use Object Specular Maps Спекулярные карты объектов @@ -837,7 +857,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Если существует файл с маской, заданной с помощью настройки 'terrain specular map pattern' то он будет использоваться в качестве спекулярной карты. Он должен представлять собой текстуру, содержащую цвет слоя в RGB-канале и мощность спекулярного освещения в альфа-канале.</p></body></html> - Auto use terrain specular maps + Auto Use Terrain Specular Maps Спекулярные карты ландшафта @@ -851,7 +871,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov </p></body></html> - Bump/reflect map local lighting + Bump/Reflect Map Local Lighting Локальное освещение карт отражений @@ -859,7 +879,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Позволяет MSAA работать с моделями с альфа-тестированием, что позволяет улучшить отображение граней и избежать их пикселизации. Может снизить производительность.</p></body></html> - Use anti-alias alpha testing + Use Anti-Aliased Alpha Testing Сглаживание альфа-тестирования @@ -871,7 +891,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Эмуляция сохранения покрытия в MIP-картах, чтобы модели с альфа-тестированием не усыхали по мере удаления от камеры. Однако если MIP-карты на уровне самих текстур уже созданы с сохранением покрытия, покрытие будет расти по мере удаления от камеры, так что читайте инструкции к вашим модам.</p></body></html> - Adjust coverage for alpha test + Adjust Coverage for Alpha Test Покрытие альфа-тестирования @@ -889,7 +909,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov С этой настройкой плотность тумана для объекта зависит от расстояния от камеры до объекта (т.н. евклидово расстояние), благодаря чему туман выглядит не так искусственно, особенно при большом поле зрения.</p></body></html> - Radial fog + Radial Fog Радиальный туман @@ -897,7 +917,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Использование экспоненциальной формулы для тумана. По умолчанию используется линейный туман.</p></body></html> - Exponential fog + Exponential Fog Экспоненциальный туман @@ -905,7 +925,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Снизить видимость плоскости отсечения с помощью смешения объектов с небом.</p></body></html> - Sky blending + Sky Blending Смешивать с небом @@ -913,7 +933,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Множитель расстояния обзора, определяющий начало смешения.</p></body></html> - Sky blending start + Sky Blending Start Минимальное расстояние смешения @@ -921,7 +941,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Ландшафт - Viewing distance + Viewing Distance Расстояние обзора @@ -929,7 +949,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Определяет, насколько большим должен быть объект, чтобы отображаться на удаленном ландшафте. Для этого размер объекта делится на его расстояние до камеры, и результат сравнивается со значением этой настройки. Чем меньше значение, тем больше объектов будет отображаться в сцене.</p></body></html> - Object paging min size + Object Paging Min Size Минимальный размер склеиваемых объектов @@ -937,7 +957,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Когда включено, на удаленном ландшафте будут отображаться объекты, склеенные вместе с помощью специального алгоритма. Когда выключено, будет отображаться только сам ландшафт.</p></body></html> - Distant land + Distant Land Удаленный ландшафт @@ -945,7 +965,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Склеивать объекты в активных ячейках.</p></body></html> - Active grid object paging + Active Grid Object Paging Склеивание в активных ячейках @@ -953,7 +973,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Если эта настройка включена, модели с поддержкой переключения в зависимости от времени суток будут ее использовать.</p></body></html> - Day night switch nodes + Day Night Switch Nodes Переключение узлов дня и ночи @@ -965,7 +985,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Если эта настройка включена, то будет включена постобработка графики.</p></body></html> - Enable post processing + Enable Post Processing Включить постобработку @@ -973,7 +993,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Отрисовывать прозрачные объекты заново - со строгой границей прозрачности.</p></body></html> - Transparent postpass + Transparent Postpass Дополнительный проход для прозрачных объектов @@ -981,7 +1001,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Определяет, насколько быстро может меняться эффект аккомодации глаза от кадра к кадру. Более низкие значения означают более медленные преобразования.</p></body></html> - Auto exposure speed + Auto Exposure Speed Скорость автоматической экспозиции @@ -1025,7 +1045,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Эта настройка отвечает за масштабирование окон внутриигрового интерфейса. Значение 1.0 соответствует нормальному размеру интерфейса.</p></body></html> - GUI scaling factor + GUI Scaling Factor Масштаб интерфейса @@ -1033,7 +1053,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Когда настройка включена, в подсказке, всплывающей при наведении курсора на иконку магического эффекта, будет отображаться оставшаяся длительность магического эффекта или источника света.</p><p>По умолчанию настройка отключена.</p></body></html> - Show effect duration + Show Effect Duration Показывать длительность эффектов @@ -1041,15 +1061,15 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Если эта настройка включена, у тем диалогов будет другой цвет, если у вашего собеседника есть уникальная реплика по заданной теме, или же если вы уже видели текст темы. Цвета могут быть настроены через settings.cfg.</p><p>По умолчанию настройка отключена.</p></body></html> - Change dialogue topic color + Change Dialogue Topic Color Смена цвета тем для диалогов Size of characters in game texts. - Размер символов в текстах + Размер символов в текстах. - Font size + Font Size Размер шрифтов @@ -1057,7 +1077,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Включить возможность масштабирования на локальной и глобальной картах.</p></body></html> - Can zoom on maps + Can Zoom on Maps Включить масштабирование карты @@ -1069,7 +1089,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Если эта настройка включена, на всплывающих подсказках стрел и болтов будет показан их бонус к урону.</p><p>По умолчанию настройка выключена.</p></body></html> - Show projectile damage + Show Projectile Damage Показывать урон снарядов @@ -1077,7 +1097,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Если эта настройка включена, на всплывающих подсказках оружия ближнего боя будут показаны его дальность и скорость атаки.</p><p>По умолчанию настройка выключена.</p></body></html> - Show melee info + Show Melee Info Показывать информацию об оружии @@ -1085,11 +1105,11 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Растягивать фон меню, экранов загрузки и т.д., чтобы изображение соответствовало соотношению сторон выбранного разрешения экрана.</p></body></html> - Stretch menu background + Stretch Menu Background Растягивать фон меню - Show owned objects + Show Owned Objects Выделять объекты с владельцами @@ -1097,7 +1117,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Показывать шанс успеха в меню зачарования, или же нет.</p><p>По умолчанию настройка выключена.</p></body></html> - Show enchant chance + Show Enchant Chance Показывать шанс успеха зачарования @@ -1105,7 +1125,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Предотвращает экипировку торговцами предметов, которые им проданы.</p></body></html> - Merchant equipping fix + Merchant Equipping Fix Фикс экипировки предметов торговцами @@ -1125,7 +1145,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Эта настройка определяет, будет ли отображаться время с начала новой игры для выбранного сохранения в меню загрузки.</p></body></html> - Add "Time Played" to saves + Add "Time Played" to Saves Выводить "Время в игре" в сохранениях @@ -1141,7 +1161,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov TGA - Notify on saved screenshot + Notify on Saved Screenshot Уведомление при сохранении снимка @@ -1157,23 +1177,23 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Когда эта настройка включена, OpenMW будет управлять курсором мыши.</p><p>В “режиме обзора”, OpenMW будет захватывать курсор в центре экрана вне зависимости от значения этой настройки (потому что курсор всегда расположен по центру в окне OpenMW). Однако в режиме меню эта настройка определяет поведение выхода курсора за пределы окна OpenMW. Если настройка включена, курсор остановится на краю окна, предотвращая доступ к другим приложениям. Если выключена, курсор может свободно перемещаться по рабочему столу.</p><p>Эта настройка не применяется к экрану, на котором нажата клавиша “Escape” (там курсор никогда не захватывается). Вне зависимости от значения этой настройки “Alt-Tab” и некоторые другие зависимые от операционной системы комбинации клавиш могут быть использованы, чтобы вернуть управление курсором операционной системе. Эта настройка также взаимодействует с настройкой "minimize on focus loss", определяя, что именно считать потерей фокуса. На системах с двумя экранами может быть проще получить доступ ко второму экрану, если настройка выключена.</p><p>Замечание для разработчиков: лучше запускать игру с отключенной настройкой при запуске игры через отладчик, чтобы курсор не становился недоступен, когда игра останавливается на точке останова.</p></body></html> - Grab cursor + Grab Cursor Захватывать курсор - Skip menu and generate default character + Skip Menu and Generate Default Character Пропустить меню и создать персонажа по умолчанию - Start default character at + Start Default Character at Запустить с персонажем по умолчанию в локации - default cell + Default Cell по умолчанию - Run script after startup: + Run Script After Startup: Запустить скрипт после запуска: @@ -1185,7 +1205,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>Когда настройка включена, в фоне создаются навигационные меши для геометрии игрового мира. Когда она отключена, то используются только путевые точки из игровых файлов. На однопоточных системах включенный навигатор может значительно снизить скорость смены локаций. На многопоточных системах влияние на производительность незначительное. Также на многопоточных системах задержки могут зависеть от других настроек или производительности системы. Передвижение по открытому миру, а также вход в локации и выход из них могут привести к обновлению кэша. Персонажи могут не иметь возможности найти путь вокруг них, пока обновление не будет завершено. Можете попробовать отключить навигатор, если вы предпочитаете старомодный ИИ, который не знает, как до вас добраться, пока вы стоите за камнем и колдуете огненные стрелы.</p></body></html> - Use navigation mesh for pathfinding + Use Navigation Mesh for Pathfinding Использовать навигационную сетку @@ -1193,7 +1213,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>В режиме вида от третьего лица камера перемещается согласно анимациям движения персонажа игрока. Включение этой настройки отключает это поведение, убирая зависимость движения персонажа игрока от состояния его анимаций. Такое поведение было в OpenMW версии 0.48 и ранее.</p></body></html> - Player movement ignores animation + Player Movement Ignores Animation Движение игрока обходит анимации @@ -1201,7 +1221,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Игровой процесс - Always allow actors to follow over water + Always Allow Actors to Follow over Water Позволить всем следовать по воде @@ -1209,11 +1229,11 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Если настройка включена, требуются магические метательные снаряды, чтобы обойти сопротивление обычному оружию или уязвимость к нему. Если отключена, то требуются магические снаряды или магическое оружие дальнего боя.</p></body></html> - Only magical ammo bypass resistance + Only Magical Ammo Bypass Resistance Только снаряды обходят сопротивление - Graphic herbalism + Graphic Herbalism Графический гербализм @@ -1221,31 +1241,31 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Разрешает зачарованному оружию без флага Магическое обходить сопротивление обычному оружию, как в Morrowind.</p></body></html> - Trainers choose offered skills by base value + Trainers Choose Offered Skills by Base Value Учителя выбирают базовые навыки - Steal from knocked out actors in combat + Steal from Knocked out Actors in Combat Кража у персонажей без сознания в бою - Factor strength into hand-to-hand combat + Factor Strength into Hand-to-Hand Combat Учет Силы в рукопашном бою - Background physics threads + Background Physics Threads Количество фоновых потоков для физики - Actor collision shape type + Actor Collision Shape Type Форма объекта столкновений для персонажей - Soft particles + Soft Particles Мягкие частицы - Weather particle occlusion + Weather Particle Occlusion Окклюзия погодных частиц @@ -1257,23 +1277,23 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Тени - bounds - по границам объектов + Bounds + По границам объектов - primitives - по примитивам + Primitives + По примитивам - none - отключено + None + Отключено <html><head/><body><p>Type of "compute scene bounds" computation method to be used. Bounds (default) for good balance between performance and shadow quality, primitives for better looking shadows or none for no computation.</p></body></html> <html><head/><body><p>Определяет используемый тип вычисления границ сцены. Вычисление по границам объектов (по умолчанию) дает хороший баланс между производительностью и качеством теней, вычисление по примитивам улучшает качество теней, а еще можно полностью отключить вычисление.</p></body></html> - Shadow planes computation method + Shadow Planes Computation Method Метод определения границ для теней @@ -1289,7 +1309,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Включить тени от NPC и существ, кроме персонажа игрока. Может незначительно снизить производительность.</p></body></html> - Enable actor shadows + Enable Actor Shadows Включить тени от персонажей @@ -1313,7 +1333,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Множитель от расстояния выше, при котором тени начнут плавно угасать.</p></body></html> - Fade start multiplier + Fade Start Multiplier Множитель начала угасания теней @@ -1321,7 +1341,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Включить тени от персонажа игрока. Может очень незначительно снизить производительность.</p></body></html> - Enable player shadows + Enable Player Shadows Включить тени от персонажа игрока @@ -1329,7 +1349,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Разрешение отдельных теневых карт. Увеличение значения улучшает качество теней, но может незначительно снизить производительность.</p></body></html> - Shadow map resolution + Shadow Map Resolution Разрешение теневой карты @@ -1337,7 +1357,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Расстояние от камеры, при достижении которого тени полностью исчезают.</p></body></html> - Shadow distance limit: + Shadow Distance Limit: Максимальная дистанция теней: @@ -1345,7 +1365,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Включить тени от объектов. Может значительно снизить производительность.</p></body></html> - Enable object shadows + Enable Object Shadows Включить тени от объектов @@ -1353,7 +1373,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Из-за ограничений в файлах Morrowind, только персонажи могут отбрасывать тени в интерьерах, что может раздражать.</p><p>Не имеет эффекта, если тени от персонажей отключены.</p></body></html> - Enable indoor shadows + Enable Indoor Shadows Включить тени в интерьерах @@ -1361,7 +1381,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Включить тени от ландшафта (включая удаленный ландшафт). Может значительно снизить производительность и качество теней.</p></body></html> - Enable terrain shadows + Enable Terrain Shadows Включить тени от ландшафта @@ -1369,15 +1389,15 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Освещение - Lighting method + Lighting Method Способ освещения - Audio device + Audio Device Звуковое устройство - HRTF profile + HRTF Profile Профиль HRTF @@ -1389,11 +1409,11 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Прицел - Tooltip and crosshair + Tooltip and Crosshair Всплывающая подсказка и прицел - Maximum quicksaves + Maximum Quicksaves Количество быстрых сохранений @@ -1401,7 +1421,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Снимки экрана - Screenshot format + Screenshot Format Формат снимков экрана @@ -1409,7 +1429,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Максимальное расстояние, на котором будут отображаться источники света (во внутриигровых единицах измерения).</p><p>Если 0, то расстояние не ограничено.</p></body></html> - Lights maximum distance + Maximum Light Distance Дальность отображения источников света @@ -1417,17 +1437,13 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Максимальное количество источников света для каждого объекта.</p><p>Низкие числа (близкие к значению по умолчанию) приводят к резким перепадам освещения, как при устаревшем методе освещения.</p></body></html> - Max light sources + Max Lights Макс. кол-во источников света <html><head/><body><p>Fraction of maximum distance at which lights will start to fade.</p><p>Set this to a low value for slower transitions or a high value for quicker transitions.</p></body></html> <html><head/><body><p>Доля расстояния (относительно дальности отображения источников света), на которой свет начинает затухать.</p><p>Низкие значения ведут к плавному затуханию, высокие - к резкому.</p></body></html> - - Lights fade multiplier - Множитель начала затухания - <html><head/><body><p>Set the internal handling of light sources.</p> <p> "Legacy" always uses 8 lights per object and provides a lighting closest to an original game.</p> @@ -1451,7 +1467,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Множитель размера ограничивающей сферы источников света.</p><p>Высокие значения делают затухание света плавнее, но требуют более высокого максимального количества источников света.</p><p>Настройка не влияет на уровень освещения или мощность источников света.</p></body></html> - Lights bounding sphere multiplier + Bounding Sphere Multiplier Множитель размера ограничивающей сферы @@ -1459,7 +1475,15 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Минимальный уровень фонового освещения в помещениях.</p><p>Увеличьте значение, если помещения в игре кажутся слишком темными.</p></body></html> - Lights minimum interior brightness + In third-person view, use the camera as the sound listener instead of the player character. + Использовать в виде от третьего лица положение камеры, а не персонажа игрока для прослушивания звуков. + + + Use the Camera as the Sound Listener + Использовать камеру как слушателя + + + Minimum Interior Brightness Минимальный уровень освещения в помещениях diff --git a/files/lang/launcher_sv.ts b/files/lang/launcher_sv.ts new file mode 100644 index 0000000000..bf7acfd370 --- /dev/null +++ b/files/lang/launcher_sv.ts @@ -0,0 +1,1502 @@ + + + + + DataFilesPage + + Content Files + Innehållsfiler + + + Data Directories + Datakataloger + + + Scan directories for likely data directories and append them at the end of the list. + Skanna kataloger för troliga datakataloger och lägg till dem i slutet på listan. + + + Append + Lägg till + + + Scan directories for likely data directories and insert them above the selected position + Skanna kataloger för troliga datakataloger och lägg till dem ovanför den markerade positionen + + + Insert Above + Lägg till ovanför + + + Move selected directory one position up + Flytta markerad katalog en position upp + + + Move Up + Flytta upp + + + Move selected directory one position down + Flytta markerad katalog en position ner + + + Move Down + Flytta ner + + + Remove selected directory + Ta bort markerad katalog + + + Remove + Ta bort + + + Archive Files + Arkivfiler + + + Move selected archive one position up + Flytta markerat arkiv en position upp + + + Move selected archive one position down + Flytta markerat arkiv en position ner + + + Navigation Mesh Cache + Cache för navigeringsmesh + + + Generate navigation mesh cache for all content. Will be used by the engine to make cell loading faster. + Generera navigeringsmesh för allt innehåll. Kommer användas av motorn för att ladda celler snabbare. + + + Update + Uppdatera + + + Cancel navigation mesh generation. Already processed data will be saved. + Avbryt generering av navigationsmesh. Redan processad data kommer sparas. + + + Cancel + Avbryt + + + MiB + MiB + + + Content List + Innehållslista + + + Select a content list + Välj en innehållslista + + + New Content List + Ny innehållslista + + + &New Content List + &Ny innehållslista + + + Clone Content List + Klona innehållslista + + + Delete Content List + Radera innehållslista + + + Ctrl+N + Ctrl+N + + + Ctrl+G + Ctrl+G + + + Ctrl+D + Ctrl+D + + + Check Selection + Kryssa markering + + + Uncheck Selection + Avkryssa markering + + + Refresh Data Files + Uppdatera datafiler + + + Ctrl+R + Ctrl+R + + + <html><head/><body><p>Note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + <html><head/><body><p>Notera: innehållsfiler som inte är del av aktuell innehållslista är <span style=" font-style:italic;font-weight: bold">markerade</span></p></body></html> + + + <html><head/><body><p>Note: directories that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + <html><head/><body><p>Notera: kataloger som inte är del av aktuell innehållslista är <span style=" font-style:italic;font-weight: bold">markerade</span></p></body></html> + + + <html><head/><body><p>Note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + <html><head/><body><p>Notera: arkiv som inte är del av aktuell innehållslista är <span style=" font-style:italic;font-weight: bold">markerade</span></p></body></html> + + + Remove Unused Tiles + Ta bort oanvända celler + + + Max Size + Maximal storlek + + + + GraphicsPage + + 0 + 0 + + + 2 + 2 + + + 4 + 4 + + + 8 + 8 + + + 16 + 16 + + + Custom: + Egen: + + + Standard: + Standard: + + + Fullscreen + Helskärm + + + Windowed Fullscreen + Helskärm i fönsterläge + + + Windowed + Fönster + + + Disabled + Inaktiverad + + + Enabled + Aktiverad + + + Adaptive + Adaptiv + + + FPS + FPS + + + × + × + + + Screen + Skärm + + + Resolution + Upplösning + + + Window Mode + Fönsterläge + + + Framerate Limit + Gränsvärde bilduppdateringsfrekvens + + + Window Border + Fönster, ram + + + Anti-Aliasing + Kantutjämning + + + Vertical Synchronization + Vertikal synkronisering + + + + ImportPage + + Form + Can be translated in different ways depending on context. Will return to later. + + + + Morrowind Installation Wizard + Installationsguide för Morrowind + + + Run &Installation Wizard + Kör &Installationsguide + + + Morrowind Settings Importer + Inställningsimporterare för Morrowind + + + Browse... + Bläddra... + + + Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, +so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar +to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. + Fonter som kommer med ordinarie spelmotor är suddiga med gränssnittsuppskalning och stödjer bara ett litet antal tecken, +så OpenMW tillhandahåller andra fonter för att undvika dessa problem. Dessa fonter använder TrueType-teknologi och är ganska lika +de ordinarie fonterna i Morrowind. Bocka denna ruta om du ändå föredrar ordinarie fonter istället för OpenMWs, alternativt om du använder egna bitmapfonter. + + + Run &Settings Importer + Kör &Inställningsimporterare + + + File to Import Settings From: + Fil att importera inställningar från: + + + Import Bitmap Fonts + Importera bitmapfonter + + + Import Add-on and Plugin Selection + Importera tillägg- och pluginmarkeringar + + + + Launcher::DataFilesPage + + English + Engelska + + + French + Franska + + + German + Tyska + + + Italian + Italienska + + + Polish + Polska + + + Russian + Ryska + + + Spanish + Spanska + + + New Content List + Ny innehållslista + + + Content List name: + Namn på innehållslista: + + + Clone Content List + Klona innehållslista + + + Select Directory + Välj katalog + + + Delete Content List + Radera innehållslista + + + Are you sure you want to delete <b>%1</b>? + Är du säker på att du vill radera <b>%1</b>? + + + Delete + Radera + + + Contains content file(s) + Innehåller innehållsfil(er) + + + Will be added to the current profile + Kommer läggas till nuvarande profil + + + &Check Selected + &Kryssa markerade + + + &Uncheck Selected + &Avkryssa markerade + + + Resolved as %1 + Löst som %1 + + + This is the data-local directory and cannot be disabled + Det här är den data-lokala katalogen och kan inte inaktiveras + + + This directory is part of OpenMW and cannot be disabled + Denna katalog är en del av OpenMW och kan inte inaktiveras + + + This directory is enabled in an openmw.cfg other than the user one + Denna katalog är aktiverad i en annan openmw.cfg än användarens + + + This archive is enabled in an openmw.cfg other than the user one + Detta arkiv är aktiverat i en annan openmw.cfg än användarens + + + + Launcher::GraphicsPage + + Error receiving number of screens + Det gick inte att ta emot antalet skärmar + + + <br><b>SDL_GetNumVideoDisplays failed:</b><br><br> + <br><b>SDL_GetNumVideoDisplays misslyckades:</b><br><br> + + + Screen + Skärm + + + Error receiving resolutions + Det gick inte att ta emot upplösningar + + + <br><b>SDL_GetNumDisplayModes failed:</b><br><br> + <br><b>SDL_GetNumDisplayModes misslyckades:</b><br><br> + + + <br><b>SDL_GetDisplayMode failed:</b><br><br> + <br><b>SDL_GetDisplayMode misslyckades:</b><br><br> + + + + Launcher::ImportPage + + Error writing OpenMW configuration file + Det gick inte att skriva en OpenMW-konfigurationsfil + + + Morrowind configuration file (*.ini) + Morrowind konfigurationsfil (*.ini) + + + Importer finished + Importeraren klar + + + Failed to import settings from INI file. + Misslyckades att importera inställningar från INI-fil. + + + <html><head/><body><p><b>Could not open or create %1 for writing </b></p><p>Please make sure you have the right permissions and try again.</p></body></html> + <html><head/><body><p><b>Kunde inte öppna eller skapa %1 för att skriva </b></p><p>Kontrollera att du har rätt behörigheter och försök igen.</p></body></html> + + + + Launcher::MainDialog + + Close + Stäng + + + Launch OpenMW + Starta OpenMW + + + Help + Hjälp + + + Error opening OpenMW configuration file + Fel när OpenMW-konfigurationsfil skulle öppnas + + + First run + Första körning + + + Run &Installation Wizard + Kör &Installationsguide + + + Skip + Hoppa över + + + OpenMW %1 release + OpenMW version %1 + + + OpenMW development (%1) + OpenMW utvecklarversion (%1) + + + Compiled on %1 %2 + Kompilerad den %1 %2 + + + Error detecting Morrowind installation + Kunde inte hitta Morrowindinstallation + + + Run &Installation Wizard... + Kör &Installationsguide... + + + Error reading OpenMW configuration files + Fel när OpenMW-konfigurationsfil skulle läsas + + + Error writing OpenMW configuration file + Fel när OpenMW-konfigurationsfil skulle skrivas> + + + Error writing user settings file + Fel när användarinställningsfil skulle skrivas + + + Error writing Launcher configuration file + Fel när Startarens-konfigurationsfil skulle skrivas + + + No game file selected + Ingen spelfil vald + + + <html><head/><body><p><b>Welcome to OpenMW!</b></p><p>It is recommended to run the Installation Wizard.</p><p>The Wizard will let you select an existing Morrowind installation, or install Morrowind for OpenMW to use.</p></body></html> + <html><head/><body><p><b>Välkommen till OpenMW!</b></p><p>Det är rekommenderat att du kör Installationsguiden.</p><p>Installationsguiden låter dig välja en befintlig Morrowindinstallation eller installera Morrowind för OpenMW.</p></body></html> + + + <br><b>Could not open %0 for reading:</b><br><br>%1<br><br>Please make sure you have the right permissions and try again.<br> + <br><b>Kunde inte öppna %0 för läsning:</b><br><br>%1<br><br>Se till att du har rätt behörigheter och försök igen.<br> + + + <br><b>Could not open %0 for reading</b><br><br>Please make sure you have the right permissions and try again.<br> + <br><b>Kunde inte öppna %0 för läsning</b><br><br>Se till att du har rätt behörigheter och försök igen.<br> + + + <br><b>Could not find the Data Files location</b><br><br>The directory containing the data files was not found. + <br><b>Kunde inte hitta Data Files-platsen</b><br><br>Katalogen som innehåller datafilerna hittades inte. + + + <br>The problem may be due to an incomplete installation of OpenMW.<br>Reinstalling OpenMW may resolve the problem.<br> + <br>Problemet kan bero på en ofullständig installation av OpenMW.<br>Ominstallation av OpenMW kan lösa problemet.<br> + + + <br><b>Could not open or create %0 for writing</b><br><br>Please make sure you have the right permissions and try again.<br> + <br><b>Kunde inte öppna eller skapa %0 för att skriva</b><br><br>Se till att du har rätt behörigheter och försök igen.<br> + + + <br><b>You do not have a game file selected.</b><br><br>OpenMW will not start without a game file selected.<br> + <br><b>Du har ingen spelfil markerad.</b><br><br>OpenMW kan inte starta utan en spelfil markerad.<br> + + + Error creating OpenMW configuration directory: code %0 + Kunde inte skapa konfigurationskatalog för OpenMW: kod %0 + + + <br><b>Could not create directory %0</b><br><br>%1<br> + <br><b>Kunde inte skapa katalog %0</b><br><br>%1<br> + + + + Launcher::SettingsPage + + Text file (*.txt) + Textfil (*.txt) + + + + MainWindow + + OpenMW Launcher + OpenMW Startare + + + OpenMW version + OpenMW version + + + toolBar + toolBar + + + Data Files + Datafiler + + + Allows to setup data files and directories + Låter dig ställa in datafiler och kataloger + + + Settings + Inställningar + + + Allows to tweak engine settings + Låter dig justera spelmotorinställningar + + + Import + Importera + + + Allows to import data from original engine + Låter dig importera data från originalmotorn + + + Display + Bild + + + Allows to change display settings + Låter dig ändra bildinställningar + + + + QObject + + Select configuration file + Välj konfigurationsfil + + + Select script file + Välj skriptfil + + + + SelectSubdirs + + Select directories you wish to add + Välj kataloger du vill lägga till + + + + SettingsPage + + <html><head/><body><p>Make disposition change of merchants caused by trading permanent.</p></body></html> + <html><head/><body><p>Gör dispositionsändring av handlare orsakad av handel permanent.</p></body></html> + + + <html><head/><body><p>Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.</p></body></html> + <html><head/><body><p>Gör att följare och eskorter till spelaren själva påbörjar strid med fiender som påbörjat strid med dem eller spelaren. Annars kommer de vänta tills fienden eller spelaren har attackerat dem först.</p></body></html> + + + <html><head/><body><p>Make Damage Fatigue magic effect uncapped like Drain Fatigue effect.</p><p>This means that unlike Morrowind you will be able to knock down actors using this effect.</p></body></html> + <html><head/><body><p>Gör att "Damage Fatigue"-magieffekten blir obegränsad såsom "Drain Fatigue"-effekten.</p><p>Det innebär att du, till skillnad från i Morrowind, kommer kunna slå ner figurer till marken med denna effekt.</p></body></html> + + + Uncapped Damage Fatigue + Obegränsad "Damage Fatigue" + + + <html><head/><body><p>Stops combat with NPCs affected by Calm spells every frame -- like in Morrowind without the MCP.</p></body></html> + <html><head/><body><p>Avbryter strid med icke-spelbara figurer påverkade av "Calm"-besvjärjelser varje bildruta – såsom i Morrowind utan MCP.</p></body></html> + + + <html><head/><body><p>Make the value of filled soul gems dependent only on soul magnitude.</p></body></html> + <html><head/><body><p>Gör värderingen av fyllda "Soulgems" endast baserad på själens magnitud.</p></body></html> + + + <html><head/><body><p>Makes player swim a bit upward from the line of sight. Applies only in third person mode. Intended to make simpler swimming without diving.</p></body></html> + <html><head/><body><p>Får spelaren att simma något uppåt från siktlinjen. Endast applicerat på tredjepersonsperspektivet. Avsett att göra simning enklare.</p></body></html> + + + <html><head/><body><p>Make stealing items from NPCs that were knocked down possible during combat.</p></body></html> + <html><head/><body><p>Gör det möjligt att stjäla föremål från icke-spelbara figurer som är avsvimmade.</p></body></html> + + + <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>When enabled, a navigation mesh is built in the background for world geometry to be used for pathfinding. When disabled only the path grid is used to build paths. Single-core CPU systems may have a big performance impact on existing interior location and moving across the exterior world. May slightly affect performance on multi-core CPU systems. Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. Moving across external world, entering/exiting location produce nav mesh update. NPC and creatures may not be able to find path before nav mesh is built around them. Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and cast a firebolt.</p></body></html> + <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>Om aktiv kommer en en navigeringsmesh av världsgeometrin byggas i bakgrunden som används för pathfinding. Om inaktiv kommer endast pathgrid användas för att bygga vägar. Enkelkärnade processorer kan få kraftigt försämrad prestanda. Kan påverka prestandan på flerkärniga processorer något. Flerkärniga processorer kan ha olika fördröjning för att uppdatera navigeringsmesh. Förflyttning mellan externa världar och att gå in eller ut ur en plats producerar en uppdatering av navigeringsmesh. Icke-spelbara figurer kan inte hitta vägar innan navigeringsmesh har skapats runt om dem. Testa att inaktivera denna funktion om du vill ha en mer gammaldags AI som inte vet var den ska gå när du står bakom den där stenen och skjuter ett eldklot.</p></body></html> + + + <html><head/><body><p>If enabled NPCs apply evasion maneuver to avoid collisions with others.</p></body></html> + <html><head/><body><p>Om aktiverat kommer icke-spelbara figurer göra undanmanövrar för att undvika kollisioner med andra.</p></body></html> + + + <html><head/><body><p>Don't use race weight in NPC movement speed calculations.</p></body></html> + <html><head/><body><p>Ta inte med varelsers vikt i beräkningen av hastighet.</p></body></html> + + + <html><head/><body><p>If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. In this case we have to increment death counter and run disposed actor's script instantly.</p><p>If this setting is false, player has to wait until end of death animation in all cases. Makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation.</p></body></html> + <html><head/><body><p>Om denna inställning är aktiv tillåts spelaren plundra figurer (exempelvis tillkallade varelser) under deras dödsanimation, om de inte är i strid.</p><p>Om inställningen är inaktiv måste spelaren vänta tills dödsanimationen är slut. Detta gör det mycket svårare att exploatera tillkallade varelser (exempelvis plundra Draemoror eller Golden Saints för att få dyrbara vapen). Inställningen är i konflikt med skyltdocks-moddar som använder SkipAnim för att förhindra avslutning av dödsanimationer.</p></body></html> + + + <html><head/><body><p>Makes unarmed creature attacks able to reduce armor condition, just as attacks from NPCs and armed creatures.</p></body></html> + <html><head/><body><p>Gör att obeväpnade varelseattacker kan reducera rustningars skick, precis som attacker från icke-spelbara figurer och beväpnade varelser.</p></body></html> + + + Off + Av + + + <html><head/><body><p>How many threads will be spawned to compute physics update in the background. A value of 0 means that the update will be performed in the main thread.</p><p>A value greater than 1 requires the Bullet library be compiled with multithreading support.</p></body></html> + <html><head/><body><p>Antalet exekveringstrådar som kommer användas för att beräkna fysikuppdateringar i bakgrunden.</p><p>Ett värde högre än 1 kräver att Bullet-biblioteket är kompilerat med multithreading-stöd.</p></body></html> + + + Cylinder + Cylinder + + + Visuals + Visuellt + + + Animations + Animationer + + + <html><head/><body><p>Use casting animations for magic items, just as for spells.</p></body></html> + <html><head/><body><p>Använd animationer för magiska föremål, precis som för besvärjelser.</p></body></html> + + + <html><head/><body><p>Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.</p></body></html> + <html><head/><body><p>Gör spelarens och icke-spelbara figurers rörelser mjukare. Rekommenderas att användas tillsammans med "vänd mot rörelseriktningen" aktiverad.</p></body></html> + + + <html><head/><body><p>Load per-group KF-files and skeleton files from Animations folder</p></body></html> + <html><head/><body><p>Ladda per-grupp KF-filer och skelettfiler från Animations-katalogen</p></body></html> + + + <html><head/><body><p>Affects side and diagonal movement. Enabling this setting makes movement more realistic.</p><p>If disabled then the whole character's body is pointed to the direction of view. Diagonal movement has no special animation and causes sliding.</p><p>If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it changes straight right and straight left movement as well. Also turns the whole body up or down when swimming according to the movement direction.</p></body></html> + <html><head/><body><p>Påverkar sido- och diagonalförflyttningar. Förflyttningar blir mer realistiska om aktiv.</p><p>Om aktiv kommer spelarrollfiguren vända underkroppen i rikting mot förflyttningen. Överkroppen vänds delvis. Huvudet pekar alltid dit kameran ser. I stidsläge fungerar det bara för diagonal förflyttning. Utanför strid ändrar inställningen förflyttningar rakt höger och vänster också. Inställningen vänder också hela kroppen upp eller ner vid simning enligt förflyttningsriktningen.</p></body></html> + + + <html><head/><body><p>Render holstered weapons (with quivers and scabbards), requires modded assets.</p></body></html> + <html><head/><body><p>Rendera hölstrade vapen (med koger och vapenslidor), kräver moddat innehåll.</p></body></html> + + + <html><head/><body><p>Render holstered shield, requires modded assets.</p></body></html> + <html><head/><body><p>Rendera hölstrade sköldar, kräver moddat innehåll.</p></body></html> + + + <html><head/><body><p>In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.</p></body></html> + <html><head/><body><p>I tredjepersonsperspektiv kommer kameran svänga efter spelarens förflyttningsanimationer. Detta var det förvalda beteendet i OpenMW 0.48.0 och tidigare.</p></body></html> + + + Shaders + Shader + + + <html><head/><body><p>If this option is enabled, normal maps are automatically recognized and used if they are named appropriately + (see 'normal map pattern', e.g. for a base texture foo.dds, the normal map texture would have to be named foo_n.dds). + If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file (.nif or .osg file). Affects objects.</p></body></html> + <html><head/><body><p>Om funktionen är aktiv kommer normalkartor (normal maps) att hittas och användas automatiskt om de har korrekt namn + (se 'normal map pattern', t.ex. för en bastextur foo.dds ska normalkartan heta foo_n.dds). + Om funktionen är inaktiverad kommer normalkartor bara användas om texturerna är explicit listade i 3D-modell-filen (.nif eller .osg fil). Påverkar objekt.</p></body></html> + + + <html><head/><body><p>See 'auto use object normal maps'. Affects terrain.</p></body></html> + <html><head/><body><p>Se 'autoanvänd normalkartor (normal maps) på objekt'. Påverkar terräng.</p></body></html> + + + <html><head/><body><p>If this option is enabled, specular maps are automatically recognized and used if they are named appropriately + (see 'specular map pattern', e.g. for a base texture foo.dds, + the specular map texture would have to be named foo_spec.dds). + If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file + (.osg file, not supported in .nif files). Affects objects.</p></body></html> + <html><head/><body><p>Om den här funktionen är aktiverad kommer spekularitetskartor (specular maps) att hittas och användas + (see 'specular map pattern', t.ex. för en bastextur foo.dds, + ska spekularitetskartan heta foo_spec.dds). + Om funktionen är inaktiverad kommer normalkartor bara användas om texturerna är explicit listade i 3D-modell-filen + (.nif eller .osg fil). Påverkar objekt.</p></body></html> + + + <html><head/><body><p>If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. The texture must contain the layer colour in the RGB channel (as usual), and a specular multiplier in the alpha channel.</p></body></html> + <html><head/><body><p>Om en fil med mönstret 'terrain specular map pattern' finns, använd den filen som en 'diffuse specular'-karta. Texturen måste innehålla färglagren i RGB-kanalerna (som vanligt) och spekularitet i alfakanalen.</p></body></html> + + + <html><head/><body><p>Normally environment map reflections aren't affected by lighting, which makes environment-mapped (and thus bump-mapped objects) glow in the dark. + Morrowind Code Patch includes an option to remedy that by doing environment-mapping before applying lighting, this is the equivalent of that option. + Affected objects will use shaders. + </p></body></html> + <html><head/><body><p>Normalt sett påverkas omgivningskartors reflektioner inte av ljus, vilket gör att omgivningskartor (och således så kallade bump mappade objekt) glöder i mörkret. + Morrowind Code Patch inkluderar en inställning att kringå detta genom att lägga omgivningskartor före ljussättningen. Det här är motsvarigheten till den inställningen. + Påverkade objekt kommer använda shaders. + </p></body></html> + + + <html><head/><body><p>Allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. Can negatively impact performance.</p></body></html> + <html><head/><body><p>Gör att MSAA fungerar med alfa-testade modeller, vilket ger snyggare kanter utan synliga pixlar. Kan påverka prestandan negativt.</p></body></html> + + + <html><head/><body><p>Enables soft particles for particle effects. This technique softens the intersection between individual particles and other opaque geometry by blending between them.</p></body></html> + <html><head/><body><p>Aktiverar mjuka partiklar på partikeleffekter. Denna teknik mjukar upp övergången mellan individuella partiklar och annan ogenomskinlig geometri genom att smälta samman dem.</p></body></html> + + + <html><head/><body><p>Simulate coverage-preserving mipmaps to prevent alpha-tested meshes shrinking as they get further away. Will cause meshes whose textures have coverage-preserving mipmaps to grow, though, so refer to mod installation instructions for how to set this.</p></body></html> + <html><head/><body><p>Simulerar mip maps med coverage-preserving för att förhindra att alfa-testade modeller krymper när de kommer längre bort. Kommer göra att dessa modeller växer istället; se instruktioner i modinstallationen för hur detta ska ställas in.</p></body></html> + + + <html><head/><body><p>EXPERIMENTAL: Stop rain and snow from falling through overhangs and roofs.</p></body></html> + <html><head/><body><p>EXPERIMENTELLT: Förhindra att regn och snö faller genom tak och överhäng.</p></body></html> + + + Fog + Dimma + + + <html><head/><body><p>By default, the fog becomes thicker proportionally to your distance from the clipping plane set at the clipping distance, which causes distortion at the edges of the screen. + This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV.</p></body></html> + <html><head/><body><p>Som standard blir dimman tätare proportionellt med ditt avstånd till avklippningsdistansen, vilket ger distortion vid skärmens kanter. + Denna inställning gör att dimman använder det faktiska ögonpunktsavståndet (så kallat euklidiskt avstånd) för att beräkna dimman, vilket får dimman att se mindre artificiell ut, särskilt vid hög FoV.</p></body></html> + + + <html><head/><body><p>Use exponential fog formula. By default, linear fog is used.</p></body></html> + <html><head/><body><p>Använd exponentiell formel för dimma. Som standard används linjär dimma.</p></body></html> + + + <html><head/><body><p>Reduce visibility of clipping plane by blending objects with the sky.</p></body></html> + <html><head/><body><p>Reducera synligheten av avklippsplanet genom att smälta samman objekt med himmelen.</p></body></html> + + + <html><head/><body><p>The fraction of the maximum distance at which blending with the sky starts.</p></body></html> + <html><head/><body><p>Fraktionen av det maximala avståndet där utsmetning mellan himmel och horisont påbörjas.</p></body></html> + + + Terrain + Terräng + + + <html><head/><body><p>Controls how large an object must be to be visible in the scene. The object’s size is divided by its distance to the camera and the result of the division is compared with this value. The smaller this value is, the more objects you will see in the scene.</p></body></html> + <html><head/><body><p>Bestämmer hur stort ett objekt måste vara för att vara synligt på skärmen. Objektets storlek divideras med sitt avstånd till kameran. Resultatet av denna division jämförs med detta värde. Ju mindre värdet är, desto fler objekt kommer du se i scenen.</p></body></html> + + + <html><head/><body><p>If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.</p></body></html> + <html><head/><body><p>Om aktiverat används paging och LOD-algoritmer för att rita upp all terräng. Om inaktiverat visas endast terrängen i den laddade cellen.</p></body></html> + + + <html><head/><body><p>Use object paging for active cells grid.</p></body></html> + <html><head/><body><p>Använd objekt-paging för aktiva cellers rutnät.</p></body></html> + + + <html><head/><body><p>If this setting is true, supporting models will make use of day night switch nodes.</p></body></html> + <html><head/><body><p>Om den här inställningen är aktiv kommer modeller med stöd för det att använda dag- och natt-bytesnoder.</p></body></html> + + + Post Processing + Efterbehandling + + + <html><head/><body><p>If this setting is true, post processing will be enabled.</p></body></html> + <html><head/><body><p>Om denna inställning är på kommer efterbehandling (post processing) att vara aktiverat.</p></body></html> + + + <html><head/><body><p>Re-render transparent objects with forced alpha clipping.</p></body></html> + <html><head/><body><p>Återrendera transparenta objekt med forcerad alpha clipping.</p></body></html> + + + <html><head/><body><p>Controls how much eye adaptation can change from frame to frame. Smaller values makes for slower transitions.</p></body></html> + <html><head/><body><p>Bestämmer hur mycket ögonanpassningen kan förändras från bildruta till bildruta. Lägre värden ger långsammare övergångar.</p></body></html> + + + Audio + Ljud + + + Select your preferred audio device. + Välj din föredragna ljudenhet. + + + Default + Förvalt + + + This setting controls HRTF, which simulates 3D sound on stereo systems. + Denna inställning kontrollerar HRTF, vilket simulerar 3D-ljud på stereosystem. + + + HRTF + HRTF + + + Automatic + Automatisk + + + On + + + + Select your preferred HRTF profile. + Välj din föredragna HRTF-profil. + + + Interface + Gränssnitt + + + <html><head/><body><p>This setting scales GUI windows. A value of 1.0 results in the normal scale.</p></body></html> + <html><head/><body><p>Denna inställning skalar grafiska fönster i gränssnittet. Ett värde på 1.0 ger den normala skalan.</p></body></html> + + + <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> + <html><head/><body><p>Visar den återstående tiden för magiska effekter och ljus om denna inställning är på. Den återstående tiden visas som en inforuta när muspekaren befinner sig över den magiska effekten. </p><p>Förvalt är av.</p></body></html> + + + <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> + <html><head/><body><p>Om denna inställning är på kommer dialogämnen ha en annan färg om ämnet är specifikt till den icke-spelbara figur du pratar med eller om ämnet redan har setts. Färger kan ändras i settings.cfg.</p><p>Förvalt är av.</p></body></html> + + + Size of characters in game texts. + Storlek på tecken i speltext. + + + <html><head/><body><p>Enable zooming on local and global maps.</p></body></html> + <html><head/><body><p>Aktivera zoomning på lokala och globala kartor.</p></body></html> + + + <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> + <html><head/><body><p>Om denna inställning är på kommer behållare som stödjer grafisk örtplockning (graphic herbalism) använda den funktionen istället för att öppna menyn.</p></body></html> + + + <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> + <html><head/><body><p>Om denna inställning är på kommer skadebonus från pilar visas på föremålens inforuta.</p><p>Förvalt är av.</p></body></html> + + + <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> + <html><head/><body><p>Om denna inställning är på kommer närstridsvapens räckvidd och hastighet att visas på föremåls inforuta.</p><p>Förvalt är av.</p></body></html> + + + <html><head/><body><p>Stretch menus, load screens, etc. to the window aspect ratio.</p></body></html> + <html><head/><body><p>Sträck ut menyer, laddskärmar o.s.v. till fönstrets aspektratio.</p></body></html> + + + <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> + <html><head/><body><p>Huruvida chansen att lyckas kommer visas i förtrollningsmenyn.</p><p>Förvalt är av.</p></body></html> + + + <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> + <html><head/><body><p>Förhindrar att handlare tar på sig föremål som säljs till dem.</p></body></html> + + + <html><head/><body><p>Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.</p></body></html> + <html><head/><body><p>Tränare väljer nu endast de färdigheter som kan tränas genom att använda deras basfärdighetsvärde, vilket tillåter förbättring av merkantil utan att göra merkantil till en erbjuden färdighet.</p></body></html> + + + Miscellaneous + Diverse + + + Saves + Sparfiler + + + <html><head/><body><p>This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.</p></body></html> + <html><head/><body><p>Denna inställning avgör huruvida mängden tid spelaren har spenderat i spelet kommer visas för varje sparat spel i Ladda spel-menyn.</p></body></html> + + + JPG + JPG + + + PNG + PNG + + + TGA + TGA + + + Testing + Testning + + + These settings are intended for testing mods and will cause issues if used for normal gameplay. + Dessa inställningar är avsedda för att testa moddar och kommer orsaka problem vid normalt spelande. + + + <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> + <html><head/><body><p>OpenMW kommer ta kontroll av muspekaren om denna inställning är aktiverad.</p><p>I ”tittläge” kommer OpenMW centrera muspekaren oavsett värdet på denna inställning (eftersom muspekaren/hårkorset alltid är centrerat i OpenMW-fönstret). I gränssnittsläge däremot kommer denna inställning bedöma beteendet när muspekaren flyttas utanför OpenMW-fönstret. Om på kommer muspekarrörelsen stanna vid kanten av fönstret, vilket förhindrar tillgång till andra applikationer. Om av tillåts muspekaren att röras fritt över skrivbordet.</p><p>Denna inställning appliceras inte på skärmen där Escape har blivit tryckt, då muspekaren aldrig tas över. Oavsett denna inställning kan ”Alt-Tab” eller annan operativsystemberoende knappsekvens användas för att ge operativsystemet åter tillgång till muspekaren. Denna inställning interagerar med minimera vid fokusförlust-inställningen genom att påverka vad som räknas som en fokusförlust. Specifikt på en tvåskärmskonfiguration kan det vara mer smidigt att få tillgång till den andra skärmen med inställningen inaktiverad.</p><p>Notis för utvecklare: det är önskvärt att ha denna inställning inaktiverad när OpenMW körs i debug-läge för att förhindra att musen blir oanvändbar när spelet pausar vid en brytpunkt.</p></body></html> + + + Browse… + Bläddra… + + + Collision is used for both physics simulation and navigation mesh generation for pathfinding. Cylinder gives the best consistency between available navigation paths and ability to move by them. Changing this value affects navigation mesh generation therefore navigation mesh disk cache generated for one value will not be useful with another. + Kollision används för både fysiksimulering och navigationsmeshgenerering för pathfinding. Cylinder ger bäst förenlighet mellan tillgängliga navigeringsvägar och möjlighet att förflytta förbi dem. Ändring av detta värde påverkar navigeringsmeshgenereringen – därför kommer inte navigeringsmesh disk cache för ett värde vara användbart för ett annat. + + + Gameplay + Spelmekanik + + + <html><head/><body><p>If enabled, a magical ammunition is required to bypass normal weapon resistance or weakness. If disabled, a magical ranged weapon or a magical ammunition is required.</p></body></html> + <html><head/><body><p>Om aktiverad kommer magisk ammunition krävas för att förbigå normal vapenmotståndskraft eller -svaghet. Om inaktiverad krävs ett magiskt avståndsvapen eller en magisk ammunition.</p></body></html> + + + <html><head/><body><p>Make enchanted weapons without Magical flag bypass normal weapons resistance, like in Morrowind.</p></body></html> + <html><head/><body><p>Få förtrollade vapen utan Magisk-flagga att förbigå normal vapenmotståndskraft, såsom i Morrowind.</p></body></html> + + + cells + celler + + + Shadows + Skuggor + + + <html><head/><body><p>Type of "compute scene bounds" computation method to be used. Bounds (default) for good balance between performance and shadow quality, primitives for better looking shadows or none for no computation.</p></body></html> + <html><head/><body><p>Typ av "compute scene bounds" beräkningsmetod att använda. Bounds (förvalt) för en bra balans mellan prestanda och skuggkvalitet, primitives för snyggare skuggor eller none för ingen beräkning alls.</p></body></html> + + + <html><head/><body><p>64 game units is 1 real life yard or about 0.9 m</p></body></html> + <html><head/><body><p>64 spelenheter är 1 yard eller ungefär 0,9 meter i verkligheten</p></body></html> + + + unit(s) + enhet(er) + + + <html><head/><body><p>Enable shadows for NPCs and creatures besides the player character. May have a minor performance impact.</p></body></html> + <html><head/><body><p>Aktiverar skuggor för icke-spelbara figurer och varelser bortsett från spelarrollfiguren. Kan ha en liten negativ prestandapåverkan.</p></body></html> + + + 512 + 512 + + + 1024 + 1024 + + + 2048 + 2048 + + + 4096 + 4096 + + + <html><head/><body><p>The fraction of the limit above at which shadows begin to gradually fade away.</p></body></html> + <html><head/><body><p>Den fraktion av gränsen ovan vid vilken skuggor gradvis börjar blekna bort.</p></body></html> + + + <html><head/><body><p>Enable shadows exclusively for the player character. May have a very minor performance impact.</p></body></html> + <html><head/><body><p>Aktivera skuggor exklusivt för spelarrollfiguren. Kan ha en mycket liten negativ prestandapåverkan.</p></body></html> + + + <html><head/><body><p>The resolution of each individual shadow map. Increasing it significantly improves shadow quality but may have a minor performance impact.</p></body></html> + <html><head/><body><p>Upplösningen för varje individuell skuggkarta. Ökning av den ökar skuggkvalitén signifikant, men kan ha en lättare negativ prestandapåverkan.</p></body></html> + + + <html><head/><body><p>The distance from the camera at which shadows completely disappear.</p></body></html> + <html><head/><body><p>Avståndet från kameran vid vilken skuggor helt försvinner.</p></body></html> + + + <html><head/><body><p>Enable shadows for primarily inanimate objects. May have a significant performance impact.</p></body></html> + <html><head/><body><p>Aktivera skuggor för huvudsakligen icke-rörliga objekt. Kan ha en signifikant negativ prestandapåverkan.</p></body></html> + + + <html><head/><body><p>Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting.</p><p>Has no effect if actor/player shadows are not enabled.</p></body></html> + <html><head/><body><p>På grund av begränsningar med Morrowinds data kan endast figurer ge ifrån sig skuggor inomhus, vilket vissa kan tycka vara distraherande.</p><p>Har ingen effekt om figur/spelarskuggor inte är aktiverat.</p></body></html> + + + <html><head/><body><p>Enable shadows for the terrain including distant terrain. May have a significant performance and shadow quality impact.</p></body></html> + <html><head/><body><p>Aktivera skuggor för terräng, inklusive avlägsen terräng. Kan ha en signifikant negativ påverkan på prestanda och skuggkvalité.</p></body></html> + + + Lighting + Ljussättning + + + Tooltip + Inforuta + + + Crosshair + Hårkors + + + Screenshots + Skärmdumpar + + + <html><head/><body><p>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.</p></body></html> + <html><head/><body><p>Ge figurer en möjlighet att simma över vattenytan när de följer andra figurer, oberoende av deras förmåga att simma. Har endast effekt när navigeringsmesh är aktiverat.</p></body></html> + + + <html><head/><body><p>Effects of reflected Absorb spells are not mirrored - like in Morrowind.</p></body></html> + <html><head/><body><p>Effekter av reflekterade "Absorb"-besvärjelser speglas inte – såsom i Morrowind.</p></body></html> + + + <html><head/><body><p>Maximum distance at which lights will appear (measured in units).</p><p>Set this to 0 to use an unlimited distance.</p></body></html> + <html><head/><body><p>Maximala avståndet där ljuskällor syns (mätt i enheter).</p><p>Värdet 0 ger oändligt avstånd.</p></body></html> + + + <html><head/><body><p>Maximum number of lights per object.</p><p>A low number near default will cause light popping similar to what you would see with legacy lighting.</p></body></html> + <html><head/><body><p>Maximalt antal ljuskällor per objekt.</p><p>Ett lågt tal nära det förvalda kommer orsaka att ljuskällor poppar upp som vid ljussättningsmetoden Gammaldags.</p></body></html> + + + <html><head/><body><p>Fraction of maximum distance at which lights will start to fade.</p><p>Set this to a low value for slower transitions or a high value for quicker transitions.</p></body></html> + <html><head/><body><p>Fraktion av det maximala avståndet från vilket ljuskällor börjar blekna.</p><p>Välj ett lågt värde för långsammare övergång eller högre värde för snabbare övergång.</p></body></html> + + + <html><head/><body><p>Set the internal handling of light sources.</p> +<p> "Legacy" always uses 8 lights per object and provides a lighting closest to an original game.</p> +<p>"Shaders (compatibility)" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.</p> +<p> "Shaders" carries all of the benefits that "Shaders (compatibility)" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware.</p></body></html> + <html><head/><body><p>Välj intern hantering av ljuskällor.</p> +<p> "Gammaldags" använder alltid max 8 ljuskällor per objekt och ger ljussättning likt ett gammaldags spel.</p> +<p>"Shader (kompatibilitet)" tar bort begränsningen med max 8 ljuskällor per objekt. Detta läge aktiverar också ljus på marktäckning och ett konfigurerbart ljusbleknande. Rekommenderas för äldre hårdvara tillsammans med en ljusbegränsning nära 8.</p> +<p> "Shader" har alla fördelar som "Shader (kompatibilitet)" har, men med ett modernt förhållningssätt som möjliggör fler maximalt antal ljuskällor med liten eller ingen prestandaförlust på modern hårdvara.</p></body></html> + + + Legacy + Gammaldags + + + Shaders (compatibility) + Shader (kompatibilitet) + + + <html><head/><body><p>Multipler for bounding sphere of lights.</p><p>Higher numbers allows for smooth falloff but require an increase in number of max lights.</p><p>Does not effect the illumination or strength of lights.</p></body></html> + <html><head/><body><p>Multiplikator för ljusens gränssfär.</p><p>Högre värden ger mjukare minskning av gränssfären, men kräver högre värde i Max antal ljuskällor.</p><p>Påverkar inte ljusstyrkan.</p></body></html> + + + <html><head/><body><p>Minimum ambient interior brightness.</p><p>Increase this if you feel interiors are too dark.</p></body></html> + <html><head/><body><p>Minsta omgivande ljusstyrka i interiörer.</p><p>Öka värdet om du anser att interiörer är för mörka.</p></body></html> + + + In third-person view, use the camera as the sound listener instead of the player character. + Använd kameran som ljudlyssnare istället för spelarrollfiguren i tredjepersonsperspektivet. + + + Permanent Barter Disposition Changes + Permanenta handelsförändringar + + + Classic Calm Spells Behavior + Klassiskt beteende för "Calm"-besvärjelser + + + NPCs Avoid Collisions + Icke-spelbara figurer undviker kollisioner + + + Soulgem Values Rebalance + Ombalansering av "Soulgems" värdering + + + Day Night Switch Nodes + Dag/natt-bytesnoder + + + Followers Defend Immediately + Följare försvarar omedelbart + + + Only Magical Ammo Bypass Resistance + Bara magisk ammunition förbigår motstånd + + + Graphic Herbalism + Grafisk örtplockning + + + Swim Upward Correction + Simma uppåt korrigering + + + Enchanted Weapons Are Magical + Förtrollade vapen är magiska + + + Merchant Equipping Fix + Handlare klär inte på sig + + + Can Loot During Death Animation + Kan plundra under dödsanimation + + + Classic Reflected Absorb Spells Behavior + Klassiskt beteende för reflekterade "Absorb"-besvärjelser + + + Unarmed Creature Attacks Damage Armor + Obeväpnad attack från varelser skadar rustning + + + Affect Werewolves + Påverka varulvar + + + Do Not Affect Werewolves + Påverka inte varulvar + + + Background Physics Threads + Fysiktrådar i bakgrunden + + + Actor Collision Shape Type + Figurers kollisionsformtyp + + + Axis-Aligned Bounding Box + Axeljusterad begränsningslåda + + + Rotating Box + Roterande låda + + + Smooth Movement + Mjuka rörelser + + + Use Additional Animation Sources + Använd extra animationskällor + + + Weapon Sheathing + Vapenhölstring + + + Shield Sheathing + Sköldhölstring + + + Player Movement Ignores Animation + Spelarförflyttningar ignorerar animation + + + Use Magic Item Animation + Animationer för magiska föremål + + + Auto Use Object Normal Maps + Använd automatiskt normalkartor på objekt + + + Soft Particles + Mjuka partiklar + + + Auto Use Object Specular Maps + Använd automatiskt spekularitetskartor på objekt + + + Auto Use Terrain Normal Maps + Använd automatiskt normalkartor på terräng + + + Auto Use Terrain Specular Maps + Använd automatiskt spekularitetskartor på terräng + + + Use Anti-Aliased Alpha Testing + Använd kantutjämnad alfa-testning + + + Bump/Reflect Map Local Lighting + Bump/Reflektionskartor lokalt ljus + + + Weather Particle Occlusion + Regn/snö blockeras av tak + + + Exponential Fog + Exponentiell dimma + + + Radial Fog + Radiell dimma + + + Sky Blending Start + Smeta ut horisont/himmel, start + + + Sky Blending + Smeta ut horisont/himmel + + + Object Paging Min Size + Object paging needs a better translation + Minsta storlek för objektpaging + + + Viewing Distance + Siktavstånd + + + Distant Land + Avlägsen terräng + + + Active Grid Object Paging + Object paging needs a better translation + Objektpaging i aktivt rutnät + + + Transparent Postpass + Will return to later + + + + Auto Exposure Speed + Autoexponeringshastighet + + + Enable Post Processing + Aktivera efterbehandling (post processing) + + + Shadow Planes Computation Method + Skuggplaner beräkningsmetod + + + Enable Actor Shadows + Aktivera rollfigurskuggor + + + Fade Start Multiplier + Blekningsstartmultiplikator + + + Enable Player Shadows + Aktivera spelarskuggor + + + Shadow Map Resolution + Skuggkartsupplösning + + + Shadow Distance Limit: + Skuggavståndsgräns: + + + Enable Object Shadows + Aktivera objektskuggor + + + Enable Indoor Shadows + Aktivera interiöra skuggor + + + Enable Terrain Shadows + Aktivera terrängskuggor + + + Maximum Light Distance + Maximalt ljusavstånd + + + Max Lights + Max antal ljuskällor + + + Lighting Method + Ljussättningsmetod + + + Bounding Sphere Multiplier + Gränssfärsmultiplikator + + + Minimum Interior Brightness + Minsta ljusstyrka i interiörer + + + Audio Device + Ljudenhet + + + HRTF Profile + HRTF-profil + + + Tooltip and Crosshair + Inforuta och hårkors + + + GUI Scaling Factor + Skalningsfaktor för gränssnitt + + + Show Effect Duration + Visa effektvaraktighet + + + Change Dialogue Topic Color + Ändra färg på dialogämnen + + + Font Size + Fontstorlek + + + Show Projectile Damage + Visa projektilskada + + + Show Melee Info + Visa närstridsinfo + + + Stretch Menu Background + Sträck ut menybakgrund + + + Show Owned Objects + Visa ägda objekt + + + Show Enchant Chance + Visa chans för "Enchant" + + + Maximum Quicksaves + Max snabbsparfiler + + + Screenshot Format + Skärmdumpformat + + + Grab Cursor + Ta över muspekaren + + + Default Cell + Förinställd cell + + + Bounds + Bounds + + + Primitives + Primitives + + + None + None + + + Always Allow Actors to Follow over Water + Tillåt alltid figurer att följa över vatten + + + Racial Variation in Speed Fix + Fix för varelsers hastighetsvariation + + + Use Navigation Mesh for Pathfinding + Använd navigeringsmesh för pathfinding + + + Trainers Choose Offered Skills by Base Value + Tränare väljer erbjudna färdigheter efter grundvärde + + + Steal from Knocked out Actors in Combat + Stjäl från avsvimmade rollfigurer i strid + + + Factor Strength into Hand-to-Hand Combat + Räkna in styrka i obeväpnad strid + + + Turn to Movement Direction + Vänd mot rörelseriktningen + + + Adjust Coverage for Alpha Test + Justera täckning för alfa-testning + + + Use the Camera as the Sound Listener + Använd kameran som ljudlyssnaren + + + Can Zoom on Maps + Kan zooma på kartor + + + Add "Time Played" to Saves + Lägg till spelad tid i sparfiler + + + Notify on Saved Screenshot + Ge notis vid sparad skärmdump + + + Skip Menu and Generate Default Character + Hoppa över meny och generera förinställd rollfigur + + + Start Default Character at + Starta förinställd rollfigur vid + + + Run Script After Startup: + Kör skript efter uppstart: + + + <html><head/><body><p>Force the use of per pixel lighting. By default, only bump and normal mapped objects use per-pixel lighting. Only affects objects drawn with shaders. Enabling per-pixel lighting results in visual differences to the original MW engine as certain lights in Morrowind rely on vertex lighting to look as intended. Note that groundcover shaders and particle effects ignore this setting.</p></body></html> + + + + Force per-pixel lighting + + + + diff --git a/files/lang/wizard_de.ts b/files/lang/wizard_de.ts index 7bf54e90b1..c3ef55b04a 100644 --- a/files/lang/wizard_de.ts +++ b/files/lang/wizard_de.ts @@ -4,27 +4,22 @@ ComponentSelectionPage - WizardPage - Select Components - Which components should be installed? - <html><head/><body><p>Select which official Morrowind expansions should be installed. For best results, it is recommended to have both expansions installed.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to install expansions later by re-running this Wizard.<br/></p></body></html> - Selected components: @@ -32,17 +27,14 @@ ConclusionPage - WizardPage - Completing the OpenMW Wizard - Placeholder @@ -50,27 +42,22 @@ ExistingInstallationPage - WizardPage - Select Existing Installation - Select an existing installation for OpenMW to use or modify. - Detected installations: - Browse... @@ -78,42 +65,34 @@ ImportPage - WizardPage - Import Settings - Import settings from the Morrowind installation. - <html><head/><body><p>OpenMW needs to import settings from the Morrowind configuration file in order to function properly.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to import settings later by re-running this Wizard.</p><p/></body></html> - - Import settings from Morrowind.ini + Import Settings From Morrowind.ini - - Import add-on and plugin selection + Import Add-on and Plugin Selection - - Import bitmap fonts setup from Morrowind.ini + Import Bitmap Fonts Setup From Morrowind.ini - Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. @@ -123,17 +102,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov InstallationPage - WizardPage - Installing - Please wait while Morrowind is installed on your computer. @@ -141,32 +117,22 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov InstallationTargetPage - WizardPage - Select Installation Destination - Where should Morrowind be installed? - - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - - - - Morrowind will be installed to the following location. - Browse... @@ -174,17 +140,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov IntroPage - WizardPage - Welcome to the OpenMW Wizard - This Wizard will help you install Morrowind and its add-ons for OpenMW to use. @@ -192,27 +155,18 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov LanguageSelectionPage - WizardPage - Select Morrowind Language - What is the language of the Morrowind installation? - - <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> - - - - Select the language of the Morrowind installation. @@ -220,62 +174,38 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov MethodSelectionPage - WizardPage - Select Installation Method - <html><head/><body><p>Select how you would like to install <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> - Retail CD/DVD - - <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> - - - - Install from a retail disc to a new location. - Existing Installation - - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - - - - Select an existing installation. - Don't have a copy? - - <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> - - - - Buy the game @@ -283,42 +213,34 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov QObject - <br><b>Could not find Morrowind.ini</b><br><br>The Wizard needs to update settings in this file.<br><br>Press "Browse..." to specify the location manually.<br> - B&rowse... - Select configuration file - <b>Morrowind.bsa</b> is missing!<br>Make sure your Morrowind installation is complete. - <br><b>There may be a more recent version of Morrowind available.</b><br><br>Do you wish to continue anyway?<br> - Most recent Morrowind not detected - Select a valid %1 installation media.<br><b>Hint</b>: make sure that it contains at least one <b>.cab</b> file. - There may be a more recent version of Morrowind available.<br><br>Do you wish to continue anyway? @@ -326,57 +248,46 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ComponentSelectionPage - &Install - &Skip - Morrowind (installed) - Morrowind - Tribunal (installed) - Tribunal - Bloodmoon (installed) - Bloodmoon - About to install Tribunal after Bloodmoon - <html><head/><body><p><b>You are about to install Tribunal</b></p><p>Bloodmoon is already installed on your computer.</p><p>However, it is recommended that you install Tribunal before Bloodmoon.</p><p>Would you like to re-install Bloodmoon?</p></body></html> - Re-install &Bloodmoon @@ -384,17 +295,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ConclusionPage - <html><head/><body><p>The OpenMW Wizard successfully installed Morrowind on your computer.</p></body></html> - <html><head/><body><p>The OpenMW Wizard successfully modified your existing Morrowind installation.</body></html> - <html><head/><body><p>The OpenMW Wizard failed to install Morrowind on your computer.</p><p>Please report any bugs you might have encountered to our <a href="https://gitlab.com/OpenMW/openmw/issues">bug tracker</a>.<br/>Make sure to include the installation log.</p><br/></body></html> @@ -402,32 +310,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ExistingInstallationPage - No existing installations detected - Error detecting Morrowind configuration - Morrowind configuration file (*.ini) - Select Morrowind.esm (located in Data Files) - Morrowind master file (Morrowind.esm) - Error detecting Morrowind files @@ -435,78 +337,62 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::InstallationPage - <p>Attempting to install component %1.</p> - Attempting to install component %1. - %1 Installation - Select %1 installation media - - <p><br/><span style="color:red;"><b>Error: The installation was aborted by the user</b></span></p> - <p>Detected old version of component Morrowind.</p> - Detected old version of component Morrowind. - Morrowind Installation - Installation finished - Installation completed successfully! - Installation failed! - <p><br/><span style="color:red;"><b>Error: %1</b></p> - <p><span style="color:red;"><b>%1</b></p> - <html><head/><body><p><b>The Wizard has encountered an error</b></p><p>The error reported was:</p><p>%1</p><p>Press &quot;Show Details...&quot; for more information.</p></body></html> - An error occurred @@ -514,37 +400,30 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::InstallationTargetPage - Error creating destination - <html><head/><body><p><b>Could not create the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> - <html><head/><body><p><b>Could not write to the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> - <html><head/><body><p><b>The destination directory is not empty</b></p><p>An existing Morrowind installation is present in the specified location.</p><p>Please specify a different location, or go back and select the location as an existing installation.</p></body></html> - Insufficient permissions - Destination not empty - Select where to install Morrowind @@ -552,37 +431,30 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::LanguageSelectionPage - English - French - German - Italian - Polish - Russian - Spanish @@ -590,59 +462,42 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::MainWizard - OpenMW Wizard - - Error opening Wizard log file - - - <html><head/><body><p><b>Could not open %1 for writing</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - - <html><head/><body><p><b>Could not open %1 for reading</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - - - Error opening OpenMW configuration file - Quit Wizard - Are you sure you want to exit the Wizard? - Error creating OpenMW configuration directory - <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - - Error writing OpenMW configuration file @@ -650,207 +505,150 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::UnshieldWorker - - Failed to open Morrowind configuration file! - - Opening %1 failed: %2. - Failed to write Morrowind configuration file! - Writing to %1 failed: %2. - Installing: %1 - - Installing: %1 directory - Installation finished! - - Component parameter is invalid! - - An invalid component parameter was supplied. - Failed to find a valid archive containing %1.bsa! Retrying. - Installing %1 - Installation media path not set! - The source path for %1 was not set. - - Cannot create temporary directory! - - Failed to create %1. - Cannot move into temporary directory! - Failed to move into %1. - Moving installation files - - - - Could not install directory! - - - - Installing %1 to %2 failed. - Could not install translation file! - Failed to install *%1 files. - Could not install Morrowind data file! - - Failed to install %1. - Could not install Morrowind configuration file! - Installing: Sound directory - Could not find Tribunal data file! - - - Failed to find %1. - Could not find Tribunal patch file! - Could not find Bloodmoon data file! - Updating Morrowind configuration file - %1 installation finished! - Extracting: %1 - - - Failed to open InstallShield Cabinet File. - - - Opening %1 failed. - Failed to extract %1. - Complete path: %1 diff --git a/files/lang/wizard_fr.ts b/files/lang/wizard_fr.ts index 7f42087dbf..010883d0c4 100644 --- a/files/lang/wizard_fr.ts +++ b/files/lang/wizard_fr.ts @@ -4,855 +4,653 @@ ComponentSelectionPage - WizardPage - + Assistant d'installation d'OpenMW - Select Components - + Sélection des composantes de jeu - Which components should be installed? - + Quelles composantes du jeu doivent être installées ? - <html><head/><body><p>Select which official Morrowind expansions should be installed. For best results, it is recommended to have both expansions installed.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to install expansions later by re-running this Wizard.<br/></p></body></html> - + <html><head/><body><p>Sélectionnez quelle(s) extension(s) officielle(s) doivent être installée(s). Pour une meilleure expérience de jeu, il est recommandé d'installer toutes les extensions.</p><p><span style=" font-weight:bold;">Note :</span> Il est possible d'installer les extensions plus tard en relançant l'Assistant d'installation.<br/></p></body></html> - Selected components: - + Composantes sélectionnées : ConclusionPage - WizardPage - + Assistant d'installation d'OpenMW - Completing the OpenMW Wizard - + Fin de l'Assistant d'installation d'OpenMW - Placeholder - + Placeholder ExistingInstallationPage - WizardPage - + Assistant d'installation d'OpenMW - Select Existing Installation - + Sélection d'une installation existante - Select an existing installation for OpenMW to use or modify. - + Sélectionnez une installation existante pour l'utiliser ou la modifier avec OpenMW - Detected installations: - + Installation(s) détectée(s) : - Browse... - + Parcourir... ImportPage - WizardPage - + Assistant d'installation d'OpenMW - Import Settings - + Import des paramètres - Import settings from the Morrowind installation. - + Importe les paramètres depuis une installation de Morrowind. - <html><head/><body><p>OpenMW needs to import settings from the Morrowind configuration file in order to function properly.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to import settings later by re-running this Wizard.</p><p/></body></html> - + <html><head/><body><p>Afin de fonctionner correctement, OpenMW nécessite d'importer les paramètres depuis le fichier de configuration de Morrowind.</p><p><span style=" font-weight:bold;">Note :</span> Il est possible d'importer les paramètres plus tard en relançant l'Assistant d'installation.</p><p/></body></html> - - Import settings from Morrowind.ini - + Import Settings From Morrowind.ini + Importer les paramètres depuis le fichier Morrowind.ini - - Import add-on and plugin selection - + Import Add-on and Plugin Selection + Importer les extensions et la sélection de modules complémentaires - - Import bitmap fonts setup from Morrowind.ini - + Import Bitmap Fonts Setup From Morrowind.ini + Importer les paramètres des polices en bitmap depuis Morrowind.ini - Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. - + Les polices fournies avec le moteur de jeu original sont floues lorsque l'interface utilisateur est agrandie. De plus, elles ne supportent qu'un faible nombre de caractères. Afin d'éviter ces désagréments, OpenMW propose son propre ensemble de polices. Ces polices sont encodées au format TrueType et sont fort similaires aux polices originales. Sélectionnez cette option si vous préférez utiliser les polices originales - à la place de celles fournies par OpenMW - ou si vous utilisez vos propres polices au format bitmap. InstallationPage - WizardPage - + Assistant d'installation d'OpenMW - Installing - + Installation en cours - Please wait while Morrowind is installed on your computer. - + Veuillez patienter pendant l'installation de Morrowind sur votre ordinateur. InstallationTargetPage - WizardPage - + Assistant d'installation d'OpenMW - Select Installation Destination - + Sélection de l'emplacement de l'installation - Where should Morrowind be installed? - + À quel emplacement Morrowind doit-il être installé ? - - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - - - - Morrowind will be installed to the following location. - + Morrowind sera installé à l'emplacement suivant : - Browse... - + Parcourir... IntroPage - WizardPage - + Assistant d'installation d'OpenMW - Welcome to the OpenMW Wizard - + Bienvenue sur l'assistant d'installation d'OpenMW - This Wizard will help you install Morrowind and its add-ons for OpenMW to use. - + Cet assistant vous aidera à installer Morrowind et ses extensions afin que vous lanciez le jeu avec OpenMW. LanguageSelectionPage - WizardPage - + Assistant d'installation d'OpenMW - Select Morrowind Language - + Sélection de la langue de Morrowind - What is the language of the Morrowind installation? - + Dans quelle langue est cette installation de Morrowind ? - - <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> - - - - Select the language of the Morrowind installation. - + Sélectionnez la langue de cette installation de Morrowind. MethodSelectionPage - WizardPage - + Assistant d'installation d'OpenMW - Select Installation Method - + Méthode d'installation - <html><head/><body><p>Select how you would like to install <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> - + <html><head/><body><p>Sélectionnez une méthode d'installation de <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> - Retail CD/DVD - - - - - <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> - + Copie CD/DVD physique - Install from a retail disc to a new location. - + Installer depuis un CD/DVD et choisir la destination sur l'ordinateur. - Existing Installation - - - - - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - + Installation existante - Select an existing installation. - + Sélectionnez une installation existante. - Don't have a copy? - - - - - <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> - + Vous n'avez pas de copie du jeu ? - Buy the game - + Achetez le jeu. QObject - <br><b>Could not find Morrowind.ini</b><br><br>The Wizard needs to update settings in this file.<br><br>Press "Browse..." to specify the location manually.<br> - + <br><b>Impossible de trouver Morrowind.ini</b><br><br>L'assistant d'installation requière de mettre à jour les paramètres de ce fichier.<br><br>Cliquez sur "Parcourir..." afin de spécifier manuellement son emplacement.<br> - B&rowse... - + P&arcourir... - Select configuration file - + Sélectionnez le fichier de configuration - <b>Morrowind.bsa</b> is missing!<br>Make sure your Morrowind installation is complete. - + L'archive <b>Morrowind.bsa</b> est manquante !<br>Assurez-vous que votre installation de Morrowind est complète. - <br><b>There may be a more recent version of Morrowind available.</b><br><br>Do you wish to continue anyway?<br> - + <br><b>Il semble qu'une version plus à jour de Morrowind soit disponible.</b><br><br>Voulez-vous continuer malgré tout ?<br> - Most recent Morrowind not detected - + Version la plus à jour de Morrowind non détectée - Select a valid %1 installation media.<br><b>Hint</b>: make sure that it contains at least one <b>.cab</b> file. - + Sélectionnez un support %1 d'installation valide.<br><b>Aide</b> : Assurez-vous qu'il contienne au moins un fichier <b>.cab</b>. - There may be a more recent version of Morrowind available.<br><br>Do you wish to continue anyway? - + Il semble qu'une version plus à jour de Morrowind soit disponible.<br><br>Voulez-vous continuer malgré tout ? Wizard::ComponentSelectionPage - &Install - + &Installer - &Skip - + &Passer - Morrowind (installed) - + Morrowind (installé) - Morrowind - + Morrowind - Tribunal (installed) - + Tribunal (installé) - Tribunal - + Tribunal - Bloodmoon (installed) - + Bloodmoon (installé) - Bloodmoon - + Bloodmoon - About to install Tribunal after Bloodmoon - + À propos de l'installation de Tribunal après Bloodmoon - <html><head/><body><p><b>You are about to install Tribunal</b></p><p>Bloodmoon is already installed on your computer.</p><p>However, it is recommended that you install Tribunal before Bloodmoon.</p><p>Would you like to re-install Bloodmoon?</p></body></html> - + <html><head/><body><p><b>Vous vous apprêtez à installer l'extension Tribunal.</b></p><p>L'extension Bloodmoon est déjà installée sur votre ordinateur.</p><p>Néanmoins, il est recommandé d'installer Tribunal avant Bloodmoon.</p><p>Désirez-vous réinstaller Bloodmoon ?</p></body></html> - Re-install &Bloodmoon - + Réinstaller &Bloodmoon Wizard::ConclusionPage - <html><head/><body><p>The OpenMW Wizard successfully installed Morrowind on your computer.</p></body></html> - + <html><head/><body><p>L'Assistant d'installation d'OpenMW a installé Morrowind sur votre ordinateur avec succès !</p></body></html> - <html><head/><body><p>The OpenMW Wizard successfully modified your existing Morrowind installation.</body></html> - + <html><head/><body><p>L'Assistant d'installation d'OpenMW a modifié votre installation de Morrowind avec succès !</p></body></html> - <html><head/><body><p>The OpenMW Wizard failed to install Morrowind on your computer.</p><p>Please report any bugs you might have encountered to our <a href="https://gitlab.com/OpenMW/openmw/issues">bug tracker</a>.<br/>Make sure to include the installation log.</p><br/></body></html> - + <html><head/><body><p>L'assistant d'installation d'OpenMW a échoué l'installation de Morrowind.</p><p>Veuillez signaler les bogues que vous avez pu rencontrer sur notre. <a href="https://gitlab.com/OpenMW/openmw/issues">bug tracker</a> (en anglais).<br/>Assurez-vous d'inclure le journal (log) d'installation.</p><br/></body></html> Wizard::ExistingInstallationPage - No existing installations detected - + Aucune installation existante détectée - Error detecting Morrowind configuration - + Erreur lors de la détection du fichier de configuration de Morrowind - Morrowind configuration file (*.ini) - + Fichier de configuration de Morrowind (*.ini) - Select Morrowind.esm (located in Data Files) - + Sélectionnez Morrowind.esm (situé dans Data Files) - Morrowind master file (Morrowind.esm) - + Fichier principal de Morrowind (Morrowind.esm) - Error detecting Morrowind files - + Erreur lors de la détection des fichiers de Morrowind Wizard::InstallationPage - <p>Attempting to install component %1.</p> - + <p>Tentative d'installation de la composante %1.</p> - Attempting to install component %1. - + Tentative d'installation de la composante %1. - %1 Installation - + Installation de %1 - Select %1 installation media - + Sélectionnez le support d'installation de %1 - - <p><br/><span style="color:red;"><b>Error: The installation was aborted by the user</b></span></p> - + <p><br/><span style="color:red;"><b>Erreur : L'installation a été annulée par l'utilisateur.</b></span></p> - <p>Detected old version of component Morrowind.</p> - + <p>Ancienne version de la composante Morrowind détectée.</p> - Detected old version of component Morrowind. - + Ancienne version de la composante Morrowind détectée. - Morrowind Installation - + Installation de Morrowind - Installation finished - + Installation terminée - Installation completed successfully! - + Installation complétée avec succès ! - Installation failed! - + Échec de l'installation ! - <p><br/><span style="color:red;"><b>Error: %1</b></p> - + <p><br/><span style="color:red;"><b>Erreur : %1</b></p> - <p><span style="color:red;"><b>%1</b></p> - + <p><span style="color:red;"><b>%1</b></p> - <html><head/><body><p><b>The Wizard has encountered an error</b></p><p>The error reported was:</p><p>%1</p><p>Press &quot;Show Details...&quot; for more information.</p></body></html> - + <html><head/><body><p><b>L'assistant d'installation a rencontré une erreur.</b></p><p>L'erreur reportée est :</p><p>%1</p><p>Cliquez sur "Montrer les détails..." pour plus d'informations.</p></body></html> - An error occurred - + Une erreur est survenue Wizard::InstallationTargetPage - Error creating destination - + Erreur lors de la création de l'emplacement de l'installation - <html><head/><body><p><b>Could not create the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> - + <html><head/><body><p><b>Impossible de créer le dossier d'installation</b></p><p>Veuillez vous assurer que vous avez les bons droits d'accès puis réessayez, ou spécifiez une autre destination.</p></body></html> - <html><head/><body><p><b>Could not write to the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> - + <html><head/><body><p><b>Impossible d'écrire dans le dossier d'installation</b></p><p>Veuillez vous assurer que vous avez les bons droits d'accès puis réessayez, ou spécifiez une autre destination.</p></body></html> - <html><head/><body><p><b>The destination directory is not empty</b></p><p>An existing Morrowind installation is present in the specified location.</p><p>Please specify a different location, or go back and select the location as an existing installation.</p></body></html> - + <html><head/><body><p><b>Le dossier d'installation n'est pas vide.</b></p><p>Une installation de Morrowind est déjà présente dans ce dossier.</p><p>Veuillez spécifier un autre enmplacement pour l'installation, ou revenez en arrière et spécifiez que l'emplacement contient déjà une installation.</p></body></html> - Insufficient permissions - + Droits d'accès insuffisants - Destination not empty - + Emplacement non vide - Select where to install Morrowind - + Sélectionnez où installer Morrowind Wizard::LanguageSelectionPage - English - + Anglais - French - + Français - German - + Allemand - Italian - + Italien - Polish - + Polonais - Russian - + Russe - Spanish - + Espagnol Wizard::MainWizard - OpenMW Wizard - + Assistant d'installation d'OpenMW - - Error opening Wizard log file - + Erreur lors de l'ouverture du journal de l'Assistant d'installation - - - <html><head/><body><p><b>Could not open %1 for writing</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - + <html><head/><body><p><b>Impossible d'ouvrir %1 en écriture.</b></p><p>Assurez-vous d'avoir les bons droits d'accès puis réessayez.</p></body></html> - - <html><head/><body><p><b>Could not open %1 for reading</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - + <html><head/><body><p><b>Impossible d'ouvrir %1 en lecture.</b></p><p>Assurez-vous d'avoir les bons droits d'accès puis réessayez.</p></body></html> - - - Error opening OpenMW configuration file - + Erreur lors de l'ouverture du fichier de configuration. - Quit Wizard - + Quitter l'Assistant - Are you sure you want to exit the Wizard? - + Êtes-vous sûr de vouloir quitter l'Assistant d'installation ? - Error creating OpenMW configuration directory - + Erreur lors de la création du dossier de configuration d'OpenMW - <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - + <html><head/><body><p><b>Impossible de créer %1.</b></p><p>Assurez-vous d'avoir les bons droits d'accès puis réessayez.</p></body></html> - - Error writing OpenMW configuration file - + Erreur lors de l'écriture dans le fichier de configuration d'OpenMW. Wizard::UnshieldWorker - - Failed to open Morrowind configuration file! - + Échec de l'ouverture du fichier de configuration de Morrowind ! - - Opening %1 failed: %2. - + Échec lors de l'ouverture de %1 : %2. - Failed to write Morrowind configuration file! - + Échec de l'ouverture du fichier de configuration de Morrowind ! - Writing to %1 failed: %2. - + Échec lors de l'écriture dans %1 : %2. - Installing: %1 - + Installation : %1 - - Installing: %1 directory - + Installation : dossier %1 - Installation finished! - + Installation terminée ! - - Component parameter is invalid! - + Le paramètre d'une des composantes est invalide ! - - An invalid component parameter was supplied. - + Un paramètre invalide d'une composante a été fournis. - Failed to find a valid archive containing %1.bsa! Retrying. - + Impossible de trouver une archive contenant %1.bsa ! Nouvel essai. - Installing %1 - + Installation de %1 - Installation media path not set! - + Support d'installation non défini ! - The source path for %1 was not set. - + L'emplacement de %1 n'a pas été défini. - - Cannot create temporary directory! - + Impossible de créer un dossier temporaire ! - - Failed to create %1. - + Impossible de créer %1. - Cannot move into temporary directory! - + Impossible d'accéder au dossier temporaire ! - Failed to move into %1. - + Impossible d'accéder à %1. - Moving installation files - + Déplacement des fichiers d'installation. - - - - Could not install directory! - + Impossible d'installer le dossier ! - - - - Installing %1 to %2 failed. - + Échec de l'installation de %1 dans %2. - Could not install translation file! - + Impossible d'installer le fichier de localisation ! - Failed to install *%1 files. - + Échec lors de l'installation du fichier %1. - Could not install Morrowind data file! - + Impossible d'installer le fichier de données de Morrowind ! - - Failed to install %1. - + Échec lors de l'installation de %1. - Could not install Morrowind configuration file! - + Impossible d'installer le fichier de configuration de Morrowind ! - Installing: Sound directory - + Installation : Dossier des sons. - Could not find Tribunal data file! - + Impossible d'installer le fichier de données de Tribunal ! - - - Failed to find %1. - + Impossible de trouver %1. - Could not find Tribunal patch file! - + Impossible de trouver le fichier de correctifs de Tribunal ! - Could not find Bloodmoon data file! - + Impossible d'installer le fichier de données de Bloodmoon ! - Updating Morrowind configuration file - + Mise à jour du fichier de configuration de Morrowind. - %1 installation finished! - + Installation de %1 terminée ! - Extracting: %1 - + Extraction : %1 - - - Failed to open InstallShield Cabinet File. - + Impossible d'ouvrir le fichier InstallShield Cabinet. - - - Opening %1 failed. - + Échec lors de l'ouverture de %1. - Failed to extract %1. - + Échec lors de l'extraction de %1. - Complete path: %1 - + Emplacement complet : %1. diff --git a/files/lang/wizard_ru.ts b/files/lang/wizard_ru.ts index 3113774cd3..ba9815f634 100644 --- a/files/lang/wizard_ru.ts +++ b/files/lang/wizard_ru.ts @@ -4,27 +4,22 @@ ComponentSelectionPage - WizardPage WizardPage - Select Components Выбор компонентов - Which components should be installed? Какие компоненты должны быть установлены? - <html><head/><body><p>Select which official Morrowind expansions should be installed. For best results, it is recommended to have both expansions installed.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to install expansions later by re-running this Wizard.<br/></p></body></html> <html><head/><body><p>Выберите, какие дополнения для Morrowind нужно установить. Для достижения наилучших результатов рекомендуется установить оба дополнения.</p><p><span style=" font-weight:bold;">Подсказка:</span> Можно установить дополнения позже, запустив этот Мастер установки заново.<br/></p></body></html> - Selected components: Выбранные компоненты: @@ -32,17 +27,14 @@ ConclusionPage - WizardPage WizardPage - Completing the OpenMW Wizard Завершение работы Мастера установки OpenMW - Placeholder Placeholder @@ -50,27 +42,22 @@ ExistingInstallationPage - WizardPage WizardPage - Select Existing Installation Выбрать установленную копию игры - Select an existing installation for OpenMW to use or modify. Выбрать установленную копию игры для использования или изменения через OpenMW. - Detected installations: Обнаруженные установленные копии: - Browse... Выбрать... @@ -78,42 +65,34 @@ ImportPage - WizardPage WizardPage - Import Settings Импортировать настройки - Import settings from the Morrowind installation. Импортировать настройки из установленной копии Morrowind. - <html><head/><body><p>OpenMW needs to import settings from the Morrowind configuration file in order to function properly.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to import settings later by re-running this Wizard.</p><p/></body></html> <html><head/><body><p>Чтобы OpenMW мог работать правильно, ему нужно импортировать настройки из файла с настройками Morrowind.</p><p><span style=" font-weight:bold;">Подсказка:</span> Также можно импортировать настройки позже, запустив Мастер импорта заново.</p><p/></body></html> - - Import settings from Morrowind.ini + Import Settings From Morrowind.ini Импортировать настройки из Morrowind.ini - - Import add-on and plugin selection - Импортировать список подключенных плагинов + Import Add-on and Plugin Selection + Импортировать список подключенных аддонов и плагинов - - Import bitmap fonts setup from Morrowind.ini + Import Bitmap Fonts Setup From Morrowind.ini Импортировать растровые шрифты из Morrowind.ini - Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. @@ -125,17 +104,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov InstallationPage - WizardPage WizardPage - Installing Установка - Please wait while Morrowind is installed on your computer. Пожалуйста, подождите, пока Morrowind устанавливается на ваш компьютер. @@ -143,32 +119,22 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov InstallationTargetPage - WizardPage WizardPage - Select Installation Destination Выберите путь для установки - Where should Morrowind be installed? Куда нужно установить Morrowind? - - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - - - Morrowind will be installed to the following location. - Morrowind будет установлен в следующее место. + Morrowind будет установлен в следующее место. - Browse... Выбрать... @@ -176,17 +142,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov IntroPage - WizardPage WizardPage - Welcome to the OpenMW Wizard Добро пожаловать в Мастер установки - This Wizard will help you install Morrowind and its add-ons for OpenMW to use. Этот Мастер поможет вам установить Morrowind и его дополнения, чтобы OpenMW мог их использовать. @@ -194,27 +157,18 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov LanguageSelectionPage - WizardPage WizardPage - Select Morrowind Language Выберите язык вашей копии Morrowind - What is the language of the Morrowind installation? Какой язык использует ваша копия Morrowind? - - <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> - ><html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> - - - Select the language of the Morrowind installation. Выберите язык, используемый вашей копией Morrowind. @@ -222,62 +176,38 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov MethodSelectionPage - WizardPage WizardPage - Select Installation Method Выберите способ установки - <html><head/><body><p>Select how you would like to install <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> <html><head/><body><p>Выберите способ установки <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> - Retail CD/DVD CD/DVD-диск - - <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> - <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> - - - Install from a retail disc to a new location. - Установить игру с диска + Установить игру с диска. - Existing Installation Установленная копия игры - - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - - - Select an existing installation. Выбрать установленную копию игры. - Don't have a copy? Нет копии игры? - - <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> - <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> - - - Buy the game Купить игру @@ -285,42 +215,34 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov QObject - <br><b>Could not find Morrowind.ini</b><br><br>The Wizard needs to update settings in this file.<br><br>Press "Browse..." to specify the location manually.<br> <br><b>Не удалось найти Morrowind.ini</b><br><br>Мастеру требуется обновить настройки в этом файле.<br><br>Нажмите "Выбрать...", чтобы задать местоположение файла вручную.<br> - B&rowse... &Выбрать... - Select configuration file Выберите файл с настройками - <b>Morrowind.bsa</b> is missing!<br>Make sure your Morrowind installation is complete. <b>Morrowind.bsa</b> не найден!<br>Убедитесь, что Morrowind был установлен правильно. - <br><b>There may be a more recent version of Morrowind available.</b><br><br>Do you wish to continue anyway?<br> <br><b>Может существовать более свежая версия Morrowind.</b><br><br>Все равно продолжить?<br> - Most recent Morrowind not detected Актуальная версия Morrowind не найдена - Select a valid %1 installation media.<br><b>Hint</b>: make sure that it contains at least one <b>.cab</b> file. Выберите корректный установочный дистрибутив %1.<br><b>Подсказка</b>: он должен содержать как минимум один <b>.cab</b>-файл. - There may be a more recent version of Morrowind available.<br><br>Do you wish to continue anyway? Может существовать более свежая версия Morrowind.<br><br>Все равно продолжить? @@ -328,57 +250,46 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ComponentSelectionPage - &Install &Установить - &Skip &Пропустить - Morrowind (installed) Morrowind (установлен) - Morrowind Morrowind - Tribunal (installed) Tribunal (установлен) - Tribunal Tribunal - Bloodmoon (installed) Bloodmoon (установлен) - Bloodmoon Bloodmoon - About to install Tribunal after Bloodmoon Попытка установить Tribunal после Bloodmoon - <html><head/><body><p><b>You are about to install Tribunal</b></p><p>Bloodmoon is already installed on your computer.</p><p>However, it is recommended that you install Tribunal before Bloodmoon.</p><p>Would you like to re-install Bloodmoon?</p></body></html> <html><head/><body><p><b>Вы собираетесь установить Tribunal</b></p><p>Bloodmoon уже установлен на ваш компьютер.</p><p>Tribunal рекомендуется устанавлить перед установкой Bloodmoon.</p><p>Желаете ли вы переустановить Bloodmoon?</p></body></html> - Re-install &Bloodmoon Переустановить &Bloodmoon @@ -386,17 +297,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ConclusionPage - <html><head/><body><p>The OpenMW Wizard successfully installed Morrowind on your computer.</p></body></html> <html><head/><body><p>Мастер OpenMW успешно установил Morrowind на ваш компьютер.</p></body></html> - <html><head/><body><p>The OpenMW Wizard successfully modified your existing Morrowind installation.</body></html> <html><head/><body><p>Мастер OpenMW успешно завершил изменение вашей установленной копии Morrowind.</body></html> - <html><head/><body><p>The OpenMW Wizard failed to install Morrowind on your computer.</p><p>Please report any bugs you might have encountered to our <a href="https://gitlab.com/OpenMW/openmw/issues">bug tracker</a>.<br/>Make sure to include the installation log.</p><br/></body></html> <html><head/><body><p>Мастеру OpenMW не удалось установить Morrowind на ваш компьютер.</p><p>Пожалуйста, сообщите о встреченных вами ошибках на наш <a href="https://gitlab.com/OpenMW/openmw/issues">багтрекер</a>.<br/>Не забудьте включить туда лог установки.</p><br/></body></html> @@ -404,32 +312,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ExistingInstallationPage - No existing installations detected Установленные копии игры не найдены - Error detecting Morrowind configuration Попытка найти настройки Morrowind завершилась ошибкой - Morrowind configuration file (*.ini) Файл настроек Morrowind (*.ini) - Select Morrowind.esm (located in Data Files) Выберите Morrowind.esm (расположен в Data Files) - Morrowind master file (Morrowind.esm) Мастер-файл Morrowind (Morrowind.esm) - Error detecting Morrowind files Не удалось обнаружить файлы Morrowind @@ -437,78 +339,62 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::InstallationPage - <p>Attempting to install component %1.</p> <p>Попытка установить компонент %1.</p> - Attempting to install component %1. Попытка установить компонент %1. - %1 Installation Установка %1 - Select %1 installation media Выберите установочный дистрибутив %1 - - <p><br/><span style="color:red;"><b>Error: The installation was aborted by the user</b></span></p> <p><br/><span style="color:red;"><b>Ошибка: Установка была прервана пользователем</b></span></p> - <p>Detected old version of component Morrowind.</p> lt;p>Обнаружена устаревшая версия компонента Morrowind.</p> - Detected old version of component Morrowind. Обнаружена устаревшая версия компонента Morrowind. - Morrowind Installation Установка Morrowind - Installation finished Установка завершена - Installation completed successfully! Установка успешно завершена! - Installation failed! Установка не удалась! - <p><br/><span style="color:red;"><b>Error: %1</b></p> <p><br/><span style="color:red;"><b>Ошибка: %1</b></p> - <p><span style="color:red;"><b>%1</b></p> <p><span style="color:red;"><b>%1</b></p> - <html><head/><body><p><b>The Wizard has encountered an error</b></p><p>The error reported was:</p><p>%1</p><p>Press &quot;Show Details...&quot; for more information.</p></body></html> <html><head/><body><p><b>При работе Мастера возникла ошибка</b></p><p>Обнаруженная ошибка:</p><p>%1</p><p>Нажмите &quot;Показать детали...&quot; для получения дополнительной информации.</p></body></html> - An error occurred Произошла ошибка @@ -516,37 +402,30 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::InstallationTargetPage - Error creating destination Не удалось создать директорию назначения - <html><head/><body><p><b>Could not create the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> <html><head/><body><p><b>Не удалось создать директорию назначения</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку, или же выберите другую директорию.</p></body> - <html><head/><body><p><b>Could not write to the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> <html><head/><body><p><b>Не удалось записать данные в директорию назначения</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку, или же выберите другую директорию.</p></body></html> - <html><head/><body><p><b>The destination directory is not empty</b></p><p>An existing Morrowind installation is present in the specified location.</p><p>Please specify a different location, or go back and select the location as an existing installation.</p></body></html> <html><head/><body><p><b>Директория назначения содержит файлы</b></p><p>В указанной директории найдена установленная копия Morrowind.</p><p>Пожалуйста, выберите другую директорию, или же вернитесь на предыдущий шаг и выберите подключение установленной копии игры.</p></body></html> - Insufficient permissions Не хватает прав доступа - Destination not empty Выбранная директория не пустая - Select where to install Morrowind Выберите, куда установить Morrowind @@ -554,37 +433,30 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::LanguageSelectionPage - English Английский - French Французский - German Немецкий - Italian Итальянский - Polish Польский - Russian Русский - Spanish Испанский @@ -592,59 +464,42 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::MainWizard - OpenMW Wizard Мастер OpenMW - - Error opening Wizard log file Не удалось открыть лог-файл Мастера - - - <html><head/><body><p><b>Could not open %1 for writing</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> <html><head/><body><p><b>Не удалось открыть %1 для записи</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку.</p></body></html> - - <html><head/><body><p><b>Could not open %1 for reading</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> <html><head/><body><p><b>Не удалось открыть %1 для чтения</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку.</p></body></html> - - - Error opening OpenMW configuration file Не удалось открыть файл с настройками OpenMW - Quit Wizard Завершить работу Мастера - Are you sure you want to exit the Wizard? Вы уверены, что хотите завершить работу Мастера? - Error creating OpenMW configuration directory Не удалось создать директорию для настроек OpenMW - <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> <html><head/><body><p><b>Не удалось создать %1</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку.</p></body></html> - - Error writing OpenMW configuration file Не удалось записать данные в файл с настройками OpenMW @@ -652,207 +507,150 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::UnshieldWorker - - Failed to open Morrowind configuration file! Не удалось открыть файл с настройками Morrowind! - - Opening %1 failed: %2. Попытка открыть %1 не удалась: %2. - Failed to write Morrowind configuration file! Не удалось записать данные в файл с настройками Morrowind! - Writing to %1 failed: %2. Запись в %1 завершилась с ошибкой: %2. - Installing: %1 Установка: %1 - - Installing: %1 directory Установка: директория %1 - Installation finished! Установка завершена! - - Component parameter is invalid! Некорректный параметр для компонента! - - An invalid component parameter was supplied. Задан некорректный параметр для компонента. - Failed to find a valid archive containing %1.bsa! Retrying. Не удалось найти архив, содержащий %1.bsa! Повторная попытка. - Installing %1 Установка %1 - Installation media path not set! путь к установочному дистрибутиву не задан! - The source path for %1 was not set. Исходный пусть для %1 не задан. - - Cannot create temporary directory! Не удалось создать временную директорию! - - Failed to create %1. Не удалось создать %1. - Cannot move into temporary directory! Не удалось переместить во временную директорию! - Failed to move into %1. Не удалось переместить в %1. - Moving installation files Перемещение файлов установки - - - - Could not install directory! Не удалось установить директорию! - - - - Installing %1 to %2 failed. Не удалось установить %1 в %2. - Could not install translation file! Не удалось установить файл с переводом! - Failed to install *%1 files. Не удалось установить файлы *%1. - Could not install Morrowind data file! Не удалось установить файл с данными Morrowind! - - Failed to install %1. Не удалось установить %1. - Could not install Morrowind configuration file! Не удалось установить файл с настройками Morrowind! - Installing: Sound directory Установка: директория Sound - Could not find Tribunal data file! Не удалось найти файл с данными Tribunal! - - - Failed to find %1. Не удалось найти %1. - Could not find Tribunal patch file! Не удалось найти файл с патчем для Tribunal! - Could not find Bloodmoon data file! Не удалось найти файл с данными Bloodmoon! - Updating Morrowind configuration file Обновление файла с настройками Morrowind - %1 installation finished! Установка %1 завершена! - Extracting: %1 Извлечение: %1 - - - Failed to open InstallShield Cabinet File. Не удалось открыть файл InstallShield Cabinet. - - - Opening %1 failed. Не удалось открыть %1. - Failed to extract %1. Не удалось извлечь %1. - Complete path: %1 Полный путь: %1 diff --git a/files/lang/wizard_sv.ts b/files/lang/wizard_sv.ts new file mode 100644 index 0000000000..099a95d128 --- /dev/null +++ b/files/lang/wizard_sv.ts @@ -0,0 +1,658 @@ + + + + + ComponentSelectionPage + + WizardPage + WizardPage + + + Select Components + Välj komponenter + + + Which components should be installed? + Vilka komponenter ska installeras? + + + <html><head/><body><p>Select which official Morrowind expansions should be installed. For best results, it is recommended to have both expansions installed.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to install expansions later by re-running this Wizard.<br/></p></body></html> + <html><head/><body><p>Välj vilka officiella Morrowindexpansioner som ska installeras. För bäst resultat rekommenderas att båda expansionerna installeras.</p><p><span style=" font-weight:bold;">Note:</span> Det är möjligt att installera expansioner senare genom att köra denna guide igen.<br/></p></body></html> + + + Selected components: + Valda komponenter: + + + + ConclusionPage + + WizardPage + WizardPage + + + Completing the OpenMW Wizard + Färdigställer OpenMWs installationsguide + + + Placeholder + Placeholder + + + + ExistingInstallationPage + + WizardPage + WizardPage + + + Select Existing Installation + Välj befintlig installation + + + Select an existing installation for OpenMW to use or modify. + Välj en befintlig installation som OpenMW kan använda eller modifiera. + + + Detected installations: + Hittade installationer: + + + Browse... + Bläddra... + + + + ImportPage + + WizardPage + WizardPage + + + Import Settings + Importera inställningar + + + Import settings from the Morrowind installation. + Importera inställningar från Morrowindinstallationen. + + + <html><head/><body><p>OpenMW needs to import settings from the Morrowind configuration file in order to function properly.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to import settings later by re-running this Wizard.</p><p/></body></html> + <html><head/><body><p>OpenMW behöver importera inställningar från Morrowinds konfigurationsfil för att fungera korrekt.</p><p><span style=" font-weight:bold;">Notera:</span> Det är möjligt att importera inställningarna senare genom att köra denna guide igen.</p><p/></body></html> + + + Import Settings From Morrowind.ini + Importera inställningar från Morrowind.ini + + + Import Add-on and Plugin Selection + Importera tillägg- och pluginmarkering + + + Import Bitmap Fonts Setup From Morrowind.ini + Importera bitmapfonter från Morrowind.ini + + + Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, +so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar +to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. + Fonter som kommer med ordinarie spelmotor är suddiga med gränssnittsuppskalning och stödjer bara ett litet antal tecken, +så OpenMW tillhandahåller därför andra fonter för att undvika dessa problem. Dessa fonter använder TrueType-teknologi och är ganska lika +de ordinarie fonterna i Morrowind. Bocka i denna ruta om du ändå föredrar ordinarie fonter istället för OpenMWs, alternativt om du använder egna bitmapfonter. + + + + InstallationPage + + WizardPage + WizardPage + + + Installing + Installerar + + + Please wait while Morrowind is installed on your computer. + Vänta medan Morrowind installeras på din dator. + + + + InstallationTargetPage + + WizardPage + WizardPage + + + Select Installation Destination + Välj installationsplats + + + Where should Morrowind be installed? + Var ska Morrowind installeras? + + + Morrowind will be installed to the following location. + Morrowind kommer installeras på följande plats. + + + Browse... + Bläddra... + + + + IntroPage + + WizardPage + WizardPage + + + Welcome to the OpenMW Wizard + Välkommen till OpenMWs installationsguide + + + This Wizard will help you install Morrowind and its add-ons for OpenMW to use. + Denna guide hjälper dig att installera Morrowind och expansionerna för OpenMW. + + + + LanguageSelectionPage + + WizardPage + WizardPage + + + Select Morrowind Language + Välj Morrowinds språk + + + What is the language of the Morrowind installation? + Vad är språket på Morrowindinstallationen? + + + Select the language of the Morrowind installation. + Välj språket på Morrowindinstallationen. + + + + MethodSelectionPage + + WizardPage + WizardPage + + + Select Installation Method + Välj installationsmetod + + + <html><head/><body><p>Select how you would like to install <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> + <html><head/><body><p>Välj hur du vill installera <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> + + + Retail CD/DVD + Köpt CD/DVD + + + Install from a retail disc to a new location. + Installera från en köpt skiva till en ny plats. + + + Existing Installation + Befintlig installation + + + Select an existing installation. + Välj en befintlig installation. + + + Don't have a copy? + Äger du inte spelet? + + + Buy the game + Köp spelet + + + + QObject + + <br><b>Could not find Morrowind.ini</b><br><br>The Wizard needs to update settings in this file.<br><br>Press "Browse..." to specify the location manually.<br> + <br><b>Kunde inte hitta Morrowind.ini</b><br><br>Guiden behöver uppdatera inställningarna i denna fil.<br><br>Tryck på "Bläddra..." för att specificera filens plats manuellt.<br> + + + B&rowse... + B&läddra... + + + Select configuration file + Välj konfigurationsfil + + + <b>Morrowind.bsa</b> is missing!<br>Make sure your Morrowind installation is complete. + <b>Morrowind.bsa</b> saknas!<br>Se till att din Morrowindinstallation är komplett. + + + <br><b>There may be a more recent version of Morrowind available.</b><br><br>Do you wish to continue anyway?<br> + <br><b>Det kan finnas nyare version av Morrowind tillgänglig.</b><br><br>Vill du fortsätta ändå?<br> + + + Most recent Morrowind not detected + Senaste versionen av Morrowind hittades inte + + + Select a valid %1 installation media.<br><b>Hint</b>: make sure that it contains at least one <b>.cab</b> file. + Välj ett giltigt %1 installationsmedium.<br><b>Tips</b>: säkerställ att det finns åtminstone en <b>.cab</b>-fil. + + + There may be a more recent version of Morrowind available.<br><br>Do you wish to continue anyway? + Det kan finnas en mer uppdaterad version av Morrowind tillgänglig.<br><br>Vill du fortsätta ändå? + + + + Wizard::ComponentSelectionPage + + &Install + &Installera + + + &Skip + &Hoppa över + + + Morrowind (installed) + Morrowind (installerat) + + + Morrowind + Morrowind + + + Tribunal (installed) + Tribunal (installerat) + + + Tribunal + Tribunal + + + Bloodmoon (installed) + Bloodmoon (installerat) + + + Bloodmoon + Bloodmoon + + + About to install Tribunal after Bloodmoon + På väg att installera Tribunal efter Bloodmoon + + + <html><head/><body><p><b>You are about to install Tribunal</b></p><p>Bloodmoon is already installed on your computer.</p><p>However, it is recommended that you install Tribunal before Bloodmoon.</p><p>Would you like to re-install Bloodmoon?</p></body></html> + <html><head/><body><p><b>Du håller på att installera Tribunal</b></p><p>Bloodmoon finns redan installerat på din dator.</p><p>Det är dock rekommenderat att du installerar Tribunal före Bloodmoon.</p><p>Vill du ominstallera Bloodmoon?</p></body></html> + + + Re-install &Bloodmoon + Ominstallera &Bloodmoon + + + + Wizard::ConclusionPage + + <html><head/><body><p>The OpenMW Wizard successfully installed Morrowind on your computer.</p></body></html> + <html><head/><body><p>OpenMWs Installationsguide har installerat Morrowind på din dator.</p></body></html> + + + <html><head/><body><p>The OpenMW Wizard successfully modified your existing Morrowind installation.</body></html> + <html><head/><body><p>OpenMWs installationsguide har justerat din befintliga Morrowindinstallation.</body></html> + + + <html><head/><body><p>The OpenMW Wizard failed to install Morrowind on your computer.</p><p>Please report any bugs you might have encountered to our <a href="https://gitlab.com/OpenMW/openmw/issues">bug tracker</a>.<br/>Make sure to include the installation log.</p><br/></body></html> + <html><head/><body><p>OpenMWs installationsguide misslyckades med att installera Morrowind på din dator.</p><p>Vänligen rapportera eventuella buggar på vår <a href="https://gitlab.com/OpenMW/openmw/issues">bug tracker</a>.<br/>Se till att du inkluderar installationsloggen.</p><br/></body></html> + + + + Wizard::ExistingInstallationPage + + No existing installations detected + Inga befintliga installationer hittades + + + Error detecting Morrowind configuration + Kunde inte hitta Morrowind-konfigurationsfil + + + Morrowind configuration file (*.ini) + Morrowind konfigurationsfil (*.ini) + + + Select Morrowind.esm (located in Data Files) + Markera Morrowind.esm (hittas i Data Files) + + + Morrowind master file (Morrowind.esm) + Morrowind masterfil (Morrowind.esm) + + + Error detecting Morrowind files + Kunde inte hitta Morrowindfiler + + + + Wizard::InstallationPage + + <p>Attempting to install component %1.</p> + <p>Försöker installera komponent %1.</p> + + + Attempting to install component %1. + Försöker installera komponent %1. + + + %1 Installation + %1 Installation + + + Select %1 installation media + Välj %1 installationsmedia + + + <p><br/><span style="color:red;"><b>Error: The installation was aborted by the user</b></span></p> + <p><br/><span style="color:red;"><b>Fel: Installationen avbröts av användaren</b></span></p> + + + <p>Detected old version of component Morrowind.</p> + <p>Hittade gammal version av Morrowind.</p> + + + Detected old version of component Morrowind. + Hittade gammal version av komponenten Morrowind. + + + Morrowind Installation + Installation av Morrowind + + + Installation finished + Installationen klar + + + Installation completed successfully! + Installationen slutfördes! + + + Installation failed! + Installationen misslyckades! + + + <p><br/><span style="color:red;"><b>Error: %1</b></p> + <p><br/><span style="color:red;"><b>Fel: %1</b></p> + + + <p><span style="color:red;"><b>%1</b></p> + <p><span style="color:red;"><b>%1</b></p> + + + <html><head/><body><p><b>The Wizard has encountered an error</b></p><p>The error reported was:</p><p>%1</p><p>Press &quot;Show Details...&quot; for more information.</p></body></html> + <html><head/><body><p><b>Guiden har stött på ett fel</b></p><p>Felet som rapporterades var:</p><p>%1</p><p>Tryck på &quot;Visa detaljer...&quot; för mer information.</p></body></html> + + + An error occurred + Ett fel uppstod + + + + Wizard::InstallationTargetPage + + Error creating destination + Fel när målkatalogen skulle skapas + + + <html><head/><body><p><b>Could not create the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> + <html><head/><body><p><b>Kunde inte skapa målkatalogen</b></p><p>Se till att du har rätt behörigheter och försök igen, eller specificera en annan katalog.</p></body></html> + + + <html><head/><body><p><b>Could not write to the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> + <html><head/><body><p><b>Kunde inte skriva till målkatalogen</b></p><p>Se till att du har rätt behörigheter och försök igen, eller specificera en annan katalog.</p></body></html> + + + <html><head/><body><p><b>The destination directory is not empty</b></p><p>An existing Morrowind installation is present in the specified location.</p><p>Please specify a different location, or go back and select the location as an existing installation.</p></body></html> + <html><head/><body><p><b>Målkatalogen är inte tom</b></p><p>Det finns en befintlig Morrowindinstallation i den specificerade katalogen.</p><p>Välj en annan katalog eller gå tillbaka och välj katalogen som en befintlig installation.</p></body></html> + + + Insufficient permissions + Otillräckliga behörigheter + + + Destination not empty + Platsen är inte tom + + + Select where to install Morrowind + Välj där Morrowind ska installeras + + + + Wizard::LanguageSelectionPage + + English + Engelska + + + French + Franska + + + German + Tyska + + + Italian + Italienska + + + Polish + Polska + + + Russian + Ryska + + + Spanish + Spanska + + + + Wizard::MainWizard + + OpenMW Wizard + OpenMW installationsguide + + + Error opening Wizard log file + Kunde inte öppna guidens loggfil + + + <html><head/><body><p><b>Could not open %1 for writing</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> + <html><head/><body><p><b>Kunde inte öppna %1 för att skriva</b></p><p>Se till att du har rätt behörigheter och försök igen.</p></body></html> + + + <html><head/><body><p><b>Could not open %1 for reading</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> + <html><head/><body><p><b>Kunde inte öppna %1 för läsning</b></p><p>Se till att du har rätt behörigheter och försök igen.</p></body></html> + + + Error opening OpenMW configuration file + Fel när OpenMW-konfigurationsfil skulle öppnas + + + Quit Wizard + Avsluta guiden + + + Are you sure you want to exit the Wizard? + Är du säker på att du vill avsluta guiden? + + + Error creating OpenMW configuration directory + Fel vid skapande av OpenMW-konfigurationskatalog + + + <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> + <html><head/><body><p><b>Kunde inte skapa %1</b></p><p>Se till att du har rätt behörigheter och försök igen.</p></body></html> + + + Error writing OpenMW configuration file + Kunde inte skriva OpenMW-konfigurationsfil + + + + Wizard::UnshieldWorker + + Failed to open Morrowind configuration file! + Kunde inte öppna en Morrowind-konfigurationsfil! + + + Opening %1 failed: %2. + Öppnar %1 misslyckad: %2. + + + Failed to write Morrowind configuration file! + Kunde inte skriva en Morrowind-konfigurationsfil! + + + Writing to %1 failed: %2. + Skriver till %1 misslyckad: %2. + + + Installing: %1 + Installerar: %1 + + + Installing: %1 directory + Installerar: %1 katalog + + + Installation finished! + Installationen slutförd! + + + Component parameter is invalid! + Komponentparametern är ogiltig! + + + An invalid component parameter was supplied. + En ogiltig komponentparameter angavs. + + + Failed to find a valid archive containing %1.bsa! Retrying. + Misslyckades att hitta ett giltigt arkiv som innehåller %1.bsa! Försöker igen. + + + Installing %1 + Installerar %1 + + + Installation media path not set! + Sökväg till installationsmedia inte inställd! + + + The source path for %1 was not set. + Källsökvägen för %1 ställdes inte in. + + + Cannot create temporary directory! + Kan inte skapa temporär katalog! + + + Failed to create %1. + Kunde inte skapa %1. + + + Cannot move into temporary directory! + Kan inte flytta till temporär katalog! + + + Failed to move into %1. + Misslyckades att flytta till %1. + + + Moving installation files + Flyttar installationsfiler + + + Could not install directory! + Kunde inte installera katalog! + + + Installing %1 to %2 failed. + Installation %1 till %2 misslyckades. + + + Could not install translation file! + Kunde inte installera översättningsfil! + + + Failed to install *%1 files. + Kunde inte installera *%1 filer. + + + Could not install Morrowind data file! + Kunde inte installera Morrowind-datafil! + + + Failed to install %1. + Misslyckades att installera %1. + + + Could not install Morrowind configuration file! + Kunde inte installera Morrowind-konfigurationsfil! + + + Installing: Sound directory + Installerar: Ljudkatalog + + + Could not find Tribunal data file! + Tribunal-datafil hittades inte! + + + Failed to find %1. + Misslyckades att hitta %1. + + + Could not find Tribunal patch file! + Tribunal-patchfil hittades inte! + + + Could not find Bloodmoon data file! + Bloodmoon-datafil hittades inte! + + + Updating Morrowind configuration file + Uppdaterar Morrowind-konfigurationsfil + + + %1 installation finished! + %1 installation klar! + + + Extracting: %1 + Extraherar: %1 + + + Failed to open InstallShield Cabinet File. + Misslyckades att öppna en InstallShield Cabinet-fil. + + + Opening %1 failed. + Misslyckades att öppna %1. + + + Failed to extract %1. + Misslyckades att extrahera %1. + + + Complete path: %1 + Färdigställ sökväg: %1 + + + diff --git a/files/lua_api/CMakeLists.txt b/files/lua_api/CMakeLists.txt index 0b960ea259..526ee90955 100644 --- a/files/lua_api/CMakeLists.txt +++ b/files/lua_api/CMakeLists.txt @@ -17,6 +17,7 @@ set(LUA_API_FILES openmw/debug.lua openmw/input.lua openmw/interfaces.lua + openmw/markup.lua openmw/menu.lua openmw/nearby.lua openmw/postprocessing.lua diff --git a/files/lua_api/openmw/ambient.lua b/files/lua_api/openmw/ambient.lua index c10e50ff4a..ff776f84fb 100644 --- a/files/lua_api/openmw/ambient.lua +++ b/files/lua_api/openmw/ambient.lua @@ -95,4 +95,26 @@ -- @return #boolean -- @usage local isPlaying = ambient.isMusicPlaying(); +--- +-- Play an ambient voiceover. +-- @function [parent=#ambient] say +-- @param #string fileName Path to sound file in VFS +-- @param #string text Subtitle text (optional) +-- @usage -- play voiceover and print messagebox +-- ambient.say("Sound\\Vo\\Misc\\voice.mp3", "Subtitle text") +-- @usage -- play voiceover, without messagebox +-- ambient.say("Sound\\Vo\\Misc\\voice.mp3") + +--- +-- Stop an ambient voiceover +-- @function [parent=#ambient] stopSay +-- @param #string fileName Path to sound file in VFS +-- @usage ambient.stopSay(); + +--- +-- Check if an ambient voiceover is playing +-- @function [parent=#Sound] isSayActive +-- @return #boolean +-- @usage local isActive = isSayActive(); + return nil diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index ca2da8bb23..e7090fd986 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -10,10 +10,6 @@ -- The revision of OpenMW Lua API. It is an integer that is incremented every time the API is changed. See the actual value at the top of the page. -- @field [parent=#core] #number API_REVISION ---- --- A read-only list of all @{#FactionRecord}s in the world database. --- @field [parent=#core] #list<#FactionRecord> factions - --- -- Terminates the game and quits to the OS. Should be used only for testing purposes. -- @function [parent=#core] quit @@ -142,9 +138,9 @@ -- @return #string -- @usage if obj.recordId == core.getFormId('Skyrim.esm', 0x4d7da) then ... end -- @usage -- In ESM3 content files (e.g. Morrowind) ids are human-readable strings --- obj.ownerFactionId = 'blades' +-- obj.owner.factionId = 'blades' -- -- In ESM4 (e.g. Skyrim) ids should be constructed using `core.getFormId`: --- obj.ownerFactionId = core.getFormId('Skyrim.esm', 0x72834) +-- obj.owner.factionId = core.getFormId('Skyrim.esm', 0x72834) -- @usage -- local scripts -- local obj = nearby.getObjectByFormId(core.getFormId('Morrowind.esm', 128964)) -- @usage -- global scripts @@ -343,6 +339,11 @@ -- @field #string id Record id of the spell or item used to cast the spell -- @field #GameObject item The enchanted item used to cast the spell, or nil if the spell was not cast from an enchanted item. Note that if the spell was cast for a single-use enchantment such as a scroll, this will be nil. -- @field #GameObject caster The caster object, or nil if the spell has no defined caster +-- @field #boolean fromEquipment If set, this spell is tied to an equipped item and can only be ended by unequipping the item. +-- @field #boolean temporary If set, this spell effect is temporary and should end on its own. Either after a single application or after its duration has run out. +-- @field #boolean affectsBaseValues If set, this spell affects the base values of affected stats, rather than modifying current values. +-- @field #boolean stackable If set, this spell can be applied multiple times. If not set, the same spell can only be applied once from the same source (where source is determined by caster + item). In vanilla rules, consumables are stackable while spells and enchantments are not. +-- @field #number activeSpellId A number uniquely identifying this active spell within the affected actor's list of active spells. -- @field #list<#ActiveSpellEffect> effects The active effects (@{#ActiveSpellEffect}) of this spell. --- @@ -373,7 +374,7 @@ -- @type Enchantment -- @field #string id Enchantment id -- @field #number type @{#EnchantmentType} --- @field #number autocalcFlag If set, the casting cost should be computer rather than reading the cost field +-- @field #boolean autocalcFlag If set, the casting cost should be computed based on the effect list rather than read from the cost field -- @field #number cost -- @field #number charge Charge capacity. Should not be confused with current charge. -- @field #list<#MagicEffectWithParams> effects The effects (@{#MagicEffectWithParams}) of the enchantment @@ -622,41 +623,52 @@ -- @field #number Curse Curse -- @field #number Power Power, can be used once a day +--- @{#Spells}: Spells +-- @field [parent=#Magic] #Spells spells --- List of all @{#Spell}s. --- @field [parent=#Magic] #list<#Spell> spells --- @usage local spell = core.magic.spells['thunder fist'] -- get by id --- @usage local spell = core.magic.spells[1] -- get by index +-- @field [parent=#Spells] #list<#Spell> records A read-only list of all @{#Spell} records in the world database, may be indexed by recordId. +-- Implements [iterables#List](iterables.html#List) of #Spell. +-- @usage local spell = core.magic.spells.records['thunder fist'] -- get by id +-- @usage local spell = core.magic.spells.records[1] -- get by index -- @usage -- Print all powers --- for _, spell in pairs(core.magic.spells) do +-- for _, spell in pairs(core.magic.spells.records) do -- if spell.types == core.magic.SPELL_TYPE.Power then -- print(spell.name) -- end -- end +--- @{#Effects}: Magic Effects +-- @field [parent=#Magic] #Effects effects + --- Map from @{#MagicEffectId} to @{#MagicEffect} --- @field [parent=#Magic] #map<#number, #MagicEffect> effects +-- @field [parent=#Effects] #map<#number, #MagicEffect> records -- @usage -- Print all harmful effects --- for _, effect in pairs(core.magic.effects) do +-- for _, effect in pairs(core.magic.effects.records) do -- if effect.harmful then -- print(effect.name) -- end -- end -- @usage -- Look up the record of a specific effect and print its icon --- local mgef = core.magic.effects[core.magic.EFFECT_TYPE.Reflect] +-- local mgef = core.magic.effects.records[core.magic.EFFECT_TYPE.Reflect] -- print('Reflect Icon: '..tostring(mgef.icon)) ---- List of all @{#Enchantment}s. --- @field [parent=#Magic] #list<#Enchantment> enchantments --- @usage local enchantment = core.magic.enchantments['marara's boon'] -- get by id --- @usage local enchantment = core.magic.enchantments[1] -- get by index +--- @{#Enchantments}: Enchantments +-- @field [parent=#Magic] #Enchantments enchantments + +--- A read-only list of all @{#Enchantment} records in the world database, may be indexed by recordId. +-- Implements [iterables#List](iterables.html#List) and [iterables#Map](iterables.html#map-iterable) of #Enchantment. +-- @field [parent=#Enchantments] #list<#Enchantment> records +-- @usage local enchantment = core.magic.enchantments.records['marara's boon'] -- get by id +-- @usage local enchantment = core.magic.enchantments.records[1] -- get by index -- @usage -- Print all enchantments with constant effect --- for _, ench in pairs(core.magic.enchantments) do +-- for _, ench in pairs(core.magic.enchantments.records) do -- if ench.type == core.magic.ENCHANTMENT_TYPE.ConstantEffect then -- print(ench.id) -- end -- end + --- -- @type Spell -- @field #string id Spell id @@ -664,6 +676,8 @@ -- @field #number type @{#SpellType} -- @field #number cost -- @field #list<#MagicEffectWithParams> effects The effects (@{#MagicEffectWithParams}) of the spell +-- @field #boolean alwaysSucceedFlag If set, the spell should ignore skill checks and always succeed. +-- @field #boolean autocalcFlag If set, the casting cost should be computed based on the effect list rather than read from the cost field --- -- @type MagicEffect @@ -673,16 +687,28 @@ -- @field #string school Skill ID that is this effect's school -- @field #number baseCost -- @field openmw.util#Color color --- @field #boolean harmful +-- @field #boolean harmful If set, the effect is considered harmful and should elicit a hostile reaction from affected NPCs. -- @field #boolean continuousVfx Whether the magic effect's vfx should loop or not +-- @field #boolean hasDuration If set, the magic effect has a duration. As an example, divine intervention has no duration while fire damage does. +-- @field #boolean hasMagnitude If set, the magic effect depends on a magnitude. As an example, cure common disease has no magnitude while chameleon does. +-- @field #boolean isAppliedOnce If set, the magic effect is applied fully on cast, rather than being continuously applied over the effect's duration. For example, chameleon is applied once, while fire damage is continuously applied for the duration. +-- @field #boolean casterLinked If set, it is implied the magic effect links back to the caster in some way and should end immediately or never be applied if the caster dies or is not an actor. +-- @field #boolean nonRecastable If set, this effect cannot be re-applied until it has ended. This is used by bound equipment spells. -- @field #string particle Identifier of the particle texture --- @field #string castingStatic Identifier of the vfx static used for casting +-- @field #string castStatic Identifier of the vfx static used for casting -- @field #string hitStatic Identifier of the vfx static used on hit -- @field #string areaStatic Identifier of the vfx static used for AOE spells +-- @field #string boltStatic Identifier of the projectile vfx static used for ranged spells +-- @field #string castSound Identifier of the sound used for casting +-- @field #string hitSound Identifier of the sound used on hit +-- @field #string areaSound Identifier of the sound used for AOE spells +-- @field #string boltSound Identifier of the projectile sound used for ranged spells + --- -- @type MagicEffectWithParams -- @field #MagicEffect effect @{#MagicEffect} +-- @field #string id ID of the associated @{#MagicEffect} -- @field #string affectedSkill Optional skill ID -- @field #string affectedAttribute Optional attribute ID -- @field #number range @@ -690,6 +716,7 @@ -- @field #number magnitudeMin -- @field #number magnitudeMax -- @field #number duration +-- @field #number index Index of this effect within the original list of @{#MagicEffectWithParams} of the spell/enchantment/potion this effect came from. --- -- @type ActiveEffect @@ -701,6 +728,7 @@ -- @field #number magnitude current magnitude of the effect. Will be set to 0 when effect is removed or expires. -- @field #number magnitudeBase -- @field #number magnitudeModifier +-- @field #number index Index of this effect within the original list of @{#MagicEffectWithParams} of the spell/enchantment/potion this effect came from. --- @{#Sound}: Sounds and Speech -- @field [parent=#core] #Sound sound @@ -714,6 +742,8 @@ --- -- Play a 3D sound, attached to object +-- +-- In local scripts can be used only on self. -- @function [parent=#Sound] playSound3d -- @param #string soundId ID of Sound record to play -- @param #GameObject object Object to which we attach the sound @@ -733,6 +763,8 @@ --- -- Play a 3D sound file, attached to object +-- +-- In local scripts can be used only on self. -- @function [parent=#Sound] playSoundFile3d -- @param #string fileName Path to sound file in VFS -- @param #GameObject object Object to which we attach the sound @@ -752,6 +784,8 @@ --- -- Stop a 3D sound, attached to object +-- +-- In local scripts can be used only on self. -- @function [parent=#Sound] stopSound3d -- @param #string soundId ID of Sound record to stop -- @param #GameObject object Object on which we want to stop sound @@ -759,6 +793,8 @@ --- -- Stop a 3D sound file, attached to object +-- +-- In local scripts can be used only on self. -- @function [parent=#Sound] stopSoundFile3d -- @param #string fileName Path to sound file in VFS -- @param #GameObject object Object on which we want to stop sound @@ -781,42 +817,32 @@ -- @usage local isPlaying = core.sound.isSoundFilePlaying("Sound\\test.mp3", object); --- --- Play an animated voiceover. Has two overloads: --- --- * With an "object" argument: play sound for given object, with speaking animation if possible --- * Without an "object" argument: play sound globally, without object +-- Play an animated voiceover. +-- In local scripts can be used only on self. -- @function [parent=#Sound] say -- @param #string fileName Path to sound file in VFS --- @param #GameObject object Object on which we want to play an animated voiceover (optional) +-- @param #GameObject object Object on which we want to play an animated voiceover -- @param #string text Subtitle text (optional) -- @usage -- play voiceover for object and print messagebox -- core.sound.say("Sound\\Vo\\Misc\\voice.mp3", object, "Subtitle text") --- @usage -- play voiceover globally and print messagebox --- core.sound.say("Sound\\Vo\\Misc\\voice.mp3", "Subtitle text") --- @usage -- play voiceover for object without messagebox +-- @usage -- play voiceover for object, without messagebox -- core.sound.say("Sound\\Vo\\Misc\\voice.mp3", object) --- @usage -- play voiceover globally without messagebox --- core.sound.say("Sound\\Vo\\Misc\\voice.mp3") --- --- Stop animated voiceover +-- Stop an animated voiceover +-- +-- In local scripts can be used only on self. -- @function [parent=#Sound] stopSay -- @param #string fileName Path to sound file in VFS --- @param #GameObject object Object on which we want to stop an animated voiceover (optional) --- @usage -- stop voice for given object --- core.sound.stopSay(object); --- @usage -- stop global voice --- core.sound.stopSay(); +-- @param #GameObject object Object on which we want to stop an animated voiceover +-- @usage core.sound.stopSay(object); --- --- Check if animated voiceover is playing +-- Check if an animated voiceover is playing -- @function [parent=#Sound] isSayActive --- @param #GameObject object Object on which we want to check an animated voiceover (optional) +-- @param #GameObject object Object on which we want to check an animated voiceover -- @return #boolean --- @usage -- check voice for given object --- local isActive = isSayActive(object); --- @usage -- check global voice --- local isActive = isSayActive(); +-- @usage local isActive = isSayActive(object); --- -- @type SoundRecord @@ -827,11 +853,12 @@ -- @field #number maxRange Raw maximal range value, from 0 to 255 --- List of all @{#SoundRecord}s. --- @field [parent=#Sound] #list<#SoundRecord> sounds --- @usage local sound = core.sound.sounds['Ashstorm'] -- get by id --- @usage local sound = core.sound.sounds[1] -- get by index +-- @field [parent=#Sound] #list<#SoundRecord> records A read-only list of all @{#SoundRecord}s in the world database, may be indexed by recordId. +-- Implements [iterables#List](iterables.html#List) of #SoundRecord. +-- @usage local sound = core.sound.records['Ashstorm'] -- get by id +-- @usage local sound = core.sound.records[1] -- get by index -- @usage -- Print all sound files paths --- for _, sound in pairs(core.sound.sounds) do +-- for _, sound in pairs(core.sound.records) do -- print(sound.fileName) -- end @@ -844,7 +871,10 @@ --- `core.stats.Attribute` -- @type Attribute --- @field #list<#AttributeRecord> records A read-only list of all @{#AttributeRecord}s in the world database. +-- @field #list<#AttributeRecord> records A read-only list of all @{#AttributeRecord}s in the world database, may be indexed by recordId. +-- Implements [iterables#List](iterables.html#List) of #AttributeRecord. +-- @usage local record = core.stats.Attribute.records['example_recordid'] +-- @usage local record = core.stats.Attribute.records[1] --- -- Returns a read-only @{#AttributeRecord} @@ -857,7 +887,10 @@ --- `core.stats.Skill` -- @type Skill --- @field #list<#SkillRecord> records A read-only list of all @{#SkillRecord}s in the world database. +-- @field #list<#SkillRecord> records A read-only list of all @{#SkillRecord}s in the world database, may be indexed by recordId. +-- Implements [iterables#List](iterables.html#List) of #SkillRecord. +-- @usage local record = core.stats.Skill.records['example_recordid'] +-- @usage local record = core.stats.Skill.records[1] --- -- Returns a read-only @{#SkillRecord} @@ -881,6 +914,7 @@ -- @field #string specialization Skill specialization. Either combat, magic, or stealth. -- @field #MagicSchoolData school Optional magic school -- @field #string attribute The id of the skill's governing attribute +-- @field #table skillGain Table of the 4 possible skill gain values. See [SkillProgression#SkillUseType](interface_skill_progression.html#SkillUseType). --- -- @type MagicSchoolData @@ -891,6 +925,15 @@ -- @field #string failureSound VFS path to the failure sound -- @field #string hitSound VFS path to the hit sound +--- @{#Factions}: Factions +-- @field [parent=#core] #Factions factions + +--- +-- A read-only list of all @{#FactionRecord}s in the world database. +-- @field [parent=#Factions] #list<#FactionRecord> records +-- @usage local record = core.factions.records['example_recordid'] +-- @usage local record = core.factions.records[1] + --- -- Faction data record -- @type FactionRecord diff --git a/files/lua_api/openmw/interfaces.lua b/files/lua_api/openmw/interfaces.lua index 57103768d2..5ed98bd8be 100644 --- a/files/lua_api/openmw/interfaces.lua +++ b/files/lua_api/openmw/interfaces.lua @@ -26,6 +26,9 @@ --- -- @field [parent=#interfaces] scripts.omw.usehandlers#scripts.omw.usehandlers ItemUsage +--- +-- @field [parent=#interfaces] scripts.omw.skillhandlers#scripts.omw.skillhandlers SkillProgression + --- -- @function [parent=#interfaces] __index -- @param #interfaces self diff --git a/files/lua_api/openmw/markup.lua b/files/lua_api/openmw/markup.lua new file mode 100644 index 0000000000..c8776281d3 --- /dev/null +++ b/files/lua_api/openmw/markup.lua @@ -0,0 +1,37 @@ +--- +-- `openmw.markup` allows to work with markup languages. +-- @module markup +-- @usage local markup = require('openmw.markup') + + + +--- +-- Convert YAML data to Lua object +-- @function [parent=#markup] decodeYaml +-- @param #string inputData Data to decode. It has such limitations: +-- +-- 1. YAML format of [version 1.2](https://yaml.org/spec/1.2.2) is used. +-- 2. Map keys should be scalar values (strings, booleans, numbers). +-- 3. YAML tag system is not supported. +-- 4. If scalar is quoted, it is treated like a string. +-- Othewise type deduction works according to YAML 1.2 [Core Schema](https://yaml.org/spec/1.2.2/#103-core-schema). +-- 5. Circular dependencies between YAML nodes are not allowed. +-- 6. Lua 5.1 does not have integer numbers - all numeric scalars use a #number type (which use a floating point). +-- 7. Integer scalars numbers values are limited by the "int" range. Use floating point notation for larger number in YAML files. +-- @return #any Lua object (can be table or scalar value). +-- @usage local result = markup.decodeYaml('{ "x": 1 }'); +-- -- prints 1 +-- print(result["x"]) + +--- +-- Load YAML file from VFS to Lua object. Conventions are the same as in @{#markup.decodeYaml}. +-- @function [parent=#markup] loadYaml +-- @param #string fileName YAML file path in VFS. +-- @return #any Lua object (can be table or scalar value). +-- @usage -- file contains '{ "x": 1 }' data +-- local result = markup.loadYaml('test.yaml'); +-- -- prints 1 +-- print(result["x"]) + + +return nil diff --git a/files/lua_api/openmw/nearby.lua b/files/lua_api/openmw/nearby.lua index 70b09efd90..ea1b8738bc 100644 --- a/files/lua_api/openmw/nearby.lua +++ b/files/lua_api/openmw/nearby.lua @@ -89,6 +89,11 @@ -- radius = 10, -- }) +--- +-- A table of parameters for @{#nearby.castRenderingRay} and @{#nearby.asyncCastRenderingRay} +-- @type CastRenderingRayOptions +-- @field #table ignore A list of @{openmw.core#GameObject} to ignore while doing the ray cast + --- -- Cast ray from one point to another and find the first visual intersection with anything in the scene. -- As opposite to `castRay` can find an intersection with an object without collisions. @@ -97,6 +102,7 @@ -- @function [parent=#nearby] castRenderingRay -- @param openmw.util#Vector3 from Start point of the ray. -- @param openmw.util#Vector3 to End point of the ray. +-- @param #CastRenderingRayOptions -- @return #RayCastingResult --- @@ -105,6 +111,7 @@ -- @param openmw.async#Callback callback The callback to pass the result to (should accept a single argument @{openmw.nearby#RayCastingResult}). -- @param openmw.util#Vector3 from Start point of the ray. -- @param openmw.util#Vector3 to End point of the ray. +-- @param #CastRenderingRayOptions --- -- @type NAVIGATOR_FLAGS diff --git a/files/lua_api/openmw/self.lua b/files/lua_api/openmw/self.lua index 005017e5c3..6123c36ae6 100644 --- a/files/lua_api/openmw/self.lua +++ b/files/lua_api/openmw/self.lua @@ -22,15 +22,6 @@ -- The object the script is attached to (readonly) -- @field [parent=#self] openmw.core#GameObject object ---- NPC who owns the object or `nil` (mutable). --- @field [parent=#self] #string ownerRecordId - ---- Faction who owns the object or `nil` (mutable). --- @field [parent=#self] #string ownerFactionId - ---- Rank required to be allowed to pick up the object (mutable). --- @field [parent=#self] #number ownerFactionRank - --- -- Movement controls (only for actors) diff --git a/files/lua_api/openmw/storage.lua b/files/lua_api/openmw/storage.lua index 2335719be8..575b0f83d9 100644 --- a/files/lua_api/openmw/storage.lua +++ b/files/lua_api/openmw/storage.lua @@ -15,6 +15,15 @@ -- end -- end)) +--- Possible @{#LifeTime} values +-- @field [parent=#storage] #LifeTime LIFE_TIME + +--- `storage.LIFE_TIME` +-- @type LifeTime +-- @field #number Persistent "0" Data is stored for the whole game session and remains on disk after quitting the game +-- @field #number GameSession "1" Data is stored for the whole game session +-- @field #number Temporary "2" Data is stored until script context reset + --- -- Get a section of the global storage; can be used by any script, but only global scripts can change values. -- Menu scripts can only access it when a game is running. @@ -83,12 +92,28 @@ -- @param #table values (optional) New values --- --- Make the whole section temporary: will be removed on exit or when load a save. +-- (DEPRECATED, use `setLifeTime(openmw.storage.LIFE_TIME.Temporary)`) Make the whole section temporary: will be removed on exit or when load a save. -- Temporary sections have the same interface to get/set values, the only difference is they will not -- be saved to the permanent storage on exit. --- This function can not be used for a global storage section from a local script. +-- This function can be used for a global storage section from a global script or for a player storage section from a player or menu script. -- @function [parent=#StorageSection] removeOnExit -- @param self +-- @usage +-- local storage = require('openmw.storage') +-- local myModData = storage.globalSection('MyModExample') +-- myModData:removeOnExit() + +--- +-- Set the life time of given storage section. +-- New sections initially have a Persistent life time. +-- This function can be used for a global storage section from a global script or for a player storage section from a player or menu script. +-- @function [parent=#StorageSection] setLifeTime +-- @param self +-- @param #LifeTime lifeTime Section life time +-- @usage +-- local storage = require('openmw.storage') +-- local myModData = storage.globalSection('MyModExample') +-- myModData:setLifeTime(storage.LIFE_TIME.Temporary) --- -- Set value by a string key; can not be used for global storage from a local script. diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 44dd578e41..90344cbae1 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -16,11 +16,17 @@ -- @return #number --- --- Check if the given actor is dead. +-- Check if the given actor is dead (health reached 0, so death process started). -- @function [parent=#Actor] isDead -- @param openmw.core#GameObject actor -- @return #boolean +--- +-- Check if the given actor's death process is finished. +-- @function [parent=#Actor] isDeathFinished +-- @param openmw.core#GameObject actor +-- @return #boolean + --- -- Agent bounds to be used for pathfinding functions. -- @function [parent=#Actor] getPathfindingAgentBounds @@ -68,7 +74,7 @@ -- @field #number Ammunition --- --- Available @{#EQUIPMENT_SLOT} values. Used in `Actor.equipment(obj)` and `Actor.setEquipment(obj, eqp)`. +-- Available @{#EQUIPMENT_SLOT} values. Used in `Actor.getEquipment(obj)` and `Actor.setEquipment(obj, eqp)`. -- @field [parent=#Actor] #EQUIPMENT_SLOT EQUIPMENT_SLOT --- @@ -296,17 +302,59 @@ -- end --- --- Get whether a specific spell is active on the actor. +-- Get whether any instance of the specific spell is active on the actor. -- @function [parent=#ActorActiveSpells] isSpellActive -- @param self --- @param #any recordOrId record or string record ID of the active spell's source. valid records are @{openmw.core#Spell}, @{openmw.core#Enchantment}, #IngredientRecord, or #PotionRecord +-- @param #any recordOrId A record or string record ID. Valid records are @{openmw.core#Spell}, enchanted @{#Item}, @{#IngredientRecord}, or @{#PotionRecord}. -- @return true if spell is active, false otherwise --- --- Remove the given spell and all its effects from the given actor's active spells. +-- If true, the actor has not used this power in the last 24h. Will return true for powers the actor does not have. +-- @function [parent=#ActorActiveSpells] canUsePower +-- @param self +-- @param #any spellOrId A @{openmw.core#Spell} or string record id. + +--- +-- Remove an active spell based on active spell ID (see @{openmw.core#ActiveSpell.activeSpellId}). Can only be used in global scripts or on self. Can only be used to remove spells with the temporary flag set (see @{openmw.core#ActiveSpell.temporary}). -- @function [parent=#ActorActiveSpells] remove -- @param self --- @param #any spellOrId @{openmw.core#Spell} or string spell id +-- @param #any id Active spell ID. + +--- +-- Adds a new spell to the list of active spells (only in global scripts or on self). +-- Note that this does not play any related VFX or sounds. +-- @function [parent=#ActorActiveSpells] add +-- @param self +-- @param #table options A table of parameters. Must contain the following required parameters: +-- +-- * `id` - A string record ID. Valid records are @{openmw.core#Spell}, enchanted @{#Item}, @{#IngredientRecord}, or @{#PotionRecord}. +-- * `effects` - A list of indexes of the effects to be applied. These indexes must be in range of the record's list of @{openmw.core#MagicEffectWithParams}. Note that for Ingredients, normal ingredient consumption rules will be applied to effects. +-- +-- And may contain the following optional parameters: +-- +-- * `name` - The name to show in the list of active effects in the UI. Default: Name of the record identified by the id. +-- * `ignoreResistances` - If true, resistances will be ignored. Default: false +-- * `ignoreSpellAbsorption` - If true, spell absorption will not be applied. Default: false. +-- * `ignoreReflect` - If true, reflects will not be applied. Default: false. +-- * `caster` - A game object that identifies the caster. Default: nil +-- * `item` - A game object that identifies the specific enchanted item instance used to cast the spell. Default: nil +-- * `stackable` - If true, the spell will be able to stack. If false, existing instances of spells with the same id from the same source (where source is caster + item) +-- * `quiet` - If true, no messages will be printed if the spell is an Ingredient and it had no effect. Always true if the target is not the player. +-- @usage +-- -- Adds the effect of the chameleon spell to the character +-- Actor.activeSpells(self):add({id = 'chameleon', effects = { 0 }}) +-- @usage +-- -- Adds the effect of a standard potion of intelligence, without consuming any potions from the character's inventory. +-- -- Note that stackable = true to let the effect stack like a potion should. +-- Actor.activeSpells(self):add({id = 'p_fortify_intelligence_s', effects = { 0 }, stackable = true}) +-- @usage +-- -- Adds the negative effect of Greef twice over, and renames it to Good Greef. +-- Actor.activeSpells(self):add({id = 'potion_comberry_brandy_01', effects = { 1, 1 }, stackable = true, name = 'Good Greef'}) +-- @usage +-- -- Has the same effect as if the actor ate a chokeweed. With the same variable effect based on skill / random chance. +-- Actor.activeSpells(self):add({id = 'ingred_chokeweed_01', effects = { 0 }, stackable = true, name = 'Chokeweed'}) +-- -- Same as above, but uses a different index. Note that if multiple indexes are used, the randomicity is applied separately for each effect. +-- Actor.activeSpells(self):add({id = 'ingred_chokeweed_01', effects = { 1 }, stackable = true, name = 'Chokeweed'}) --- -- Return the spells (@{#ActorSpells}) of the given actor. @@ -352,10 +400,29 @@ -- @function [parent=#ActorSpells] clear -- @param self +--- Values affect how much each attribute can be increased at level up, and are all reset to 0 upon level up. +-- @type SkillIncreasesForAttributeStats +-- @field #number agility Number of contributions to agility for the next level up. +-- @field #number endurance Number of contributions to endurance for the next level up. +-- @field #number intelligence Number of contributions to intelligence for the next level up. +-- @field #number luck Number of contributions to luck for the next level up. +-- @field #number personality Number of contributions to personality for the next level up. +-- @field #number speed Number of contributions to speed for the next level up. +-- @field #number strength Number of contributions to strength for the next level up. +-- @field #number willpower Number of contributions to willpower for the next level up. + +--- Values affect the graphic used on the level up screen, and are all reset to 0 upon level up. +-- @type SkillIncreasesForSpecializationStats +-- @field #number combat Number of contributions to combat specialization for the next level up. +-- @field #number magic Number of contributions to magic specialization for the next level up. +-- @field #number stealth Number of contributions to stealth specialization for the next level up. + --- -- @type LevelStat -- @field #number current The actor's current level. --- @field #number progress The NPC's level progress (read-only.) +-- @field #number progress The NPC's level progress. +-- @field #SkillIncreasesForAttributeStats skillIncreasesForAttribute The NPC's attribute contributions towards the next level up. Values affect how much each attribute can be increased at level up. +-- @field #SkillIncreasesForSpecializationStats skillIncreasesForSpecialization The NPC's attribute contributions towards the next level up. Values affect the graphic used on the level up screen. --- -- @type DynamicStat @@ -378,6 +445,12 @@ -- @field #number modifier The skill's modifier. -- @field #number progress [0-1] The NPC's skill progress. +--- +-- @type AIStat +-- @field #number base The stat's base value. +-- @field #number modifier The stat's modifier. +-- @field #number modified The actor's current ai value (read-only.) + --- -- @type DynamicStats @@ -399,6 +472,33 @@ -- @param openmw.core#GameObject actor -- @return #DynamicStat +--- +-- @type AIStats + +--- +-- Alarm (returns @{#AIStat}) +-- @function [parent=#AIStats] alarm +-- @param openmw.core#GameObject actor +-- @return #AIStat + +--- +-- Fight (returns @{#AIStat}) +-- @function [parent=#AIStats] fight +-- @param openmw.core#GameObject actor +-- @return #AIStat + +--- +-- Flee (returns @{#AIStat}) +-- @function [parent=#AIStats] flee +-- @param openmw.core#GameObject actor +-- @return #AIStat + +--- +-- Hello (returns @{#AIStat}) +-- @function [parent=#AIStats] hello +-- @param openmw.core#GameObject actor +-- @return #AIStat + --- -- @type AttributeStats @@ -619,6 +719,7 @@ -- @type ActorStats -- @field #DynamicStats dynamic -- @field #AttributeStats attributes +-- @field #AIStats ai --- -- Level (returns @{#LevelStat}) @@ -693,7 +794,13 @@ -- @type Creature -- @extends #Actor -- @field #Actor baseType @{#Actor} --- @field #list<#CreatureRecord> records A read-only list of all @{#CreatureRecord}s in the world database. + +--- +-- A read-only list of all @{#CreatureRecord}s in the world database, may be indexed by recordId. +-- Implements [iterables#List](iterables.html#List) of #CreatureRecord. +-- @field [parent=#Creature] #list<#CreatureRecord> records +-- @usage local record = types.NPC.classes['example_recordid'] +-- @usage local record = types.NPC.classes[1] --- -- Whether the object is a creature. @@ -747,7 +854,13 @@ -- @extends #Actor -- @field #Actor baseType @{#Actor} -- @field [parent=#NPC] #NpcStats stats --- @field #list<#NpcRecord> records A read-only list of all @{#NpcRecord}s in the world database. + +--- +-- A read-only list of all @{#NpcRecord}s in the world database, may be indexed by recordId. +-- Implements [iterables#List](iterables.html#List) of #NpcRecord. +-- @field [parent=#NPC] #map<#NpcRecord> records +-- @usage local record = types.NPC.classes['example_recordid'] +-- @usage local record = types.NPC.classes[1] --- -- Whether the object is an NPC or a Player. @@ -900,8 +1013,11 @@ -- @field [parent=#NPC] #Classes classes --- --- A read-only list of all @{#ClassRecord}s in the world database. +-- A read-only list of all @{#ClassRecord}s in the world database, may be indexed by recordId. +-- Implements [iterables#List](iterables.html#List) of #ClassRecord. -- @field [parent=#Classes] #list<#ClassRecord> records +-- @usage local record = types.NPC.classes['example_recordid'] +-- @usage local record = types.NPC.classes[1] --- -- Returns a read-only @{#ClassRecord} @@ -933,6 +1049,43 @@ -- @param #any objectOrRecordId -- @return #NpcRecord +--- @{#Races}: Race data +-- @field [parent=#NPC] #Races races + +--- +-- A read-only list of all @{#RaceRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #RaceRecord. +-- @field [parent=#Races] #list<#RaceRecord> records +-- @usage local record = types.NPC.classes['example_recordid'] +-- @usage local record = types.NPC.classes[1] + +--- +-- Returns a read-only @{#RaceRecord} +-- @function [parent=#Races] record +-- @param #string recordId +-- @return #RaceRecord + +--- +-- Race data record +-- @type RaceRecord +-- @field #string id Race id +-- @field #string name Race name +-- @field #string description Race description +-- @field #map<#string, #number> skills A map of bonus skill points by skill ID +-- @field #list<#string> spells A read-only list containing the ids of all spells inherent to the race +-- @field #bool isPlayable True if the player can pick this race in character generation +-- @field #bool isBeast True if this race is a beast race +-- @field #GenderedNumber height Height values +-- @field #GenderedNumber weight Weight values +-- @field #map<#string, #GenderedNumber> attributes A read-only table of attribute ID to base value +-- @usage -- Get base strength for men +-- strength = types.NPC.races.records[1].attributes.strength.male + +--- +-- @type GenderedNumber +-- @field #number male Male value +-- @field #number female Female value + --- -- @type NpcRecord -- @field #string id The record ID of the NPC @@ -970,6 +1123,12 @@ -- @param openmw.core#GameObject player -- @return #number +--- +-- Sets the bounty or crime level of the player, may only be used in global scripts +-- @function [parent=#Player] setCrimeLevel +-- @param openmw.core#GameObject player +-- @param #number crimeLevel The requested crime level + --- -- Whether the character generation for this player is finished. -- @function [parent=#Player] isCharGenFinished @@ -1045,9 +1204,45 @@ -- Values that can be used with getControlSwitch/setControlSwitch. -- @field [parent=#Player] #CONTROL_SWITCH CONTROL_SWITCH +--- +-- @function [parent=#Player] getBirthSign +-- @param openmw.core#GameObject player +-- @return #string The player's birth sign + +--- +-- Can be used only in global scripts. Note that this does not update the player's spells. +-- @function [parent=#Player] setBirthSign +-- @param openmw.core#GameObject player +-- @param #any recordOrId Record or string ID of the birth sign to assign + +--- @{#BirthSigns}: Birth Sign Data +-- @field [parent=#Player] #BirthSigns birthSigns + +--- +-- A read-only list of all @{#BirthSignRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #BirthSignRecord. +-- @field [parent=#BirthSigns] #list<#BirthSignRecord> records +-- @usage local record = types.NPC.classes['example_recordid'] +-- @usage local record = types.NPC.classes[1] + +--- +-- Returns a read-only @{#BirthSignRecord} +-- @function [parent=#BirthSigns] record +-- @param #string recordId +-- @return #BirthSignRecord + +--- +-- Birth sign data record +-- @type BirthSignRecord +-- @field #string id Birth sign id +-- @field #string name Birth sign name +-- @field #string description Birth sign description +-- @field #string texture Birth sign texture +-- @field #list<#string> spells A read-only list containing the ids of all spells gained from this sign. + --- -- Send an event to menu scripts. --- @function [parent=#core] sendMenuEvent +-- @function [parent=#Player] sendMenuEvent -- @param openmw.core#GameObject player -- @param #string eventName -- @param eventData @@ -1060,7 +1255,6 @@ -- @type Armor -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#ArmorRecord> records A read-only list of all @{#ArmorRecord}s in the world database. --- -- Whether the object is an Armor. @@ -1082,6 +1276,13 @@ -- @field #number LBracer -- @field #number RBracer +--- +-- A read-only list of all @{#ArmorRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #ArmorRecord. +-- @field [parent=#Armor] #list<#ArmorRecord> records +-- @usage local record = types.Armor.records['example_recordid'] +-- @usage local record = types.Armor.records[1] + --- @{#ArmorTYPE} -- @field [parent=#Armor] #ArmorTYPE TYPE @@ -1127,7 +1328,13 @@ -- @type Book -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#BookRecord> records A read-only list of all @{#BookRecord}s in the world database. + +--- +-- A read-only list of all @{#BookRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #BookRecord. +-- @field [parent=#Book] #list<#BookRecord> records +-- @usage local record = types.Book.records['example_recordid'] +-- @usage local record = types.Book.records[1] --- -- Whether the object is a Book. @@ -1203,7 +1410,13 @@ -- @type Clothing -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#ClothingRecord> records A read-only list of all @{#ClothingRecord}s in the world database. + +--- +-- A read-only list of all @{#ClothingRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #ClothingRecord. +-- @field [parent=#Clothing] #list<#ClothingRecord> records +-- @usage local record = types.Clothing.records['example_recordid'] +-- @usage local record = types.Clothing.records[1] --- -- Whether the object is a Clothing. @@ -1269,7 +1482,13 @@ -- @type Ingredient -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#IngredientRecord> records A read-only list of all @{#IngredientRecord}s in the world database. + +--- +-- A read-only list of all @{#IngredientRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #IngredientRecord. +-- @field [parent=#Ingredient] #list<#IngredientRecord> records +-- @usage local record = types.Ingredient.records['example_recordid'] +-- @usage local record = types.Ingredient.records[1] --- -- Whether the object is an Ingredient. @@ -1356,7 +1575,13 @@ -- @type Light -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#LightRecord> records A read-only list of all @{#LightRecord}s in the world database. + +--- +-- A read-only list of all @{#LightRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #LightRecord. +-- @field [parent=#Light] #list<#LightRecord> records +-- @usage local record = types.Light.records['example_recordid'] +-- @usage local record = types.Light.records[1] --- -- Whether the object is a Light. @@ -1394,7 +1619,13 @@ -- @type Miscellaneous -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#MiscellaneousRecord> records A read-only list of all @{#MiscellaneousRecord}s in the world database. + +--- +-- A read-only list of all @{#MiscellaneousRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #MiscellaneousRecord. +-- @field [parent=#Miscellaneous] #list<#MiscellaneousRecord> records +-- @usage local record = types.Miscellaneous.records['example_recordid'] +-- @usage local record = types.Miscellaneous.records[1] --- -- Whether the object is a Miscellaneous. @@ -1445,7 +1676,13 @@ -- @type Potion -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#PotionRecord> records A read-only list of all @{#PotionRecord}s in the world database. + +--- +-- A read-only list of all @{#PotionRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #PotionRecord. +-- @field [parent=#Potion] #list<#PotionRecord> records +-- @usage local record = types.Potion.records['example_recordid'] +-- @usage local record = types.Potion.records[1] --- -- Whether the object is a Potion. @@ -1486,7 +1723,13 @@ -- @type Weapon -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#WeaponRecord> records A read-only list of all @{#WeaponRecord}s in the world database. + +--- +-- A read-only list of all @{#WeaponRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #WeaponRecord. +-- @field [parent=#Weapon] #list<#WeaponRecord> records +-- @usage local record = types.Weapon.records['example_recordid'] +-- @usage local record = types.Weapon.records[1] --- -- Whether the object is a Weapon. @@ -1558,7 +1801,14 @@ -- @type Apparatus -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#ApparatusRecord> records A read-only list of all @{#ApparatusRecord}s in the world database. + + +--- +-- A read-only list of all @{#ApparatusRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #ApparatusRecord. +-- @field [parent=#Apparatus] #list<#ApparatusRecord> records +-- @usage local record = types.Apparatus.records['example_recordid'] +-- @usage local record = types.Apparatus.records[1] --- -- Whether the object is an Apparatus. @@ -1601,7 +1851,13 @@ -- @type Lockpick -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#LockpickRecord> records A read-only list of all @{#LockpickRecord}s in the world database. + +--- +-- A read-only list of all @{#LockpickRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #LockpickRecord. +-- @field [parent=#Lockpick] #list<#LockpickRecord> records +-- @usage local record = types.Lockpick.records['example_recordid'] +-- @usage local record = types.Lockpick.records[1] --- -- Whether the object is a Lockpick. @@ -1634,7 +1890,13 @@ -- @type Probe -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#ProbeRecord> records A read-only list of all @{#ProbeRecord}s in the world database. + +--- +-- A read-only list of all @{#ProbeRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #ProbeRecord. +-- @field [parent=#Probe] #list<#ProbeRecord> records +-- @usage local record = types.Probe.records['example_recordid'] +-- @usage local record = types.Probe.records[1] --- -- Whether the object is a Probe. @@ -1667,7 +1929,13 @@ -- @type Repair -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#RepairRecord> records A read-only list of all @{#RepairRecord}s in the world database. + +--- +-- A read-only list of all @{#RepairRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #RepairRecord. +-- @field [parent=#Repair] #list<#RepairRecord> records +-- @usage local record = types.Repair.records['example_recordid'] +-- @usage local record = types.Repair.records[1] --- -- Whether the object is a Repair. @@ -1698,7 +1966,13 @@ --- -- @type Activator --- @field #list<#ActivatorRecord> records A read-only list of all @{#ActivatorRecord}s in the world database. + +--- +-- A read-only list of all @{#ActivatorRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #ActivatorRecord. +-- @field [parent=#Activator] #list<#ActivatorRecord> records +-- @usage local record = types.Activator.records['example_recordid'] +-- @usage local record = types.Activator.records[1] --- -- Whether the object is an Activator. @@ -1735,7 +2009,13 @@ -- @type Container -- @extends #Lockable -- @field #Lockable baseType @{#Lockable} --- @field #list<#ContainerRecord> records A read-only list of all @{#ContainerRecord}s in the world database. + +--- +-- A read-only list of all @{#ContainerRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #ContainerRecord. +-- @field [parent=#Container] #list<#ContainerRecord> records +-- @usage local record = types.Container.records['example_recordid'] +-- @usage local record = types.Container.records[1] --- -- Container content. @@ -1790,7 +2070,13 @@ -- @type Door -- @extends #Lockable -- @field #Lockable baseType @{#Lockable} --- @field #list<#DoorRecord> records A read-only list of all @{#DoorRecord}s in the world database. + +--- +-- A read-only list of all @{#DoorRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #DoorRecord. +-- @field [parent=#Door] #list<#DoorRecord> records +-- @usage local record = types.Door.records['example_recordid'] +-- @usage local record = types.Door.records[1] --- -- Whether the object is a Door. @@ -1844,7 +2130,13 @@ --- -- @type Static --- @field #list<#StaticRecord> records A read-only list of all @{#StaticRecord}s in the world database. + +--- +-- A read-only list of all @{#StaticRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #StaticRecord. +-- @field [parent=#Static] #list<#StaticRecord> records +-- @usage local record = types.Static.records['example_recordid'] +-- @usage local record = types.Static.records[1] --- -- Whether the object is a Static. @@ -1869,7 +2161,13 @@ --- -- @type CreatureLevelledList --- @field #list<#CreatureLevelledListRecord> records A read-only list of all @{#CreatureLevelledListRecord}s in the world database. + +--- +-- A read-only list of all @{#CreatureLevelledListRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #CreatureLevelledListRecord. +-- @field [parent=#CreatureLevelledList] #list<#CreatureLevelledListRecord> records +-- @usage local record = types.CreatureLevelledList.records['example_recordid'] +-- @usage local record = types.CreatureLevelledList.records[1] --- -- Whether the object is a CreatureLevelledList. @@ -1953,7 +2251,13 @@ --- -- @type ESM4Terminal --- @field #list<#ESM4TerminalRecord> records A read-only list of all @{#ESM4TerminalRecord}s in the world database. + +--- +-- A read-only list of all @{#ESM4TerminalRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #ESM4TerminalRecord. +-- @field [parent=#ESM4Terminal] #list<#ESM4TerminalRecord> records +-- @usage local record = types.ESM4Terminal.records['example_recordid'] +-- @usage local record = types.ESM4Terminal.records[1] --- -- Whether the object is a ESM4Terminal. @@ -2018,9 +2322,11 @@ -- @return #ESM4DoorRecord --- --- Returns a read-only list of all @{#ESM4DoorRecord}s in the world database. --- @function [parent=#ESM4Door] records --- @return #list<#ESM4DoorRecord> +-- A read-only list of all @{#ESM4DoorRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #ESM4DoorRecord. +-- @field [parent=#ESM4Door] #list<#ESM4DoorRecord> records +-- @usage local record = types.ESM4Door.records['example_recordid'] +-- @usage local record = types.ESM4Door.records[1] --- -- @type ESM4DoorRecord diff --git a/files/opencs/activator.png b/files/opencs/activator.png deleted file mode 100644 index ded6ab835b..0000000000 Binary files a/files/opencs/activator.png and /dev/null differ diff --git a/files/opencs/activator.svg b/files/opencs/activator.svg new file mode 100644 index 0000000000..11a23003da --- /dev/null +++ b/files/opencs/activator.svg @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/apparatus.png b/files/opencs/apparatus.png deleted file mode 100644 index 4e95397dfa..0000000000 Binary files a/files/opencs/apparatus.png and /dev/null differ diff --git a/files/opencs/apparatus.svg b/files/opencs/apparatus.svg new file mode 100644 index 0000000000..1345efee56 --- /dev/null +++ b/files/opencs/apparatus.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + diff --git a/files/opencs/armor.png b/files/opencs/armor.png deleted file mode 100644 index 6f5cc83c59..0000000000 Binary files a/files/opencs/armor.png and /dev/null differ diff --git a/files/opencs/armor.svg b/files/opencs/armor.svg new file mode 100644 index 0000000000..e7925a23bc --- /dev/null +++ b/files/opencs/armor.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + diff --git a/files/opencs/attribute.png b/files/opencs/attribute.png deleted file mode 100644 index c124567179..0000000000 Binary files a/files/opencs/attribute.png and /dev/null differ diff --git a/files/opencs/attribute.svg b/files/opencs/attribute.svg new file mode 100644 index 0000000000..5e02090f07 --- /dev/null +++ b/files/opencs/attribute.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + diff --git a/files/opencs/birthsign.png b/files/opencs/birthsign.png deleted file mode 100644 index 861dbd4d10..0000000000 Binary files a/files/opencs/birthsign.png and /dev/null differ diff --git a/files/opencs/birthsign.svg b/files/opencs/birthsign.svg new file mode 100644 index 0000000000..c9316ed9d6 --- /dev/null +++ b/files/opencs/birthsign.svg @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/body-part.png b/files/opencs/body-part.png deleted file mode 100644 index 8baec72024..0000000000 Binary files a/files/opencs/body-part.png and /dev/null differ diff --git a/files/opencs/body-part.svg b/files/opencs/body-part.svg new file mode 100644 index 0000000000..60c06defc8 --- /dev/null +++ b/files/opencs/body-part.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + diff --git a/files/opencs/book.png b/files/opencs/book.png deleted file mode 100644 index fdecb1585a..0000000000 Binary files a/files/opencs/book.png and /dev/null differ diff --git a/files/opencs/book.svg b/files/opencs/book.svg new file mode 100644 index 0000000000..b34827d7b1 --- /dev/null +++ b/files/opencs/book.svg @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/brush-circle.png b/files/opencs/brush-circle.png deleted file mode 100644 index 22e92c1c7f..0000000000 Binary files a/files/opencs/brush-circle.png and /dev/null differ diff --git a/files/opencs/brush-circle.svg b/files/opencs/brush-circle.svg new file mode 100644 index 0000000000..8bb2491173 --- /dev/null +++ b/files/opencs/brush-circle.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/brush-custom.png b/files/opencs/brush-custom.png deleted file mode 100644 index 58c0485502..0000000000 Binary files a/files/opencs/brush-custom.png and /dev/null differ diff --git a/files/opencs/brush-custom.svg b/files/opencs/brush-custom.svg new file mode 100644 index 0000000000..9eb59597cc --- /dev/null +++ b/files/opencs/brush-custom.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/brush-point.png b/files/opencs/brush-point.png deleted file mode 100644 index 19b2354f8c..0000000000 Binary files a/files/opencs/brush-point.png and /dev/null differ diff --git a/files/opencs/brush-point.svg b/files/opencs/brush-point.svg new file mode 100644 index 0000000000..384ed0af4a --- /dev/null +++ b/files/opencs/brush-point.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/brush-square.png b/files/opencs/brush-square.png deleted file mode 100644 index 08628772e7..0000000000 Binary files a/files/opencs/brush-square.png and /dev/null differ diff --git a/files/opencs/brush-square.svg b/files/opencs/brush-square.svg new file mode 100644 index 0000000000..09fdcb3e16 --- /dev/null +++ b/files/opencs/brush-square.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/camera-first-person.png b/files/opencs/camera-first-person.png deleted file mode 100644 index b497198df5..0000000000 Binary files a/files/opencs/camera-first-person.png and /dev/null differ diff --git a/files/opencs/camera-first-person.svg b/files/opencs/camera-first-person.svg new file mode 100644 index 0000000000..d56c6b5afb --- /dev/null +++ b/files/opencs/camera-first-person.svg @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/camera-free.png b/files/opencs/camera-free.png deleted file mode 100644 index d8e7ccae5d..0000000000 Binary files a/files/opencs/camera-free.png and /dev/null differ diff --git a/files/opencs/camera-free.svg b/files/opencs/camera-free.svg new file mode 100644 index 0000000000..ee1ed5afa0 --- /dev/null +++ b/files/opencs/camera-free.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/camera-orbit.png b/files/opencs/camera-orbit.png deleted file mode 100644 index 1aedf28479..0000000000 Binary files a/files/opencs/camera-orbit.png and /dev/null differ diff --git a/files/opencs/camera-orbit.svg b/files/opencs/camera-orbit.svg new file mode 100644 index 0000000000..904463b9b8 --- /dev/null +++ b/files/opencs/camera-orbit.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/cell.png b/files/opencs/cell.png deleted file mode 100644 index 4c3fbe2515..0000000000 Binary files a/files/opencs/cell.png and /dev/null differ diff --git a/files/opencs/cell.svg b/files/opencs/cell.svg new file mode 100644 index 0000000000..9348165dc9 --- /dev/null +++ b/files/opencs/cell.svg @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/class.png b/files/opencs/class.png deleted file mode 100644 index 272f2630c7..0000000000 Binary files a/files/opencs/class.png and /dev/null differ diff --git a/files/opencs/class.svg b/files/opencs/class.svg new file mode 100644 index 0000000000..c9d5e7c5f5 --- /dev/null +++ b/files/opencs/class.svg @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/clothing.png b/files/opencs/clothing.png deleted file mode 100644 index e42988e749..0000000000 Binary files a/files/opencs/clothing.png and /dev/null differ diff --git a/files/opencs/clothing.svg b/files/opencs/clothing.svg new file mode 100644 index 0000000000..9b1ffb54b3 --- /dev/null +++ b/files/opencs/clothing.svg @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/configure.svg b/files/opencs/configure.svg new file mode 100644 index 0000000000..91a5efca5c --- /dev/null +++ b/files/opencs/configure.svg @@ -0,0 +1,501 @@ + + + +image/svg+xml diff --git a/files/opencs/container.png b/files/opencs/container.png deleted file mode 100644 index 336a05dbfe..0000000000 Binary files a/files/opencs/container.png and /dev/null differ diff --git a/files/opencs/container.svg b/files/opencs/container.svg new file mode 100644 index 0000000000..91b417e9e3 --- /dev/null +++ b/files/opencs/container.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/creature.png b/files/opencs/creature.png deleted file mode 100644 index 003684dca0..0000000000 Binary files a/files/opencs/creature.png and /dev/null differ diff --git a/files/opencs/creature.svg b/files/opencs/creature.svg new file mode 100644 index 0000000000..e7fe8cf5a3 --- /dev/null +++ b/files/opencs/creature.svg @@ -0,0 +1,189 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/debug-profile.png b/files/opencs/debug-profile.png deleted file mode 100644 index ddb01da437..0000000000 Binary files a/files/opencs/debug-profile.png and /dev/null differ diff --git a/files/opencs/debug-profile.svg b/files/opencs/debug-profile.svg new file mode 100644 index 0000000000..28be0e4a16 --- /dev/null +++ b/files/opencs/debug-profile.svg @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/dialogue-greeting.png b/files/opencs/dialogue-greeting.png deleted file mode 100644 index de6b22b42c..0000000000 Binary files a/files/opencs/dialogue-greeting.png and /dev/null differ diff --git a/files/opencs/dialogue-info.png b/files/opencs/dialogue-info.png deleted file mode 100644 index 6242eddf4f..0000000000 Binary files a/files/opencs/dialogue-info.png and /dev/null differ diff --git a/files/opencs/dialogue-info.svg b/files/opencs/dialogue-info.svg new file mode 100644 index 0000000000..a0b4f4236e --- /dev/null +++ b/files/opencs/dialogue-info.svg @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/dialogue-journal.png b/files/opencs/dialogue-journal.png deleted file mode 100644 index 086cb8a422..0000000000 Binary files a/files/opencs/dialogue-journal.png and /dev/null differ diff --git a/files/opencs/dialogue-persuasion.png b/files/opencs/dialogue-persuasion.png deleted file mode 100644 index 3138862c83..0000000000 Binary files a/files/opencs/dialogue-persuasion.png and /dev/null differ diff --git a/files/opencs/dialogue-regular.png b/files/opencs/dialogue-regular.png deleted file mode 100644 index 933afc5952..0000000000 Binary files a/files/opencs/dialogue-regular.png and /dev/null differ diff --git a/files/opencs/dialogue-topic-infos.png b/files/opencs/dialogue-topic-infos.png deleted file mode 100644 index 6242eddf4f..0000000000 Binary files a/files/opencs/dialogue-topic-infos.png and /dev/null differ diff --git a/files/opencs/dialogue-topics.png b/files/opencs/dialogue-topics.png deleted file mode 100644 index caa6d7e7cb..0000000000 Binary files a/files/opencs/dialogue-topics.png and /dev/null differ diff --git a/files/opencs/dialogue-topics.svg b/files/opencs/dialogue-topics.svg new file mode 100644 index 0000000000..57dd0b9444 --- /dev/null +++ b/files/opencs/dialogue-topics.svg @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/dialogue-voice.png b/files/opencs/dialogue-voice.png deleted file mode 100644 index 1d67745e55..0000000000 Binary files a/files/opencs/dialogue-voice.png and /dev/null differ diff --git a/files/opencs/door.png b/files/opencs/door.png deleted file mode 100644 index a1a823ecdc..0000000000 Binary files a/files/opencs/door.png and /dev/null differ diff --git a/files/opencs/door.svg b/files/opencs/door.svg new file mode 100644 index 0000000000..846e5efb8e --- /dev/null +++ b/files/opencs/door.svg @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/editing-instance.png b/files/opencs/editing-instance.png deleted file mode 100644 index 7349e8f66d..0000000000 Binary files a/files/opencs/editing-instance.png and /dev/null differ diff --git a/files/opencs/editing-instance.svg b/files/opencs/editing-instance.svg new file mode 100644 index 0000000000..92e20fade3 --- /dev/null +++ b/files/opencs/editing-instance.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/editing-pathgrid.png b/files/opencs/editing-pathgrid.png deleted file mode 100644 index 0ec024cadf..0000000000 Binary files a/files/opencs/editing-pathgrid.png and /dev/null differ diff --git a/files/opencs/editing-pathgrid.svg b/files/opencs/editing-pathgrid.svg new file mode 100644 index 0000000000..2fe9499553 --- /dev/null +++ b/files/opencs/editing-pathgrid.svg @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/editing-terrain-movement.png b/files/opencs/editing-terrain-movement.png deleted file mode 100644 index 40777334bd..0000000000 Binary files a/files/opencs/editing-terrain-movement.png and /dev/null differ diff --git a/files/opencs/editing-terrain-movement.svg b/files/opencs/editing-terrain-movement.svg new file mode 100644 index 0000000000..6c1417fdd0 --- /dev/null +++ b/files/opencs/editing-terrain-movement.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + diff --git a/files/opencs/editing-terrain-shape.png b/files/opencs/editing-terrain-shape.png deleted file mode 100644 index a11bd95d54..0000000000 Binary files a/files/opencs/editing-terrain-shape.png and /dev/null differ diff --git a/files/opencs/editing-terrain-shape.svg b/files/opencs/editing-terrain-shape.svg new file mode 100644 index 0000000000..8f16b10dbf --- /dev/null +++ b/files/opencs/editing-terrain-shape.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + diff --git a/files/opencs/editing-terrain-texture.png b/files/opencs/editing-terrain-texture.png deleted file mode 100644 index 4a88353eea..0000000000 Binary files a/files/opencs/editing-terrain-texture.png and /dev/null differ diff --git a/files/opencs/editing-terrain-texture.svg b/files/opencs/editing-terrain-texture.svg new file mode 100644 index 0000000000..e0386d5061 --- /dev/null +++ b/files/opencs/editing-terrain-texture.svg @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/editing-terrain-vertex-paint.png b/files/opencs/editing-terrain-vertex-paint.png deleted file mode 100644 index 2b3f0beac0..0000000000 Binary files a/files/opencs/editing-terrain-vertex-paint.png and /dev/null differ diff --git a/files/opencs/editing-terrain-vertex-paint.svg b/files/opencs/editing-terrain-vertex-paint.svg new file mode 100644 index 0000000000..7970597ea4 --- /dev/null +++ b/files/opencs/editing-terrain-vertex-paint.svg @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scalable/editor-icons.svg b/files/opencs/editor-icons.svg similarity index 99% rename from files/opencs/scalable/editor-icons.svg rename to files/opencs/editor-icons.svg index d41fdbde60..921c5f6d8d 100644 --- a/files/opencs/scalable/editor-icons.svg +++ b/files/opencs/editor-icons.svg @@ -8973,9 +8973,9 @@ height="100%" /> + + + + + + + + + + + + + + + diff --git a/files/opencs/error-log.png b/files/opencs/error-log.png deleted file mode 100644 index df698d1453..0000000000 Binary files a/files/opencs/error-log.png and /dev/null differ diff --git a/files/opencs/error-log.svg b/files/opencs/error-log.svg new file mode 100644 index 0000000000..1feb39dfe6 --- /dev/null +++ b/files/opencs/error-log.svg @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/faction.png b/files/opencs/faction.png deleted file mode 100644 index b58756bdc0..0000000000 Binary files a/files/opencs/faction.png and /dev/null differ diff --git a/files/opencs/faction.svg b/files/opencs/faction.svg new file mode 100644 index 0000000000..c0bba7f235 --- /dev/null +++ b/files/opencs/faction.svg @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/filter.png b/files/opencs/filter.png deleted file mode 100644 index 55f442377c..0000000000 Binary files a/files/opencs/filter.png and /dev/null differ diff --git a/files/opencs/filter.svg b/files/opencs/filter.svg new file mode 100644 index 0000000000..12ae65d398 --- /dev/null +++ b/files/opencs/filter.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/global-variable.png b/files/opencs/global-variable.png deleted file mode 100644 index e1642ac355..0000000000 Binary files a/files/opencs/global-variable.png and /dev/null differ diff --git a/files/opencs/global-variable.svg b/files/opencs/global-variable.svg new file mode 100644 index 0000000000..00221d24c8 --- /dev/null +++ b/files/opencs/global-variable.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/gmst.png b/files/opencs/gmst.png deleted file mode 100644 index 2605fb4b96..0000000000 Binary files a/files/opencs/gmst.png and /dev/null differ diff --git a/files/opencs/gmst.svg b/files/opencs/gmst.svg new file mode 100644 index 0000000000..2d96e4cc39 --- /dev/null +++ b/files/opencs/gmst.svg @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/info.png b/files/opencs/info.png deleted file mode 100644 index d7bdad6cb1..0000000000 Binary files a/files/opencs/info.png and /dev/null differ diff --git a/files/opencs/info.svg b/files/opencs/info.svg new file mode 100644 index 0000000000..6594827943 --- /dev/null +++ b/files/opencs/info.svg @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/ingredient.png b/files/opencs/ingredient.png deleted file mode 100644 index f31e6f5813..0000000000 Binary files a/files/opencs/ingredient.png and /dev/null differ diff --git a/files/opencs/ingredient.svg b/files/opencs/ingredient.svg new file mode 100644 index 0000000000..af2f736e64 --- /dev/null +++ b/files/opencs/ingredient.svg @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/instance.png b/files/opencs/instance.png deleted file mode 100644 index ce63e64ed6..0000000000 Binary files a/files/opencs/instance.png and /dev/null differ diff --git a/files/opencs/instance.svg b/files/opencs/instance.svg new file mode 100644 index 0000000000..fe344c905d --- /dev/null +++ b/files/opencs/instance.svg @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/journal-topic-infos.png b/files/opencs/journal-topic-infos.png deleted file mode 100644 index 4cc4464897..0000000000 Binary files a/files/opencs/journal-topic-infos.png and /dev/null differ diff --git a/files/opencs/journal-topic-infos.svg b/files/opencs/journal-topic-infos.svg new file mode 100644 index 0000000000..4351d0668b --- /dev/null +++ b/files/opencs/journal-topic-infos.svg @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/journal-topics.png b/files/opencs/journal-topics.png deleted file mode 100644 index d4e58a2885..0000000000 Binary files a/files/opencs/journal-topics.png and /dev/null differ diff --git a/files/opencs/journal-topics.svg b/files/opencs/journal-topics.svg new file mode 100644 index 0000000000..ecb78d869b --- /dev/null +++ b/files/opencs/journal-topics.svg @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/land-heightmap.png b/files/opencs/land-heightmap.png deleted file mode 100644 index 5b460a0024..0000000000 Binary files a/files/opencs/land-heightmap.png and /dev/null differ diff --git a/files/opencs/land-heightmap.svg b/files/opencs/land-heightmap.svg new file mode 100644 index 0000000000..701b246640 --- /dev/null +++ b/files/opencs/land-heightmap.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/land-texture.png b/files/opencs/land-texture.png deleted file mode 100644 index a96c4bf8c7..0000000000 Binary files a/files/opencs/land-texture.png and /dev/null differ diff --git a/files/opencs/land-texture.svg b/files/opencs/land-texture.svg new file mode 100644 index 0000000000..3f9c7dfe1b --- /dev/null +++ b/files/opencs/land-texture.svg @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/levelled-creature.png b/files/opencs/levelled-creature.png deleted file mode 100644 index e6cb1f54c7..0000000000 Binary files a/files/opencs/levelled-creature.png and /dev/null differ diff --git a/files/opencs/levelled-creature.svg b/files/opencs/levelled-creature.svg new file mode 100644 index 0000000000..7c996d23a9 --- /dev/null +++ b/files/opencs/levelled-creature.svg @@ -0,0 +1,198 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/levelled-item.png b/files/opencs/levelled-item.png deleted file mode 100644 index 3c819c56d0..0000000000 Binary files a/files/opencs/levelled-item.png and /dev/null differ diff --git a/files/opencs/levelled-item.svg b/files/opencs/levelled-item.svg new file mode 100644 index 0000000000..e81aaed75b --- /dev/null +++ b/files/opencs/levelled-item.svg @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/light.png b/files/opencs/light.png deleted file mode 100644 index 55d03bcd12..0000000000 Binary files a/files/opencs/light.png and /dev/null differ diff --git a/files/opencs/light.svg b/files/opencs/light.svg new file mode 100644 index 0000000000..35f0664f22 --- /dev/null +++ b/files/opencs/light.svg @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/lighting-lamp.png b/files/opencs/lighting-lamp.png deleted file mode 100644 index c86517aa57..0000000000 Binary files a/files/opencs/lighting-lamp.png and /dev/null differ diff --git a/files/opencs/lighting-lamp.svg b/files/opencs/lighting-lamp.svg new file mode 100644 index 0000000000..5d34d68817 --- /dev/null +++ b/files/opencs/lighting-lamp.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + diff --git a/files/opencs/lighting-moon.png b/files/opencs/lighting-moon.png deleted file mode 100644 index 36a6e9b5b1..0000000000 Binary files a/files/opencs/lighting-moon.png and /dev/null differ diff --git a/files/opencs/lighting-moon.svg b/files/opencs/lighting-moon.svg new file mode 100644 index 0000000000..0441eb3bd5 --- /dev/null +++ b/files/opencs/lighting-moon.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + diff --git a/files/opencs/lighting-sun.png b/files/opencs/lighting-sun.png deleted file mode 100644 index a54d0ab124..0000000000 Binary files a/files/opencs/lighting-sun.png and /dev/null differ diff --git a/files/opencs/lighting-sun.svg b/files/opencs/lighting-sun.svg new file mode 100644 index 0000000000..2873a9f89c --- /dev/null +++ b/files/opencs/lighting-sun.svg @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/list-added.png b/files/opencs/list-added.png deleted file mode 100644 index 4da2659839..0000000000 Binary files a/files/opencs/list-added.png and /dev/null differ diff --git a/files/opencs/list-added.svg b/files/opencs/list-added.svg new file mode 100644 index 0000000000..ecfa8595e5 --- /dev/null +++ b/files/opencs/list-added.svg @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/list-base.png b/files/opencs/list-base.png deleted file mode 100644 index 336d4c59c1..0000000000 Binary files a/files/opencs/list-base.png and /dev/null differ diff --git a/files/opencs/list-base.svg b/files/opencs/list-base.svg new file mode 100644 index 0000000000..a050b15629 --- /dev/null +++ b/files/opencs/list-base.svg @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/list-modified.png b/files/opencs/list-modified.png deleted file mode 100644 index 8b269a31d3..0000000000 Binary files a/files/opencs/list-modified.png and /dev/null differ diff --git a/files/opencs/list-modified.svg b/files/opencs/list-modified.svg new file mode 100644 index 0000000000..6efcab4cd4 --- /dev/null +++ b/files/opencs/list-modified.svg @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/list-removed.png b/files/opencs/list-removed.png deleted file mode 100644 index 618a202bb9..0000000000 Binary files a/files/opencs/list-removed.png and /dev/null differ diff --git a/files/opencs/list-removed.svg b/files/opencs/list-removed.svg new file mode 100644 index 0000000000..157d4ab5e1 --- /dev/null +++ b/files/opencs/list-removed.svg @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/lockpick.png b/files/opencs/lockpick.png deleted file mode 100644 index 7b1865f504..0000000000 Binary files a/files/opencs/lockpick.png and /dev/null differ diff --git a/files/opencs/lockpick.svg b/files/opencs/lockpick.svg new file mode 100644 index 0000000000..b7e8ca1c3d --- /dev/null +++ b/files/opencs/lockpick.svg @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/magic-effect.png b/files/opencs/magic-effect.png deleted file mode 100644 index 44b682bf4f..0000000000 Binary files a/files/opencs/magic-effect.png and /dev/null differ diff --git a/files/opencs/magic-effect.svg b/files/opencs/magic-effect.svg new file mode 100644 index 0000000000..0e949c04bb --- /dev/null +++ b/files/opencs/magic-effect.svg @@ -0,0 +1,194 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-close.png b/files/opencs/menu-close.png deleted file mode 100644 index 81bc986775..0000000000 Binary files a/files/opencs/menu-close.png and /dev/null differ diff --git a/files/opencs/menu-close.svg b/files/opencs/menu-close.svg new file mode 100644 index 0000000000..6bd1e9f275 --- /dev/null +++ b/files/opencs/menu-close.svg @@ -0,0 +1,202 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-exit.png b/files/opencs/menu-exit.png deleted file mode 100644 index f583536fb6..0000000000 Binary files a/files/opencs/menu-exit.png and /dev/null differ diff --git a/files/opencs/menu-exit.svg b/files/opencs/menu-exit.svg new file mode 100644 index 0000000000..e1256eab72 --- /dev/null +++ b/files/opencs/menu-exit.svg @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-merge.png b/files/opencs/menu-merge.png deleted file mode 100644 index c76288ae16..0000000000 Binary files a/files/opencs/menu-merge.png and /dev/null differ diff --git a/files/opencs/menu-merge.svg b/files/opencs/menu-merge.svg new file mode 100644 index 0000000000..e2d7f394fb --- /dev/null +++ b/files/opencs/menu-merge.svg @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-new-addon.png b/files/opencs/menu-new-addon.png deleted file mode 100644 index df137b2b20..0000000000 Binary files a/files/opencs/menu-new-addon.png and /dev/null differ diff --git a/files/opencs/menu-new-addon.svg b/files/opencs/menu-new-addon.svg new file mode 100644 index 0000000000..46aaa1e1ec --- /dev/null +++ b/files/opencs/menu-new-addon.svg @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-new-game.png b/files/opencs/menu-new-game.png deleted file mode 100644 index 701daf34b1..0000000000 Binary files a/files/opencs/menu-new-game.png and /dev/null differ diff --git a/files/opencs/menu-new-game.svg b/files/opencs/menu-new-game.svg new file mode 100644 index 0000000000..416a774d30 --- /dev/null +++ b/files/opencs/menu-new-game.svg @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-new-window.png b/files/opencs/menu-new-window.png deleted file mode 100644 index 4a42da0d12..0000000000 Binary files a/files/opencs/menu-new-window.png and /dev/null differ diff --git a/files/opencs/menu-new-window.svg b/files/opencs/menu-new-window.svg new file mode 100644 index 0000000000..33262366dc --- /dev/null +++ b/files/opencs/menu-new-window.svg @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-open.png b/files/opencs/menu-open.png deleted file mode 100644 index 3766e17549..0000000000 Binary files a/files/opencs/menu-open.png and /dev/null differ diff --git a/files/opencs/menu-open.svg b/files/opencs/menu-open.svg new file mode 100644 index 0000000000..a895b4157f --- /dev/null +++ b/files/opencs/menu-open.svg @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-preferences.png b/files/opencs/menu-preferences.png deleted file mode 100644 index 4644297ad0..0000000000 Binary files a/files/opencs/menu-preferences.png and /dev/null differ diff --git a/files/opencs/menu-preferences.svg b/files/opencs/menu-preferences.svg new file mode 100644 index 0000000000..f7c657deb2 --- /dev/null +++ b/files/opencs/menu-preferences.svg @@ -0,0 +1,232 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-redo.png b/files/opencs/menu-redo.png deleted file mode 100644 index 0cd0eedcae..0000000000 Binary files a/files/opencs/menu-redo.png and /dev/null differ diff --git a/files/opencs/menu-redo.svg b/files/opencs/menu-redo.svg new file mode 100644 index 0000000000..f19f236e7e --- /dev/null +++ b/files/opencs/menu-redo.svg @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-reload.png b/files/opencs/menu-reload.png deleted file mode 100644 index 2b780598c4..0000000000 Binary files a/files/opencs/menu-reload.png and /dev/null differ diff --git a/files/opencs/menu-reload.svg b/files/opencs/menu-reload.svg new file mode 100644 index 0000000000..32cefa7548 --- /dev/null +++ b/files/opencs/menu-reload.svg @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-save.png b/files/opencs/menu-save.png deleted file mode 100644 index 4be88765be..0000000000 Binary files a/files/opencs/menu-save.png and /dev/null differ diff --git a/files/opencs/menu-save.svg b/files/opencs/menu-save.svg new file mode 100644 index 0000000000..c9cb6f4bef --- /dev/null +++ b/files/opencs/menu-save.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-search.png b/files/opencs/menu-search.png deleted file mode 100644 index 1ec63f0e82..0000000000 Binary files a/files/opencs/menu-search.png and /dev/null differ diff --git a/files/opencs/menu-search.svg b/files/opencs/menu-search.svg new file mode 100644 index 0000000000..10b5b20b00 --- /dev/null +++ b/files/opencs/menu-search.svg @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-status-bar.png b/files/opencs/menu-status-bar.png deleted file mode 100644 index dfb72b1e13..0000000000 Binary files a/files/opencs/menu-status-bar.png and /dev/null differ diff --git a/files/opencs/menu-status-bar.svg b/files/opencs/menu-status-bar.svg new file mode 100644 index 0000000000..a6b4d15d1e --- /dev/null +++ b/files/opencs/menu-status-bar.svg @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-undo.png b/files/opencs/menu-undo.png deleted file mode 100644 index bd177ce65a..0000000000 Binary files a/files/opencs/menu-undo.png and /dev/null differ diff --git a/files/opencs/menu-undo.svg b/files/opencs/menu-undo.svg new file mode 100644 index 0000000000..1fffc9959d --- /dev/null +++ b/files/opencs/menu-undo.svg @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-verify.png b/files/opencs/menu-verify.png deleted file mode 100644 index a7878ebb3f..0000000000 Binary files a/files/opencs/menu-verify.png and /dev/null differ diff --git a/files/opencs/menu-verify.svg b/files/opencs/menu-verify.svg new file mode 100644 index 0000000000..d72d229edd --- /dev/null +++ b/files/opencs/menu-verify.svg @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/metadata.png b/files/opencs/metadata.png deleted file mode 100644 index 34c78ffd61..0000000000 Binary files a/files/opencs/metadata.png and /dev/null differ diff --git a/files/opencs/metadata.svg b/files/opencs/metadata.svg new file mode 100644 index 0000000000..acccf4f4b7 --- /dev/null +++ b/files/opencs/metadata.svg @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/miscellaneous.png b/files/opencs/miscellaneous.png deleted file mode 100644 index b21f6e2141..0000000000 Binary files a/files/opencs/miscellaneous.png and /dev/null differ diff --git a/files/opencs/miscellaneous.svg b/files/opencs/miscellaneous.svg new file mode 100644 index 0000000000..ac1bda5e5a --- /dev/null +++ b/files/opencs/miscellaneous.svg @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/multitype.png b/files/opencs/multitype.png deleted file mode 100644 index 05676e2de0..0000000000 Binary files a/files/opencs/multitype.png and /dev/null differ diff --git a/files/opencs/multitype.svg b/files/opencs/multitype.svg new file mode 100644 index 0000000000..c42da95866 --- /dev/null +++ b/files/opencs/multitype.svg @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/npc.png b/files/opencs/npc.png deleted file mode 100644 index 5b5b199be6..0000000000 Binary files a/files/opencs/npc.png and /dev/null differ diff --git a/files/opencs/npc.svg b/files/opencs/npc.svg new file mode 100644 index 0000000000..3ae9a3127c --- /dev/null +++ b/files/opencs/npc.svg @@ -0,0 +1,316 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/object.png b/files/opencs/object.png deleted file mode 100644 index 4f552151db..0000000000 Binary files a/files/opencs/object.png and /dev/null differ diff --git a/files/opencs/object.svg b/files/opencs/object.svg new file mode 100644 index 0000000000..8f9261fd18 --- /dev/null +++ b/files/opencs/object.svg @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/pathgrid.png b/files/opencs/pathgrid.png deleted file mode 100644 index 710ff13576..0000000000 Binary files a/files/opencs/pathgrid.png and /dev/null differ diff --git a/files/opencs/pathgrid.svg b/files/opencs/pathgrid.svg new file mode 100644 index 0000000000..ba16efcacf --- /dev/null +++ b/files/opencs/pathgrid.svg @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/potion.png b/files/opencs/potion.png deleted file mode 100644 index cb173bb9ed..0000000000 Binary files a/files/opencs/potion.png and /dev/null differ diff --git a/files/opencs/potion.svg b/files/opencs/potion.svg new file mode 100644 index 0000000000..b59d12683f --- /dev/null +++ b/files/opencs/potion.svg @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/probe.png b/files/opencs/probe.png deleted file mode 100644 index 2e405d3653..0000000000 Binary files a/files/opencs/probe.png and /dev/null differ diff --git a/files/opencs/probe.svg b/files/opencs/probe.svg new file mode 100644 index 0000000000..0d812c5ace --- /dev/null +++ b/files/opencs/probe.svg @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/qt.png b/files/opencs/qt.png deleted file mode 100644 index 381cb2251f..0000000000 Binary files a/files/opencs/qt.png and /dev/null differ diff --git a/files/opencs/qt.svg b/files/opencs/qt.svg new file mode 100644 index 0000000000..cc84a363e5 --- /dev/null +++ b/files/opencs/qt.svg @@ -0,0 +1,180 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/race.png b/files/opencs/race.png deleted file mode 100644 index aeed2fdf3e..0000000000 Binary files a/files/opencs/race.png and /dev/null differ diff --git a/files/opencs/race.svg b/files/opencs/race.svg new file mode 100644 index 0000000000..cd7778127e --- /dev/null +++ b/files/opencs/race.svg @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/random.png b/files/opencs/random.png deleted file mode 100644 index 2667630f5c..0000000000 Binary files a/files/opencs/random.png and /dev/null differ diff --git a/files/opencs/raster/startup/big/configure.png b/files/opencs/raster/startup/big/configure.png deleted file mode 100644 index f0be888a15..0000000000 Binary files a/files/opencs/raster/startup/big/configure.png and /dev/null differ diff --git a/files/opencs/raster/startup/small/configure.png b/files/opencs/raster/startup/small/configure.png deleted file mode 100644 index e91b7f7731..0000000000 Binary files a/files/opencs/raster/startup/small/configure.png and /dev/null differ diff --git a/files/opencs/raster/startup/small/create-addon.png b/files/opencs/raster/startup/small/create-addon.png deleted file mode 100644 index 64fd138be5..0000000000 Binary files a/files/opencs/raster/startup/small/create-addon.png and /dev/null differ diff --git a/files/opencs/raster/startup/small/edit-content.png b/files/opencs/raster/startup/small/edit-content.png deleted file mode 100644 index 6297f1169c..0000000000 Binary files a/files/opencs/raster/startup/small/edit-content.png and /dev/null differ diff --git a/files/opencs/raster/startup/small/new-game.png b/files/opencs/raster/startup/small/new-game.png deleted file mode 100644 index 0d7d14c558..0000000000 Binary files a/files/opencs/raster/startup/small/new-game.png and /dev/null differ diff --git a/files/opencs/record-add.png b/files/opencs/record-add.png deleted file mode 100644 index d477f19460..0000000000 Binary files a/files/opencs/record-add.png and /dev/null differ diff --git a/files/opencs/record-add.svg b/files/opencs/record-add.svg new file mode 100644 index 0000000000..d29682bc59 --- /dev/null +++ b/files/opencs/record-add.svg @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/record-clone.png b/files/opencs/record-clone.png deleted file mode 100644 index 262a67a424..0000000000 Binary files a/files/opencs/record-clone.png and /dev/null differ diff --git a/files/opencs/record-clone.svg b/files/opencs/record-clone.svg new file mode 100644 index 0000000000..3014b2b4e0 --- /dev/null +++ b/files/opencs/record-clone.svg @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/record-delete.png b/files/opencs/record-delete.png deleted file mode 100644 index 817988a5d1..0000000000 Binary files a/files/opencs/record-delete.png and /dev/null differ diff --git a/files/opencs/record-delete.svg b/files/opencs/record-delete.svg new file mode 100644 index 0000000000..9a21973542 --- /dev/null +++ b/files/opencs/record-delete.svg @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/record-down.png b/files/opencs/record-down.png deleted file mode 100644 index 92fe788602..0000000000 Binary files a/files/opencs/record-down.png and /dev/null differ diff --git a/files/opencs/record-down.svg b/files/opencs/record-down.svg new file mode 100644 index 0000000000..9febe57065 --- /dev/null +++ b/files/opencs/record-down.svg @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/record-edit.png b/files/opencs/record-edit.png deleted file mode 100644 index 8b94e16341..0000000000 Binary files a/files/opencs/record-edit.png and /dev/null differ diff --git a/files/opencs/record-edit.svg b/files/opencs/record-edit.svg new file mode 100644 index 0000000000..5e7a058782 --- /dev/null +++ b/files/opencs/record-edit.svg @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/record-next.png b/files/opencs/record-next.png deleted file mode 100644 index 76866e4734..0000000000 Binary files a/files/opencs/record-next.png and /dev/null differ diff --git a/files/opencs/record-next.svg b/files/opencs/record-next.svg new file mode 100644 index 0000000000..bbc44e7459 --- /dev/null +++ b/files/opencs/record-next.svg @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/record-preview.png b/files/opencs/record-preview.png deleted file mode 100644 index e3adb6ede4..0000000000 Binary files a/files/opencs/record-preview.png and /dev/null differ diff --git a/files/opencs/record-preview.svg b/files/opencs/record-preview.svg new file mode 100644 index 0000000000..a516d6b596 --- /dev/null +++ b/files/opencs/record-preview.svg @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/record-previous.png b/files/opencs/record-previous.png deleted file mode 100644 index 2009d84e04..0000000000 Binary files a/files/opencs/record-previous.png and /dev/null differ diff --git a/files/opencs/record-previous.svg b/files/opencs/record-previous.svg new file mode 100644 index 0000000000..2479c8bd49 --- /dev/null +++ b/files/opencs/record-previous.svg @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/record-revert.png b/files/opencs/record-revert.png deleted file mode 100644 index bd177ce65a..0000000000 Binary files a/files/opencs/record-revert.png and /dev/null differ diff --git a/files/opencs/record-revert.svg b/files/opencs/record-revert.svg new file mode 100644 index 0000000000..11920a329a --- /dev/null +++ b/files/opencs/record-revert.svg @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/record-touch.png b/files/opencs/record-touch.png deleted file mode 100644 index 808e1b6c4b..0000000000 Binary files a/files/opencs/record-touch.png and /dev/null differ diff --git a/files/opencs/record-touch.svg b/files/opencs/record-touch.svg new file mode 100644 index 0000000000..39e54c3ff1 --- /dev/null +++ b/files/opencs/record-touch.svg @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/record-up.png b/files/opencs/record-up.png deleted file mode 100644 index c8bfa4a342..0000000000 Binary files a/files/opencs/record-up.png and /dev/null differ diff --git a/files/opencs/record-up.svg b/files/opencs/record-up.svg new file mode 100644 index 0000000000..351cb14d4a --- /dev/null +++ b/files/opencs/record-up.svg @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/region-map.png b/files/opencs/region-map.png deleted file mode 100644 index 7631847bee..0000000000 Binary files a/files/opencs/region-map.png and /dev/null differ diff --git a/files/opencs/region-map.svg b/files/opencs/region-map.svg new file mode 100644 index 0000000000..0067fb9901 --- /dev/null +++ b/files/opencs/region-map.svg @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/region.png b/files/opencs/region.png deleted file mode 100644 index 2ebaeb0285..0000000000 Binary files a/files/opencs/region.png and /dev/null differ diff --git a/files/opencs/region.svg b/files/opencs/region.svg new file mode 100644 index 0000000000..4458c7e376 --- /dev/null +++ b/files/opencs/region.svg @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/repair.png b/files/opencs/repair.png deleted file mode 100644 index 1b5a9ccc11..0000000000 Binary files a/files/opencs/repair.png and /dev/null differ diff --git a/files/opencs/repair.svg b/files/opencs/repair.svg new file mode 100644 index 0000000000..5e2d09d58c --- /dev/null +++ b/files/opencs/repair.svg @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/resources-icon.png b/files/opencs/resources-icon.png deleted file mode 100644 index d84c90d5dc..0000000000 Binary files a/files/opencs/resources-icon.png and /dev/null differ diff --git a/files/opencs/resources-icon.svg b/files/opencs/resources-icon.svg new file mode 100644 index 0000000000..2054ba0b2e --- /dev/null +++ b/files/opencs/resources-icon.svg @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/resources-mesh.png b/files/opencs/resources-mesh.png deleted file mode 100644 index fdfa3528b1..0000000000 Binary files a/files/opencs/resources-mesh.png and /dev/null differ diff --git a/files/opencs/resources-mesh.svg b/files/opencs/resources-mesh.svg new file mode 100644 index 0000000000..ba93da8091 --- /dev/null +++ b/files/opencs/resources-mesh.svg @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/resources-music.png b/files/opencs/resources-music.png deleted file mode 100644 index 53775109c5..0000000000 Binary files a/files/opencs/resources-music.png and /dev/null differ diff --git a/files/opencs/resources-music.svg b/files/opencs/resources-music.svg new file mode 100644 index 0000000000..bf81130e3c --- /dev/null +++ b/files/opencs/resources-music.svg @@ -0,0 +1,216 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/resources-sound.png b/files/opencs/resources-sound.png deleted file mode 100644 index 86871611f5..0000000000 Binary files a/files/opencs/resources-sound.png and /dev/null differ diff --git a/files/opencs/resources-sound.svg b/files/opencs/resources-sound.svg new file mode 100644 index 0000000000..c2019ca176 --- /dev/null +++ b/files/opencs/resources-sound.svg @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/resources-texture.png b/files/opencs/resources-texture.png deleted file mode 100644 index a96c4bf8c7..0000000000 Binary files a/files/opencs/resources-texture.png and /dev/null differ diff --git a/files/opencs/resources-texture.svg b/files/opencs/resources-texture.svg new file mode 100644 index 0000000000..fd9fa88c93 --- /dev/null +++ b/files/opencs/resources-texture.svg @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/resources-video.png b/files/opencs/resources-video.png deleted file mode 100644 index d86bc6025e..0000000000 Binary files a/files/opencs/resources-video.png and /dev/null differ diff --git a/files/opencs/resources-video.svg b/files/opencs/resources-video.svg new file mode 100644 index 0000000000..fa1ce3c1b6 --- /dev/null +++ b/files/opencs/resources-video.svg @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/resources.qrc b/files/opencs/resources.qrc index eef5c01bf8..3b5a17c730 100644 --- a/files/opencs/resources.qrc +++ b/files/opencs/resources.qrc @@ -1,187 +1,163 @@ - openmw-cs.png - activator.png - list-added.png - apparatus.png - armor.png - attribute.png - list-base.png - birthsign.png - body-part.png - book.png - cell.png - class.png - clothing.png - container.png - creature.png - debug-profile.png - dialogue-info.png - dialogue-journal.png - dialogue-regular.png - dialogue-greeting.png - dialogue-persuasion.png - dialogue-voice.png - dialogue-topics.png - dialogue-topic-infos.png - journal-topic-infos.png - journal-topics.png - door.png - enchantment.png - error-log.png - faction.png - filter.png - global-variable.png - gmst.png - info.png - ingredient.png - instance.png - land-heightmap.png - land-texture.png - levelled-creature.png - levelled-item.png - light.png - lockpick.png - magic-effect.png - probe.png - menu-close.png - menu-exit.png - menu-new-addon.png - menu-new-game.png - menu-new-window.png - menu-merge.png - menu-open.png - menu-preferences.png - menu-reload.png - menu-redo.png - menu-save.png - menu-search.png - menu-status-bar.png - menu-undo.png - menu-verify.png - metadata.png - miscellaneous.png - list-modified.png - npc.png - object.png - pathgrid.png - potion.png - qt.png - race.png - random.png - list-removed.png - region.png - region-map.png - repair.png - run-log.png - run-openmw.png - scene.png - script.png - skill.png - start-script.png - stop-openmw.png - sound-generator.png - sound.png - spell.png - static.png - weapon.png - multitype.png - record-next.png - record-previous.png - record-down.png - record-up.png - record-delete.png - record-preview.png - record-clone.png - record-add.png - record-edit.png - record-touch.png - record-revert.png - resources-icon.png - resources-mesh.png - resources-music.png - resources-sound.png - resources-texture.png - resources-video.png + activator.svg + apparatus.svg + armor.svg + attribute.svg + birthsign.svg + body-part.svg + book.svg + cell.svg + class.svg + clothing.svg + container.svg + creature.svg + debug-profile.svg + dialogue-info.svg + dialogue-topics.svg + door.svg + record-add.svg + record-clone.svg + record-delete.svg + record-edit.svg + record-preview.svg + record-touch.svg + record-revert.svg + enchantment.svg + error-log.svg + faction.svg + filter.svg + global-variable.svg + gmst.svg + info.svg + ingredient.svg + instance.svg + journal-topic-infos.svg + journal-topics.svg + land-heightmap.svg + land-texture.svg + levelled-creature.svg + levelled-item.svg + light.svg + list-base.svg + list-added.svg + list-modified.svg + list-removed.svg + lockpick.svg + magic-effect.svg + menu-close.svg + menu-exit.svg + menu-merge.svg + menu-new-addon.svg + menu-new-game.svg + menu-new-window.svg + menu-open.svg + menu-preferences.svg + menu-redo.svg + menu-reload.svg + menu-save.svg + menu-search.svg + menu-status-bar.svg + menu-undo.svg + menu-verify.svg + metadata.svg + miscellaneous.svg + multitype.svg + npc.svg + object.svg + openmw-cs.png + pathgrid.svg placeholder.png + potion.svg + probe.svg + qt.svg + race.svg + record-down.svg + record-next.svg + record-previous.svg + record-up.svg + region.svg + region-map.svg + repair.svg + resources-icon.svg + resources-mesh.svg + resources-music.svg + resources-sound.svg + resources-texture.svg + resources-video.svg + run-log.svg + run-openmw.svg + scene.svg + script.svg + skill.svg + sound-generator.svg + sound.svg + spell.svg + start-script.svg + static.svg + stop-openmw.svg + weapon.svg raster/startup/big/create-addon.png raster/startup/big/new-game.png raster/startup/big/edit-content.png - raster/startup/small/configure.png + configure.svg - lighting-moon.png - lighting-sun.png - lighting-lamp.png - camera-first-person.png - camera-free.png - camera-orbit.png - run-game.png - scene-view-instance.png - scene-view-terrain.png - scene-view-water.png - scene-view-pathgrid.png - scene-view-fog.png - scene-view-status-0.png - scene-view-status-1.png - scene-view-status-2.png - scene-view-status-3.png - scene-view-status-4.png - scene-view-status-5.png - scene-view-status-6.png - scene-view-status-7.png - scene-view-status-8.png - scene-view-status-9.png - scene-view-status-10.png - scene-view-status-11.png - scene-view-status-12.png - scene-view-status-13.png - scene-view-status-14.png - scene-view-status-15.png - scene-view-status-16.png - scene-view-status-17.png - scene-view-status-18.png - scene-view-status-19.png - scene-view-status-20.png - scene-view-status-21.png - scene-view-status-22.png - scene-view-status-23.png - scene-view-status-24.png - scene-view-status-25.png - scene-view-status-26.png - scene-view-status-27.png - scene-view-status-28.png - scene-view-status-29.png - scene-view-status-30.png - scene-view-status-31.png - scene-exterior-arrows.png - scene-exterior-borders.png - scene-exterior-markers.png - scene-exterior-status-0.png - scene-exterior-status-1.png - scene-exterior-status-2.png - scene-exterior-status-3.png - scene-exterior-status-4.png - scene-exterior-status-5.png - scene-exterior-status-6.png - scene-exterior-status-7.png - editing-instance.png - editing-pathgrid.png - editing-terrain-movement.png - editing-terrain-shape.png - editing-terrain-texture.png - editing-terrain-vertex-paint.png - transform-move.png - transform-rotate.png - transform-scale.png - selection-mode-cube.png - selection-mode-cube-corner.png - selection-mode-cube-sphere.png - brush-point.png - brush-square.png - brush-circle.png - brush-custom.png + lighting-moon.svg + lighting-sun.svg + lighting-lamp.svg + camera-first-person.svg + camera-free.svg + camera-orbit.svg + run-game.svg + scene-view-instance.svg + scene-view-terrain.svg + scene-view-water.svg + scene-view-pathgrid.svg + scene-view-status-0.svg + scene-view-status-1.svg + scene-view-status-2.svg + scene-view-status-3.svg + scene-view-status-4.svg + scene-view-status-5.svg + scene-view-status-6.svg + scene-view-status-7.svg + scene-view-status-8.svg + scene-view-status-9.svg + scene-view-status-10.svg + scene-view-status-11.svg + scene-view-status-12.svg + scene-view-status-13.svg + scene-view-status-14.svg + scene-view-status-15.svg + scene-exterior-arrows.svg + scene-exterior-borders.svg + scene-exterior-markers.svg + scene-exterior-status-0.svg + scene-exterior-status-1.svg + scene-exterior-status-2.svg + scene-exterior-status-3.svg + scene-exterior-status-4.svg + scene-exterior-status-5.svg + scene-exterior-status-6.svg + scene-exterior-status-7.svg + editing-instance.svg + editing-pathgrid.svg + editing-terrain-movement.svg + editing-terrain-shape.svg + editing-terrain-texture.svg + editing-terrain-vertex-paint.svg + transform-move.svg + transform-rotate.svg + transform-scale.svg + selection-mode-cube.svg + selection-mode-cube-corner.svg + selection-mode-sphere.svg + brush-point.svg + brush-square.svg + brush-circle.svg + brush-custom.svg diff --git a/files/opencs/run-game.png b/files/opencs/run-game.png deleted file mode 100644 index f5038654fa..0000000000 Binary files a/files/opencs/run-game.png and /dev/null differ diff --git a/files/opencs/run-game.svg b/files/opencs/run-game.svg new file mode 100644 index 0000000000..818d1dd5b7 --- /dev/null +++ b/files/opencs/run-game.svg @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/run-log.png b/files/opencs/run-log.png deleted file mode 100644 index 463ead176d..0000000000 Binary files a/files/opencs/run-log.png and /dev/null differ diff --git a/files/opencs/run-log.svg b/files/opencs/run-log.svg new file mode 100644 index 0000000000..96e0d037b7 --- /dev/null +++ b/files/opencs/run-log.svg @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/run-openmw.png b/files/opencs/run-openmw.png deleted file mode 100644 index 1033d62baa..0000000000 Binary files a/files/opencs/run-openmw.png and /dev/null differ diff --git a/files/opencs/run-openmw.svg b/files/opencs/run-openmw.svg new file mode 100644 index 0000000000..f0ae78619e --- /dev/null +++ b/files/opencs/run-openmw.svg @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scalable/startup/configure.svgz b/files/opencs/scalable/startup/configure.svgz deleted file mode 100644 index 1275ec53fa..0000000000 Binary files a/files/opencs/scalable/startup/configure.svgz and /dev/null differ diff --git a/files/opencs/scene-exterior-arrows.png b/files/opencs/scene-exterior-arrows.png deleted file mode 100644 index 661bb81ae7..0000000000 Binary files a/files/opencs/scene-exterior-arrows.png and /dev/null differ diff --git a/files/opencs/scene-exterior-arrows.svg b/files/opencs/scene-exterior-arrows.svg new file mode 100644 index 0000000000..1f923c91bf --- /dev/null +++ b/files/opencs/scene-exterior-arrows.svg @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-exterior-borders.png b/files/opencs/scene-exterior-borders.png deleted file mode 100644 index ec5040dc88..0000000000 Binary files a/files/opencs/scene-exterior-borders.png and /dev/null differ diff --git a/files/opencs/scene-exterior-borders.svg b/files/opencs/scene-exterior-borders.svg new file mode 100644 index 0000000000..4a9c2ea4fa --- /dev/null +++ b/files/opencs/scene-exterior-borders.svg @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-exterior-markers.png b/files/opencs/scene-exterior-markers.png deleted file mode 100644 index 6fffcbbcca..0000000000 Binary files a/files/opencs/scene-exterior-markers.png and /dev/null differ diff --git a/files/opencs/scene-exterior-markers.svg b/files/opencs/scene-exterior-markers.svg new file mode 100644 index 0000000000..63900d752f --- /dev/null +++ b/files/opencs/scene-exterior-markers.svg @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-exterior-status-0.png b/files/opencs/scene-exterior-status-0.png deleted file mode 100644 index 6fa47b4394..0000000000 Binary files a/files/opencs/scene-exterior-status-0.png and /dev/null differ diff --git a/files/opencs/scene-exterior-status-0.svg b/files/opencs/scene-exterior-status-0.svg new file mode 100644 index 0000000000..7e1f8aece3 --- /dev/null +++ b/files/opencs/scene-exterior-status-0.svg @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-exterior-status-1.png b/files/opencs/scene-exterior-status-1.png deleted file mode 100644 index 2e1ed0f650..0000000000 Binary files a/files/opencs/scene-exterior-status-1.png and /dev/null differ diff --git a/files/opencs/scene-exterior-status-1.svg b/files/opencs/scene-exterior-status-1.svg new file mode 100644 index 0000000000..f699d767c2 --- /dev/null +++ b/files/opencs/scene-exterior-status-1.svg @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-exterior-status-2.png b/files/opencs/scene-exterior-status-2.png deleted file mode 100644 index 8ccd356aa9..0000000000 Binary files a/files/opencs/scene-exterior-status-2.png and /dev/null differ diff --git a/files/opencs/scene-exterior-status-2.svg b/files/opencs/scene-exterior-status-2.svg new file mode 100644 index 0000000000..2d1ab9bbf8 --- /dev/null +++ b/files/opencs/scene-exterior-status-2.svg @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-exterior-status-3.png b/files/opencs/scene-exterior-status-3.png deleted file mode 100644 index 70fdc4111e..0000000000 Binary files a/files/opencs/scene-exterior-status-3.png and /dev/null differ diff --git a/files/opencs/scene-exterior-status-3.svg b/files/opencs/scene-exterior-status-3.svg new file mode 100644 index 0000000000..401f19a5fa --- /dev/null +++ b/files/opencs/scene-exterior-status-3.svg @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-exterior-status-4.png b/files/opencs/scene-exterior-status-4.png deleted file mode 100644 index 2f2b907fc8..0000000000 Binary files a/files/opencs/scene-exterior-status-4.png and /dev/null differ diff --git a/files/opencs/scene-exterior-status-4.svg b/files/opencs/scene-exterior-status-4.svg new file mode 100644 index 0000000000..aebc14b932 --- /dev/null +++ b/files/opencs/scene-exterior-status-4.svg @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-exterior-status-5.png b/files/opencs/scene-exterior-status-5.png deleted file mode 100644 index b294c1b15a..0000000000 Binary files a/files/opencs/scene-exterior-status-5.png and /dev/null differ diff --git a/files/opencs/scene-exterior-status-5.svg b/files/opencs/scene-exterior-status-5.svg new file mode 100644 index 0000000000..3fb8b98a5f --- /dev/null +++ b/files/opencs/scene-exterior-status-5.svg @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-exterior-status-6.png b/files/opencs/scene-exterior-status-6.png deleted file mode 100644 index 872568b266..0000000000 Binary files a/files/opencs/scene-exterior-status-6.png and /dev/null differ diff --git a/files/opencs/scene-exterior-status-6.svg b/files/opencs/scene-exterior-status-6.svg new file mode 100644 index 0000000000..93c905935a --- /dev/null +++ b/files/opencs/scene-exterior-status-6.svg @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-exterior-status-7.png b/files/opencs/scene-exterior-status-7.png deleted file mode 100644 index c19431025c..0000000000 Binary files a/files/opencs/scene-exterior-status-7.png and /dev/null differ diff --git a/files/opencs/scene-exterior-status-7.svg b/files/opencs/scene-exterior-status-7.svg new file mode 100644 index 0000000000..89b97866d8 --- /dev/null +++ b/files/opencs/scene-exterior-status-7.svg @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-fog.png b/files/opencs/scene-view-fog.png deleted file mode 100644 index 65ea108ac5..0000000000 Binary files a/files/opencs/scene-view-fog.png and /dev/null differ diff --git a/files/opencs/scene-view-instance.png b/files/opencs/scene-view-instance.png deleted file mode 100644 index 6f5e7cb2ac..0000000000 Binary files a/files/opencs/scene-view-instance.png and /dev/null differ diff --git a/files/opencs/scene-view-instance.svg b/files/opencs/scene-view-instance.svg new file mode 100644 index 0000000000..771432bebf --- /dev/null +++ b/files/opencs/scene-view-instance.svg @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-pathgrid.png b/files/opencs/scene-view-pathgrid.png deleted file mode 100644 index edb350b8b5..0000000000 Binary files a/files/opencs/scene-view-pathgrid.png and /dev/null differ diff --git a/files/opencs/scene-view-pathgrid.svg b/files/opencs/scene-view-pathgrid.svg new file mode 100644 index 0000000000..b1a0c8845e --- /dev/null +++ b/files/opencs/scene-view-pathgrid.svg @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-0.png b/files/opencs/scene-view-status-0.png deleted file mode 100644 index 1906fd89cd..0000000000 Binary files a/files/opencs/scene-view-status-0.png and /dev/null differ diff --git a/files/opencs/scene-view-status-0.svg b/files/opencs/scene-view-status-0.svg new file mode 100644 index 0000000000..6820c6f013 --- /dev/null +++ b/files/opencs/scene-view-status-0.svg @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-1.png b/files/opencs/scene-view-status-1.png deleted file mode 100644 index 6f5e7cb2ac..0000000000 Binary files a/files/opencs/scene-view-status-1.png and /dev/null differ diff --git a/files/opencs/scene-view-status-1.svg b/files/opencs/scene-view-status-1.svg new file mode 100644 index 0000000000..3dbfa1622a --- /dev/null +++ b/files/opencs/scene-view-status-1.svg @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-10.png b/files/opencs/scene-view-status-10.png deleted file mode 100644 index 1216c180fe..0000000000 Binary files a/files/opencs/scene-view-status-10.png and /dev/null differ diff --git a/files/opencs/scene-view-status-10.svg b/files/opencs/scene-view-status-10.svg new file mode 100644 index 0000000000..8263818403 --- /dev/null +++ b/files/opencs/scene-view-status-10.svg @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-11.png b/files/opencs/scene-view-status-11.png deleted file mode 100644 index dbe8276f07..0000000000 Binary files a/files/opencs/scene-view-status-11.png and /dev/null differ diff --git a/files/opencs/scene-view-status-11.svg b/files/opencs/scene-view-status-11.svg new file mode 100644 index 0000000000..8c25452211 --- /dev/null +++ b/files/opencs/scene-view-status-11.svg @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-12.png b/files/opencs/scene-view-status-12.png deleted file mode 100644 index ba6d1f3235..0000000000 Binary files a/files/opencs/scene-view-status-12.png and /dev/null differ diff --git a/files/opencs/scene-view-status-12.svg b/files/opencs/scene-view-status-12.svg new file mode 100644 index 0000000000..2e16eb8256 --- /dev/null +++ b/files/opencs/scene-view-status-12.svg @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-13.png b/files/opencs/scene-view-status-13.png deleted file mode 100644 index 3651cd6094..0000000000 Binary files a/files/opencs/scene-view-status-13.png and /dev/null differ diff --git a/files/opencs/scene-view-status-13.svg b/files/opencs/scene-view-status-13.svg new file mode 100644 index 0000000000..1f2b3ab398 --- /dev/null +++ b/files/opencs/scene-view-status-13.svg @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-14.png b/files/opencs/scene-view-status-14.png deleted file mode 100644 index 4bddb7ebc3..0000000000 Binary files a/files/opencs/scene-view-status-14.png and /dev/null differ diff --git a/files/opencs/scene-view-status-14.svg b/files/opencs/scene-view-status-14.svg new file mode 100644 index 0000000000..e948ba19e7 --- /dev/null +++ b/files/opencs/scene-view-status-14.svg @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-15.png b/files/opencs/scene-view-status-15.png deleted file mode 100644 index bdd43407c9..0000000000 Binary files a/files/opencs/scene-view-status-15.png and /dev/null differ diff --git a/files/opencs/scene-view-status-15.svg b/files/opencs/scene-view-status-15.svg new file mode 100644 index 0000000000..a8a3ce8573 --- /dev/null +++ b/files/opencs/scene-view-status-15.svg @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-16.png b/files/opencs/scene-view-status-16.png deleted file mode 100644 index b0051eb127..0000000000 Binary files a/files/opencs/scene-view-status-16.png and /dev/null differ diff --git a/files/opencs/scene-view-status-17.png b/files/opencs/scene-view-status-17.png deleted file mode 100644 index def1560323..0000000000 Binary files a/files/opencs/scene-view-status-17.png and /dev/null differ diff --git a/files/opencs/scene-view-status-18.png b/files/opencs/scene-view-status-18.png deleted file mode 100644 index 7d92726876..0000000000 Binary files a/files/opencs/scene-view-status-18.png and /dev/null differ diff --git a/files/opencs/scene-view-status-19.png b/files/opencs/scene-view-status-19.png deleted file mode 100644 index 17b75ce0e2..0000000000 Binary files a/files/opencs/scene-view-status-19.png and /dev/null differ diff --git a/files/opencs/scene-view-status-2.png b/files/opencs/scene-view-status-2.png deleted file mode 100644 index edb350b8b5..0000000000 Binary files a/files/opencs/scene-view-status-2.png and /dev/null differ diff --git a/files/opencs/scene-view-status-2.svg b/files/opencs/scene-view-status-2.svg new file mode 100644 index 0000000000..656c5d9d39 --- /dev/null +++ b/files/opencs/scene-view-status-2.svg @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-20.png b/files/opencs/scene-view-status-20.png deleted file mode 100644 index bb950d94e3..0000000000 Binary files a/files/opencs/scene-view-status-20.png and /dev/null differ diff --git a/files/opencs/scene-view-status-21.png b/files/opencs/scene-view-status-21.png deleted file mode 100644 index 76ad0022c7..0000000000 Binary files a/files/opencs/scene-view-status-21.png and /dev/null differ diff --git a/files/opencs/scene-view-status-22.png b/files/opencs/scene-view-status-22.png deleted file mode 100644 index e33820a166..0000000000 Binary files a/files/opencs/scene-view-status-22.png and /dev/null differ diff --git a/files/opencs/scene-view-status-23.png b/files/opencs/scene-view-status-23.png deleted file mode 100644 index 6c1fcc54f2..0000000000 Binary files a/files/opencs/scene-view-status-23.png and /dev/null differ diff --git a/files/opencs/scene-view-status-24.png b/files/opencs/scene-view-status-24.png deleted file mode 100644 index 057532b77a..0000000000 Binary files a/files/opencs/scene-view-status-24.png and /dev/null differ diff --git a/files/opencs/scene-view-status-25.png b/files/opencs/scene-view-status-25.png deleted file mode 100644 index 061336c995..0000000000 Binary files a/files/opencs/scene-view-status-25.png and /dev/null differ diff --git a/files/opencs/scene-view-status-26.png b/files/opencs/scene-view-status-26.png deleted file mode 100644 index 8fbcf68c63..0000000000 Binary files a/files/opencs/scene-view-status-26.png and /dev/null differ diff --git a/files/opencs/scene-view-status-27.png b/files/opencs/scene-view-status-27.png deleted file mode 100644 index 30eb053b9b..0000000000 Binary files a/files/opencs/scene-view-status-27.png and /dev/null differ diff --git a/files/opencs/scene-view-status-28.png b/files/opencs/scene-view-status-28.png deleted file mode 100644 index 3a8f77457d..0000000000 Binary files a/files/opencs/scene-view-status-28.png and /dev/null differ diff --git a/files/opencs/scene-view-status-29.png b/files/opencs/scene-view-status-29.png deleted file mode 100644 index eff6106662..0000000000 Binary files a/files/opencs/scene-view-status-29.png and /dev/null differ diff --git a/files/opencs/scene-view-status-3.png b/files/opencs/scene-view-status-3.png deleted file mode 100644 index ddf27c2db1..0000000000 Binary files a/files/opencs/scene-view-status-3.png and /dev/null differ diff --git a/files/opencs/scene-view-status-3.svg b/files/opencs/scene-view-status-3.svg new file mode 100644 index 0000000000..56b471de20 --- /dev/null +++ b/files/opencs/scene-view-status-3.svg @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-30.png b/files/opencs/scene-view-status-30.png deleted file mode 100644 index 63ce7d0d25..0000000000 Binary files a/files/opencs/scene-view-status-30.png and /dev/null differ diff --git a/files/opencs/scene-view-status-31.png b/files/opencs/scene-view-status-31.png deleted file mode 100644 index 1906fd89cd..0000000000 Binary files a/files/opencs/scene-view-status-31.png and /dev/null differ diff --git a/files/opencs/scene-view-status-4.png b/files/opencs/scene-view-status-4.png deleted file mode 100644 index 246c5aae92..0000000000 Binary files a/files/opencs/scene-view-status-4.png and /dev/null differ diff --git a/files/opencs/scene-view-status-4.svg b/files/opencs/scene-view-status-4.svg new file mode 100644 index 0000000000..8fc4e9c31a --- /dev/null +++ b/files/opencs/scene-view-status-4.svg @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-5.png b/files/opencs/scene-view-status-5.png deleted file mode 100644 index 63d37f8be5..0000000000 Binary files a/files/opencs/scene-view-status-5.png and /dev/null differ diff --git a/files/opencs/scene-view-status-5.svg b/files/opencs/scene-view-status-5.svg new file mode 100644 index 0000000000..5a74f8e829 --- /dev/null +++ b/files/opencs/scene-view-status-5.svg @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-6.png b/files/opencs/scene-view-status-6.png deleted file mode 100644 index 051aa64ae7..0000000000 Binary files a/files/opencs/scene-view-status-6.png and /dev/null differ diff --git a/files/opencs/scene-view-status-6.svg b/files/opencs/scene-view-status-6.svg new file mode 100644 index 0000000000..4321be4269 --- /dev/null +++ b/files/opencs/scene-view-status-6.svg @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-7.png b/files/opencs/scene-view-status-7.png deleted file mode 100644 index 6b2e5fdc16..0000000000 Binary files a/files/opencs/scene-view-status-7.png and /dev/null differ diff --git a/files/opencs/scene-view-status-7.svg b/files/opencs/scene-view-status-7.svg new file mode 100644 index 0000000000..bcaefa0bba --- /dev/null +++ b/files/opencs/scene-view-status-7.svg @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-8.png b/files/opencs/scene-view-status-8.png deleted file mode 100644 index 65ea108ac5..0000000000 Binary files a/files/opencs/scene-view-status-8.png and /dev/null differ diff --git a/files/opencs/scene-view-status-8.svg b/files/opencs/scene-view-status-8.svg new file mode 100644 index 0000000000..5f6107f774 --- /dev/null +++ b/files/opencs/scene-view-status-8.svg @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-9.png b/files/opencs/scene-view-status-9.png deleted file mode 100644 index 72d0d9fb77..0000000000 Binary files a/files/opencs/scene-view-status-9.png and /dev/null differ diff --git a/files/opencs/scene-view-status-9.svg b/files/opencs/scene-view-status-9.svg new file mode 100644 index 0000000000..5d101ad1f4 --- /dev/null +++ b/files/opencs/scene-view-status-9.svg @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-terrain.png b/files/opencs/scene-view-terrain.png deleted file mode 100644 index b0051eb127..0000000000 Binary files a/files/opencs/scene-view-terrain.png and /dev/null differ diff --git a/files/opencs/scene-view-terrain.svg b/files/opencs/scene-view-terrain.svg new file mode 100644 index 0000000000..e816d8e3b5 --- /dev/null +++ b/files/opencs/scene-view-terrain.svg @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-water.png b/files/opencs/scene-view-water.png deleted file mode 100644 index 246c5aae92..0000000000 Binary files a/files/opencs/scene-view-water.png and /dev/null differ diff --git a/files/opencs/scene-view-water.svg b/files/opencs/scene-view-water.svg new file mode 100644 index 0000000000..68edc166e4 --- /dev/null +++ b/files/opencs/scene-view-water.svg @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene.png b/files/opencs/scene.png deleted file mode 100644 index a9fc03f769..0000000000 Binary files a/files/opencs/scene.png and /dev/null differ diff --git a/files/opencs/scene.svg b/files/opencs/scene.svg new file mode 100644 index 0000000000..a516d6b596 --- /dev/null +++ b/files/opencs/scene.svg @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/script.png b/files/opencs/script.png deleted file mode 100644 index 29d622e19f..0000000000 Binary files a/files/opencs/script.png and /dev/null differ diff --git a/files/opencs/script.svg b/files/opencs/script.svg new file mode 100644 index 0000000000..76f3b7b28c --- /dev/null +++ b/files/opencs/script.svg @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/selection-mode-cube-corner.png b/files/opencs/selection-mode-cube-corner.png deleted file mode 100644 index b69ef1782f..0000000000 Binary files a/files/opencs/selection-mode-cube-corner.png and /dev/null differ diff --git a/files/opencs/selection-mode-cube-corner.svg b/files/opencs/selection-mode-cube-corner.svg new file mode 100644 index 0000000000..63d5f73c56 --- /dev/null +++ b/files/opencs/selection-mode-cube-corner.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/selection-mode-cube-sphere.png b/files/opencs/selection-mode-cube-sphere.png deleted file mode 100644 index cb5432eea6..0000000000 Binary files a/files/opencs/selection-mode-cube-sphere.png and /dev/null differ diff --git a/files/opencs/selection-mode-cube.png b/files/opencs/selection-mode-cube.png deleted file mode 100644 index 1344a3fb1a..0000000000 Binary files a/files/opencs/selection-mode-cube.png and /dev/null differ diff --git a/files/opencs/selection-mode-cube.svg b/files/opencs/selection-mode-cube.svg new file mode 100644 index 0000000000..e03138bafe --- /dev/null +++ b/files/opencs/selection-mode-cube.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/selection-mode-sphere.svg b/files/opencs/selection-mode-sphere.svg new file mode 100644 index 0000000000..9f8ed79340 --- /dev/null +++ b/files/opencs/selection-mode-sphere.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/skill.png b/files/opencs/skill.png deleted file mode 100644 index 0ef7cb1cb7..0000000000 Binary files a/files/opencs/skill.png and /dev/null differ diff --git a/files/opencs/skill.svg b/files/opencs/skill.svg new file mode 100644 index 0000000000..21ae4d12c8 --- /dev/null +++ b/files/opencs/skill.svg @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/sound-generator.png b/files/opencs/sound-generator.png deleted file mode 100644 index 79833df9c3..0000000000 Binary files a/files/opencs/sound-generator.png and /dev/null differ diff --git a/files/opencs/sound-generator.svg b/files/opencs/sound-generator.svg new file mode 100644 index 0000000000..4e27f19f41 --- /dev/null +++ b/files/opencs/sound-generator.svg @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/sound.png b/files/opencs/sound.png deleted file mode 100644 index 86871611f5..0000000000 Binary files a/files/opencs/sound.png and /dev/null differ diff --git a/files/opencs/sound.svg b/files/opencs/sound.svg new file mode 100644 index 0000000000..ead929c862 --- /dev/null +++ b/files/opencs/sound.svg @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/spell.png b/files/opencs/spell.png deleted file mode 100644 index 5890ea751b..0000000000 Binary files a/files/opencs/spell.png and /dev/null differ diff --git a/files/opencs/spell.svg b/files/opencs/spell.svg new file mode 100644 index 0000000000..e726d85a7e --- /dev/null +++ b/files/opencs/spell.svg @@ -0,0 +1,264 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/start-script.png b/files/opencs/start-script.png deleted file mode 100644 index 73ed157b9e..0000000000 Binary files a/files/opencs/start-script.png and /dev/null differ diff --git a/files/opencs/start-script.svg b/files/opencs/start-script.svg new file mode 100644 index 0000000000..f7348c493a --- /dev/null +++ b/files/opencs/start-script.svg @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/static.png b/files/opencs/static.png deleted file mode 100644 index 9f458e5d04..0000000000 Binary files a/files/opencs/static.png and /dev/null differ diff --git a/files/opencs/static.svg b/files/opencs/static.svg new file mode 100644 index 0000000000..8e8ce541ce --- /dev/null +++ b/files/opencs/static.svg @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/stop-openmw.png b/files/opencs/stop-openmw.png deleted file mode 100644 index d8f8096724..0000000000 Binary files a/files/opencs/stop-openmw.png and /dev/null differ diff --git a/files/opencs/stop-openmw.svg b/files/opencs/stop-openmw.svg new file mode 100644 index 0000000000..73c5936fee --- /dev/null +++ b/files/opencs/stop-openmw.svg @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/transform-move.png b/files/opencs/transform-move.png deleted file mode 100644 index 1e5bd573d3..0000000000 Binary files a/files/opencs/transform-move.png and /dev/null differ diff --git a/files/opencs/transform-move.svg b/files/opencs/transform-move.svg new file mode 100644 index 0000000000..1b9490f001 --- /dev/null +++ b/files/opencs/transform-move.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + diff --git a/files/opencs/transform-rotate.png b/files/opencs/transform-rotate.png deleted file mode 100644 index b6c6bc58a7..0000000000 Binary files a/files/opencs/transform-rotate.png and /dev/null differ diff --git a/files/opencs/transform-rotate.svg b/files/opencs/transform-rotate.svg new file mode 100644 index 0000000000..29bc3fdad5 --- /dev/null +++ b/files/opencs/transform-rotate.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + diff --git a/files/opencs/transform-scale.png b/files/opencs/transform-scale.png deleted file mode 100644 index c641259bd7..0000000000 Binary files a/files/opencs/transform-scale.png and /dev/null differ diff --git a/files/opencs/transform-scale.svg b/files/opencs/transform-scale.svg new file mode 100644 index 0000000000..e4d1d16422 --- /dev/null +++ b/files/opencs/transform-scale.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + diff --git a/files/opencs/weapon.png b/files/opencs/weapon.png deleted file mode 100644 index e2c1d3dc16..0000000000 Binary files a/files/opencs/weapon.png and /dev/null differ diff --git a/files/opencs/weapon.svg b/files/opencs/weapon.svg new file mode 100644 index 0000000000..6168812305 --- /dev/null +++ b/files/opencs/weapon.svg @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/openmw.appdata.xml b/files/openmw.appdata.xml index aacdc1dd96..d20d0740a8 100644 --- a/files/openmw.appdata.xml +++ b/files/openmw.appdata.xml @@ -21,6 +21,15 @@ Copyright 2020 Bret Curtis

    org.openmw.launcher.desktop + + OpenMW Contributors + + + + #dcccb8 + #574526 + + https://wiki.openmw.org/images/Openmw_0.11.1_launcher_1.png @@ -28,15 +37,15 @@ Copyright 2020 Bret Curtis https://wiki.openmw.org/images/0.40_Screenshot-Balmora_3.png - The Mournhold's plaza on OpenMW + Balmora at morning on OpenMW https://wiki.openmw.org/images/Screenshot_mournhold_plaza_0.35.png - Vivec seen from Ebonheart on OpenMW + The Mournhold's plaza on OpenMW https://wiki.openmw.org/images/Screenshot_Vivec_seen_from_Ebonheart_0.35.png - Balmora at morning on OpenMW + Vivec seen from Ebonheart on OpenMW diff --git a/files/openmw.cfg b/files/openmw.cfg index 37ecda3b1d..20e11323cf 100644 --- a/files/openmw.cfg +++ b/files/openmw.cfg @@ -2,7 +2,6 @@ # Modifications should be done on the user openmw.cfg file instead # (see: https://openmw.readthedocs.io/en/master/reference/modding/paths.html) -content=builtin.omwscripts data-local="?userdata?data" user-data="?userdata?" config="?userconfig?" diff --git a/files/openmw.cfg.local b/files/openmw.cfg.local index c9949f2447..65f8b31136 100644 --- a/files/openmw.cfg.local +++ b/files/openmw.cfg.local @@ -2,7 +2,6 @@ # Modifications should be done on the user openmw.cfg file instead # (see: https://openmw.readthedocs.io/en/master/reference/modding/paths.html) -content=builtin.omwscripts data-local="?userdata?data" user-data="?userdata?" config="?userconfig?" diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 4a90a46cc5..10c25bb430 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -405,19 +405,23 @@ console history buffer size = 4096 [Shaders] -# Force rendering with shaders. By default, only bump-mapped objects will use shaders. -# Enabling this option may cause slightly different visuals if the "clamp lighting" option -# is set to false. Otherwise, there should not be a visual difference. +# Force rendering with shaders, even for objects that don't strictly need them. +# By default, only objects with certain effects, such as bump or normal maps will use shaders. +# With enhancements enabled, such as "enable shadows" and "reverse z", shaders must be used for all objects, as if this setting is true. +# Some settings, such as "clamp lighting" only apply to objects using shaders, so enabling this option may cause slightly different visuals when used at the same time. +# Otherwise, there should not be a visual difference. force shaders = false -# Force the use of per pixel lighting. By default, only bump mapped objects use per-pixel lighting. -# Has no effect if the 'force shaders' option is false. +# Force the use of per pixel lighting. By default, only bump and normal mapped objects use per-pixel lighting. +# Only affects objects drawn with shaders (see "force shaders" option). # Enabling per-pixel lighting can result in visual differences to the original MW engine as # certain lights in Morrowind rely on vertex lighting to look as intended. +# Note that groundcover shaders and particle effects ignore this setting. force per pixel lighting = false # Restrict the amount of lighting that an object can receive to a maximum of (1,1,1). -# Only affects objects that render with shaders (see 'force shaders' option). Always affects terrain. +# Only affects objects that render with shaders (see 'force shaders' option). +# When disabled, terrain is always drawn with shaders to prevent seams between tiles that are and that aren't. # Setting this option to 'true' results in fixed-function compatible lighting, but the lighting # may appear 'dull' and there might be color shifts. # Setting this option to 'false' results in more realistic lighting. @@ -608,6 +612,9 @@ hrtf enable = -1 # Specifies which HRTF to use when HRTF is used. Blank means use the default. hrtf = +# Specifies whether to use camera as audio listener +camera listener = false + [Video] # Resolution of the OpenMW window or screen. @@ -673,6 +680,12 @@ small feature culling pixel size = 20.0 # By what factor water downscales objects. Only works with water shader and refractions on. refraction scale = 1.0 +# Make incident sunlight spread through water. +sunlight scattering = true + +# Fade and wobble water plane edges to avoid harsh shoreline transitions. +wobbly shores = true + [Windows] # Location and sizes of windows as a fraction of the OpenMW window or @@ -968,7 +981,7 @@ enable agents paths render = false enable recast mesh render = false # Max number of navmesh tiles (value >= 0) -max tiles number = 512 +max tiles number = 1024 # Min time duration for the same tile update in milliseconds (value >= 0) min update interval ms = 250 @@ -986,6 +999,9 @@ write to navmeshdb = true # Approximate maximum file size of navigation mesh cache stored on disk in bytes (value > 0) max navmeshdb file size = 2147483648 +# Wait until all queued async navmesh jobs are processed before exiting the engine (true, false) +wait for all jobs on exit = false + [Shadows] # Enable or disable shadows. Bear in mind that this will force OpenMW to use shaders as if "[Shaders]/force shaders" was set to true. diff --git a/files/shaders/compatibility/bs/default.frag b/files/shaders/compatibility/bs/default.frag index 77131c6a52..3c665752d1 100644 --- a/files/shaders/compatibility/bs/default.frag +++ b/files/shaders/compatibility/bs/default.frag @@ -77,7 +77,11 @@ void main() vec3 specularColor = getSpecularColor().xyz; #if @normalMap vec4 normalTex = texture2D(normalMap, normalMapUV); - vec3 viewNormal = normalToView(normalTex.xyz * 2.0 - 1.0); + vec3 normal = normalTex.xyz * 2.0 - 1.0; +#if @reconstructNormalZ + normal.z = sqrt(1.0 - dot(normal.xy, normal.xy)); +#endif + vec3 viewNormal = normalToView(normal); specularColor *= normalTex.a; #else vec3 viewNormal = normalize(gl_NormalMatrix * passNormal); diff --git a/files/shaders/compatibility/groundcover.frag b/files/shaders/compatibility/groundcover.frag index dfdd6518c3..96a79c8793 100644 --- a/files/shaders/compatibility/groundcover.frag +++ b/files/shaders/compatibility/groundcover.frag @@ -59,7 +59,12 @@ void main() gl_FragData[0].a = alphaTest(gl_FragData[0].a, alphaRef); #if @normalMap - vec3 viewNormal = normalToView(texture2D(normalMap, normalMapUV).xyz * 2.0 - 1.0); + vec4 normalTex = texture2D(normalMap, normalMapUV); + vec3 normal = normalTex.xyz * 2.0 - 1.0; +#if @reconstructNormalZ + normal.z = sqrt(1.0 - dot(normal.xy, normal.xy)); +#endif + vec3 viewNormal = normalToView(normal); #else vec3 viewNormal = normalToView(normalize(passNormal)); #endif diff --git a/files/shaders/compatibility/objects.frag b/files/shaders/compatibility/objects.frag index 56c7abf27c..7f163580cc 100644 --- a/files/shaders/compatibility/objects.frag +++ b/files/shaders/compatibility/objects.frag @@ -167,7 +167,12 @@ vec2 screenCoords = gl_FragCoord.xy / screenRes; gl_FragData[0].a = alphaTest(gl_FragData[0].a, alphaRef); #if @normalMap - vec3 viewNormal = normalToView(texture2D(normalMap, normalMapUV + offset).xyz * 2.0 - 1.0); + vec4 normalTex = texture2D(normalMap, normalMapUV + offset); + vec3 normal = normalTex.xyz * 2.0 - 1.0; +#if @reconstructNormalZ + normal.z = sqrt(1.0 - dot(normal.xy, normal.xy)); +#endif + vec3 viewNormal = normalToView(normal); #else vec3 viewNormal = normalize(gl_NormalMatrix * passNormal); #endif diff --git a/files/shaders/compatibility/ripples_blobber.frag b/files/shaders/compatibility/ripples_blobber.frag index ea874af83e..d9cadcda98 100644 --- a/files/shaders/compatibility/ripples_blobber.frag +++ b/files/shaders/compatibility/ripples_blobber.frag @@ -13,7 +13,7 @@ uniform vec2 offset; void main() { - vec2 uv = (gl_FragCoord.xy + offset) / @ripple_map_size; + vec2 uv = (gl_FragCoord.xy + offset) / @rippleMapSize; vec4 color = texture2D(imageIn, uv); float wavesizeMultiplier = getTemporalWaveSizeMultiplier(osg_SimulationTime); diff --git a/files/shaders/compatibility/ripples_simulate.frag b/files/shaders/compatibility/ripples_simulate.frag index f36cab5b40..fb416df2c6 100644 --- a/files/shaders/compatibility/ripples_simulate.frag +++ b/files/shaders/compatibility/ripples_simulate.frag @@ -6,9 +6,9 @@ uniform sampler2D imageIn; void main() { - vec2 uv = gl_FragCoord.xy / @ripple_map_size; + vec2 uv = gl_FragCoord.xy / @rippleMapSize; - float pixelSize = 1.0 / @ripple_map_size; + float pixelSize = 1.0 / @rippleMapSize; float oneOffset = pixelSize; float oneAndHalfOffset = 1.5 * pixelSize; diff --git a/files/shaders/compatibility/shadows_vertex.glsl b/files/shaders/compatibility/shadows_vertex.glsl index a99a4a10e6..23fbc74988 100644 --- a/files/shaders/compatibility/shadows_vertex.glsl +++ b/files/shaders/compatibility/shadows_vertex.glsl @@ -3,7 +3,6 @@ #if SHADOWS @foreach shadow_texture_unit_index @shadow_texture_unit_list uniform mat4 shadowSpaceMatrix@shadow_texture_unit_index; - uniform int shadowTextureUnit@shadow_texture_unit_index; varying vec4 shadowSpaceCoords@shadow_texture_unit_index; #if @perspectiveShadowMaps diff --git a/files/shaders/compatibility/terrain.frag b/files/shaders/compatibility/terrain.frag index abc7425eb0..9d89217f35 100644 --- a/files/shaders/compatibility/terrain.frag +++ b/files/shaders/compatibility/terrain.frag @@ -63,7 +63,12 @@ void main() #endif #if @normalMap - vec3 viewNormal = normalToView(texture2D(normalMap, adjustedUV).xyz * 2.0 - 1.0); + vec4 normalTex = texture2D(normalMap, adjustedUV); + vec3 normal = normalTex.xyz * 2.0 - 1.0; +#if @reconstructNormalZ + normal.z = sqrt(1.0 - dot(normal.xy, normal.xy)); +#endif + vec3 viewNormal = normalToView(normal); #else vec3 viewNormal = normalize(gl_NormalMatrix * passNormal); #endif diff --git a/files/shaders/compatibility/water.frag b/files/shaders/compatibility/water.frag index 5817b0c5ae..749dcf27cd 100644 --- a/files/shaders/compatibility/water.frag +++ b/files/shaders/compatibility/water.frag @@ -10,26 +10,19 @@ #include "lib/core/fragment.h.glsl" -#define REFRACTION @refraction_enabled - // Inspired by Blender GLSL Water by martinsh ( https://devlog-martinsh.blogspot.de/2012/07/waterundewater-shader-wip.html ) // tweakables -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- const float VISIBILITY = 2500.0; +const float VISIBILITY_DEPTH = VISIBILITY * 1.5; +const float DEPTH_FADE = 0.15; -const float BIG_WAVES_X = 0.1; // strength of big waves -const float BIG_WAVES_Y = 0.1; - -const float MID_WAVES_X = 0.1; // strength of middle sized waves -const float MID_WAVES_Y = 0.1; -const float MID_WAVES_RAIN_X = 0.2; -const float MID_WAVES_RAIN_Y = 0.2; - -const float SMALL_WAVES_X = 0.1; // strength of small waves -const float SMALL_WAVES_Y = 0.1; -const float SMALL_WAVES_RAIN_X = 0.3; -const float SMALL_WAVES_RAIN_Y = 0.3; +const vec2 BIG_WAVES = vec2(0.1, 0.1); // strength of big waves +const vec2 MID_WAVES = vec2(0.1, 0.1); // strength of middle sized waves +const vec2 MID_WAVES_RAIN = vec2(0.2, 0.2); +const vec2 SMALL_WAVES = vec2(0.1, 0.1); // strength of small waves +const vec2 SMALL_WAVES_RAIN = vec2(0.3, 0.3); const float WAVE_CHOPPYNESS = 0.05; // wave choppyness const float WAVE_SCALE = 75.0; // overall wave scale @@ -39,22 +32,26 @@ const float BUMP_RAIN = 2.5; const float REFL_BUMP = 0.10; // reflection distortion amount const float REFR_BUMP = 0.07; // refraction distortion amount +#if @sunlightScattering const float SCATTER_AMOUNT = 0.3; // amount of sunlight scattering const vec3 SCATTER_COLOUR = vec3(0.0,1.0,0.95); // colour of sunlight scattering +const vec3 SUN_EXT = vec3(0.45, 0.55, 0.68); // sunlight extinction +#endif -const vec3 SUN_EXT = vec3(0.45, 0.55, 0.68); //sunlight extinction const float SUN_SPEC_FADING_THRESHOLD = 0.15; // visibility at which sun specularity starts to fade - const float SPEC_HARDNESS = 256.0; // specular highlights hardness const float BUMP_SUPPRESS_DEPTH = 300.0; // at what water depth bumpmap will be suppressed for reflections and refractions (prevents artifacts at shores) +const float REFR_FOG_DISTORT_DISTANCE = 3000.0; // at what distance refraction fog will be calculated using real water depth instead of distorted depth (prevents splotchy shores) const vec2 WIND_DIR = vec2(0.5f, -0.8f); const float WIND_SPEED = 0.2f; const vec3 WATER_COLOR = vec3(0.090195, 0.115685, 0.12745); +#if @wobblyShores const float WOBBLY_SHORE_FADE_DISTANCE = 6200.0; // fade out wobbly shores to mask precision errors, the effect is almost impossible to see at a distance +#endif // -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - @@ -97,7 +94,6 @@ uniform vec2 screenRes; void main(void) { vec2 UV = worldPos.xy / (8192.0*5.0) * 3.0; - UV.y *= -1.0; float shadow = unshadowedLightRatio(linearDepth); @@ -127,63 +123,61 @@ void main(void) float distortionLevel = 2.0; rippleAdd += distortionLevel * vec3(texture2D(rippleMap, rippleMapUV).ba * blendFar * blendClose, 0.0); - vec2 bigWaves = vec2(BIG_WAVES_X,BIG_WAVES_Y); - vec2 midWaves = mix(vec2(MID_WAVES_X,MID_WAVES_Y),vec2(MID_WAVES_RAIN_X,MID_WAVES_RAIN_Y),rainIntensity); - vec2 smallWaves = mix(vec2(SMALL_WAVES_X,SMALL_WAVES_Y),vec2(SMALL_WAVES_RAIN_X,SMALL_WAVES_RAIN_Y),rainIntensity); + vec2 bigWaves = BIG_WAVES; + vec2 midWaves = mix(MID_WAVES, MID_WAVES_RAIN, rainIntensity); + vec2 smallWaves = mix(SMALL_WAVES, SMALL_WAVES_RAIN, rainIntensity); float bump = mix(BUMP,BUMP_RAIN,rainIntensity); vec3 normal = (normal0 * bigWaves.x + normal1 * bigWaves.y + normal2 * midWaves.x + normal3 * midWaves.y + normal4 * smallWaves.x + normal5 * smallWaves.y + rippleAdd); normal = normalize(vec3(-normal.x * bump, -normal.y * bump, normal.z)); - vec3 lVec = normalize((gl_ModelViewMatrixInverse * vec4(lcalcPosition(0).xyz, 0.0)).xyz); + vec3 sunWorldDir = normalize((gl_ModelViewMatrixInverse * vec4(lcalcPosition(0).xyz, 0.0)).xyz); vec3 cameraPos = (gl_ModelViewMatrixInverse * vec4(0,0,0,1)).xyz; - vec3 vVec = normalize(position.xyz - cameraPos.xyz); + vec3 viewDir = normalize(position.xyz - cameraPos.xyz); float sunFade = length(gl_LightModel.ambient.xyz); // fresnel float ior = (cameraPos.z>0.0)?(1.333/1.0):(1.0/1.333); // air to water; water to air - float fresnel = clamp(fresnel_dielectric(vVec, normal, ior), 0.0, 1.0); - - float radialise = 1.0; - -#if @radialFog - float radialDepth = distance(position.xyz, cameraPos); - // TODO: Figure out how to properly radialise refraction depth and thus underwater fog - // while avoiding oddities when the water plane is close to the clipping plane - // radialise = radialDepth / linearDepth; -#else - float radialDepth = 0.0; -#endif + float fresnel = clamp(fresnel_dielectric(viewDir, normal, ior), 0.0, 1.0); vec2 screenCoordsOffset = normal.xy * REFL_BUMP; -#if REFRACTION - float depthSample = linearizeDepth(sampleRefractionDepthMap(screenCoords), near, far) * radialise; - float depthSampleDistorted = linearizeDepth(sampleRefractionDepthMap(screenCoords-screenCoordsOffset), near, far) * radialise; - float surfaceDepth = linearizeDepth(gl_FragCoord.z, near, far) * radialise; +#if @waterRefraction + float depthSample = linearizeDepth(sampleRefractionDepthMap(screenCoords), near, far); + float surfaceDepth = linearizeDepth(gl_FragCoord.z, near, far); float realWaterDepth = depthSample - surfaceDepth; // undistorted water depth in view direction, independent of frustum - screenCoordsOffset *= clamp(realWaterDepth / BUMP_SUPPRESS_DEPTH,0,1); + float depthSampleDistorted = linearizeDepth(sampleRefractionDepthMap(screenCoords - screenCoordsOffset), near, far); + float waterDepthDistorted = max(depthSampleDistorted - surfaceDepth, 0.0); + screenCoordsOffset *= clamp(realWaterDepth / BUMP_SUPPRESS_DEPTH, 0.0, 1.0); #endif // reflection vec3 reflection = sampleReflectionMap(screenCoords + screenCoordsOffset).rgb; - // specular - float specular = pow(max(dot(reflect(vVec, normal), lVec), 0.0),SPEC_HARDNESS) * shadow; - vec3 waterColor = WATER_COLOR * sunFade; vec4 sunSpec = lcalcSpecular(0); // alpha component is sun visibility; we want to start fading lighting effects when visibility is low sunSpec.a = min(1.0, sunSpec.a / SUN_SPEC_FADING_THRESHOLD); + // specular + float specular = pow(max(dot(reflect(viewDir, normal), sunWorldDir), 0.0), SPEC_HARDNESS) * shadow * sunSpec.a; + // artificial specularity to make rain ripples more noticeable vec3 skyColorEstimate = vec3(max(0.0, mix(-0.3, 1.0, sunFade))); vec3 rainSpecular = abs(rainRipple.w)*mix(skyColorEstimate, vec3(1.0), 0.05)*0.5; + float waterTransparency = clamp(fresnel * 6.0 + specular, 0.0, 1.0); -#if REFRACTION - // no alpha here, so make sure raindrop ripple specularity gets properly subdued - rainSpecular *= clamp(fresnel*6.0 + specular * sunSpec.a, 0.0, 1.0); +#if @waterRefraction + // selectively nullify screenCoordsOffset to eliminate remaining shore artifacts, not needed for reflection + if (cameraPos.z > 0.0 && realWaterDepth <= VISIBILITY_DEPTH && waterDepthDistorted > VISIBILITY_DEPTH) + screenCoordsOffset = vec2(0.0); + + depthSampleDistorted = linearizeDepth(sampleRefractionDepthMap(screenCoords - screenCoordsOffset), near, far); + waterDepthDistorted = max(depthSampleDistorted - surfaceDepth, 0.0); + + // fade to realWaterDepth at a distance to compensate for physically inaccurate depth calculation + waterDepthDistorted = mix(waterDepthDistorted, realWaterDepth, min(surfaceDepth / REFR_FOG_DISTORT_DISTANCE, 1.0)); // refraction vec3 refraction = sampleRefractionMap(screenCoords - screenCoordsOffset).rgb; @@ -193,32 +187,52 @@ void main(void) if (cameraPos.z < 0.0) refraction = clamp(refraction * 1.5, 0.0, 1.0); else - refraction = mix(refraction, waterColor, clamp(depthSampleDistorted/VISIBILITY, 0.0, 1.0)); - - // sunlight scattering - // normal for sunlight scattering - vec3 lNormal = (normal0 * bigWaves.x * 0.5 + normal1 * bigWaves.y * 0.5 + normal2 * midWaves.x * 0.2 + - normal3 * midWaves.y * 0.2 + normal4 * smallWaves.x * 0.1 + normal5 * smallWaves.y * 0.1 + rippleAdd); - lNormal = normalize(vec3(-lNormal.x * bump, -lNormal.y * bump, lNormal.z)); - float sunHeight = lVec.z; - vec3 scatterColour = mix(SCATTER_COLOUR*vec3(1.0,0.4,0.0), SCATTER_COLOUR, clamp(1.0-exp(-sunHeight*SUN_EXT), 0.0, 1.0)); - vec3 lR = reflect(lVec, lNormal); - float lightScatter = clamp(dot(lVec,lNormal)*0.7+0.3, 0.0, 1.0) * clamp(dot(lR, vVec)*2.0-1.2, 0.0, 1.0) * SCATTER_AMOUNT * sunFade * sunSpec.a * clamp(1.0-exp(-sunHeight), 0.0, 1.0); - gl_FragData[0].xyz = mix(mix(refraction, scatterColour, lightScatter), reflection, fresnel) + specular * sunSpec.rgb * sunSpec.a + rainSpecular; - gl_FragData[0].w = 1.0; + { + float depthCorrection = sqrt(1.0 + 4.0 * DEPTH_FADE * DEPTH_FADE); + float factor = DEPTH_FADE * DEPTH_FADE / (-0.5 * depthCorrection + 0.5 - waterDepthDistorted / VISIBILITY) + 0.5 * depthCorrection + 0.5; + refraction = mix(refraction, waterColor, clamp(factor, 0.0, 1.0)); + } + +#if @sunlightScattering + vec3 scatterNormal = (normal0 * bigWaves.x * 0.5 + normal1 * bigWaves.y * 0.5 + normal2 * midWaves.x * 0.2 + + normal3 * midWaves.y * 0.2 + normal4 * smallWaves.x * 0.1 + normal5 * smallWaves.y * 0.1 + rippleAdd); + scatterNormal = normalize(vec3(-scatterNormal.xy * bump, scatterNormal.z)); + float sunHeight = sunWorldDir.z; + vec3 scatterColour = mix(SCATTER_COLOUR * vec3(1.0, 0.4, 0.0), SCATTER_COLOUR, max(1.0 - exp(-sunHeight * SUN_EXT), 0.0)); + float scatterLambert = max(dot(sunWorldDir, scatterNormal) * 0.7 + 0.3, 0.0); + float scatterReflectAngle = max(dot(reflect(sunWorldDir, scatterNormal), viewDir) * 2.0 - 1.2, 0.0); + float lightScatter = scatterLambert * scatterReflectAngle * SCATTER_AMOUNT * sunFade * sunSpec.a * max(1.0 - exp(-sunHeight), 0.0); + refraction = mix(refraction, scatterColour, lightScatter); +#endif + gl_FragData[0].rgb = mix(refraction, reflection, fresnel); + gl_FragData[0].a = 1.0; + // no alpha here, so make sure raindrop ripple specularity gets properly subdued + rainSpecular *= waterTransparency; +#else + gl_FragData[0].rgb = mix(waterColor, reflection, (1.0 + fresnel) * 0.5); + gl_FragData[0].a = waterTransparency; +#endif + + gl_FragData[0].rgb += specular * sunSpec.rgb + rainSpecular; + +#if @waterRefraction && @wobblyShores // wobbly water: hard-fade into refraction texture at extremely low depth, with a wobble based on normal mapping vec3 normalShoreRippleRain = texture2D(normalMap,normalCoords(UV, 2.0, 2.7, -1.0*waterTimer, 0.05, 0.1, normal3)).rgb - 0.5 + texture2D(normalMap,normalCoords(UV, 2.0, 2.7, waterTimer, 0.04, -0.13, normal4)).rgb - 0.5; - float verticalWaterDepth = realWaterDepth * mix(abs(vVec.z), 1.0, 0.2); // an estimate + float viewFactor = mix(abs(viewDir.z), 1.0, 0.2); + float verticalWaterDepth = realWaterDepth * viewFactor; // an estimate float shoreOffset = verticalWaterDepth - (normal2.r + mix(0.0, normalShoreRippleRain.r, rainIntensity) + 0.15)*8.0; - float fuzzFactor = min(1.0, 1000.0/surfaceDepth) * mix(abs(vVec.z), 1.0, 0.2); + float fuzzFactor = min(1.0, 1000.0 / surfaceDepth) * viewFactor; shoreOffset *= fuzzFactor; shoreOffset = clamp(mix(shoreOffset, 1.0, clamp(linearDepth / WOBBLY_SHORE_FADE_DISTANCE, 0.0, 1.0)), 0.0, 1.0); - gl_FragData[0].xyz = mix(rawRefraction, gl_FragData[0].xyz, shoreOffset); + gl_FragData[0].rgb = mix(rawRefraction, gl_FragData[0].rgb, shoreOffset); +#endif + +#if @radialFog + float radialDepth = distance(position.xyz, cameraPos); #else - gl_FragData[0].xyz = mix(reflection, waterColor, (1.0-fresnel)*0.5) + specular * sunSpec.rgb * sunSpec.a + rainSpecular; - gl_FragData[0].w = clamp(fresnel*6.0 + specular * sunSpec.a, 0.0, 1.0); //clamp(fresnel*2.0 + specular * gl_LightSource[0].specular.a, 0.0, 1.0); + float radialDepth = 0.0; #endif gl_FragData[0] = applyFogAtDist(gl_FragData[0], radialDepth, linearDepth, far); diff --git a/files/shaders/compatibility/water.vert b/files/shaders/compatibility/water.vert index 93796a7b9c..a67f412a07 100644 --- a/files/shaders/compatibility/water.vert +++ b/files/shaders/compatibility/water.vert @@ -21,7 +21,7 @@ void main(void) position = gl_Vertex; worldPos = position.xyz + nodePosition.xyz; - rippleMapUV = (worldPos.xy - playerPos.xy + (@ripple_map_size * @ripple_map_world_scale / 2.0)) / @ripple_map_size / @ripple_map_world_scale; + rippleMapUV = (worldPos.xy - playerPos.xy + (@rippleMapSize * @rippleMapWorldScale / 2.0)) / @rippleMapSize / @rippleMapWorldScale; vec4 viewPos = modelToView(gl_Vertex); linearDepth = getLinearDepth(gl_Position.z, viewPos.z); diff --git a/files/shaders/lib/core/fragment.glsl b/files/shaders/lib/core/fragment.glsl index 68fbe5e39d..9b983148cb 100644 --- a/files/shaders/lib/core/fragment.glsl +++ b/files/shaders/lib/core/fragment.glsl @@ -9,7 +9,7 @@ vec4 sampleReflectionMap(vec2 uv) return texture2D(reflectionMap, uv); } -#if @refraction_enabled +#if @waterRefraction uniform sampler2D refractionMap; uniform sampler2D refractionDepthMap; diff --git a/files/shaders/lib/core/fragment.h.glsl b/files/shaders/lib/core/fragment.h.glsl index 1bc2a1f79a..b8c3f9a32b 100644 --- a/files/shaders/lib/core/fragment.h.glsl +++ b/files/shaders/lib/core/fragment.h.glsl @@ -6,7 +6,7 @@ vec4 sampleReflectionMap(vec2 uv); -#if @refraction_enabled +#if @waterRefraction vec4 sampleRefractionMap(vec2 uv); float sampleRefractionDepthMap(vec2 uv); #endif diff --git a/files/shaders/lib/core/fragment_multiview.glsl b/files/shaders/lib/core/fragment_multiview.glsl index cc804d6c80..2880087104 100644 --- a/files/shaders/lib/core/fragment_multiview.glsl +++ b/files/shaders/lib/core/fragment_multiview.glsl @@ -12,7 +12,7 @@ vec4 sampleReflectionMap(vec2 uv) return texture(reflectionMap, vec3((uv), gl_ViewID_OVR)); } -#if @refraction_enabled +#if @waterRefraction uniform sampler2DArray refractionMap; uniform sampler2DArray refractionDepthMap; diff --git a/files/shaders/lib/water/rain_ripples.glsl b/files/shaders/lib/water/rain_ripples.glsl index 4e5f85017b..6ec3f101fe 100644 --- a/files/shaders/lib/water/rain_ripples.glsl +++ b/files/shaders/lib/water/rain_ripples.glsl @@ -1,8 +1,6 @@ #ifndef LIB_WATER_RIPPLES #define LIB_WATER_RIPPLES -#define RAIN_RIPPLE_DETAIL @rain_ripple_detail - const float RAIN_RIPPLE_GAPS = 10.0; const float RAIN_RIPPLE_RADIUS = 0.2; @@ -51,7 +49,7 @@ vec4 circle(vec2 coords, vec2 corner, float adjusted_time) float d = length(toCenter); float ringfollower = (phase-d/r)/RAIN_RING_TIME_OFFSET-1.0; // -1.0 ~ +1.0 cover the breadth of the ripple's ring -#if RAIN_RIPPLE_DETAIL > 0 +#if @rainRippleDetail > 0 // normal mapped ripples if(ringfollower < -1.0 || ringfollower > 1.0) return vec4(0.0); @@ -88,7 +86,7 @@ vec4 rain(vec2 uv, float time) vec2 f_part = fract(uv); vec2 i_part = floor(uv); float adjusted_time = time * 1.2 + randPhase(i_part); -#if RAIN_RIPPLE_DETAIL > 0 +#if @rainRippleDetail > 0 vec4 a = circle(f_part, i_part, adjusted_time); vec4 b = circle(f_part, i_part, adjusted_time - RAIN_RING_TIME_OFFSET); vec4 c = circle(f_part, i_part, adjusted_time - RAIN_RING_TIME_OFFSET*2.0); @@ -115,11 +113,11 @@ vec4 rainCombined(vec2 uv, float time) // returns ripple normal in xyz and fake return rain(uv, time) + rain(complex_mult(uv, vec2(0.4, 0.7)) + vec2(1.2, 3.0),time) - #if RAIN_RIPPLE_DETAIL == 2 +#if @rainRippleDetail == 2 + rain(uv * 0.75 + vec2( 3.7,18.9),time) + rain(uv * 0.9 + vec2( 5.7,30.1),time) + rain(uv * 1.0 + vec2(10.5 ,5.7),time) - #endif +#endif ; } diff --git a/files/windows/openmw-cs.exe.manifest b/files/windows/openmw-cs.exe.manifest new file mode 100644 index 0000000000..3107f64706 --- /dev/null +++ b/files/windows/openmw-cs.exe.manifest @@ -0,0 +1,9 @@ + + + + + true + PerMonitorV2 + + + \ No newline at end of file diff --git a/files/wizard/icons/dollar.svg b/files/wizard/icons/dollar.svg new file mode 100644 index 0000000000..60c5a4b23c --- /dev/null +++ b/files/wizard/icons/dollar.svg @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/wizard/icons/folder.svg b/files/wizard/icons/folder.svg new file mode 100644 index 0000000000..a976bad226 --- /dev/null +++ b/files/wizard/icons/folder.svg @@ -0,0 +1,338 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + 2009-01-13, 2020-12-18 + + + Jakub Steiner, Daniel S. Fowler + + + + https://tekeye.uk/free_resources/tango_desktop_project/index + + + folder + directory + icon + + + + + Public Domain + + + + + https://tekeye.uk/ + + + https://tekeye.uk/free_resources/tango_desktop_project/images/folder.svg + computing + A representation of a folder for file storage. + en-GB + https://tekeye.uk/free_resources/tango_desktop_project/images/folder-remote.svg + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/wizard/icons/preferences-desktop-locale.svg b/files/wizard/icons/preferences-desktop-locale.svg new file mode 100644 index 0000000000..51098cfa13 --- /dev/null +++ b/files/wizard/icons/preferences-desktop-locale.svg @@ -0,0 +1,307 @@ + + + Locale Preferences Icon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + Jakub Steiner, Daniel S. Fowler + + + https://tekeye.uk/free_resources/tango_desktop_project/index + + Locale Preferences Icon + + + locale + settings + preferences + location + flag + computer + icon + + + 2021-01-02 + + + Public Domain + + + + + https://tekeye.uk/ + + + https://tekeye.uk/free_resources/tango_desktop_project/images/svg/apps/preferences-desktop-locale.svg + https://tekeye.uk/free_resources/tango_desktop_project/images/svg/apps/preferences-desktop-screensaver.svg + en-GB + computing + An icon for locale settings. + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/wizard/icons/system-installer.svg b/files/wizard/icons/system-installer.svg new file mode 100644 index 0000000000..7d95eea080 --- /dev/null +++ b/files/wizard/icons/system-installer.svg @@ -0,0 +1,456 @@ + + + System Installer Icon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + System Installer Icon + + + Jakub Steiner, Daniel S. Fowler + + + https://tekeye.uk/free_resources/tango_desktop_project/index + + 2021-01-02 + + + Public Domain + + + + + https://tekeye.uk/ + + + https://tekeye.uk/free_resources/tango_desktop_project/images/svg/apps/system-installer.svg + https://tekeye.uk/free_resources/tango_desktop_project/images/svg/apps/preferences-desktop-screensaver.svg + en-GB + + + install + setup + packages + manager + settings + preferences + cd + dvd + computer + icon + + + computing + An icon for system setup and installation. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/wizard/icons/tango/48x48/dollar.png b/files/wizard/icons/tango/48x48/dollar.png deleted file mode 100644 index a14ba2505d..0000000000 Binary files a/files/wizard/icons/tango/48x48/dollar.png and /dev/null differ diff --git a/files/wizard/icons/tango/48x48/folder.png b/files/wizard/icons/tango/48x48/folder.png deleted file mode 100644 index e93d7cf8f8..0000000000 Binary files a/files/wizard/icons/tango/48x48/folder.png and /dev/null differ diff --git a/files/wizard/icons/tango/48x48/preferences-desktop-locale.png b/files/wizard/icons/tango/48x48/preferences-desktop-locale.png deleted file mode 100644 index f56497bd23..0000000000 Binary files a/files/wizard/icons/tango/48x48/preferences-desktop-locale.png and /dev/null differ diff --git a/files/wizard/icons/tango/48x48/system-installer.png b/files/wizard/icons/tango/48x48/system-installer.png deleted file mode 100644 index dbd08f7e2f..0000000000 Binary files a/files/wizard/icons/tango/48x48/system-installer.png and /dev/null differ diff --git a/files/wizard/icons/tango/index.theme b/files/wizard/icons/tango/index.theme deleted file mode 100644 index 3ffebebc9a..0000000000 --- a/files/wizard/icons/tango/index.theme +++ /dev/null @@ -1,8 +0,0 @@ -[Icon Theme] -Name=Tango -Comment=Tango Theme -Inherits=default -Directories=48x48 - -[48x48] -Size=48 \ No newline at end of file diff --git a/files/wizard/wizard.qrc b/files/wizard/wizard.qrc index 7dbb8fe080..6f7bf69eb0 100644 --- a/files/wizard/wizard.qrc +++ b/files/wizard/wizard.qrc @@ -1,10 +1,9 @@ - - icons/tango/48x48/preferences-desktop-locale.png - icons/tango/index.theme - icons/tango/48x48/folder.png - icons/tango/48x48/system-installer.png - icons/tango/48x48/dollar.png + + icons/preferences-desktop-locale.svg + icons/folder.svg + icons/system-installer.svg + icons/dollar.svg images/intropage-background.png diff --git a/scripts/data/integration_tests/test_lua_api/test.lua b/scripts/data/integration_tests/test_lua_api/test.lua index 2ec9f09b97..863cdd0f57 100644 --- a/scripts/data/integration_tests/test_lua_api/test.lua +++ b/scripts/data/integration_tests/test_lua_api/test.lua @@ -2,6 +2,8 @@ local testing = require('testing_util') local core = require('openmw.core') local async = require('openmw.async') local util = require('openmw.util') +local types = require('openmw.types') +local world = require('openmw.world') local function testTimers() testing.expectAlmostEqual(core.getGameTimeScale(), 30, 'incorrect getGameTimeScale() result') @@ -64,6 +66,67 @@ local function testGetGMST() testing.expectEqual(core.getGMST('Level_Up_Level2'), 'something') end +local function testMWScript() + local variableStoreCount = 18 + local variableStore = world.mwscript.getGlobalVariables(player) + testing.expectEqual(variableStoreCount, #variableStore) + + variableStore.year = 5 + testing.expectEqual(5, variableStore.year) + variableStore.year = 1 + local indexCheck = 0 + for index, value in ipairs(variableStore) do + testing.expectEqual(variableStore[index], value) + indexCheck = indexCheck + 1 + end + testing.expectEqual(variableStoreCount, indexCheck) + indexCheck = 0 + for index, value in pairs(variableStore) do + testing.expectEqual(variableStore[index], value) + indexCheck = indexCheck + 1 + end + testing.expectEqual(variableStoreCount, indexCheck) +end + +local function testRecordStore(store,storeName,skipPairs) + testing.expect(store.records) + local firstRecord = store.records[1] + if not firstRecord then return end + testing.expectEqual(firstRecord.id,store.records[firstRecord.id].id) + local status, _ = pcall(function() + for index, value in ipairs(store.records) do + if value.id == firstRecord.id then + testing.expectEqual(index,1,storeName) + break + end + end + end) + + testing.expectEqual(status,true,storeName) + +end + +local function testRecordStores() + for key, type in pairs(types) do + if type.records then + testRecordStore(type,key) + end + end + testRecordStore(core.magic.enchantments,"enchantments") + testRecordStore(core.magic.effects,"effects",true) + testRecordStore(core.magic.spells,"spells") + + testRecordStore(core.stats.Attribute,"Attribute") + testRecordStore(core.stats.Skill,"Skill") + + testRecordStore(core.sound,"sound") + testRecordStore(core.factions,"factions") + + testRecordStore(types.NPC.classes,"classes") + testRecordStore(types.NPC.races,"races") + testRecordStore(types.Player.birthSigns,"birthSigns") +end + local function initPlayer() player:teleport('', util.vector3(4096, 4096, 867.237), util.transform.identity) coroutine.yield() @@ -101,6 +164,8 @@ tests = { end}, {'teleport', testTeleport}, {'getGMST', testGetGMST}, + {'recordStores', testRecordStores}, + {'mwscript', testMWScript}, } return { diff --git a/scripts/data/morrowind_tests/test.lua b/scripts/data/morrowind_tests/test.lua index 8898420b82..3515002f2d 100644 --- a/scripts/data/morrowind_tests/test.lua +++ b/scripts/data/morrowind_tests/test.lua @@ -2,6 +2,7 @@ local testing = require('testing_util') local util = require('openmw.util') local world = require('openmw.world') local core = require('openmw.core') +local types = require('openmw.types') if not core.contentFiles.has('Morrowind.esm') then error('This test requires Morrowind.esm') @@ -18,6 +19,28 @@ local tests = { coroutine.yield() testing.runLocalTest(world.players[1], 'Guard in Imperial Prison Ship should find path (#7241)') end}, + {'Should keep reference to an object moved into container (#7663)', function() + world.players[1]:teleport('ToddTest', util.vector3(2176, 3648, -191), util.transform.rotateZ(math.rad(0))) + coroutine.yield() + local barrel = world.createObject('barrel_01', 1) + local fargothRing = world.createObject('ring_keley', 1) + coroutine.yield() + testing.expectEqual(types.Container.inventory(barrel):find('ring_keley'), nil) + fargothRing:moveInto(types.Container.inventory(barrel)) + coroutine.yield() + testing.expectEqual(fargothRing.recordId, 'ring_keley') + local isFargothRing = function(actual) + if actual == nil then + return 'ring_keley is not found' + end + if actual.id ~= fargothRing.id then + return 'found ring_keley id does not match expected: actual=' .. tostring(actual.id) + .. ', expected=' .. tostring(fargothRing.id) + end + return '' + end + testing.expectThat(types.Container.inventory(barrel):find('ring_keley'), isFargothRing) + end}, } return { diff --git a/scripts/osg_stats.py b/scripts/osg_stats.py index 3cdd0febae..81739c1b57 100755 --- a/scripts/osg_stats.py +++ b/scripts/osg_stats.py @@ -7,6 +7,7 @@ set of keys over given range of frames. import click import collections +import json import matplotlib.pyplot import numpy import operator @@ -22,11 +23,11 @@ import termtables help='Print a list of all present keys in the input file.') @click.option('--regexp_match', is_flag=True, help='Use all metric that match given key. ' - 'Can be used with stats, timeseries, commulative_timeseries, hist, hist_threshold') + 'Can be used with stats, timeseries, cumulative_timeseries, hist, hist_threshold') @click.option('--timeseries', type=str, multiple=True, help='Show a graph for given metric over time.') -@click.option('--commulative_timeseries', type=str, multiple=True, - help='Show a graph for commulative sum of a given metric over time.') +@click.option('--cumulative_timeseries', type=str, multiple=True, + help='Show a graph for cumulative sum of a given metric over time.') @click.option('--timeseries_delta', type=str, multiple=True, help='Show a graph for delta between neighbouring frames of a given metric over time.') @click.option('--hist', type=str, multiple=True, @@ -43,16 +44,20 @@ import termtables 'between Physics Actors and physics_time_taken. Format: --plot .') @click.option('--stats', type=str, multiple=True, help='Print table with stats for a given metric containing min, max, mean, median etc.') +@click.option('--stats_sum', is_flag=True, + help='Add a row to stats table for a sum per frame of all given stats metrics.') +@click.option('--stats_sort_by', type=str, default=None, multiple=True, + help='Sort stats table by given fields (source, key, sum, min, max etc).') +@click.option('--stats_table_format', type=click.Choice(['markdown', 'json']), default='markdown', + help='Print table with stats in given format.') @click.option('--precision', type=int, help='Format floating point numbers with given precision') @click.option('--timeseries_sum', is_flag=True, help='Add a graph to timeseries for a sum per frame of all given timeseries metrics.') -@click.option('--commulative_timeseries_sum', is_flag=True, - help='Add a graph to timeseries for a sum per frame of all given commulative timeseries.') +@click.option('--cumulative_timeseries_sum', is_flag=True, + help='Add a graph to timeseries for a sum per frame of all given cumulative timeseries.') @click.option('--timeseries_delta_sum', is_flag=True, help='Add a graph to timeseries for a sum per frame of all given timeseries delta.') -@click.option('--stats_sum', is_flag=True, - help='Add a row to stats table for a sum per frame of all given stats metrics.') @click.option('--begin_frame', type=int, default=0, help='Start processing from this frame.') @click.option('--end_frame', type=int, default=sys.maxsize, @@ -67,14 +72,12 @@ import termtables help='Threshold for hist_over.') @click.option('--show_common_path_prefix', is_flag=True, help='Show common path prefix when applied to multiple files.') -@click.option('--stats_sort_by', type=str, default=None, multiple=True, - help='Sort stats table by given fields (source, key, sum, min, max etc).') @click.argument('path', type=click.Path(), nargs=-1) def main(print_keys, regexp_match, timeseries, hist, hist_ratio, stdev_hist, plot, stats, precision, timeseries_sum, stats_sum, begin_frame, end_frame, path, - commulative_timeseries, commulative_timeseries_sum, frame_number_name, + cumulative_timeseries, cumulative_timeseries_sum, frame_number_name, hist_threshold, threshold_name, threshold_value, show_common_path_prefix, stats_sort_by, - timeseries_delta, timeseries_delta_sum): + timeseries_delta, timeseries_delta_sum, stats_table_format): sources = {v: list(read_data(v)) for v in path} if path else {'stdin': list(read_data(None))} if not show_common_path_prefix and len(sources) > 1: longest_common_prefix = os.path.commonprefix(list(sources.keys())) @@ -94,8 +97,8 @@ def main(print_keys, regexp_match, timeseries, hist, hist_ratio, stdev_hist, plo if timeseries: draw_timeseries(sources=frames, keys=matching_keys(timeseries), add_sum=timeseries_sum, begin_frame=begin_frame, end_frame=end_frame) - if commulative_timeseries: - draw_commulative_timeseries(sources=frames, keys=matching_keys(commulative_timeseries), add_sum=commulative_timeseries_sum, + if cumulative_timeseries: + draw_cumulative_timeseries(sources=frames, keys=matching_keys(cumulative_timeseries), add_sum=cumulative_timeseries_sum, begin_frame=begin_frame, end_frame=end_frame) if timeseries_delta: draw_timeseries_delta(sources=frames, keys=matching_keys(timeseries_delta), add_sum=timeseries_delta_sum, @@ -109,7 +112,8 @@ def main(print_keys, regexp_match, timeseries, hist, hist_ratio, stdev_hist, plo if plot: draw_plots(sources=frames, plots=plot) if stats: - print_stats(sources=frames, keys=matching_keys(stats), stats_sum=stats_sum, precision=precision, sort_by=stats_sort_by) + print_stats(sources=frames, keys=matching_keys(stats), stats_sum=stats_sum, precision=precision, + sort_by=stats_sort_by, table_format=stats_table_format) if hist_threshold: draw_hist_threshold(sources=frames, keys=matching_keys(hist_threshold), begin_frame=begin_frame, threshold_name=threshold_name, threshold_value=threshold_value) @@ -145,22 +149,17 @@ def collect_per_frame(sources, keys, begin_frame, end_frame, frame_number_name): for key in keys: result[name][key] = [None] * (end_frame - begin_frame) for name, frames in sources.items(): + max_index = 0 for frame in frames: number = frame[frame_number_name] if begin_frame <= number < end_frame: index = number - begin_frame + max_index = max(max_index, index) for key in keys: if key in frame: result[name][key][index] = frame[key] - for name in result.keys(): for key in keys: - prev = 0.0 - values = result[name][key] - for i in range(len(values)): - if values[i] is not None: - prev = values[i] - else: - values[i] = prev + values = result[name][key][:max_index + 1] result[name][key] = numpy.array(values) return result, begin_frame, end_frame @@ -179,26 +178,29 @@ def draw_timeseries(sources, keys, add_sum, begin_frame, end_frame): x = numpy.array(range(begin_frame, end_frame)) for name, frames in sources.items(): for key in keys: - ax.plot(x, frames[key], label=f'{key}:{name}') + y = frames[key] + ax.plot(x[:len(y)], y, label=f'{key}:{name}') if add_sum: - ax.plot(x, numpy.sum(list(frames[k] for k in keys), axis=0), label=f'sum:{name}', linestyle='--') + y = sum_arrays_with_none([frames[k] for k in keys]) + ax.plot(x[:len(y)], y, label=f'sum:{name}', linestyle='--') ax.grid(True) ax.legend() fig.canvas.manager.set_window_title('timeseries') -def draw_commulative_timeseries(sources, keys, add_sum, begin_frame, end_frame): +def draw_cumulative_timeseries(sources, keys, add_sum, begin_frame, end_frame): fig, ax = matplotlib.pyplot.subplots() x = numpy.array(range(begin_frame, end_frame)) for name, frames in sources.items(): for key in keys: - ax.plot(x, numpy.cumsum(frames[key]), label=f'{key}:{name}') + y = cumsum_with_none(frames[key]) + ax.plot(x[:len(y)], y, label=f'{key}:{name}') if add_sum: - ax.plot(x, numpy.cumsum(numpy.sum(list(frames[k] for k in keys), axis=0)), label=f'sum:{name}', - linestyle='--') + y = sum_arrays_with_none([cumsum_with_none(frames[k]) for k in keys]) + ax.plot(x[:len(y)], y, label=f'sum:{name}', linestyle='--') ax.grid(True) ax.legend() - fig.canvas.manager.set_window_title('commulative_timeseries') + fig.canvas.manager.set_window_title('cumulative_timeseries') def draw_timeseries_delta(sources, keys, add_sum, begin_frame, end_frame): @@ -206,10 +208,11 @@ def draw_timeseries_delta(sources, keys, add_sum, begin_frame, end_frame): x = numpy.array(range(begin_frame + 1, end_frame)) for name, frames in sources.items(): for key in keys: - ax.plot(x, numpy.diff(frames[key]), label=f'{key}:{name}') + y = diff_with_none(frames[key]) + ax.plot(x[:len(y)], y, label=f'{key}:{name}') if add_sum: - ax.plot(x, numpy.diff(numpy.sum(list(frames[k] for k in keys), axis=0)), label=f'sum:{name}', - linestyle='--') + y = sum_arrays_with_none([diff_with_none(frames[k]) for k in keys]) + ax.plot(x[:len(y)], y, label=f'sum:{name}', linestyle='--') ax.grid(True) ax.legend() fig.canvas.manager.set_window_title('timeseries_delta') @@ -291,7 +294,7 @@ def draw_plots(sources, plots): fig.canvas.manager.set_window_title('plots') -def print_stats(sources, keys, stats_sum, precision, sort_by): +def print_stats(sources, keys, stats_sum, precision, sort_by, table_format): stats = list() for name, frames in sources.items(): for key in keys: @@ -301,11 +304,17 @@ def print_stats(sources, keys, stats_sum, precision, sort_by): metrics = list(stats[0].keys()) if sort_by: stats.sort(key=operator.itemgetter(*sort_by)) - termtables.print( - [list(v.values()) for v in stats], - header=metrics, - style=termtables.styles.markdown, - ) + if table_format == 'markdown': + termtables.print( + [list(v.values()) for v in stats], + header=metrics, + style=termtables.styles.markdown, + ) + elif table_format == 'json': + table = [dict(zip(metrics, row.values())) for row in stats] + print(json.dumps(table)) + else: + print(f'Unsupported table format: {table_format}') def draw_hist_threshold(sources, keys, begin_frame, threshold_name, threshold_value): @@ -352,13 +361,13 @@ def make_stats(source, key, values, precision): source=source, key=key, number=len(values), - min=fixed_float(min(values), precision), - max=fixed_float(max(values), precision), - sum=fixed_float(sum(values), precision), - mean=fixed_float(statistics.mean(values), precision), - median=fixed_float(statistics.median(values), precision), - stdev=fixed_float(statistics.stdev(float(v) for v in values), precision), - q95=fixed_float(numpy.quantile(values, 0.95), precision), + min=fixed_float(min(values), precision) if values else '-', + max=fixed_float(max(values), precision) if values else '-', + sum=fixed_float(sum(values), precision) if values else '-', + mean=fixed_float(statistics.mean(values), precision) if values else '-', + median=fixed_float(statistics.median(values), precision) if values else '-', + stdev=fixed_float(statistics.stdev(float(v) for v in values), precision) if values else '-', + q95=fixed_float(numpy.quantile(values, 0.95), precision) if values else '-', ) @@ -369,5 +378,49 @@ def to_number(value): return float(value) +def cumsum_with_none(values): + cumsum = None + result = list() + for v in values: + if v is None: + result.append(None) + elif cumsum is None: + cumsum = v + result.append(cumsum) + else: + cumsum += v + result.append(cumsum) + return numpy.array(result) + + +def diff_with_none(values): + if len(values) < 2: + return numpy.array([]) + prev = values[0] + result = list() + for v in values[1:]: + if prev is None: + result.append(v) + prev = v + elif v is None: + result.append(v) + else: + result.append(v - prev) + prev = v + return numpy.array(result) + + +def sum_arrays_with_none(arrays): + size = max(len(v) for v in arrays) + result = list() + for i in range(size): + not_none_values = [v[i] for v in arrays if v[i] is not None] + if not_none_values: + result.append(sum(not_none_values)) + else: + result.append(None) + return numpy.array(result) + + if __name__ == '__main__': main()