Merge branch 'master' into feature/pplLauncherSetting

i-have-no-land-and-i-must-scream
bwyunker 8 months ago
commit 563f5b37a3

@ -210,6 +210,7 @@ Ubuntu_GCC_Debug:
CCACHE_SIZE: 3G CCACHE_SIZE: 3G
CMAKE_BUILD_TYPE: Debug CMAKE_BUILD_TYPE: Debug
CMAKE_CXX_FLAGS_DEBUG: -O0 CMAKE_CXX_FLAGS_DEBUG: -O0
BUILD_SHARED_LIBS: 1
# When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks.
timeout: 2h timeout: 2h
@ -520,19 +521,19 @@ Ubuntu_GCC_integration_tests_asan:
- build/OpenMW-*.dmg - build/OpenMW-*.dmg
- "build/**/*.log" - "build/**/*.log"
macOS13_Xcode14_arm64: macOS14_Xcode15_arm64:
extends: .MacOS extends: .MacOS
image: macos-12-xcode-14 image: macos-14-xcode-15
tags: tags:
- saas-macos-medium-m1 - saas-macos-medium-m1
cache: cache:
key: macOS12_Xcode14_arm64.v4 key: macOS14_Xcode15_arm64.v1
variables: variables:
CCACHE_SIZE: 3G CCACHE_SIZE: 3G
.Windows_Ninja_Base: .Windows_Ninja_Base:
tags: tags:
- windows - saas-windows-medium-amd64
rules: rules:
- if: $CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "merge_request_event"
before_script: before_script:
@ -569,60 +570,54 @@ macOS13_Xcode14_arm64:
- $env:CCACHE_BASEDIR = Get-Location - $env:CCACHE_BASEDIR = Get-Location
- $env:CCACHE_DIR = "$(Get-Location)\ccache" - $env:CCACHE_DIR = "$(Get-Location)\ccache"
- New-Item -Type Directory -Force -Path $env:CCACHE_DIR - New-Item -Type Directory -Force -Path $env:CCACHE_DIR
- New-Item -Type File -Force -Path MSVC2019_64_Ninja\.cmake\api\v1\query\codemodel-v2 - 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 2019 -k -V -N -b -t -C $multiview -E - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2022 -k -V -N -b -t -C $multiview -E
- Get-Volume - Get-Volume
- cd MSVC2019_64_Ninja - cd MSVC2022_64_Ninja
- .\ActivateMSVC.ps1 - .\ActivateMSVC.ps1
- cmake --build . --config $config - cmake --build . --config $config
- ccache --show-stats - ccache --show-stats -v
- cd $config - 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 - 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}"))/" - $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 - Get-ChildItem -Recurse *.ilk | Remove-Item
- | - |
if (Get-ChildItem -Recurse *.pdb) { 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) { 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 .. Push-Location ..
..\CI\Store-Symbols.ps1 ..\CI\Store-Symbols.ps1
if (Test-Path env:AWS_ACCESS_KEY_ID) { 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 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 Pop-Location
Get-ChildItem -Recurse *.pdb | Remove-Item 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) { 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 } } - if ($executables) { foreach ($exe in $executables.Split(',')) { & .\$exe } }
after_script: after_script:
- Get-Volume - Get-Volume
- Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log
cache: cache:
key: ninja-v8 key: ninja-2022-v9
paths: paths:
- ccache - ccache
- deps - deps
- MSVC2019_64_Ninja/deps/Qt - MSVC2022_64_Ninja/deps/Qt
artifacts: artifacts:
when: always when: always
paths: paths:
- "*.zip" - "*.zip"
- "*.log" - "*.log"
- MSVC2019_64_Ninja/*.log - MSVC2022_64_Ninja/*.log
- MSVC2019_64_Ninja/*/*.log - MSVC2022_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
# When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks.
timeout: 2h timeout: 2h
@ -655,7 +650,7 @@ macOS13_Xcode14_arm64:
.Windows_MSBuild_Base: .Windows_MSBuild_Base:
tags: tags:
- windows - saas-windows-medium-amd64
rules: rules:
- if: $CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "merge_request_event"
before_script: before_script:
@ -665,7 +660,6 @@ macOS13_Xcode14_arm64:
- choco source disable -n=chocolatey - choco source disable -n=chocolatey
- choco install git --force --params "/GitAndUnixToolsOnPath" -y - choco install git --force --params "/GitAndUnixToolsOnPath" -y
- choco install 7zip -y - choco install 7zip -y
- choco install ccache -y
- choco install vswhere -y - choco install vswhere -y
- choco install python -y - choco install python -y
- choco install awscli -y - choco install awscli -y
@ -688,62 +682,51 @@ macOS13_Xcode14_arm64:
- $time = (Get-Date -Format "HH:mm:ss") - $time = (Get-Date -Format "HH:mm:ss")
- echo ${time} - echo ${time}
- echo "started by ${GITLAB_USER_NAME}" - echo "started by ${GITLAB_USER_NAME}"
- $env:CCACHE_BASEDIR = Get-Location - New-Item -Type File -Force -Path MSVC2022_64\.cmake\api\v1\query\codemodel-v2
- $env:CCACHE_DIR = "$(Get-Location)\ccache" - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2022 -k -V -b -t -C $multiview -E
- New-Item -Type Directory -Force -Path $env:CCACHE_DIR - cd MSVC2022_64
- 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
- Get-Volume - Get-Volume
- cmake --build . --config $config - cmake --build . --config $config
- ccache --show-stats
- cd $config - 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 - 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}"))/" - $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 - Get-ChildItem -Recurse *.ilk | Remove-Item
- | - |
if (Get-ChildItem -Recurse *.pdb) { 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) { 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 .. Push-Location ..
..\CI\Store-Symbols.ps1 ..\CI\Store-Symbols.ps1
if (Test-Path env:AWS_ACCESS_KEY_ID) { 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 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 Pop-Location
Get-ChildItem -Recurse *.pdb | Remove-Item 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) { 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 } } - if ($executables) { foreach ($exe in $executables.Split(',')) { & .\$exe } }
after_script: after_script:
- Get-Volume - Get-Volume
- Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log
cache: cache:
key: msbuild-v8 key: msbuild-2022-v9
paths: paths:
- ccache
- deps - deps
- MSVC2019_64/deps/Qt - MSVC2022_64/deps/Qt
artifacts: artifacts:
when: always when: always
paths: paths:
- "*.zip" - "*.zip"
- "*.log" - "*.log"
- MSVC2019_64/*.log - MSVC2022_64/*.log
- MSVC2019_64/*/*.log - MSVC2022_64/**/*.log
- MSVC2019_64/*/*/*.log
- MSVC2019_64/*/*/*/*.log
- MSVC2019_64/*/*/*/*/*.log
- MSVC2019_64/*/*/*/*/*/*.log
- MSVC2019_64/*/*/*/*/*/*/*.log
- MSVC2019_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. # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks.
timeout: 2h timeout: 2h

@ -15,6 +15,7 @@ Programmers
Nicolay Korslund - Project leader 2008-2010 Nicolay Korslund - Project leader 2008-2010
scrawl - Top contributor scrawl - Top contributor
AbduSharif
Adam Hogan (aurix) Adam Hogan (aurix)
Aesylwinn Aesylwinn
aegis aegis
@ -79,6 +80,7 @@ Programmers
Eduard Cot (trombonecot) Eduard Cot (trombonecot)
Eli2 Eli2
Emanuel Guével (potatoesmaster) Emanuel Guével (potatoesmaster)
Epoch
Eris Caffee (eris) Eris Caffee (eris)
eroen eroen
escondida escondida
@ -187,6 +189,7 @@ Programmers
pkubik pkubik
PLkolek PLkolek
PlutonicOverkill PlutonicOverkill
Qlonever
Radu-Marius Popovici (rpopovici) Radu-Marius Popovici (rpopovici)
Rafael Moura (dhustkoder) Rafael Moura (dhustkoder)
Randy Davin (Kindi) Randy Davin (Kindi)

@ -33,6 +33,8 @@
Bug #5977: Fatigueless NPCs' corpse underwater changes animation on game load Bug #5977: Fatigueless NPCs' corpse underwater changes animation on game load
Bug #6025: Subrecords cannot overlap records Bug #6025: Subrecords cannot overlap records
Bug #6027: Collisionshape becomes spiderweb-like when the mesh is too complex 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 #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 #6222: global map cell size can crash openmw if set to too high a value
Bug #6313: Followers with high Fight can turn hostile 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 #6657: Distant terrain tiles become black when using FWIW mod
Bug #6661: Saved games that have no preview screenshot cause issues or crashes Bug #6661: Saved games that have no preview screenshot cause issues or crashes
Bug #6716: mwscript comparison operator handling is too restrictive 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 #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 #6758: Main menu background video can be stopped by opening the options menu
Bug #6807: Ultimate Galleon is not working properly 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 #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 #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 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 #6973: Fade in happens after the scene load and is shown
Bug #6974: Only harmful effects are reflected Bug #6974: Only harmful effects are reflected
Bug #6977: Sun damage implementation does not match research 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 #6986: Sound magic effect does not make noise
Bug #6987: Set/Mod Blindness should not darken the screen Bug #6987: Set/Mod Blindness should not darken the screen
Bug #6992: Crossbow reloading doesn't look the same as in Morrowind 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 #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 #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 #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 #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 #7122: Teleportation to underwater should cancel active water walking effect
Bug #7131: MyGUI log spam when post processing HUD is open Bug #7131: MyGUI log spam when post processing HUD is open
Bug #7134: Saves with an invalid last generated RefNum can be loaded Bug #7134: Saves with an invalid last generated RefNum can be loaded
Bug #7163: Myar Aranath: Wheat breaks the GUI 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 #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 #7204: Missing actor scripts freeze the game
Bug #7229: Error marker loading failure is not handled Bug #7229: Error marker loading failure is not handled
Bug #7243: Supporting loading external files from VFS from esm files Bug #7243: Supporting loading external files from VFS from esm files
@ -93,24 +101,29 @@
Bug #7415: Unbreakable lock discrepancies Bug #7415: Unbreakable lock discrepancies
Bug #7416: Modpccrimelevel is different from vanilla Bug #7416: Modpccrimelevel is different from vanilla
Bug #7428: AutoCalc flag is not used to calculate enchantment costs 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 #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 #7459: Icons get stacked on the cursor when picking up multiple items simultaneously
Bug #7472: Crash when enchanting last projectiles Bug #7472: Crash when enchanting last projectiles
Bug #7475: Equipping a constant effect item doesn't update the magic menu 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 #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 #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 #7553: Faction reaction loading is incorrect
Bug #7557: Terrain::ChunkManager::createChunk is called twice for the same position, lod on initial loading 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 #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 #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 #7603: Scripts menu size is not updated properly
Bug #7604: Goblins Grunt becomes idle once injured Bug #7604: Goblins Grunt becomes idle once injured
Bug #7609: ForceGreeting should not open dialogue for werewolves Bug #7609: ForceGreeting should not open dialogue for werewolves
Bug #7611: Beast races' idle animations slide after turning or jumping in place 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 #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 #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 #7630: Charm can be cast on creatures
Bug #7631: Cannot trade with/talk to Creeper or Mudcrab Merchant when they're fleeing Bug #7631: Cannot trade with/talk to Creeper or Mudcrab Merchant when they're fleeing
Bug #7633: Groundcover should ignore non-geometry Drawables
Bug #7636: Animations bug out when switching between 1st and 3rd person, while playing a scripted animation Bug #7636: Animations bug out when switching between 1st and 3rd person, while playing a scripted animation
Bug #7637: Actors can sometimes move while playing scripted animations Bug #7637: Actors can sometimes move while playing scripted animations
Bug #7639: NPCs don't use hand-to-hand if their other melee skills were damaged during combat Bug #7639: NPCs don't use hand-to-hand if their other melee skills were damaged during combat
@ -128,30 +141,51 @@
Bug #7679: Scene luminance value flashes when toggling shaders Bug #7679: Scene luminance value flashes when toggling shaders
Bug #7685: Corky sometimes doesn't follow Llovyn Andus Bug #7685: Corky sometimes doesn't follow Llovyn Andus
Bug #7712: Casting doesn't support spells and enchantments with no effects 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 #7723: Assaulting vampires and werewolves shouldn't be a crime
Bug #7724: Guards don't help vs werewolves Bug #7724: Guards don't help vs werewolves
Bug #7733: Launcher shows incorrect data paths when there's two plugins with the same name Bug #7733: Launcher shows incorrect data paths when there's two plugins with the same name
Bug #7742: Governing attribute training limit should use the modified attribute Bug #7742: Governing attribute training limit should use the modified attribute
Bug #7753: Editor: Actors Don't Scale According to Their Race
Bug #7758: Water walking is not taken into account to compute path cost on the water Bug #7758: Water walking is not taken into account to compute path cost on the water
Bug #7761: Rain and ambient loop sounds are mutually exclusive Bug #7761: Rain and ambient loop sounds are mutually exclusive
Bug #7763: Bullet shape loading problems, assorted
Bug #7765: OpenMW-CS: Touch Record option is broken Bug #7765: OpenMW-CS: Touch Record option is broken
Bug #7769: Sword of the Perithia: Broken NPCs Bug #7769: Sword of the Perithia: Broken NPCs
Bug #7770: Sword of the Perithia: Script execution failure Bug #7770: Sword of the Perithia: Script execution failure
Bug #7780: Non-ASCII texture paths in NIF files don't work Bug #7780: Non-ASCII texture paths in NIF files don't work
Bug #7785: OpenMW-CS initialising Skill and Attribute fields to 0 instead of -1 on non-FortifyStat spells Bug #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 #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 #2566: Handle NAM9 records for manual cell references
Feature #3537: Shader-based water ripples Feature #3537: Shader-based water ripples
Feature #5173: Support for NiFogProperty Feature #5173: Support for NiFogProperty
Feature #5492: Let rain and snow collide with statics Feature #5492: Let rain and snow collide with statics
Feature #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 #6152: Playing music via lua scripts
Feature #6188: Specular lighting from point light sources Feature #6188: Specular lighting from point light sources
Feature #6411: Support translations in openmw-launcher Feature #6411: Support translations in openmw-launcher
Feature #6447: Add LOD support to Object Paging Feature #6447: Add LOD support to Object Paging
Feature #6491: Add support for Qt6 Feature #6491: Add support for Qt6
Feature #6556: Lua API for sounds Feature #6556: Lua API for sounds
Feature #6679: Design a custom Input Action API
Feature #6726: Lua API for creating new objects 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 #6864: Lua file access API
Feature #6922: Improve launcher appearance Feature #6922: Improve launcher appearance
Feature #6933: Support high-resolution cursor textures Feature #6933: Support high-resolution cursor textures
@ -164,16 +198,19 @@
Feature #7125: Remembering console commands between sessions Feature #7125: Remembering console commands between sessions
Feature #7129: Add support for non-adaptive VSync Feature #7129: Add support for non-adaptive VSync
Feature #7130: Ability to set MyGUI logging verbosity Feature #7130: Ability to set MyGUI logging verbosity
Feature #7142: MWScript Lua API
Feature #7148: Optimize string literal lookup in mwscript 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 #7194: Ori to show texture paths
Feature #7214: Searching in the in-game console 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 #7468: Factions API for Lua
Feature #7477: NegativeLight Magic Effect flag Feature #7477: NegativeLight Magic Effect flag
Feature #7499: OpenMW-CS: Generate record filters by drag & dropping cell content to the filters field Feature #7499: OpenMW-CS: Generate record filters by drag & dropping cell content to the filters field
Feature #7546: Start the game on Fredas Feature #7546: Start the game on Fredas
Feature #7554: Controller binding for tab for menu navigation Feature #7554: Controller binding for tab for menu navigation
Feature #7568: Uninterruptable scripted music Feature #7568: Uninterruptable scripted music
Feature #7590: [Lua] Ability to deserialize YAML data from scripts
Feature #7606: Launcher: allow Shift-select in Archives tab Feature #7606: Launcher: allow Shift-select in Archives tab
Feature #7608: Make the missing dependencies warning when loading a savegame more helpful Feature #7608: Make the missing dependencies warning when loading a savegame more helpful
Feature #7618: Show the player character's health in the save details Feature #7618: Show the player character's health in the save details
@ -183,11 +220,20 @@
Feature #7652: Sort inactive post processing shaders list properly Feature #7652: Sort inactive post processing shaders list properly
Feature #7698: Implement sAbsorb, sDamage, sDrain, sFortify and sRestore Feature #7698: Implement sAbsorb, sDamage, sDrain, sFortify and sRestore
Feature #7709: Improve resolution selection in Launcher Feature #7709: Improve resolution selection in Launcher
Feature #7777: Support external Bethesda material files (BGSM/BGEM)
Feature #7792: Support Timescale Clouds Feature #7792: Support Timescale Clouds
Feature #7795: Support MaxNumberRipples INI setting Feature #7795: Support MaxNumberRipples INI setting
Feature #7805: Lua Menu context 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 #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 #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 #7113: Move from std::atoi to std::from_char
Task #7117: Replace boost::scoped_array with std::vector Task #7117: Replace boost::scoped_array with std::vector
Task #7151: Do not use std::strerror to get errno error message Task #7151: Do not use std::strerror to get errno error message

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

@ -347,6 +347,26 @@ add_qt_style_dlls() {
QT_STYLES[$CONFIG]="${QT_STYLES[$CONFIG]} $@" 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 if [ -z $PLATFORM ]; then
PLATFORM="$(uname -m)" PLATFORM="$(uname -m)"
fi fi
@ -528,8 +548,12 @@ if ! [ -z $UNITY_BUILD ]; then
add_cmake_opts "-DOPENMW_UNITY_BUILD=True" add_cmake_opts "-DOPENMW_UNITY_BUILD=True"
fi fi
if ! [ -z $USE_CCACHE ]; then if [ -n "$USE_CCACHE" ]; then
add_cmake_opts "-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache" 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 fi
# turn on LTO by default # turn on LTO by default
@ -549,7 +573,7 @@ ICU_VER="70_1"
LUAJIT_VER="v2.1.0-beta3-452-g7a0cf5fd" LUAJIT_VER="v2.1.0-beta3-452-g7a0cf5fd"
LZ4_VER="1.9.2" LZ4_VER="1.9.2"
OPENAL_VER="1.23.0" OPENAL_VER="1.23.0"
QT_VER="5.15.2" QT_VER="6.6.2"
OSG_ARCHIVE_NAME="OSGoS 3.6.5" OSG_ARCHIVE_NAME="OSGoS 3.6.5"
OSG_ARCHIVE="OSGoS-3.6.5-123-g68c5c573d-msvc${OSG_MSVC_YEAR}-win${BITS}" OSG_ARCHIVE="OSGoS-3.6.5-123-g68c5c573d-msvc${OSG_MSVC_YEAR}-win${BITS}"
@ -880,7 +904,7 @@ printf "Qt ${QT_VER}... "
printf "Exists. " printf "Exists. "
elif [ -z $SKIP_EXTRACT ]; then elif [ -z $SKIP_EXTRACT ]; then
pushd "$DEPS" > /dev/null pushd "$DEPS" > /dev/null
AQT_VERSION="v3.1.7" AQT_VERSION="v3.1.12"
if ! [ -f "aqt_x64-${AQT_VERSION}.exe" ]; then if ! [ -f "aqt_x64-${AQT_VERSION}.exe" ]; then
download "aqt ${AQT_VERSION}"\ download "aqt ${AQT_VERSION}"\
"https://github.com/miurahr/aqtinstall/releases/download/${AQT_VERSION}/aqt_x64.exe" \ "https://github.com/miurahr/aqtinstall/releases/download/${AQT_VERSION}/aqt_x64.exe" \
@ -901,6 +925,9 @@ printf "Qt ${QT_VER}... "
echo Done. echo Done.
fi 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 cd $QT_SDK
for CONFIGURATION in ${CONFIGURATIONS[@]}; do for CONFIGURATION in ${CONFIGURATIONS[@]}; do
if [ $CONFIGURATION == "Debug" ]; then if [ $CONFIGURATION == "Debug" ]; then
@ -908,13 +935,24 @@ printf "Qt ${QT_VER}... "
else else
DLLSUFFIX="" DLLSUFFIX=""
fi 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 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 fi
add_qt_platform_dlls $CONFIGURATION "$(pwd)/plugins/platforms/qwindows${DLLSUFFIX}.dll" 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 done
echo Done. echo Done.
} }
@ -1109,6 +1147,20 @@ fi
cp "$DLL" "${DLL_PREFIX}styles" cp "$DLL" "${DLL_PREFIX}styles"
done done
echo 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 done
#fi #fi

@ -4,8 +4,8 @@ set -o pipefail
LUPDATE="${LUPDATE:-lupdate}" LUPDATE="${LUPDATE:-lupdate}"
${LUPDATE:?} apps/wizard -ts files/lang/wizard_*.ts ${LUPDATE:?} -locations none apps/wizard -ts files/lang/wizard_*.ts
${LUPDATE:?} apps/launcher -ts files/lang/launcher_*.ts ${LUPDATE:?} -locations none apps/launcher -ts files/lang/launcher_*.ts
${LUPDATE:?} components/contentselector components/process -ts files/lang/components_*.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) ! (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)

@ -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_ui_content.cpp
apps/openmw_test_suite/lua/test_utilpackage.cpp apps/openmw_test_suite/lua/test_utilpackage.cpp
apps/openmw_test_suite/lua/test_inputactions.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_endianness.cpp
apps/openmw_test_suite/misc/test_resourcehelpers.cpp apps/openmw_test_suite/misc/test_resourcehelpers.cpp
apps/openmw_test_suite/misc/test_stringops.cpp apps/openmw_test_suite/misc/test_stringops.cpp

@ -36,7 +36,7 @@ declare -rA GROUPED_DEPS=(
libsdl2-dev libqt5opengl5-dev qttools5-dev qttools5-dev-tools libopenal-dev libsdl2-dev libqt5opengl5-dev qttools5-dev qttools5-dev-tools libopenal-dev
libunshield-dev libtinyxml-dev libbullet-dev liblz4-dev libpng-dev libjpeg-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 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. # These dependencies can alternatively be built and linked statically.

@ -19,6 +19,14 @@ if(OPENMW_GL4ES_MANUAL_INIT)
add_definitions(-DOPENMW_GL4ES_MANUAL_INIT) add_definitions(-DOPENMW_GL4ES_MANUAL_INIT)
endif() endif()
if (APPLE OR WIN32)
set(DEPLOY_QT_TRANSLATIONS_DEFAULT ON)
else ()
set(DEPLOY_QT_TRANSLATIONS_DEFAULT OFF)
endif ()
option(DEPLOY_QT_TRANSLATIONS "Deploy standard Qt translations to resources folder. Needed when OpenMW applications are deployed with Qt libraries" ${DEPLOY_QT_TRANSLATIONS_DEFAULT})
# Apps and tools # Apps and tools
option(BUILD_OPENMW "Build OpenMW" ON) option(BUILD_OPENMW "Build OpenMW" ON)
option(BUILD_LAUNCHER "Build Launcher" ON) option(BUILD_LAUNCHER "Build Launcher" ON)
@ -36,6 +44,7 @@ option(BUILD_BENCHMARKS "Build benchmarks with Google Benchmark" OFF)
option(BUILD_NAVMESHTOOL "Build navmesh tool" ON) option(BUILD_NAVMESHTOOL "Build navmesh tool" ON)
option(BUILD_BULLETOBJECTTOOL "Build Bullet object tool" ON) option(BUILD_BULLETOBJECTTOOL "Build Bullet object tool" ON)
option(BUILD_OPENCS_TESTS "Build OpenMW Construction Set tests" OFF) 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. 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_MAJOR 0)
set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_MINOR 49)
set(OPENMW_VERSION_RELEASE 0) set(OPENMW_VERSION_RELEASE 0)
set(OPENMW_LUA_API_REVISION 53) set(OPENMW_LUA_API_REVISION 59)
set(OPENMW_POSTPROCESSING_API_REVISION 1) set(OPENMW_POSTPROCESSING_API_REVISION 1)
set(OPENMW_VERSION_COMMITHASH "") 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_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) set(GIT_CHECKOUT FALSE)
if(EXISTS ${PROJECT_SOURCE_DIR}/.git) if(EXISTS ${PROJECT_SOURCE_DIR}/.git)
@ -182,6 +191,22 @@ if (MSVC)
add_compile_options(/bigobj) add_compile_options(/bigobj)
add_compile_options(/Zc:__cplusplus) 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() endif()
# Set up common paths # Set up common paths
@ -209,7 +234,7 @@ else()
endif(APPLE) endif(APPLE)
if (WIN32) 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() endif()
if(MSVC) if(MSVC)
@ -224,9 +249,9 @@ find_package(LZ4 REQUIRED)
if (USE_QT) if (USE_QT)
find_package(QT REQUIRED COMPONENTS Core NAMES Qt6 Qt5) find_package(QT REQUIRED COMPONENTS Core NAMES Qt6 Qt5)
if (QT_VERSION_MAJOR VERSION_EQUAL 5) if (QT_VERSION_MAJOR VERSION_EQUAL 5)
find_package(Qt5 5.15 COMPONENTS Core Widgets Network OpenGL LinguistTools REQUIRED) find_package(Qt5 5.15 COMPONENTS Core Widgets Network OpenGL LinguistTools Svg REQUIRED)
else() 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() endif()
message(STATUS "Using Qt${QT_VERSION}") message(STATUS "Using Qt${QT_VERSION}")
endif() endif()
@ -685,9 +710,8 @@ if (WIN32)
if (USE_DEBUG_CONSOLE AND BUILD_OPENMW) if (USE_DEBUG_CONSOLE AND BUILD_OPENMW)
set_target_properties(openmw PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE") 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 LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:CONSOLE")
set_target_properties(openmw PROPERTIES COMPILE_DEFINITIONS $<$<CONFIG:Debug>:_CONSOLE>)
elseif (BUILD_OPENMW) 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_DEBUG "/SUBSYSTEM:WINDOWS")
set_target_properties(openmw PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:WINDOWS") set_target_properties(openmw PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:WINDOWS")
endif() endif()
@ -710,67 +734,66 @@ if (WIN32)
) )
foreach(d ${WARNINGS_DISABLE}) foreach(d ${WARNINGS_DISABLE})
set(WARNINGS "${WARNINGS} /wd${d}") list(APPEND WARNINGS "/wd${d}")
endforeach(d) endforeach(d)
if(OPENMW_MSVC_WERROR) if(OPENMW_MSVC_WERROR)
set(WARNINGS "${WARNINGS} /WX") list(APPEND WARNINGS "/WX")
endif() endif()
set_target_properties(components PROPERTIES COMPILE_FLAGS "${WARNINGS}") target_compile_options(components PRIVATE ${WARNINGS})
set_target_properties(osg-ffmpeg-videoplayer PROPERTIES COMPILE_FLAGS "${WARNINGS}") target_compile_options(osg-ffmpeg-videoplayer PRIVATE ${WARNINGS})
if (MSVC_VERSION GREATER_EQUAL 1915 AND MSVC_VERSION LESS 1920) if (MSVC_VERSION GREATER_EQUAL 1915 AND MSVC_VERSION LESS 1920)
target_compile_definitions(components INTERFACE _ENABLE_EXTENDED_ALIGNED_STORAGE) target_compile_definitions(components INTERFACE _ENABLE_EXTENDED_ALIGNED_STORAGE)
endif() endif()
if (BUILD_BSATOOL) if (BUILD_BSATOOL)
set_target_properties(bsatool PROPERTIES COMPILE_FLAGS "${WARNINGS}") target_compile_options(bsatool PRIVATE ${WARNINGS})
endif() endif()
if (BUILD_ESMTOOL) if (BUILD_ESMTOOL)
set_target_properties(esmtool PROPERTIES COMPILE_FLAGS "${WARNINGS}") target_compile_options(esmtool PRIVATE ${WARNINGS})
endif() endif()
if (BUILD_ESSIMPORTER) if (BUILD_ESSIMPORTER)
set_target_properties(openmw-essimporter PROPERTIES COMPILE_FLAGS "${WARNINGS}") target_compile_options(openmw-essimporter PRIVATE ${WARNINGS})
endif() endif()
if (BUILD_LAUNCHER) if (BUILD_LAUNCHER)
set_target_properties(openmw-launcher PROPERTIES COMPILE_FLAGS "${WARNINGS}") target_compile_options(openmw-launcher PRIVATE ${WARNINGS})
endif() endif()
if (BUILD_MWINIIMPORTER) if (BUILD_MWINIIMPORTER)
set_target_properties(openmw-iniimporter PROPERTIES COMPILE_FLAGS "${WARNINGS}") target_compile_options(openmw-iniimporter PRIVATE ${WARNINGS})
endif() endif()
if (BUILD_OPENCS) if (BUILD_OPENCS)
set_target_properties(openmw-cs PROPERTIES COMPILE_FLAGS "${WARNINGS}") target_compile_options(openmw-cs PRIVATE ${WARNINGS})
endif() endif()
if (BUILD_OPENMW) if (BUILD_OPENMW)
set_target_properties(openmw PROPERTIES COMPILE_FLAGS "${WARNINGS}") target_compile_options(openmw PRIVATE ${WARNINGS})
endif() endif()
if (BUILD_WIZARD) if (BUILD_WIZARD)
set_target_properties(openmw-wizard PROPERTIES COMPILE_FLAGS "${WARNINGS}") target_compile_options(openmw-wizard PRIVATE ${WARNINGS})
endif() endif()
if (BUILD_UNITTESTS) if (BUILD_UNITTESTS)
set_target_properties(openmw_test_suite PROPERTIES COMPILE_FLAGS "${WARNINGS}") target_compile_options(openmw_test_suite PRIVATE ${WARNINGS})
endif() endif()
if (BUILD_BENCHMARKS) if (BUILD_BENCHMARKS)
set_target_properties(openmw_detournavigator_navmeshtilescache_benchmark PROPERTIES COMPILE_FLAGS "${WARNINGS}") target_compile_options(openmw_detournavigator_navmeshtilescache_benchmark PRIVATE ${WARNINGS})
endif() endif()
if (BUILD_NAVMESHTOOL) if (BUILD_NAVMESHTOOL)
set_target_properties(openmw-navmeshtool PROPERTIES COMPILE_FLAGS "${WARNINGS}") target_compile_options(openmw-navmeshtool PRIVATE ${WARNINGS})
endif() endif()
if (BUILD_BULLETOBJECTTOOL) if (BUILD_BULLETOBJECTTOOL)
set(WARNINGS "${WARNINGS} ${MT_BUILD}") target_compile_options(openmw-bulletobjecttool PRIVATE ${WARNINGS} ${MT_BUILD})
set_target_properties(openmw-bulletobjecttool PROPERTIES COMPILE_FLAGS "${WARNINGS}")
endif() endif()
endif(MSVC) endif(MSVC)
@ -802,6 +825,18 @@ if (OPENMW_OSX_DEPLOYMENT AND APPLE)
get_filename_component(QT_QMACSTYLE_PLUGIN_NAME "${QT_QMACSTYLE_PLUGIN_PATH}" NAME) 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) 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) configure_file("${OpenMW_SOURCE_DIR}/files/mac/qt.conf" "${APP_BUNDLE_DIR}/Contents/Resources/qt.conf" COPYONLY)
if (BUILD_OPENCS) if (BUILD_OPENCS)
@ -809,6 +844,8 @@ if (OPENMW_OSX_DEPLOYMENT AND APPLE)
set(OPENCS_BUNDLE_NAME "${OPENCS_BUNDLE_NAME_TMP}.app") 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_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_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) configure_file("${OpenMW_SOURCE_DIR}/files/mac/qt.conf" "${OPENCS_BUNDLE_NAME}/Contents/Resources/qt.conf" COPYONLY)
endif () endif ()
@ -1082,30 +1119,30 @@ if (USE_QT)
file(GLOB COMPONENTS_TS_FILES ${CMAKE_SOURCE_DIR}/files/lang/components_*.ts) file(GLOB COMPONENTS_TS_FILES ${CMAKE_SOURCE_DIR}/files/lang/components_*.ts)
get_target_property(QT_LUPDATE_EXECUTABLE Qt::lupdate IMPORTED_LOCATION) get_target_property(QT_LUPDATE_EXECUTABLE Qt::lupdate IMPORTED_LOCATION)
add_custom_target(translations 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 WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/components
VERBATIM VERBATIM
COMMAND_EXPAND_LISTS 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 WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/apps/wizard
VERBATIM VERBATIM
COMMAND_EXPAND_LISTS 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 WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/apps/launcher
VERBATIM VERBATIM
COMMAND_EXPAND_LISTS) COMMAND_EXPAND_LISTS)
if (BUILD_LAUNCHER OR BUILD_WIZARD) if (BUILD_LAUNCHER OR BUILD_WIZARD)
if (APPLE) 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 () else ()
get_generator_is_multi_config(multi_config) get_generator_is_multi_config(multi_config)
if (multi_config) if (multi_config)
set(QT_TRANSLATIONS_PATH "${OpenMW_BINARY_DIR}/$<CONFIG>/resources/translations") set(QT_OPENMW_TRANSLATIONS_PATH "${OpenMW_BINARY_DIR}/$<CONFIG>/resources/translations")
else () else ()
set(QT_TRANSLATIONS_PATH "${OpenMW_BINARY_DIR}/resources/translations") set(QT_OPENMW_TRANSLATIONS_PATH "${OpenMW_BINARY_DIR}/resources/translations")
endif () endif ()
endif () endif ()
@ -1121,9 +1158,30 @@ if (USE_QT)
qt_add_translation(QM_FILES ${TS_FILES} OPTIONS -silent) 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 add_custom_target(qm-files
COMMAND ${CMAKE_COMMAND} -E make_directory ${QT_TRANSLATIONS_PATH} COMMAND ${CMAKE_COMMAND} -E make_directory ${QT_OPENMW_TRANSLATIONS_PATH}
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${QM_FILES} ${QT_TRANSLATIONS_PATH} COMMAND ${CMAKE_COMMAND} -E copy_if_different ${QM_FILES} ${QT_OPENMW_TRANSLATIONS_PATH}
DEPENDS ${QM_FILES} DEPENDS ${QM_FILES}
COMMENT "Copy *.qm files to resources folder") COMMENT "Copy *.qm files to resources folder")
endif () endif ()

@ -20,7 +20,7 @@ Font Licenses:
Current Status 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. 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.

@ -5,7 +5,7 @@ if (UNIX AND NOT APPLE)
target_link_libraries(openmw_detournavigator_navmeshtilescache_benchmark ${CMAKE_THREAD_LIBS_INIT}) target_link_libraries(openmw_detournavigator_navmeshtilescache_benchmark ${CMAKE_THREAD_LIBS_INIT})
endif() endif()
if (MSVC) if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC)
target_precompile_headers(openmw_detournavigator_navmeshtilescache_benchmark PRIVATE <algorithm>) target_precompile_headers(openmw_detournavigator_navmeshtilescache_benchmark PRIVATE <algorithm>)
endif() endif()

@ -182,7 +182,7 @@ namespace
for (auto _ : state) for (auto _ : state)
{ {
const auto& key = keys[n++ % keys.size()]; 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); benchmark::DoNotOptimize(result);
} }
} }
@ -241,7 +241,7 @@ namespace
while (state.KeepRunning()) while (state.KeepRunning())
{ {
const auto& key = keys[n++ % keys.size()]; 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<PreparedNavMeshData>()); key.mAgentBounds, key.mTilePosition, key.mRecastMesh, std::make_unique<PreparedNavMeshData>());
benchmark::DoNotOptimize(result); benchmark::DoNotOptimize(result);
} }

@ -5,7 +5,7 @@ if (UNIX AND NOT APPLE)
target_link_libraries(openmw_esm_refid_benchmark ${CMAKE_THREAD_LIBS_INIT}) target_link_libraries(openmw_esm_refid_benchmark ${CMAKE_THREAD_LIBS_INIT})
endif() endif()
if (MSVC) if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC)
target_precompile_headers(openmw_esm_refid_benchmark PRIVATE <algorithm>) target_precompile_headers(openmw_esm_refid_benchmark PRIVATE <algorithm>)
endif() endif()

@ -8,7 +8,7 @@ if (UNIX AND NOT APPLE)
target_link_libraries(openmw_settings_access_benchmark ${CMAKE_THREAD_LIBS_INIT}) target_link_libraries(openmw_settings_access_benchmark ${CMAKE_THREAD_LIBS_INIT})
endif() endif()
if (MSVC) if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC)
target_precompile_headers(openmw_settings_access_benchmark PRIVATE <algorithm>) target_precompile_headers(openmw_settings_access_benchmark PRIVATE <algorithm>)
endif() endif()

@ -38,7 +38,7 @@ namespace
{ {
for (auto _ : state) 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); benchmark::DoNotOptimize(v);
} }
} }
@ -47,8 +47,8 @@ namespace
{ {
for (auto _ : state) for (auto _ : state)
{ {
static const float v1 = Settings::Manager::getFloat("near clip", "Camera"); static float v1 = Settings::Manager::getFloat("near clip", "Camera");
static const bool v2 = Settings::Manager::getBool("transparent postpass", "Post Processing"); static bool v2 = Settings::Manager::getBool("transparent postpass", "Post Processing");
benchmark::DoNotOptimize(v1); benchmark::DoNotOptimize(v1);
benchmark::DoNotOptimize(v2); benchmark::DoNotOptimize(v2);
} }
@ -58,9 +58,9 @@ namespace
{ {
for (auto _ : state) for (auto _ : state)
{ {
static const float v1 = Settings::Manager::getFloat("near clip", "Camera"); static float v1 = Settings::Manager::getFloat("near clip", "Camera");
static const bool v2 = Settings::Manager::getBool("transparent postpass", "Post Processing"); static bool v2 = Settings::Manager::getBool("transparent postpass", "Post Processing");
static const int v3 = Settings::Manager::getInt("reflection detail", "Water"); static int v3 = Settings::Manager::getInt("reflection detail", "Water");
benchmark::DoNotOptimize(v1); benchmark::DoNotOptimize(v1);
benchmark::DoNotOptimize(v2); benchmark::DoNotOptimize(v2);
benchmark::DoNotOptimize(v3); benchmark::DoNotOptimize(v3);
@ -71,7 +71,8 @@ namespace
{ {
for (auto _ : state) 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) for (auto _ : state)
{ {
benchmark::DoNotOptimize(Settings::postProcessing().mTransparentPostpass.get()); bool v1 = Settings::postProcessing().mTransparentPostpass.get();
benchmark::DoNotOptimize(Settings::camera().mNearClip.get()); float v2 = Settings::camera().mNearClip.get();
benchmark::DoNotOptimize(v1);
benchmark::DoNotOptimize(v2);
} }
} }
@ -88,9 +91,12 @@ namespace
{ {
for (auto _ : state) for (auto _ : state)
{ {
benchmark::DoNotOptimize(Settings::postProcessing().mTransparentPostpass.get()); bool v1 = Settings::postProcessing().mTransparentPostpass.get();
benchmark::DoNotOptimize(Settings::camera().mNearClip.get()); float v2 = Settings::camera().mNearClip.get();
benchmark::DoNotOptimize(Settings::water().mReflectionDetail.get()); int v3 = Settings::water().mReflectionDetail.get();
benchmark::DoNotOptimize(v1);
benchmark::DoNotOptimize(v2);
benchmark::DoNotOptimize(v3);
} }
} }

@ -18,7 +18,7 @@ if (BUILD_WITH_CODE_COVERAGE)
target_link_libraries(bsatool gcov) target_link_libraries(bsatool gcov)
endif() endif()
if (MSVC) if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC)
target_precompile_headers(bsatool PRIVATE target_precompile_headers(bsatool PRIVATE
<filesystem> <filesystem>
<fstream> <fstream>

@ -329,17 +329,19 @@ int main(int argc, char** argv)
switch (bsaVersion) switch (bsaVersion)
{ {
case Bsa::BSAVER_COMPRESSED: case Bsa::BsaVersion::Unknown:
break;
case Bsa::BsaVersion::Uncompressed:
return call<Bsa::BSAFile>(info);
case Bsa::BsaVersion::Compressed:
return call<Bsa::CompressedBSAFile>(info); return call<Bsa::CompressedBSAFile>(info);
case Bsa::BSAVER_BA2_GNRL: case Bsa::BsaVersion::BA2GNRL:
return call<Bsa::BA2GNRLFile>(info); return call<Bsa::BA2GNRLFile>(info);
case Bsa::BSAVER_BA2_DX10: case Bsa::BsaVersion::BA2DX10:
return call<Bsa::BA2DX10File>(info); return call<Bsa::BA2DX10File>(info);
case Bsa::BSAVER_UNCOMPRESSED:
return call<Bsa::BSAFile>(info);
default:
throw std::runtime_error("Unrecognised BSA archive");
} }
throw std::runtime_error("Unrecognised BSA archive");
} }
catch (std::exception& e) catch (std::exception& e)
{ {

@ -19,7 +19,7 @@ if (WIN32)
install(TARGETS openmw-bulletobjecttool RUNTIME DESTINATION ".") install(TARGETS openmw-bulletobjecttool RUNTIME DESTINATION ".")
endif() endif()
if (MSVC) if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC)
target_precompile_headers(openmw-bulletobjecttool PRIVATE target_precompile_headers(openmw-bulletobjecttool PRIVATE
<string> <string>
<vector> <vector>

@ -12,6 +12,7 @@
#include <components/files/multidircollection.hpp> #include <components/files/multidircollection.hpp>
#include <components/misc/strings/conversion.hpp> #include <components/misc/strings/conversion.hpp>
#include <components/platform/platform.hpp> #include <components/platform/platform.hpp>
#include <components/resource/bgsmfilemanager.hpp>
#include <components/resource/bulletshape.hpp> #include <components/resource/bulletshape.hpp>
#include <components/resource/bulletshapemanager.hpp> #include <components/resource/bulletshapemanager.hpp>
#include <components/resource/foreachbulletobject.hpp> #include <components/resource/foreachbulletobject.hpp>
@ -146,7 +147,9 @@ namespace
dataDirs.insert(dataDirs.begin(), resDir / "vfs"); dataDirs.insert(dataDirs.begin(), resDir / "vfs");
const Files::Collections fileCollections(dataDirs); const Files::Collections fileCollections(dataDirs);
const auto& archives = variables["fallback-archive"].as<StringsVector>(); const auto& archives = variables["fallback-archive"].as<StringsVector>();
const auto& contentFiles = variables["content"].as<StringsVector>(); StringsVector contentFiles{ "builtin.omwscripts" };
const auto& configContentFiles = variables["content"].as<StringsVector>();
contentFiles.insert(contentFiles.end(), configContentFiles.begin(), configContentFiles.end());
Fallback::Map::init(variables["fallback"].as<Fallback::FallbackMap>().mMap); Fallback::Map::init(variables["fallback"].as<Fallback::FallbackMap>().mMap);
@ -171,7 +174,8 @@ namespace
constexpr double expiryDelay = 0; constexpr double expiryDelay = 0;
Resource::ImageManager imageManager(&vfs, expiryDelay); Resource::ImageManager imageManager(&vfs, expiryDelay);
Resource::NifFileManager nifFileManager(&vfs, &encoder.getStatelessEncoder()); 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::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay);
Resource::forEachBulletObject( Resource::forEachBulletObject(

@ -25,7 +25,7 @@ if (BUILD_WITH_CODE_COVERAGE)
target_link_libraries(esmtool gcov) target_link_libraries(esmtool gcov)
endif() endif()
if (MSVC) if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC)
target_precompile_headers(esmtool PRIVATE target_precompile_headers(esmtool PRIVATE
<fstream> <fstream>
<string> <string>

@ -1,5 +1,7 @@
#include "labels.hpp" #include "labels.hpp"
#include <components/esm3/dialoguecondition.hpp>
#include <components/esm3/loadalch.hpp>
#include <components/esm3/loadbody.hpp> #include <components/esm3/loadbody.hpp>
#include <components/esm3/loadcell.hpp> #include <components/esm3/loadcell.hpp>
#include <components/esm3/loadcont.hpp> #include <components/esm3/loadcont.hpp>
@ -571,13 +573,14 @@ std::string_view enchantTypeLabel(int idx)
std::string_view ruleFunction(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[] = { static constexpr std::string_view ruleFunctions[] = {
"Reaction Low", "Lowest Faction Reaction",
"Reaction High", "Highest Faction Reaction",
"Rank Requirement", "Rank Requirement",
"NPC? Reputation", "NPC Reputation",
"Health Percent", "Health Percent",
"Player Reputation", "Player Reputation",
"NPC Level", "NPC Level",
@ -647,6 +650,7 @@ std::string_view ruleFunction(int idx)
"Flee", "Flee",
"Should Attack", "Should Attack",
"Werewolf", "Werewolf",
"Werewolf Kills",
}; };
return ruleFunctions[idx]; return ruleFunctions[idx];
} }
@ -987,3 +991,16 @@ std::string recordFlags(uint32_t flags)
properties += Misc::StringUtils::format("(0x%08X)", flags); properties += Misc::StringUtils::format("(0x%08X)", flags);
return properties; 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;
}

@ -60,6 +60,7 @@ std::string itemListFlags(int flags);
std::string lightFlags(int flags); std::string lightFlags(int flags);
std::string magicEffectFlags(int flags); std::string magicEffectFlags(int flags);
std::string npcFlags(int flags); std::string npcFlags(int flags);
std::string potionFlags(int flags);
std::string raceFlags(int flags); std::string raceFlags(int flags);
std::string spellFlags(int flags); std::string spellFlags(int flags);
std::string weaponFlags(int flags); std::string weaponFlags(int flags);

@ -57,112 +57,82 @@ namespace
std::cout << " Cell Name: " << p.mCellName << std::endl; 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) switch (ss.mFunction)
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<int>(rule.substr(2, 2), 0);
switch (type)
{ {
case '1': case ESM::DialogueCondition::Function_Global:
type_str = "Function"; type_str = "Global";
func_str = std::string(ruleFunction(func)); func_str = ss.mVariable;
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";
break; break;
case '3': case ESM::DialogueCondition::Function_Local:
if (indicator == 's') type_str = "Local";
type_str = "Local short"; func_str = ss.mVariable;
else if (indicator == 'l')
type_str = "Local long";
else if (indicator == 'f')
type_str = "Local float";
break; break;
case '4': case ESM::DialogueCondition::Function_Journal:
if (indicator == 'J') type_str = "Journal";
type_str = "Journal"; func_str = ss.mVariable;
break; break;
case '5': case ESM::DialogueCondition::Function_Item:
if (indicator == 'I') type_str = "Item count";
type_str = "Item type"; func_str = ss.mVariable;
break; break;
case '6': case ESM::DialogueCondition::Function_Dead:
if (indicator == 'D') type_str = "Dead";
type_str = "NPC Dead"; func_str = ss.mVariable;
break; break;
case '7': case ESM::DialogueCondition::Function_NotId:
if (indicator == 'X') type_str = "Not ID";
type_str = "Not ID"; func_str = ss.mVariable;
break; break;
case '8': case ESM::DialogueCondition::Function_NotFaction:
if (indicator == 'F') type_str = "Not Faction";
type_str = "Not Faction"; func_str = ss.mVariable;
break; break;
case '9': case ESM::DialogueCondition::Function_NotClass:
if (indicator == 'C') type_str = "Not Class";
type_str = "Not Class"; func_str = ss.mVariable;
break; break;
case 'A': case ESM::DialogueCondition::Function_NotRace:
if (indicator == 'R') type_str = "Not Race";
type_str = "Not Race"; func_str = ss.mVariable;
break; break;
case 'B': case ESM::DialogueCondition::Function_NotCell:
if (indicator == 'L') type_str = "Not Cell";
type_str = "Not Cell"; func_str = ss.mVariable;
break; break;
case 'C': case ESM::DialogueCondition::Function_NotLocal:
if (indicator == 's') type_str = "Not Local";
type_str = "Not Local"; func_str = ss.mVariable;
break; break;
default: default:
type_str = "Function";
func_str = ruleFunction(ss.mFunction);
break; break;
} }
// Append the variable name to the function string if any. std::string_view oper_str = "??";
if (type != '1') switch (ss.mComparison)
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)
{ {
case '0': case ESM::DialogueCondition::Comp_Eq:
oper_str = "=="; oper_str = "==";
break; break;
case '1': case ESM::DialogueCondition::Comp_Ne:
oper_str = "!="; oper_str = "!=";
break; break;
case '2': case ESM::DialogueCondition::Comp_Gt:
oper_str = "> "; oper_str = "> ";
break; break;
case '3': case ESM::DialogueCondition::Comp_Ge:
oper_str = ">="; oper_str = ">=";
break; break;
case '4': case ESM::DialogueCondition::Comp_Ls:
oper_str = "< "; oper_str = "< ";
break; break;
case '5': case ESM::DialogueCondition::Comp_Le:
oper_str = "<="; oper_str = "<=";
break; break;
default: default:
@ -170,7 +140,7 @@ namespace
} }
std::ostringstream stream; std::ostringstream stream;
stream << ss.mValue; std::visit([&](auto value) { stream << value; }, ss.mValue);
std::string result std::string result
= Misc::StringUtils::format("%-12s %-32s %2s %s", type_str, func_str, oper_str, stream.str()); = 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) void printEffectList(const ESM::EffectList& effects)
{ {
int i = 0; 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::cout << " Effect[" << i << "]: " << magicEffectLabel(effect.mData.mEffectID) << " ("
<< ")" << std::endl; << effect.mData.mEffectID << ")" << std::endl;
if (effect.mSkill != -1) if (effect.mData.mSkill != -1)
std::cout << " Skill: " << skillLabel(effect.mSkill) << " (" << (int)effect.mSkill << ")" std::cout << " Skill: " << skillLabel(effect.mData.mSkill) << " (" << (int)effect.mData.mSkill << ")"
<< std::endl; << std::endl;
if (effect.mAttribute != -1) if (effect.mData.mAttribute != -1)
std::cout << " Attribute: " << attributeLabel(effect.mAttribute) << " (" << (int)effect.mAttribute std::cout << " Attribute: " << attributeLabel(effect.mData.mAttribute) << " ("
<< ")" << std::endl; << (int)effect.mData.mAttribute << ")" << std::endl;
std::cout << " Range: " << rangeTypeLabel(effect.mRange) << " (" << effect.mRange << ")" << std::endl; std::cout << " Range: " << rangeTypeLabel(effect.mData.mRange) << " (" << effect.mData.mRange << ")"
<< std::endl;
// Area is always zero if range type is "Self" // Area is always zero if range type is "Self"
if (effect.mRange != ESM::RT_Self) if (effect.mData.mRange != ESM::RT_Self)
std::cout << " Area: " << effect.mArea << std::endl; std::cout << " Area: " << effect.mData.mArea << std::endl;
std::cout << " Duration: " << effect.mDuration << std::endl; std::cout << " Duration: " << effect.mData.mDuration << std::endl;
std::cout << " Magnitude: " << effect.mMagnMin << "-" << effect.mMagnMax << std::endl; std::cout << " Magnitude: " << effect.mData.mMagnMin << "-" << effect.mData.mMagnMax << std::endl;
i++; i++;
} }
} }
@ -479,7 +450,7 @@ namespace EsmTool
std::cout << " Script: " << mData.mScript << std::endl; std::cout << " Script: " << mData.mScript << std::endl;
std::cout << " Weight: " << mData.mData.mWeight << std::endl; std::cout << " Weight: " << mData.mData.mWeight << std::endl;
std::cout << " Value: " << mData.mData.mValue << 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); printEffectList(mData.mEffects);
std::cout << " Deleted: " << mIsDeleted << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
@ -612,7 +583,6 @@ namespace EsmTool
} }
else else
std::cout << " Map Color: " << Misc::StringUtils::format("0x%08X", mData.mMapColor) << std::endl; 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 << " RefId counter: " << mData.mRefNumCounter << std::endl;
std::cout << " Deleted: " << mIsDeleted << 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 Fight:" << (int)mData.mAiData.mFight << std::endl;
std::cout << " AI Flee:" << (int)mData.mAiData.mFlee << std::endl; std::cout << " AI Flee:" << (int)mData.mAiData.mFlee << std::endl;
std::cout << " AI Alarm:" << (int)mData.mAiData.mAlarm << std::endl; std::cout << " AI Alarm:" << (int)mData.mAiData.mAlarm << std::endl;
std::cout << " AI U1:" << (int)mData.mAiData.mU1 << std::endl;
std::cout << " AI U2:" << (int)mData.mAiData.mU2 << std::endl;
std::cout << " AI U3:" << (int)mData.mAiData.mU3 << std::endl;
std::cout << " AI Services:" << Misc::StringUtils::format("0x%08X", mData.mAiData.mServices) << std::endl; std::cout << " AI Services:" << Misc::StringUtils::format("0x%08X", mData.mAiData.mServices) << std::endl;
for (const ESM::AIPackage& package : mData.mAiPackage.mList) for (const ESM::AIPackage& package : mData.mAiPackage.mList)
@ -843,10 +810,9 @@ namespace EsmTool
std::cout << " Quest Status: " << questStatusLabel(mData.mQuestStatus) << " (" << mData.mQuestStatus << ")" std::cout << " Quest Status: " << questStatusLabel(mData.mQuestStatus) << " (" << mData.mQuestStatus << ")"
<< std::endl; << std::endl;
std::cout << " Unknown1: " << mData.mData.mUnknown1 << std::endl; std::cout << " Type: " << dialogTypeLabel(mData.mData.mType) << std::endl;
std::cout << " Unknown2: " << (int)mData.mData.mUnknown2 << std::endl;
for (const ESM::DialInfo::SelectStruct& rule : mData.mSelects) for (const auto& rule : mData.mSelects)
std::cout << " Select Rule: " << ruleString(rule) << std::endl; std::cout << " Select Rule: " << ruleString(rule) << std::endl;
if (!mData.mResultScript.empty()) if (!mData.mResultScript.empty())
@ -901,9 +867,6 @@ namespace EsmTool
if (const ESM::Land::LandData* data = mData.getLandData(mData.mDataTypes)) if (const ESM::Land::LandData* data = mData.getLandData(mData.mDataTypes))
{ {
std::cout << " Height Offset: " << data->mHeightOffset << std::endl; std::cout << " Height Offset: " << data->mHeightOffset << std::endl;
// Lots of missing members.
std::cout << " Unknown1: " << data->mUnk1 << std::endl;
std::cout << " Unknown2: " << static_cast<unsigned>(data->mUnk2) << std::endl;
} }
mData.unloadData(); mData.unloadData();
std::cout << " Deleted: " << mIsDeleted << std::endl; 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 Fight:" << (int)mData.mAiData.mFight << std::endl;
std::cout << " AI Flee:" << (int)mData.mAiData.mFlee << std::endl; std::cout << " AI Flee:" << (int)mData.mAiData.mFlee << std::endl;
std::cout << " AI Alarm:" << (int)mData.mAiData.mAlarm << std::endl; std::cout << " AI Alarm:" << (int)mData.mAiData.mAlarm << std::endl;
std::cout << " AI U1:" << (int)mData.mAiData.mU1 << std::endl;
std::cout << " AI U2:" << (int)mData.mAiData.mU2 << std::endl;
std::cout << " AI U3:" << (int)mData.mAiData.mU3 << std::endl;
std::cout << " AI Services:" << Misc::StringUtils::format("0x%08X", mData.mAiData.mServices) << std::endl; std::cout << " AI Services:" << Misc::StringUtils::format("0x%08X", mData.mAiData.mServices) << std::endl;
for (const ESM::AIPackage& package : mData.mAiPackage.mList) for (const ESM::AIPackage& package : mData.mAiPackage.mList)
@ -1144,7 +1104,6 @@ namespace EsmTool
std::cout << " Coordinates: (" << point.mX << "," << point.mY << "," << point.mZ << ")" << std::endl; std::cout << " Coordinates: (" << point.mX << "," << point.mY << "," << point.mZ << ")" << std::endl;
std::cout << " Auto-Generated: " << (int)point.mAutogenerated << std::endl; std::cout << " Auto-Generated: " << (int)point.mAutogenerated << std::endl;
std::cout << " Connections: " << (int)point.mConnectionNum << std::endl; std::cout << " Connections: " << (int)point.mConnectionNum << std::endl;
std::cout << " Unknown: " << point.mUnknown << std::endl;
i++; i++;
} }

@ -47,7 +47,7 @@ if (WIN32)
INSTALL(TARGETS openmw-essimporter RUNTIME DESTINATION ".") INSTALL(TARGETS openmw-essimporter RUNTIME DESTINATION ".")
endif(WIN32) endif(WIN32)
if (MSVC) if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC)
target_precompile_headers(openmw-essimporter PRIVATE target_precompile_headers(openmw-essimporter PRIVATE
<algorithm> <algorithm>
<filesystem> <filesystem>

@ -232,7 +232,7 @@ namespace ESSImport
esm.skip(4); esm.skip(4);
} }
esm.getExact(nam8, 32); esm.getT(nam8);
newcell.mFogOfWar.reserve(16 * 16); newcell.mFogOfWar.reserve(16 * 16);
for (int x = 0; x < 16; ++x) for (int x = 0; x < 16; ++x)

@ -1,10 +1,30 @@
#include "importcellref.hpp" #include "importcellref.hpp"
#include <components/esm3/esmreader.hpp> #include <components/esm3/esmreader.hpp>
#include <components/misc/concepts.hpp>
#include <cstdint> #include <cstdint>
namespace ESSImport namespace ESSImport
{ {
template <Misc::SameAsWithoutCvref<ACDT> 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 <Misc::SameAsWithoutCvref<ACSC> T>
void decompose(T&& v, const auto& f)
{
f(v.mUnknown1, v.mFlags, v.mUnknown2, v.mCorpseClearCountdown, v.mUnknown3);
}
template <Misc::SameAsWithoutCvref<ANIS> T>
void decompose(T&& v, const auto& f)
{
f(v.mGroupIndex, v.mUnknown, v.mTime);
}
void CellRef::load(ESM::ESMReader& esm) void CellRef::load(ESM::ESMReader& esm)
{ {
@ -45,14 +65,9 @@ namespace ESSImport
bool isDeleted = false; bool isDeleted = false;
ESM::CellRef::loadData(esm, isDeleted); ESM::CellRef::loadData(esm, isDeleted);
mActorData.mHasACDT mActorData.mHasACDT = esm.getOptionalComposite("ACDT", mActorData.mACDT);
= 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.mHasACSC = esm.getHNOT("ACSC", mActorData.mACSC.mUnknown1, mActorData.mACSC.mFlags, mActorData.mHasACSC = esm.getOptionalComposite("ACSC", mActorData.mACSC);
mActorData.mACSC.mUnknown2, mActorData.mACSC.mCorpseClearCountdown, mActorData.mACSC.mUnknown3);
if (esm.isNextSub("ACSL")) if (esm.isNextSub("ACSL"))
esm.skipHSubSize(112); esm.skipHSubSize(112);
@ -127,8 +142,7 @@ namespace ESSImport
if (esm.isNextSub("ND3D")) if (esm.isNextSub("ND3D"))
esm.skipHSub(); esm.skipHSub();
mActorData.mHasANIS mActorData.mHasANIS = esm.getOptionalComposite("ANIS", mActorData.mANIS);
= esm.getHNOT("ANIS", mActorData.mANIS.mGroupIndex, mActorData.mANIS.mUnknown, mActorData.mANIS.mTime);
if (esm.isNextSub("LVCR")) 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 // I've seen DATA *twice* on a creature record, and with the exact same content too! weird
// alarmvoi0000.ess // alarmvoi0000.ess
for (int i = 0; i < 2; ++i) for (int i = 0; i < 2; ++i)
esm.getHNOT("DATA", mPos.pos, mPos.rot); esm.getOptionalComposite("DATA", mPos);
mDeleted = 0; mDeleted = 0;
if (esm.isNextSub("DELE")) if (esm.isNextSub("DELE"))

@ -135,7 +135,7 @@ namespace ESSImport
sub.mFileOffset = esm.getFileOffset(); sub.mFileOffset = esm.getFileOffset();
sub.mName = esm.retSubName().toString(); sub.mName = esm.retSubName().toString();
sub.mData.resize(esm.getSubSize()); 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); rec.mSubrecords.push_back(sub);
} }
file.mRecords.push_back(rec); file.mRecords.push_back(rec);

@ -83,7 +83,7 @@ target_link_libraries(openmw-launcher
components_qt 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) if (BUILD_WITH_CODE_COVERAGE)
target_compile_options(openmw-launcher PRIVATE --coverage) target_compile_options(openmw-launcher PRIVATE --coverage)
@ -94,7 +94,7 @@ if(USE_QT)
set_property(TARGET openmw-launcher PROPERTY AUTOMOC ON) set_property(TARGET openmw-launcher PROPERTY AUTOMOC ON)
endif(USE_QT) endif(USE_QT)
if (MSVC) if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC)
target_precompile_headers(openmw-launcher PRIVATE target_precompile_headers(openmw-launcher PRIVATE
<boost/program_options/options_description.hpp> <boost/program_options/options_description.hpp>

@ -142,7 +142,7 @@ Launcher::DataFilesPage::DataFilesPage(const Files::ConfigurationManager& cfg, C
ui.setupUi(this); ui.setupUi(this);
setObjectName("DataFilesPage"); setObjectName("DataFilesPage");
mSelector = new ContentSelectorView::ContentSelector(ui.contentSelectorWidget, /*showOMWScripts=*/true); 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); mSelector->setEncoding(encoding);
QVector<std::pair<QString, QString>> languages = { { "English", tr("English") }, { "French", tr("French") }, QVector<std::pair<QString, QString>> 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.directoryInsertButton, &QPushButton::released, this, [this]() { this->addSubdirectories(false); });
connect(ui.directoryUpButton, &QPushButton::released, this, [this]() { this->moveDirectory(-1); }); connect(ui.directoryUpButton, &QPushButton::released, this, [this]() { this->moveDirectory(-1); });
connect(ui.directoryDownButton, &QPushButton::released, this, [this]() { this->moveDirectory(1); }); connect(ui.directoryDownButton, &QPushButton::released, this, [this]() { this->moveDirectory(1); });
connect(ui.directoryRemoveButton, &QPushButton::released, this, [this]() { this->removeDirectory(); }); connect(ui.directoryRemoveButton, &QPushButton::released, this, &DataFilesPage::removeDirectory);
connect(ui.archiveUpButton, &QPushButton::released, this, [this]() { this->moveArchives(-1); }); connect(ui.archiveUpButton, &QPushButton::released, this, [this]() { this->moveArchives(-1); });
connect(ui.archiveDownButton, &QPushButton::released, this, [this]() { this->moveArchives(1); }); connect(ui.archiveDownButton, &QPushButton::released, this, [this]() { this->moveArchives(1); });
connect( connect(ui.directoryListWidget->model(), &QAbstractItemModel::rowsMoved, this, &DataFilesPage::sortDirectories);
ui.directoryListWidget->model(), &QAbstractItemModel::rowsMoved, this, [this]() { this->sortDirectories(); }); connect(ui.archiveListWidget->model(), &QAbstractItemModel::rowsMoved, this, &DataFilesPage::sortArchives);
buildView(); buildView();
loadSettings(); loadSettings();
@ -271,65 +271,79 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
ui.archiveListWidget->clear(); ui.archiveListWidget->clear();
ui.directoryListWidget->clear(); ui.directoryListWidget->clear();
QStringList directories = mLauncherSettings.getDataDirectoryList(contentModelName); QList<Config::SettingValue> directories = mGameSettings.getDataDirs();
if (directories.isEmpty()) QStringList contentModelDirectories = mLauncherSettings.getDataDirectoryList(contentModelName);
directories = mGameSettings.getDataDirs(); 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(); mDataLocal = mGameSettings.getDataLocal();
if (!mDataLocal.isEmpty()) if (!mDataLocal.isEmpty())
directories.insert(0, mDataLocal); directories.insert(0, { mDataLocal });
const auto& globalDataDir = mGameSettings.getGlobalDataDir(); const auto& resourcesVfs = mGameSettings.getResourcesVfs();
if (!globalDataDir.empty()) if (!resourcesVfs.isEmpty())
directories.insert(0, Files::pathToQString(globalDataDir)); directories.insert(0, { resourcesVfs });
std::unordered_set<QString> visitedDirectories; std::unordered_set<QString> visitedDirectories;
for (const QString& currentDir : directories) for (const Config::SettingValue& currentDir : directories)
{ {
// normalize user supplied directories: resolve symlink, convert to native separator, make absolute if (!visitedDirectories.insert(currentDir.value).second)
const QString canonicalDirPath = QDir(QDir::cleanPath(currentDir)).canonicalPath();
if (!visitedDirectories.insert(canonicalDirPath).second)
continue; continue;
// add new achives files presents in current directory // add new achives files presents in current directory
addArchivesFromDir(currentDir); addArchivesFromDir(currentDir.value);
QString tooltip; QStringList tooltip;
// add content files presents in current directory // 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 // add current directory to list
ui.directoryListWidget->addItem(currentDir); ui.directoryListWidget->addItem(currentDir.originalRepresentation);
auto row = ui.directoryListWidget->count() - 1; auto row = ui.directoryListWidget->count() - 1;
auto* item = ui.directoryListWidget->item(row); 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 // 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(); QFont font = item->font();
font.setBold(true); font.setBold(true);
font.setItalic(true); font.setItalic(true);
item->setFont(font); item->setFont(font);
} }
// deactivate data-local and global data directory: they are always included // deactivate data-local and resources/vfs: they are always included
if (currentDir == mDataLocal || Files::pathFromQString(currentDir) == globalDataDir) // same for ones from non-user config files
if (currentDir.value == mDataLocal || currentDir.value == resourcesVfs
|| !mGameSettings.isUserSetting(currentDir))
{ {
auto flags = item->flags(); auto flags = item->flags();
item->setFlags(flags & ~(Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled)); 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 // 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")); 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 else
{ {
@ -339,19 +353,26 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
auto emptyIcon = QIcon(pixmap); auto emptyIcon = QIcon(pixmap);
item->setIcon(emptyIcon); item->setIcon(emptyIcon);
} }
item->setToolTip(tooltip); item->setToolTip(tooltip.join('\n'));
} }
mSelector->sortFiles(); mSelector->sortFiles();
QStringList selectedArchives = mLauncherSettings.getArchiveList(contentModelName); QList<Config::SettingValue> selectedArchives = mGameSettings.getArchiveList();
if (selectedArchives.isEmpty()) QStringList contentModelSelectedArchives = mLauncherSettings.getArchiveList(contentModelName);
selectedArchives = mGameSettings.getArchiveList(); 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 // sort and tick BSA according to profile
int row = 0; int row = 0;
for (const auto& archive : selectedArchives) 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()) if (match.isEmpty())
continue; continue;
const auto name = match[0]->text(); const auto name = match[0]->text();
@ -359,9 +380,25 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
ui.archiveListWidget->takeItem(oldrow); ui.archiveListWidget->takeItem(oldrow);
ui.archiveListWidget->insertItem(row, name); ui.archiveListWidget->insertItem(row, name);
ui.archiveListWidget->item(row)->setCheckState(Qt::Checked); 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++; 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)); mSelector->setProfileContent(mLauncherSettings.getContentListFiles(contentModelName));
} }
@ -389,7 +426,19 @@ void Launcher::DataFilesPage::saveSettings(const QString& profile)
{ {
fileNames.append(item->fileName()); 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); mGameSettings.setContentList(dirList, selectedArchivePaths(), fileNames);
QString language(mSelector->languageBox()->currentData().toString()); QString language(mSelector->languageBox()->currentData().toString());
@ -398,38 +447,38 @@ void Launcher::DataFilesPage::saveSettings(const QString& profile)
if (language == QLatin1String("Polish")) if (language == QLatin1String("Polish"))
{ {
mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1250")); mGameSettings.setValue(QLatin1String("encoding"), { "win1250" });
} }
else if (language == QLatin1String("Russian")) else if (language == QLatin1String("Russian"))
{ {
mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1251")); mGameSettings.setValue(QLatin1String("encoding"), { "win1251" });
} }
else else
{ {
mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1252")); mGameSettings.setValue(QLatin1String("encoding"), { "win1252" });
} }
} }
QStringList Launcher::DataFilesPage::selectedDirectoriesPaths() const QList<Config::SettingValue> Launcher::DataFilesPage::selectedDirectoriesPaths() const
{ {
QStringList dirList; QList<Config::SettingValue> dirList;
for (int i = 0; i < ui.directoryListWidget->count(); ++i) for (int i = 0; i < ui.directoryListWidget->count(); ++i)
{ {
const QListWidgetItem* item = ui.directoryListWidget->item(i); const QListWidgetItem* item = ui.directoryListWidget->item(i);
if (item->flags() & Qt::ItemIsEnabled) if (item->flags() & Qt::ItemIsEnabled)
dirList.append(item->text()); dirList.append(qvariant_cast<Config::SettingValue>(item->data(Qt::UserRole)));
} }
return dirList; return dirList;
} }
QStringList Launcher::DataFilesPage::selectedArchivePaths() const QList<Config::SettingValue> Launcher::DataFilesPage::selectedArchivePaths() const
{ {
QStringList archiveList; QList<Config::SettingValue> archiveList;
for (int i = 0; i < ui.archiveListWidget->count(); ++i) for (int i = 0; i < ui.archiveListWidget->count(); ++i)
{ {
const QListWidgetItem* item = ui.archiveListWidget->item(i); const QListWidgetItem* item = ui.archiveListWidget->item(i);
if (item->checkState() == Qt::Checked) if (item->checkState() == Qt::Checked)
archiveList.append(item->text()); archiveList.append(qvariant_cast<Config::SettingValue>(item->data(Qt::UserRole)));
} }
return archiveList; return archiveList;
} }
@ -583,7 +632,20 @@ void Launcher::DataFilesPage::on_cloneProfileAction_triggered()
if (profile.isEmpty()) if (profile.isEmpty())
return; 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); addProfile(profile, true);
} }
@ -650,6 +712,9 @@ void Launcher::DataFilesPage::addSubdirectories(bool append)
if (!ui.directoryListWidget->findItems(rootPath, Qt::MatchFixedString).isEmpty()) if (!ui.directoryListWidget->findItems(rootPath, Qt::MatchFixedString).isEmpty())
return; return;
ui.directoryListWidget->addItem(rootPath); 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); mNewDataDirs.push_back(rootPath);
refreshDataFilesView(); refreshDataFilesView();
return; return;
@ -679,8 +744,11 @@ void Launcher::DataFilesPage::addSubdirectories(bool append)
const auto* dir = select.dirListWidget->item(i); const auto* dir = select.dirListWidget->item(i);
if (dir->checkState() == Qt::Checked) 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()); 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) void Launcher::DataFilesPage::moveDirectory(int step)
{ {
int selectedRow = ui.directoryListWidget->currentRow(); 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) if (selectedRow == -1 || newRow < 0 || newRow > ui.archiveListWidget->count() - 1)
return false; return false;
const QListWidgetItem* item = ui.archiveListWidget->takeItem(selectedRow); QListWidgetItem* item = ui.archiveListWidget->takeItem(selectedRow);
ui.archiveListWidget->insertItem(newRow, item);
addArchive(item->text(), item->checkState(), newRow);
ui.archiveListWidget->setCurrentRow(newRow); ui.archiveListWidget->setCurrentRow(newRow);
return true; return true;
} }
@ -797,6 +879,7 @@ void Launcher::DataFilesPage::addArchive(const QString& name, Qt::CheckState sel
row = ui.archiveListWidget->count(); row = ui.archiveListWidget->count();
ui.archiveListWidget->insertItem(row, name); ui.archiveListWidget->insertItem(row, name);
ui.archiveListWidget->item(row)->setCheckState(selected); 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 ??? if (mKnownArchives.filter(name).isEmpty()) // XXX why contains doesn't work here ???
{ {
auto item = ui.archiveListWidget->item(row); auto item = ui.archiveListWidget->item(row);
@ -819,7 +902,7 @@ void Launcher::DataFilesPage::addArchivesFromDir(const QString& path)
for (const auto& fileinfo : dir.entryInfoList(archiveFilter)) for (const auto& fileinfo : dir.entryInfoList(archiveFilter))
{ {
const auto absPath = fileinfo.absoluteFilePath(); 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; continue;
const auto fileName = fileinfo.fileName(); const auto fileName = fileinfo.fileName();

@ -25,6 +25,7 @@ namespace ContentSelectorView
namespace Config namespace Config
{ {
class GameSettings; class GameSettings;
struct SettingValue;
class LauncherSettings; class LauncherSettings;
} }
@ -73,6 +74,7 @@ namespace Launcher
void updateCloneProfileOkButton(const QString& text); void updateCloneProfileOkButton(const QString& text);
void addSubdirectories(bool append); void addSubdirectories(bool append);
void sortDirectories(); void sortDirectories();
void sortArchives();
void removeDirectory(); void removeDirectory();
void moveArchives(int step); void moveArchives(int step);
void moveDirectory(int step); void moveDirectory(int step);
@ -146,8 +148,8 @@ namespace Launcher
* @return the file paths of all selected content files * @return the file paths of all selected content files
*/ */
QStringList selectedFilePaths() const; QStringList selectedFilePaths() const;
QStringList selectedArchivePaths() const; QList<Config::SettingValue> selectedArchivePaths() const;
QStringList selectedDirectoriesPaths() const; QList<Config::SettingValue> selectedDirectoriesPaths() const;
}; };
} }
#endif #endif

@ -37,9 +37,9 @@ Launcher::ImportPage::ImportPage(const Files::ConfigurationManager& cfg, Config:
// Detect Morrowind configuration files // Detect Morrowind configuration files
QStringList iniPaths; 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 dir.setPath(dir.canonicalPath()); // Resolve symlinks
if (dir.exists(QString("Morrowind.ini"))) if (dir.exists(QString("Morrowind.ini")))
@ -125,7 +125,7 @@ void Launcher::ImportPage::on_importerButton_clicked()
arguments.append(QString("--fonts")); arguments.append(QString("--fonts"));
arguments.append(QString("--encoding")); 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(QString("--ini"));
arguments.append(settingsComboBox->currentText()); arguments.append(settingsComboBox->currentText());
arguments.append(QString("--cfg")); arguments.append(QString("--cfg"));

@ -1,7 +1,6 @@
#include <iostream> #include <iostream>
#include <QDir> #include <QDir>
#include <QTranslator>
#include <boost/program_options/options_description.hpp> #include <boost/program_options/options_description.hpp>
#include <boost/program_options/variables_map.hpp> #include <boost/program_options/variables_map.hpp>
@ -9,6 +8,7 @@
#include <components/debug/debugging.hpp> #include <components/debug/debugging.hpp>
#include <components/files/configurationmanager.hpp> #include <components/files/configurationmanager.hpp>
#include <components/files/qtconversion.hpp> #include <components/files/qtconversion.hpp>
#include <components/l10n/qttranslations.hpp>
#include <components/platform/platform.hpp> #include <components/platform/platform.hpp>
#ifdef MAC_OS_X_VERSION_MIN_REQUIRED #ifdef MAC_OS_X_VERSION_MIN_REQUIRED
@ -41,16 +41,7 @@ int runLauncher(int argc, char* argv[])
resourcesPath = Files::pathToQString(variables["resources"].as<Files::MaybeQuotedPath>().u8string()); resourcesPath = Files::pathToQString(variables["resources"].as<Files::MaybeQuotedPath>().u8string());
} }
// Internationalization l10n::installQtTranslations(app, "launcher", resourcesPath);
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);
Launcher::MainDialog mainWin(configurationManager); Launcher::MainDialog mainWin(configurationManager);

@ -292,7 +292,7 @@ bool Launcher::MainDialog::setupLauncherSettings()
if (!QFile::exists(path)) if (!QFile::exists(path))
return true; return true;
Log(Debug::Verbose) << "Loading config file: " << path.toUtf8().constData(); Log(Debug::Info) << "Loading config file: " << path.toUtf8().constData();
QFile file(path); QFile file(path);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
@ -320,7 +320,7 @@ bool Launcher::MainDialog::setupGameSettings()
QFile file; 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<bool> { bool ignoreContent = false) -> std::optional<bool> {
file.setFileName(path); file.setFileName(path);
if (file.exists()) if (file.exists())
@ -337,7 +337,7 @@ bool Launcher::MainDialog::setupGameSettings()
QTextStream stream(&file); QTextStream stream(&file);
Misc::ensureUtf8Encoding(stream); Misc::ensureUtf8Encoding(stream);
(mGameSettings.*reader)(stream, ignoreContent); (mGameSettings.*reader)(stream, QFileInfo(path).dir().path(), ignoreContent);
file.close(); file.close();
return true; return true;
} }
@ -349,29 +349,24 @@ bool Launcher::MainDialog::setupGameSettings()
if (!loadFile(Files::getUserConfigPathQString(mCfgMgr), &Config::GameSettings::readUserFile)) if (!loadFile(Files::getUserConfigPathQString(mCfgMgr), &Config::GameSettings::readUserFile))
return false; return false;
// Now the rest - priority: user > local > global for (const auto& path : Files::getActiveConfigPathsQString(mCfgMgr))
if (auto result = loadFile(Files::getLocalConfigPathQString(mCfgMgr), &Config::GameSettings::readFile, true))
{ {
// Load global if local wasn't found Log(Debug::Info) << "Loading config file: " << path.toUtf8().constData();
if (!*result && !loadFile(Files::getGlobalConfigPathQString(mCfgMgr), &Config::GameSettings::readFile, true)) if (!loadFile(path, &Config::GameSettings::readFile))
return false; return false;
} }
else
return false;
if (!loadFile(Files::getUserConfigPathQString(mCfgMgr), &Config::GameSettings::readFile))
return false;
return true; return true;
} }
bool Launcher::MainDialog::setupGameData() bool Launcher::MainDialog::setupGameData()
{ {
QStringList dataDirs; bool foundData = false;
// Check if the paths actually contain data files // 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; QStringList filters;
filters << "*.esp" filters << "*.esp"
<< "*.esm" << "*.esm"
@ -379,10 +374,13 @@ bool Launcher::MainDialog::setupGameData()
<< "*.omwaddon"; << "*.omwaddon";
if (!dir.entryList(filters).isEmpty()) if (!dir.entryList(filters).isEmpty())
dataDirs.append(path3); {
foundData = true;
break;
}
} }
if (dataDirs.isEmpty()) if (!foundData)
{ {
QMessageBox msgBox; QMessageBox msgBox;
msgBox.setWindowTitle(tr("Error detecting Morrowind installation")); msgBox.setWindowTitle(tr("Error detecting Morrowind installation"));

@ -193,8 +193,10 @@ bool Launcher::SettingsPage::loadSettings()
loadSettingBool(Settings::game().mSmoothMovement, *smoothMovementCheckBox); loadSettingBool(Settings::game().mSmoothMovement, *smoothMovementCheckBox);
loadSettingBool(Settings::game().mPlayerMovementIgnoresAnimation, *playerMovementIgnoresAnimationCheckBox); loadSettingBool(Settings::game().mPlayerMovementIgnoresAnimation, *playerMovementIgnoresAnimationCheckBox);
distantLandCheckBox->setCheckState( connect(distantLandCheckBox, &QCheckBox::toggled, this, &SettingsPage::slotDistantLandToggled);
Settings::terrain().mDistantTerrain && Settings::terrain().mObjectPaging ? Qt::Checked : Qt::Unchecked); bool distantLandEnabled = Settings::terrain().mDistantTerrain && Settings::terrain().mObjectPaging;
distantLandCheckBox->setCheckState(distantLandEnabled ? Qt::Checked : Qt::Unchecked);
slotDistantLandToggled(distantLandEnabled);
loadSettingBool(Settings::terrain().mObjectPagingActiveGrid, *activeGridObjectPagingCheckBox); loadSettingBool(Settings::terrain().mObjectPagingActiveGrid, *activeGridObjectPagingCheckBox);
viewingDistanceComboBox->setValue(convertToCells(Settings::camera().mViewingDistance)); viewingDistanceComboBox->setValue(convertToCells(Settings::camera().mViewingDistance));
@ -244,6 +246,11 @@ bool Launcher::SettingsPage::loadSettings()
int shadowResIndex = shadowResolutionComboBox->findText(QString::number(shadowRes)); int shadowResIndex = shadowResolutionComboBox->findText(QString::number(shadowRes));
if (shadowResIndex != -1) if (shadowResIndex != -1)
shadowResolutionComboBox->setCurrentIndex(shadowResIndex); shadowResolutionComboBox->setCurrentIndex(shadowResIndex);
else
{
shadowResolutionComboBox->addItem(QString::number(shadowRes));
shadowResolutionComboBox->setCurrentIndex(shadowResolutionComboBox->count() - 1);
}
connect(shadowDistanceCheckBox, &QCheckBox::toggled, this, &SettingsPage::slotShadowDistLimitToggled); connect(shadowDistanceCheckBox, &QCheckBox::toggled, this, &SettingsPage::slotShadowDistLimitToggled);
@ -295,6 +302,7 @@ bool Launcher::SettingsPage::loadSettings()
hrtfProfileSelectorComboBox->setCurrentIndex(hrtfProfileIndex); hrtfProfileSelectorComboBox->setCurrentIndex(hrtfProfileIndex);
} }
} }
loadSettingBool(Settings::sound().mCameraListener, *cameraListenerCheckBox);
} }
// Interface Changes // Interface Changes
@ -338,7 +346,7 @@ bool Launcher::SettingsPage::loadSettings()
{ {
loadSettingBool(Settings::input().mGrabCursor, *grabCursorCheckBox); loadSettingBool(Settings::input().mGrabCursor, *grabCursorCheckBox);
bool skipMenu = mGameSettings.value("skip-menu").toInt() == 1; bool skipMenu = mGameSettings.value("skip-menu").value.toInt() == 1;
if (skipMenu) if (skipMenu)
{ {
skipMenuCheckBox->setCheckState(Qt::Checked); skipMenuCheckBox->setCheckState(Qt::Checked);
@ -346,8 +354,8 @@ bool Launcher::SettingsPage::loadSettings()
startDefaultCharacterAtLabel->setEnabled(skipMenu); startDefaultCharacterAtLabel->setEnabled(skipMenu);
startDefaultCharacterAtField->setEnabled(skipMenu); startDefaultCharacterAtField->setEnabled(skipMenu);
startDefaultCharacterAtField->setText(mGameSettings.value("start")); startDefaultCharacterAtField->setText(mGameSettings.value("start").value);
runScriptAfterStartupField->setText(mGameSettings.value("script-run")); runScriptAfterStartupField->setText(mGameSettings.value("script-run").value);
} }
return true; return true;
} }
@ -492,6 +500,9 @@ void Launcher::SettingsPage::saveSettings()
Settings::sound().mHrtf.set(hrtfProfileSelectorComboBox->currentText().toStdString()); Settings::sound().mHrtf.set(hrtfProfileSelectorComboBox->currentText().toStdString());
else else
Settings::sound().mHrtf.set({}); Settings::sound().mHrtf.set({});
const bool cCameraListener = cameraListenerCheckBox->checkState() != Qt::Unchecked;
Settings::sound().mCameraListener.set(cCameraListener);
} }
// Interface Changes // Interface Changes
@ -532,17 +543,17 @@ void Launcher::SettingsPage::saveSettings()
saveSettingBool(*grabCursorCheckBox, Settings::input().mGrabCursor); saveSettingBool(*grabCursorCheckBox, Settings::input().mGrabCursor);
int skipMenu = skipMenuCheckBox->checkState() == Qt::Checked; int skipMenu = skipMenuCheckBox->checkState() == Qt::Checked;
if (skipMenu != mGameSettings.value("skip-menu").toInt()) if (skipMenu != mGameSettings.value("skip-menu").value.toInt())
mGameSettings.setValue("skip-menu", QString::number(skipMenu)); mGameSettings.setValue("skip-menu", { QString::number(skipMenu) });
QString startCell = startDefaultCharacterAtField->text(); 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(); QString scriptRun = runScriptAfterStartupField->text();
if (scriptRun != mGameSettings.value("script-run")) if (scriptRun != mGameSettings.value("script-run").value)
mGameSettings.setValue("script-run", scriptRun); mGameSettings.setValue("script-run", { scriptRun });
} }
} }
@ -581,9 +592,16 @@ void Launcher::SettingsPage::slotShadowDistLimitToggled(bool checked)
fadeStartSpinBox->setEnabled(checked); fadeStartSpinBox->setEnabled(checked);
} }
void Launcher::SettingsPage::slotDistantLandToggled(bool checked)
{
activeGridObjectPagingCheckBox->setEnabled(checked);
objectPagingMinSizeComboBox->setEnabled(checked);
}
void Launcher::SettingsPage::slotLightTypeCurrentIndexChanged(int index) void Launcher::SettingsPage::slotLightTypeCurrentIndexChanged(int index)
{ {
lightsMaximumDistanceSpinBox->setEnabled(index != 0); lightsMaximumDistanceSpinBox->setEnabled(index != 0);
lightFadeMultiplierSpinBox->setEnabled(index != 0);
lightsMaxLightsSpinBox->setEnabled(index != 0); lightsMaxLightsSpinBox->setEnabled(index != 0);
lightsBoundingSphereMultiplierSpinBox->setEnabled(index != 0); lightsBoundingSphereMultiplierSpinBox->setEnabled(index != 0);
lightsMinimumInteriorBrightnessSpinBox->setEnabled(index != 0); lightsMinimumInteriorBrightnessSpinBox->setEnabled(index != 0);

@ -33,6 +33,7 @@ namespace Launcher
void slotPostProcessToggled(bool checked); void slotPostProcessToggled(bool checked);
void slotSkyBlendingToggled(bool checked); void slotSkyBlendingToggled(bool checked);
void slotShadowDistLimitToggled(bool checked); void slotShadowDistLimitToggled(bool checked);
void slotDistantLandToggled(bool checked);
void slotLightTypeCurrentIndexChanged(int index); void slotLightTypeCurrentIndexChanged(int index);
private: private:

@ -36,7 +36,7 @@
</sizepolicy> </sizepolicy>
</property> </property>
<property name="text"> <property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;note: content files that are not part of current Content List are &lt;span style=&quot; font-style:italic;font-weight: bold&quot;&gt;highlighted&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Note: content files that are not part of current Content List are &lt;span style=&quot; font-style:italic;font-weight: bold&quot;&gt;highlighted&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -160,7 +160,7 @@
</sizepolicy> </sizepolicy>
</property> </property>
<property name="text"> <property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;note: directories that are not part of current Content List are &lt;span style=&quot; font-style:italic;font-weight: bold&quot;&gt;highlighted&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Note: directories that are not part of current Content List are &lt;span style=&quot; font-style:italic;font-weight: bold&quot;&gt;highlighted&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -251,7 +251,7 @@
<item row="1" column="0" colspan="2"> <item row="1" column="0" colspan="2">
<widget class="QLabel" name="archiveNoteLabel"> <widget class="QLabel" name="archiveNoteLabel">
<property name="text"> <property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;note: archives that are not part of current Content List are &lt;span style=&quot; font-style:italic;font-weight: bold&quot;&gt;highlighted&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Note: archives that are not part of current Content List are &lt;span style=&quot; font-style:italic;font-weight: bold&quot;&gt;highlighted&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -305,7 +305,7 @@
<item> <item>
<widget class="QCheckBox" name="navMeshRemoveUnusedTilesCheckBox"> <widget class="QCheckBox" name="navMeshRemoveUnusedTilesCheckBox">
<property name="text"> <property name="text">
<string>Remove unused tiles</string> <string>Remove Unused Tiles</string>
</property> </property>
<property name="checked"> <property name="checked">
<bool>true</bool> <bool>true</bool>
@ -317,7 +317,7 @@
<item> <item>
<widget class="QLabel" name="navMeshMaxSizeLabel"> <widget class="QLabel" name="navMeshMaxSizeLabel">
<property name="text"> <property name="text">
<string>Max size</string> <string>Max Size</string>
</property> </property>
</widget> </widget>
</item> </item>

@ -26,7 +26,7 @@
<item row="3" column="0"> <item row="3" column="0">
<widget class="QLabel" name="windowModeLabel"> <widget class="QLabel" name="windowModeLabel">
<property name="text"> <property name="text">
<string>Window mode</string> <string>Window Mode</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -111,14 +111,14 @@
<item row="1" column="0"> <item row="1" column="0">
<widget class="QCheckBox" name="framerateLimitCheckBox"> <widget class="QCheckBox" name="framerateLimitCheckBox">
<property name="text"> <property name="text">
<string>Framerate limit</string> <string>Framerate Limit</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QCheckBox" name="windowBorderCheckBox"> <widget class="QCheckBox" name="windowBorderCheckBox">
<property name="text"> <property name="text">
<string>Window border</string> <string>Window Border</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -207,14 +207,14 @@
<item row="4" column="0"> <item row="4" column="0">
<widget class="QLabel" name="antiAliasingLabel"> <widget class="QLabel" name="antiAliasingLabel">
<property name="text"> <property name="text">
<string>Anti-aliasing</string> <string>Anti-Aliasing</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0"> <item row="2" column="0">
<widget class="QLabel" name="vSyncLabel"> <widget class="QLabel" name="vSyncLabel">
<property name="text"> <property name="text">
<string>Vertical synchronization</string> <string>Vertical Synchronization</string>
</property> </property>
</widget> </widget>
</item> </item>

@ -54,7 +54,7 @@
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="importerLabel"> <widget class="QLabel" name="importerLabel">
<property name="text"> <property name="text">
<string>File to import settings from:</string> <string>File to Import Settings From:</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -73,7 +73,7 @@
<item> <item>
<widget class="QCheckBox" name="addonsCheckBox"> <widget class="QCheckBox" name="addonsCheckBox">
<property name="text"> <property name="text">
<string>Import add-on and plugin selection (creates a new Content List)</string> <string>Import Add-on and Plugin Selection</string>
</property> </property>
<property name="checked"> <property name="checked">
<bool>true</bool> <bool>true</bool>
@ -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.</string> to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts.</string>
</property> </property>
<property name="text"> <property name="text">
<string>Import bitmap fonts setup</string> <string>Import Bitmap Fonts</string>
</property> </property>
<property name="checked"> <property name="checked">
<bool>true</bool> <bool>true</bool>

@ -39,7 +39,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Give actors an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Give actors an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Always allow actors to follow over water</string> <string>Always Allow Actors to Follow over Water</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -49,7 +49,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Make disposition change of merchants caused by trading permanent.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Make disposition change of merchants caused by trading permanent.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Permanent barter disposition changes</string> <string>Permanent Barter Disposition Changes</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -59,7 +59,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Don't use race weight in NPC movement speed calculations.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Don't use race weight in NPC movement speed calculations.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Racial variation in speed fix</string> <string>Racial Variation in Speed Fix</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -69,7 +69,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Stops combat with NPCs affected by Calm spells every frame -- like in Morrowind without the MCP.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Stops combat with NPCs affected by Calm spells every frame -- like in Morrowind without the MCP.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Classic Calm spells behavior</string> <string>Classic Calm Spells Behavior</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -79,7 +79,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If enabled NPCs apply evasion maneuver to avoid collisions with others.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If enabled NPCs apply evasion maneuver to avoid collisions with others.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>NPCs avoid collisions</string> <string>NPCs Avoid Collisions</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -89,7 +89,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Make the value of filled soul gems dependent only on soul magnitude.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Make the value of filled soul gems dependent only on soul magnitude.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Soulgem values rebalance</string> <string>Soulgem Values Rebalance</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -99,7 +99,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If this setting is true, supporting models will make use of day night switch nodes.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If this setting is true, supporting models will make use of day night switch nodes.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Day night switch nodes</string> <string>Day Night Switch Nodes</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -109,7 +109,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Followers defend immediately</string> <string>Followers Defend Immediately</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -119,7 +119,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a name=&quot;docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0&quot;/&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a name=&quot;docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0&quot;/&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Use navigation mesh for pathfinding</string> <string>Use Navigation Mesh for Pathfinding</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -129,7 +129,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Only magical ammo bypass resistance</string> <string>Only Magical Ammo Bypass Resistance</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -139,7 +139,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Graphic herbalism</string> <string>Graphic Herbalism</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -149,7 +149,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Makes player swim a bit upward from the line of sight. Applies only in third person mode. Intended to make simpler swimming without diving.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Makes player swim a bit upward from the line of sight. Applies only in third person mode. Intended to make simpler swimming without diving.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Swim upward correction</string> <string>Swim Upward Correction</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -159,7 +159,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Make enchanted weapons without Magical flag bypass normal weapons resistance, like in Morrowind.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Make enchanted weapons without Magical flag bypass normal weapons resistance, like in Morrowind.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Enchanted weapons are magical</string> <string>Enchanted Weapons Are Magical</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -169,7 +169,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Prevents merchants from equipping items that are sold to them.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Prevents merchants from equipping items that are sold to them.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Merchant equipping fix</string> <string>Merchant Equipping Fix</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -179,7 +179,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Trainers choose offered skills by base value</string> <string>Trainers Choose Offered Skills by Base Value</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -189,7 +189,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Can loot during death animation</string> <string>Can Loot During Death Animation</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -199,7 +199,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Make stealing items from NPCs that were knocked down possible during combat.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Make stealing items from NPCs that were knocked down possible during combat.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Steal from knocked out actors in combat</string> <string>Steal from Knocked out Actors in Combat</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -209,7 +209,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Effects of reflected Absorb spells are not mirrored - like in Morrowind.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Effects of reflected Absorb spells are not mirrored - like in Morrowind.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Classic reflected Absorb spells behavior</string> <string>Classic Reflected Absorb Spells Behavior</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -219,7 +219,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Makes unarmed creature attacks able to reduce armor condition, just as attacks from NPCs and armed creatures.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Makes unarmed creature attacks able to reduce armor condition, just as attacks from NPCs and armed creatures.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Unarmed creature attacks damage armor</string> <string>Unarmed Creature Attacks Damage Armor</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -230,7 +230,7 @@
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="unarmedFactorsStrengthLabel"> <widget class="QLabel" name="unarmedFactorsStrengthLabel">
<property name="text"> <property name="text">
<string>Factor strength into hand-to-hand combat</string> <string>Factor Strength into Hand-to-Hand Combat</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -246,12 +246,12 @@
</item> </item>
<item> <item>
<property name="text"> <property name="text">
<string>Affect werewolves</string> <string>Affect Werewolves</string>
</property> </property>
</item> </item>
<item> <item>
<property name="text"> <property name="text">
<string>Do not affect werewolves</string> <string>Do Not Affect Werewolves</string>
</property> </property>
</item> </item>
</widget> </widget>
@ -262,7 +262,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;p&gt;A value greater than 1 requires the Bullet library be compiled with multithreading support.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;p&gt;A value greater than 1 requires the Bullet library be compiled with multithreading support.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Background physics threads</string> <string>Background Physics Threads</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -272,7 +272,7 @@
<item row="2" column="0"> <item row="2" column="0">
<widget class="QLabel" name="labelActorCollisionShapeType"> <widget class="QLabel" name="labelActorCollisionShapeType">
<property name="text"> <property name="text">
<string>Actor collision shape type</string> <string>Actor Collision Shape Type</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -282,16 +282,16 @@
<string>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.</string> <string>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.</string>
</property> </property>
<property name="currentText"> <property name="currentText">
<string>Axis-aligned bounding box</string> <string>Axis-Aligned Bounding Box</string>
</property> </property>
<item> <item>
<property name="text"> <property name="text">
<string>Axis-aligned bounding box</string> <string>Axis-Aligned Bounding Box</string>
</property> </property>
</item> </item>
<item> <item>
<property name="text"> <property name="text">
<string>Rotating box</string> <string>Rotating Box</string>
</property> </property>
</item> </item>
<item> <item>
@ -343,7 +343,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Makes NPCs and player movement more smooth. Recommended to use with &quot;turn to movement direction&quot; enabled.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Makes NPCs and player movement more smooth. Recommended to use with &quot;turn to movement direction&quot; enabled.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Smooth movement</string> <string>Smooth Movement</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -353,7 +353,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Load per-group KF-files and skeleton files from Animations folder&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Load per-group KF-files and skeleton files from Animations folder&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Use additional animation sources</string> <string>Use Additional Animation Sources</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -376,7 +376,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Affects side and diagonal movement. Enabling this setting makes movement more realistic.&lt;/p&gt;&lt;p&gt;If disabled then the whole character's body is pointed to the direction of view. Diagonal movement has no special animation and causes sliding.&lt;/p&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Affects side and diagonal movement. Enabling this setting makes movement more realistic.&lt;/p&gt;&lt;p&gt;If disabled then the whole character's body is pointed to the direction of view. Diagonal movement has no special animation and causes sliding.&lt;/p&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Turn to movement direction</string> <string>Turn to Movement Direction</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -389,7 +389,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Render holstered weapons (with quivers and scabbards), requires modded assets.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Render holstered weapons (with quivers and scabbards), requires modded assets.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Weapon sheathing</string> <string>Weapon Sheathing</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -402,7 +402,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Render holstered shield, requires modded assets.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Render holstered shield, requires modded assets.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Shield sheathing</string> <string>Shield Sheathing</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -412,7 +412,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Player movement ignores animation</string> <string>Player Movement Ignores Animation</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -422,7 +422,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use casting animations for magic items, just as for spells.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use casting animations for magic items, just as for spells.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Use magic item animation</string> <string>Use Magic Item Animation</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> 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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Auto use object normal maps</string> <string>Auto Use Object Normal Maps</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -457,7 +457,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enables soft particles for particle effects. This technique softens the intersection between individual particles and other opaque geometry by blending between them.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enables soft particles for particle effects. This technique softens the intersection between individual particles and other opaque geometry by blending between them.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Soft particles</string> <string>Soft Particles</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -471,7 +471,7 @@
(.osg file, not supported in .nif files). Affects objects.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> (.osg file, not supported in .nif files). Affects objects.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Auto use object specular maps</string> <string>Auto Use Object Specular Maps</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -481,7 +481,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;See 'auto use object normal maps'. Affects terrain.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;See 'auto use object normal maps'. Affects terrain.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Auto use terrain normal maps</string> <string>Auto Use Terrain Normal Maps</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -504,7 +504,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Auto use terrain specular maps</string> <string>Auto Use Terrain Specular Maps</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -514,7 +514,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Adjust coverage for alpha test</string> <string>Adjust Coverage for Alpha Test</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -524,7 +524,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. Can negatively impact performance.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. Can negatively impact performance.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Use anti-alias alpha testing</string> <string>Use Anti-Aliased Alpha Testing</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -537,7 +537,7 @@
&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Bump/reflect map local lighting</string> <string>Bump/Reflect Map Local Lighting</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -547,7 +547,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;EXPERIMENTAL: Stop rain and snow from falling through overhangs and roofs.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;EXPERIMENTAL: Stop rain and snow from falling through overhangs and roofs.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Weather particle occlusion</string> <string>Weather Particle Occlusion</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -570,7 +570,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use exponential fog formula. By default, linear fog is used.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use exponential fog formula. By default, linear fog is used.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Exponential fog</string> <string>Exponential Fog</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> 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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Radial fog</string> <string>Radial Fog</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -626,7 +626,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The fraction of the maximum distance at which blending with the sky starts.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The fraction of the maximum distance at which blending with the sky starts.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Sky blending start</string> <string>Sky Blending Start</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -636,7 +636,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Reduce visibility of clipping plane by blending objects with the sky.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Reduce visibility of clipping plane by blending objects with the sky.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Sky blending</string> <string>Sky Blending</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -652,12 +652,35 @@
<item> <item>
<layout class="QGridLayout" name="terrainLayout" columnstretch="0,0"> <layout class="QGridLayout" name="terrainLayout" columnstretch="0,0">
<item row="4" column="0"> <item row="4" column="0">
<widget class="QCheckBox" name="distantLandCheckBox"> <widget class="QLabel" name="objectPagingMinSizeLabel">
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Controls how large an object must be to be visible in the scene. The objects 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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Object Paging Min Size</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QDoubleSpinBox" name="objectPagingMinSizeComboBox">
<property name="decimals">
<number>3</number>
</property> </property>
<property name="minimum">
<double>0.000000000000000</double>
</property>
<property name="maximum">
<double>0.250000000000000</double>
</property>
<property name="singleStep">
<double>0.005000000000000</double>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="viewingDistanceLabel">
<property name="text"> <property name="text">
<string>Distant land</string> <string>Viewing Distance</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -666,15 +689,18 @@
<property name="suffix"> <property name="suffix">
<string> cells</string> <string> cells</string>
</property> </property>
<property name="decimals">
<number>3</number>
</property>
<property name="minimum"> <property name="minimum">
<double>0.000000000000000</double> <double>0.000000000000000</double>
</property> </property>
<property name="singleStep"> <property name="singleStep">
<double>0.500000000000000</double> <double>0.125000000000000</double>
</property> </property>
</widget> </widget>
</item> </item>
<item row="6" column="1"> <item row="7" column="1">
<spacer name="verticalSpacer_15"> <spacer name="verticalSpacer_15">
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Vertical</enum>
@ -688,45 +714,22 @@
</spacer> </spacer>
</item> </item>
<item row="3" column="0"> <item row="3" column="0">
<widget class="QLabel" name="objectPagingMinSizeLabel"> <widget class="QCheckBox" name="distantLandCheckBox">
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Controls how large an object must be to be visible in the scene. The objects 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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Object paging min size</string>
</property> </property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="viewingDistanceLabel">
<property name="text"> <property name="text">
<string>Viewing distance</string> <string>Distant Land</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1"> <item row="3" column="1">
<widget class="QDoubleSpinBox" name="objectPagingMinSizeComboBox">
<property name="decimals">
<number>3</number>
</property>
<property name="minimum">
<double>0.000000000000000</double>
</property>
<property name="maximum">
<double>0.250000000000000</double>
</property>
<property name="singleStep">
<double>0.005000000000000</double>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QCheckBox" name="activeGridObjectPagingCheckBox"> <widget class="QCheckBox" name="activeGridObjectPagingCheckBox">
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use object paging for active cells grid.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use object paging for active cells grid.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Active grid object paging</string> <string>Active Grid Object Paging</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -741,16 +744,22 @@
<layout class="QHBoxLayout" name="horizontalPostProcessLayout"> <layout class="QHBoxLayout" name="horizontalPostProcessLayout">
<item> <item>
<layout class="QGridLayout" name="postProcessLayout" columnstretch="0,0"> <layout class="QGridLayout" name="postProcessLayout" columnstretch="0,0">
<item row="5" column="0"> <item row="5" column="1">
<widget class="QLabel" name="postprocessHDRTimeLabel"> <widget class="QDoubleSpinBox" name="postprocessHDRTimeComboBox">
<property name="enabled"> <property name="enabled">
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="toolTip"> <property name="decimals">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Controls how much eye adaptation can change from frame to frame. Smaller values makes for slower transitions.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <number>3</number>
</property> </property>
<property name="text"> <property name="minimum">
<string>Auto exposure speed</string> <double>0.010000000000000</double>
</property>
<property name="maximum">
<double>10.000000000000000</double>
</property>
<property name="singleStep">
<double>0.001000000000000</double>
</property> </property>
</widget> </widget>
</item> </item>
@ -776,26 +785,20 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Re-render transparent objects with forced alpha clipping.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Re-render transparent objects with forced alpha clipping.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Transparent postpass</string> <string>Transparent Postpass</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="1"> <item row="5" column="0">
<widget class="QDoubleSpinBox" name="postprocessHDRTimeComboBox"> <widget class="QLabel" name="postprocessHDRTimeLabel">
<property name="enabled"> <property name="enabled">
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="decimals"> <property name="toolTip">
<number>3</number> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Controls how much eye adaptation can change from frame to frame. Smaller values makes for slower transitions.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="minimum">
<double>0.010000000000000</double>
</property>
<property name="maximum">
<double>10.000000000000000</double>
</property> </property>
<property name="singleStep"> <property name="text">
<double>0.001000000000000</double> <string>Auto Exposure Speed</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -805,7 +808,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If this setting is true, post processing will be enabled.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If this setting is true, post processing will be enabled.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Enable post processing</string> <string>Enable Post Processing</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -824,17 +827,17 @@
<widget class="QComboBox" name="shadowComputeSceneBoundsComboBox"> <widget class="QComboBox" name="shadowComputeSceneBoundsComboBox">
<item> <item>
<property name="text"> <property name="text">
<string>bounds</string> <string>Bounds</string>
</property> </property>
</item> </item>
<item> <item>
<property name="text"> <property name="text">
<string>primitives</string> <string>Primitives</string>
</property> </property>
</item> </item>
<item> <item>
<property name="text"> <property name="text">
<string>none</string> <string>None</string>
</property> </property>
</item> </item>
</widget> </widget>
@ -845,7 +848,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Type of &quot;compute scene bounds&quot; 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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Type of &quot;compute scene bounds&quot; 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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Shadow planes computation method</string> <string>Shadow Planes Computation Method</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -866,6 +869,9 @@
<property name="maximum"> <property name="maximum">
<number>81920</number> <number>81920</number>
</property> </property>
<property name="singleStep">
<number>128</number>
</property>
<property name="value"> <property name="value">
<number>8192</number> <number>8192</number>
</property> </property>
@ -877,7 +883,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable shadows for NPCs and creatures besides the player character. May have a minor performance impact.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable shadows for NPCs and creatures besides the player character. May have a minor performance impact.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Enable actor shadows</string> <string>Enable Actor Shadows</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -911,7 +917,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The fraction of the limit above at which shadows begin to gradually fade away.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The fraction of the limit above at which shadows begin to gradually fade away.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Fade start multiplier</string> <string>Fade Start Multiplier</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -921,7 +927,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable shadows exclusively for the player character. May have a very minor performance impact.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable shadows exclusively for the player character. May have a very minor performance impact.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Enable player shadows</string> <string>Enable Player Shadows</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -931,7 +937,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The resolution of each individual shadow map. Increasing it significantly improves shadow quality but may have a minor performance impact.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The resolution of each individual shadow map. Increasing it significantly improves shadow quality but may have a minor performance impact.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Shadow map resolution</string> <string>Shadow Map Resolution</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -941,7 +947,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The distance from the camera at which shadows completely disappear.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The distance from the camera at which shadows completely disappear.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Shadow distance limit:</string> <string>Shadow Distance Limit:</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -951,7 +957,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable shadows for primarily inanimate objects. May have a significant performance impact.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable shadows for primarily inanimate objects. May have a significant performance impact.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Enable object shadows</string> <string>Enable Object Shadows</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -969,6 +975,9 @@
<property name="maximum"> <property name="maximum">
<double>1.000000000000000</double> <double>1.000000000000000</double>
</property> </property>
<property name="singleStep">
<double>0.010000000000000</double>
</property>
<property name="value"> <property name="value">
<double>0.900000000000000</double> <double>0.900000000000000</double>
</property> </property>
@ -993,7 +1002,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting.&lt;/p&gt;&lt;p&gt;Has no effect if actor/player shadows are not enabled.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting.&lt;/p&gt;&lt;p&gt;Has no effect if actor/player shadows are not enabled.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Enable indoor shadows</string> <string>Enable Indoor Shadows</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -1003,7 +1012,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable shadows for the terrain including distant terrain. May have a significant performance and shadow quality impact.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable shadows for the terrain including distant terrain. May have a significant performance and shadow quality impact.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Enable terrain shadows</string> <string>Enable Terrain Shadows</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -1024,7 +1033,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Maximum distance at which lights will appear (measured in units).&lt;/p&gt;&lt;p&gt;Set this to 0 to use an unlimited distance.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Maximum distance at which lights will appear (measured in units).&lt;/p&gt;&lt;p&gt;Set this to 0 to use an unlimited distance.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Lights maximum distance</string> <string>Maximum Light Distance</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -1047,7 +1056,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Maximum number of lights per object.&lt;/p&gt;&lt;p&gt;A low number near default will cause light popping similar to what you would see with legacy lighting.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Maximum number of lights per object.&lt;/p&gt;&lt;p&gt;A low number near default will cause light popping similar to what you would see with legacy lighting.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Max light sources</string> <string>Max Lights</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -1057,7 +1066,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Fraction of maximum distance at which lights will start to fade.&lt;/p&gt;&lt;p&gt;Set this to a low value for slower transitions or a high value for quicker transitions.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Fraction of maximum distance at which lights will start to fade.&lt;/p&gt;&lt;p&gt;Set this to a low value for slower transitions or a high value for quicker transitions.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Lights fade multiplier</string> <string>Fade Start Multiplier</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -1070,7 +1079,7 @@
&lt;p&gt; &quot;Shaders&quot; carries all of the benefits that &quot;Shaders (compatibility)&quot; does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> &lt;p&gt; &quot;Shaders&quot; carries all of the benefits that &quot;Shaders (compatibility)&quot; does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Lighting method</string> <string>Lighting Method</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -1099,7 +1108,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Multipler for bounding sphere of lights.&lt;/p&gt;&lt;p&gt;Higher numbers allows for smooth falloff but require an increase in number of max lights.&lt;/p&gt;&lt;p&gt;Does not effect the illumination or strength of lights.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Multipler for bounding sphere of lights.&lt;/p&gt;&lt;p&gt;Higher numbers allows for smooth falloff but require an increase in number of max lights.&lt;/p&gt;&lt;p&gt;Does not effect the illumination or strength of lights.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Lights bounding sphere multiplier</string> <string>Bounding Sphere Multiplier</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -1119,7 +1128,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Minimum ambient interior brightness.&lt;/p&gt;&lt;p&gt;Increase this if you feel interiors are too dark.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Minimum ambient interior brightness.&lt;/p&gt;&lt;p&gt;Increase this if you feel interiors are too dark.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Lights minimum interior brightness</string> <string>Minimum Interior Brightness</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -1214,7 +1223,7 @@
<string>Select your preferred audio device.</string> <string>Select your preferred audio device.</string>
</property> </property>
<property name="text"> <property name="text">
<string>Audio device</string> <string>Audio Device</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -1300,7 +1309,7 @@
<string>Select your preferred HRTF profile.</string> <string>Select your preferred HRTF profile.</string>
</property> </property>
<property name="text"> <property name="text">
<string>HRTF profile</string> <string>HRTF Profile</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -1330,6 +1339,16 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<widget class="QCheckBox" name="cameraListenerCheckBox">
<property name="toolTip">
<string>In third-person view, use the camera as the sound listener instead of the player character.</string>
</property>
<property name="text">
<string>Use the Camera as the Sound Listener</string>
</property>
</widget>
</item>
<item> <item>
<spacer> <spacer>
<property name="orientation"> <property name="orientation">
@ -1374,7 +1393,7 @@
</item> </item>
<item> <item>
<property name="text"> <property name="text">
<string>Tooltip and crosshair</string> <string>Tooltip and Crosshair</string>
</property> </property>
</item> </item>
</widget> </widget>
@ -1420,7 +1439,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This setting scales GUI windows. A value of 1.0 results in the normal scale.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This setting scales GUI windows. A value of 1.0 results in the normal scale.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>GUI scaling factor</string> <string>GUI Scaling Factor</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -1430,7 +1449,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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. &lt;/p&gt;&lt;p&gt;The default value is false.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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. &lt;/p&gt;&lt;p&gt;The default value is false.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Show effect duration</string> <string>Show Effect Duration</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -1440,7 +1459,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;p&gt;The default value is false.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;p&gt;The default value is false.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Change dialogue topic color</string> <string>Change Dialogue Topic Color</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -1450,7 +1469,7 @@
<string>Size of characters in game texts.</string> <string>Size of characters in game texts.</string>
</property> </property>
<property name="text"> <property name="text">
<string>Font size</string> <string>Font Size</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -1460,7 +1479,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable zooming on local and global maps.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable zooming on local and global maps.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Can zoom on maps</string> <string>Can Zoom on Maps</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -1470,7 +1489,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.&lt;/p&gt;&lt;p&gt;The default value is false.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.&lt;/p&gt;&lt;p&gt;The default value is false.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Show projectile damage</string> <string>Show Projectile Damage</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -1480,7 +1499,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If this setting is true, melee weapons reach and speed will be shown on item tooltip.&lt;/p&gt;&lt;p&gt;The default value is false.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If this setting is true, melee weapons reach and speed will be shown on item tooltip.&lt;/p&gt;&lt;p&gt;The default value is false.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Show melee info</string> <string>Show Melee Info</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -1490,14 +1509,14 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Stretch menus, load screens, etc. to the window aspect ratio.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Stretch menus, load screens, etc. to the window aspect ratio.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Stretch menu background</string> <string>Stretch Menu Background</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="showOwnedLabel"> <widget class="QLabel" name="showOwnedLabel">
<property name="text"> <property name="text">
<string>Show owned objects</string> <string>Show Owned Objects</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -1507,7 +1526,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Whether or not the chance of success will be displayed in the enchanting menu.&lt;/p&gt;&lt;p&gt;The default value is false.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Whether or not the chance of success will be displayed in the enchanting menu.&lt;/p&gt;&lt;p&gt;The default value is false.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Show enchant chance</string> <string>Show Enchant Chance</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -1545,7 +1564,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Add &quot;Time Played&quot; to saves</string> <string>Add &quot;Time Played&quot; to Saves</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -1554,7 +1573,7 @@
<item> <item>
<widget class="QLabel" name="maximumQuicksavesLabel"> <widget class="QLabel" name="maximumQuicksavesLabel">
<property name="text"> <property name="text">
<string>Maximum quicksaves</string> <string>Maximum Quicksaves</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -1581,7 +1600,7 @@
<item> <item>
<widget class="QLabel" name="screenshotFormatLabel"> <widget class="QLabel" name="screenshotFormatLabel">
<property name="text"> <property name="text">
<string>Screenshot format</string> <string>Screenshot Format</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -1609,7 +1628,7 @@
<item> <item>
<widget class="QCheckBox" name="notifyOnSavedScreenshotCheckBox"> <widget class="QCheckBox" name="notifyOnSavedScreenshotCheckBox">
<property name="text"> <property name="text">
<string>Notify on saved screenshot</string> <string>Notify on Saved Screenshot</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -1659,14 +1678,14 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;OpenMW will capture control of the cursor if this setting is true.&lt;/p&gt;&lt;p&gt;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.&lt;/p&gt;&lt;p&gt;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.&lt;/p&gt;&lt;p&gt;Note for developers: its 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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;OpenMW will capture control of the cursor if this setting is true.&lt;/p&gt;&lt;p&gt;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.&lt;/p&gt;&lt;p&gt;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.&lt;/p&gt;&lt;p&gt;Note for developers: its 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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Grab cursor</string> <string>Grab Cursor</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QCheckBox" name="skipMenuCheckBox"> <widget class="QCheckBox" name="skipMenuCheckBox">
<property name="text"> <property name="text">
<string>Skip menu and generate default character</string> <string>Skip Menu and Generate Default Character</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -1691,14 +1710,14 @@
<item> <item>
<widget class="QLabel" name="startDefaultCharacterAtLabel"> <widget class="QLabel" name="startDefaultCharacterAtLabel">
<property name="text"> <property name="text">
<string>Start default character at</string> <string>Start Default Character at</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLineEdit" name="startDefaultCharacterAtField"> <widget class="QLineEdit" name="startDefaultCharacterAtField">
<property name="placeholderText"> <property name="placeholderText">
<string>default cell</string> <string>Default Cell</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -1707,7 +1726,7 @@
<item> <item>
<widget class="QLabel" name="label"> <widget class="QLabel" name="label">
<property name="text"> <property name="text">
<string>Run script after startup:</string> <string>Run Script After Startup:</string>
</property> </property>
</widget> </widget>
</item> </item>

@ -33,7 +33,7 @@ if (BUILD_WITH_CODE_COVERAGE)
target_link_libraries(openmw-iniimporter gcov) target_link_libraries(openmw-iniimporter gcov)
endif() endif()
if (MSVC) if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC)
target_precompile_headers(openmw-iniimporter PRIVATE target_precompile_headers(openmw-iniimporter PRIVATE
<string> <string>
<vector> <vector>

@ -21,7 +21,7 @@ if (WIN32)
install(TARGETS openmw-navmeshtool RUNTIME DESTINATION ".") install(TARGETS openmw-navmeshtool RUNTIME DESTINATION ".")
endif() endif()
if (MSVC) if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC)
target_precompile_headers(openmw-navmeshtool PRIVATE target_precompile_headers(openmw-navmeshtool PRIVATE
<algorithm> <algorithm>
<memory> <memory>

@ -19,6 +19,7 @@
#include <components/files/conversion.hpp> #include <components/files/conversion.hpp>
#include <components/files/multidircollection.hpp> #include <components/files/multidircollection.hpp>
#include <components/platform/platform.hpp> #include <components/platform/platform.hpp>
#include <components/resource/bgsmfilemanager.hpp>
#include <components/resource/bulletshapemanager.hpp> #include <components/resource/bulletshapemanager.hpp>
#include <components/resource/imagemanager.hpp> #include <components/resource/imagemanager.hpp>
#include <components/resource/niffilemanager.hpp> #include <components/resource/niffilemanager.hpp>
@ -165,7 +166,9 @@ namespace NavMeshTool
dataDirs.insert(dataDirs.begin(), resDir / "vfs"); dataDirs.insert(dataDirs.begin(), resDir / "vfs");
const Files::Collections fileCollections(dataDirs); const Files::Collections fileCollections(dataDirs);
const auto& archives = variables["fallback-archive"].as<StringsVector>(); const auto& archives = variables["fallback-archive"].as<StringsVector>();
const auto& contentFiles = variables["content"].as<StringsVector>(); StringsVector contentFiles{ "builtin.omwscripts" };
const auto& configContentFiles = variables["content"].as<StringsVector>();
contentFiles.insert(contentFiles.end(), configContentFiles.begin(), configContentFiles.end());
const std::size_t threadsNumber = variables["threads"].as<std::size_t>(); const std::size_t threadsNumber = variables["threads"].as<std::size_t>();
if (threadsNumber < 1) if (threadsNumber < 1)
@ -218,7 +221,8 @@ namespace NavMeshTool
Resource::ImageManager imageManager(&vfs, expiryDelay); Resource::ImageManager imageManager(&vfs, expiryDelay);
Resource::NifFileManager nifFileManager(&vfs, &encoder.getStatelessEncoder()); 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::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay);
DetourNavigator::RecastGlobalAllocator::init(); DetourNavigator::RecastGlobalAllocator::init();
DetourNavigator::Settings navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager(); DetourNavigator::Settings navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager();

@ -17,6 +17,6 @@ if (BUILD_WITH_CODE_COVERAGE)
target_link_libraries(niftest gcov) target_link_libraries(niftest gcov)
endif() endif()
if (MSVC) if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC)
target_precompile_headers(niftest PRIVATE <filesystem>) target_precompile_headers(niftest PRIVATE <filesystem>)
endif() endif()

@ -8,6 +8,7 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include <components/bgsm/file.hpp>
#include <components/files/configurationmanager.hpp> #include <components/files/configurationmanager.hpp>
#include <components/files/constrainedfilestream.hpp> #include <components/files/constrainedfilestream.hpp>
#include <components/files/conversion.hpp> #include <components/files/conversion.hpp>
@ -25,7 +26,7 @@
namespace bpo = boost::program_options; namespace bpo = boost::program_options;
/// See if the file has the named extension /// 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()); const auto extension = Files::pathToUnicodeString(filename.extension());
return Misc::StringUtils::ciEqual(extension, extensionToFind); return Misc::StringUtils::ciEqual(extension, extensionToFind);
@ -36,63 +37,62 @@ bool isNIF(const std::filesystem::path& filename)
{ {
return hasExtension(filename, ".nif") || hasExtension(filename, ".kf"); 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<VFS::Archive> 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)) return hasExtension(filename, ".bsa") || hasExtension(filename, ".ba2");
{
case Bsa::BSAVER_COMPRESSED:
return std::make_unique<VFS::ArchiveSelector<Bsa::BSAVER_COMPRESSED>::type>(path);
case Bsa::BSAVER_BA2_GNRL:
return std::make_unique<VFS::ArchiveSelector<Bsa::BSAVER_BA2_GNRL>::type>(path);
case Bsa::BSAVER_BA2_DX10:
return std::make_unique<VFS::ArchiveSelector<Bsa::BSAVER_BA2_DX10>::type>(path);
case Bsa::BSAVER_UNCOMPRESSED:
return std::make_unique<VFS::ArchiveSelector<Bsa::BSAVER_UNCOMPRESSED>::type>(path);
case Bsa::BSAVER_UNKNOWN:
default:
std::cerr << "'" << Files::pathToUnicodeString(path) << "' is not a recognized BSA archive" << std::endl;
return nullptr;
}
} }
std::unique_ptr<VFS::Archive> makeArchive(const std::filesystem::path& path) std::unique_ptr<VFS::Archive> makeArchive(const std::filesystem::path& path)
{ {
if (isBSA(path)) if (isBSA(path))
return makeBsaArchive(path); return VFS::makeBsaArchive(path);
if (std::filesystem::is_directory(path)) if (std::filesystem::is_directory(path))
return std::make_unique<VFS::FileSystemArchive>(path); return std::make_unique<VFS::FileSystemArchive>(path);
return nullptr; return nullptr;
} }
void readNIF( void readFile(
const std::filesystem::path& source, const std::filesystem::path& path, const VFS::Manager* vfs, bool quiet) const std::filesystem::path& source, const std::filesystem::path& path, const VFS::Manager* vfs, bool quiet)
{ {
const std::string pathStr = Files::pathToUnicodeString(path); const std::string pathStr = Files::pathToUnicodeString(path);
const bool isNif = isNIF(path);
if (!quiet) if (!quiet)
{ {
if (hasExtension(path, ".kf")) if (isNif)
std::cout << "Reading KF file '" << pathStr << "'"; std::cout << "Reading " << (hasExtension(path, ".nif") ? "NIF" : "KF") << " file '" << pathStr << "'";
else else
std::cout << "Reading NIF file '" << pathStr << "'"; std::cout << "Reading " << (hasExtension(path, ".bgsm") ? "BGSM" : "BGEM") << " file '" << pathStr << "'";
if (!source.empty()) if (!source.empty())
std::cout << " from '" << Files::pathToUnicodeString(isBSA(source) ? source.filename() : source) << "'"; std::cout << " from '" << Files::pathToUnicodeString(isBSA(source) ? source.filename() : source) << "'";
std::cout << std::endl; std::cout << std::endl;
} }
std::filesystem::path fullPath = !source.empty() ? source / path : path; const std::filesystem::path fullPath = !source.empty() ? source / path : path;
try try
{ {
Nif::NIFFile file(fullPath); if (isNif)
Nif::Reader reader(file, nullptr); {
if (vfs != nullptr) Nif::NIFFile file(Files::pathToUnicodeString(fullPath));
reader.parse(vfs->get(pathStr)); Nif::Reader reader(file, nullptr);
if (vfs != nullptr)
reader.parse(vfs->get(pathStr));
else
reader.parse(Files::openConstrainedFileStream(fullPath));
}
else else
reader.parse(Files::openConstrainedFileStream(fullPath)); {
if (vfs != nullptr)
Bgsm::parse(vfs->get(pathStr));
else
Bgsm::parse(Files::openConstrainedFileStream(fullPath));
}
} }
catch (std::exception& e) catch (std::exception& e)
{ {
@ -114,27 +114,33 @@ void readVFS(std::unique_ptr<VFS::Archive>&& archive, const std::filesystem::pat
vfs.addArchive(std::move(archive)); vfs.addArchive(std::move(archive));
vfs.buildIndex(); 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)) if (!archivePath.empty() && !isBSA(archivePath))
{ {
Files::PathContainer dataDirs = { archivePath }; const Files::Collections fileCollections({ archivePath });
const Files::Collections fileCollections = Files::Collections(dataDirs);
const Files::MultiDirCollection& bsaCol = fileCollections.getCollection(".bsa"); const Files::MultiDirCollection& bsaCol = fileCollections.getCollection(".bsa");
const Files::MultiDirCollection& ba2Col = fileCollections.getCollection(".ba2"); 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 : collection)
} {
for (auto& file : ba2Col) try
{ {
readVFS(makeBsaArchive(file.second), file.second, quiet); 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<VFS::Archive>&& archive, const std::filesystem::pat
bool parseOptions(int argc, char** argv, Files::PathContainer& files, Files::PathContainer& archives, bool parseOptions(int argc, char** argv, Files::PathContainer& files, Files::PathContainer& archives,
bool& writeDebugLog, bool& quiet) 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: Usages:
niftest <nif files, kf files, BSA/BA2 files, or directories> niftest <nif files, kf files, bgem/bgsm files, BSA/BA2 files, or directories>
Scan the file or directories for NIF errors. Scan the file or directories for NIF errors.
Allowed options)"); Allowed options)");
@ -234,9 +240,9 @@ int main(int argc, char** argv)
const std::string pathStr = Files::pathToUnicodeString(path); const std::string pathStr = Files::pathToUnicodeString(path);
try 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)) else if (auto archive = makeArchive(path))
{ {
@ -244,7 +250,7 @@ int main(int argc, char** argv)
} }
else 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; << std::endl;
} }
} }

@ -172,7 +172,7 @@ else()
set (OPENCS_OPENMW_CFG "") set (OPENCS_OPENMW_CFG "")
endif(APPLE) endif(APPLE)
add_library(openmw-cs-lib add_library(openmw-cs-lib STATIC
${OPENCS_SRC} ${OPENCS_SRC}
${OPENCS_UI_HDR} ${OPENCS_UI_HDR}
${OPENCS_MOC_SRC} ${OPENCS_MOC_SRC}
@ -250,13 +250,14 @@ target_link_libraries(openmw-cs-lib
) )
if (QT_VERSION_MAJOR VERSION_EQUAL 6) 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() 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() endif()
if (WIN32) if (WIN32)
target_link_libraries(openmw-cs-lib ${Boost_LOCALE_LIBRARY}) target_link_libraries(openmw-cs-lib ${Boost_LOCALE_LIBRARY})
target_sources(openmw-cs PRIVATE ${CMAKE_SOURCE_DIR}/files/windows/openmw-cs.exe.manifest)
endif() endif()
if (WIN32 AND BUILD_OPENCS) if (WIN32 AND BUILD_OPENCS)
@ -292,7 +293,7 @@ if (BUILD_WITH_CODE_COVERAGE)
target_link_libraries(openmw-cs-lib gcov) target_link_libraries(openmw-cs-lib gcov)
endif() endif()
if (MSVC) if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC)
target_precompile_headers(openmw-cs-lib PRIVATE target_precompile_headers(openmw-cs-lib PRIVATE
<boost/program_options/options_description.hpp> <boost/program_options/options_description.hpp>

@ -81,7 +81,7 @@ int runApplication(int argc, char* argv[])
Application application(argc, argv); Application application(argc, argv);
application.setWindowIcon(QIcon(":./openmw-cs.png")); application.setWindowIcon(QIcon(":openmw-cs"));
CS::Editor editor(argc, argv); CS::Editor editor(argc, argv);
#ifdef __linux__ #ifdef __linux__

@ -93,7 +93,6 @@ void CSMDoc::Runner::start(bool delayed)
arguments << "--data=\"" + Files::pathToQString(mProjectPath.parent_path()) + "\""; arguments << "--data=\"" + Files::pathToQString(mProjectPath.parent_path()) + "\"";
arguments << "--replace=content"; arguments << "--replace=content";
arguments << "--content=builtin.omwscripts";
for (const auto& mContentFile : mContentFiles) for (const auto& mContentFile : mContentFiles)
{ {

@ -135,7 +135,7 @@ void CSMDoc::WriteDialogueCollectionStage::perform(int stage, Messages& messages
if (topic.mState == CSMWorld::RecordBase::State_Deleted) if (topic.mState == CSMWorld::RecordBase::State_Deleted)
{ {
// if the topic is deleted, we do not need to bother with INFO records. // 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); writer.startRecord(dialogue.sRecordId);
dialogue.save(writer, true); dialogue.save(writer, true);
writer.endRecord(dialogue.sRecordId); writer.endRecord(dialogue.sRecordId);
@ -187,6 +187,7 @@ void CSMDoc::WriteDialogueCollectionStage::perform(int stage, Messages& messages
{ {
ESM::DialInfo info = record.get(); ESM::DialInfo info = record.get();
info.mId = record.get().mOriginalId; info.mId = record.get().mOriginalId;
info.mData.mType = topic.get().mType;
if (iter == infos.begin()) if (iter == infos.begin())
info.mPrev = ESM::RefId(); info.mPrev = ESM::RefId();

@ -452,7 +452,10 @@ std::shared_ptr<CSMFilter::Node> CSMFilter::Parser::parseText()
return std::shared_ptr<Node>(); return std::shared_ptr<Node>();
} }
return std::make_shared<TextNode>(columnId, text); auto node = std::make_shared<TextNode>(columnId, text);
if (!node->isValid())
error();
return node;
} }
std::shared_ptr<CSMFilter::Node> CSMFilter::Parser::parseValue() std::shared_ptr<CSMFilter::Node> CSMFilter::Parser::parseValue()

@ -34,6 +34,8 @@ namespace CSMFilter
///< Return a string that represents this node. ///< Return a string that represents this node.
/// ///
/// \param numericColumns Use numeric IDs instead of string to represent columns. /// \param numericColumns Use numeric IDs instead of string to represent columns.
bool isValid() { return mRegExp.isValid(); }
}; };
} }

@ -90,6 +90,7 @@ void CSMPrefs::State::declare()
.setTooltip( .setTooltip(
"When editing a record, open the view in a new window," "When editing a record, open the view in a new window,"
" rather than docked in the main view."); " rather than docked in the main view.");
declareInt(mValues->mIdTables.mFilterDelay, "Delay before applying a filter (in miliseconds)");
declareCategory("ID Dialogues"); declareCategory("ID Dialogues");
declareBool(mValues->mIdDialogues.mToolbar, "Show toolbar"); declareBool(mValues->mIdDialogues.mToolbar, "Show toolbar");

@ -138,6 +138,7 @@ namespace CSMPrefs
EnumSettingValue mJumpToAdded{ mIndex, sName, "jump-to-added", sJumpAndSelectValues, 0 }; EnumSettingValue mJumpToAdded{ mIndex, sName, "jump-to-added", sJumpAndSelectValues, 0 };
Settings::SettingValue<bool> mExtendedConfig{ mIndex, sName, "extended-config", false }; Settings::SettingValue<bool> mExtendedConfig{ mIndex, sName, "extended-config", false };
Settings::SettingValue<bool> mSubviewNewWindow{ mIndex, sName, "subview-new-window", false }; Settings::SettingValue<bool> mSubviewNewWindow{ mIndex, sName, "subview-new-window", false };
Settings::SettingValue<int> mFilterDelay{ mIndex, sName, "filter-delay", 500 };
}; };
struct IdDialoguesCategory : Settings::WithIndex struct IdDialoguesCategory : Settings::WithIndex

@ -60,38 +60,38 @@ void CSMTools::EnchantmentCheckStage::perform(int stage, CSMDoc::Messages& messa
} }
else else
{ {
std::vector<ESM::ENAMstruct>::const_iterator effect = enchantment.mEffects.mList.begin(); std::vector<ESM::IndexedENAMstruct>::const_iterator effect = enchantment.mEffects.mList.begin();
for (size_t i = 1; i <= enchantment.mEffects.mList.size(); i++) for (size_t i = 1; i <= enchantment.mEffects.mList.size(); i++)
{ {
const std::string number = std::to_string(i); const std::string number = std::to_string(i);
// At the time of writing this effects, attributes and skills are hardcoded // 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); messages.add(id, "Effect #" + number + " is invalid", "", CSMDoc::Message::Severity_Error);
++effect; ++effect;
continue; continue;
} }
if (effect->mSkill < -1 || effect->mSkill > 26) if (effect->mData.mSkill < -1 || effect->mData.mSkill > 26)
messages.add( messages.add(
id, "Effect #" + number + " affected skill is invalid", "", CSMDoc::Message::Severity_Error); 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( messages.add(
id, "Effect #" + number + " affected attribute is invalid", "", CSMDoc::Message::Severity_Error); 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); 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); 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); messages.add(id, "Effect #" + number + " duration is negative", "", CSMDoc::Message::Severity_Error);
if (effect->mMagnMin < 0) if (effect->mData.mMagnMin < 0)
messages.add( messages.add(
id, "Effect #" + number + " minimum magnitude is negative", "", CSMDoc::Message::Severity_Error); id, "Effect #" + number + " minimum magnitude is negative", "", CSMDoc::Message::Severity_Error);
if (effect->mMagnMax < 0) if (effect->mData.mMagnMax < 0)
messages.add( messages.add(
id, "Effect #" + number + " maximum magnitude is negative", "", CSMDoc::Message::Severity_Error); 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", "", messages.add(id, "Effect #" + number + " minimum magnitude is higher than maximum magnitude", "",
CSMDoc::Message::Severity_Error); CSMDoc::Message::Severity_Error);
++effect; ++effect;

@ -58,7 +58,12 @@ void CSMTools::MagicEffectCheckStage::perform(int stage, CSMDoc::Messages& messa
return; return;
ESM::MagicEffect effect = record.get(); 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()) if (effect.mDescription.empty())
{ {

@ -171,10 +171,9 @@ void CSMTools::TopicInfoCheckStage::perform(int stage, CSMDoc::Messages& message
// Check info conditions // Check info conditions
for (std::vector<ESM::DialInfo::SelectStruct>::const_iterator it = topicInfo.mSelects.begin(); for (const auto& select : topicInfo.mSelects)
it != topicInfo.mSelects.end(); ++it)
{ {
verifySelectStruct((*it), id, messages); verifySelectStruct(select, id, messages);
} }
} }
@ -308,49 +307,15 @@ bool CSMTools::TopicInfoCheckStage::verifyItem(
} }
bool CSMTools::TopicInfoCheckStage::verifySelectStruct( 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); 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); messages.add(id, "Invalid condition '" + infoCondition.toString() + "'", "", CSMDoc::Message::Severity_Error);
return false; 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()) else if (infoCondition.conditionIsAlwaysTrue())
{ {
messages.add( messages.add(
@ -365,48 +330,48 @@ bool CSMTools::TopicInfoCheckStage::verifySelectStruct(
} }
// Id checks // Id checks
if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Global if (select.mFunction == ESM::DialogueCondition::Function_Global
&& !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mGlobals, id, messages)) && !verifyId(ESM::RefId::stringRefId(select.mVariable), mGlobals, id, messages))
{ {
return false; return false;
} }
else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Journal else if (select.mFunction == ESM::DialogueCondition::Function_Journal
&& !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mJournals, id, messages)) && !verifyId(ESM::RefId::stringRefId(select.mVariable), mJournals, id, messages))
{ {
return false; return false;
} }
else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Item else if (select.mFunction == ESM::DialogueCondition::Function_Item
&& !verifyItem(ESM::RefId::stringRefId(infoCondition.getVariableName()), id, messages)) && !verifyItem(ESM::RefId::stringRefId(select.mVariable), id, messages))
{ {
return false; return false;
} }
else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Dead else if (select.mFunction == ESM::DialogueCondition::Function_Dead
&& !verifyActor(ESM::RefId::stringRefId(infoCondition.getVariableName()), id, messages)) && !verifyActor(ESM::RefId::stringRefId(select.mVariable), id, messages))
{ {
return false; return false;
} }
else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotId else if (select.mFunction == ESM::DialogueCondition::Function_NotId
&& !verifyActor(ESM::RefId::stringRefId(infoCondition.getVariableName()), id, messages)) && !verifyActor(ESM::RefId::stringRefId(select.mVariable), id, messages))
{ {
return false; return false;
} }
else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotFaction else if (select.mFunction == ESM::DialogueCondition::Function_NotFaction
&& !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mFactions, id, messages)) && !verifyId(ESM::RefId::stringRefId(select.mVariable), mFactions, id, messages))
{ {
return false; return false;
} }
else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotClass else if (select.mFunction == ESM::DialogueCondition::Function_NotClass
&& !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mClasses, id, messages)) && !verifyId(ESM::RefId::stringRefId(select.mVariable), mClasses, id, messages))
{ {
return false; return false;
} }
else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotRace else if (select.mFunction == ESM::DialogueCondition::Function_NotRace
&& !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mRaces, id, messages)) && !verifyId(ESM::RefId::stringRefId(select.mVariable), mRaces, id, messages))
{ {
return false; return false;
} }
else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotCell else if (select.mFunction == ESM::DialogueCondition::Function_NotCell
&& !verifyCell(infoCondition.getVariableName(), id, messages)) && !verifyCell(select.mVariable, id, messages))
{ {
return false; return false;
} }

@ -84,7 +84,7 @@ namespace CSMTools
const ESM::RefId& name, int rank, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); 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 verifyItem(const ESM::RefId& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages);
bool verifySelectStruct( 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); bool verifySound(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages);
template <typename T> template <typename T>

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

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

@ -585,19 +585,22 @@ namespace CSMWorld
void set(Record<ESXRecordT>& record, const QVariant& data) override void set(Record<ESXRecordT>& record, const QVariant& data) override
{ {
ESXRecordT record2 = record.get(); ESXRecordT record2 = record.get();
float bodyAttr = std::clamp(data.toFloat(), 0.5f, 2.0f);
if (mWeight) if (mWeight)
{ {
if (mMale) if (mMale)
record2.mData.mMaleWeight = data.toFloat(); record2.mData.mMaleWeight = bodyAttr;
else else
record2.mData.mFemaleWeight = data.toFloat(); record2.mData.mFemaleWeight = bodyAttr;
} }
else else
{ {
if (mMale) if (mMale)
record2.mData.mMaleHeight = data.toFloat(); record2.mData.mMaleHeight = bodyAttr;
else else
record2.mData.mFemaleHeight = data.toFloat(); record2.mData.mFemaleHeight = bodyAttr;
} }
record.setModified(record2); record.setModified(record2);
} }
@ -968,7 +971,7 @@ namespace CSMWorld
void set(Record<ESXRecordT>& record, const QVariant& data) override void set(Record<ESXRecordT>& record, const QVariant& data) override
{ {
ESXRecordT record2 = record.get(); ESXRecordT record2 = record.get();
record2.mScale = data.toFloat(); record2.mScale = std::clamp(data.toFloat(), 0.5f, 2.0f);
record.setModified(record2); record.setModified(record2);
} }
@ -1133,8 +1136,8 @@ namespace CSMWorld
template <typename ESXRecordT> template <typename ESXRecordT>
struct TeleportColumn : public Column<ESXRecordT> struct TeleportColumn : public Column<ESXRecordT>
{ {
TeleportColumn() TeleportColumn(int flags)
: Column<ESXRecordT>(Columns::ColumnId_Teleport, ColumnBase::Display_Boolean) : Column<ESXRecordT>(Columns::ColumnId_Teleport, ColumnBase::Display_Boolean, flags)
{ {
} }
@ -1162,6 +1165,8 @@ namespace CSMWorld
QVariant get(const Record<ESXRecordT>& record) const override QVariant get(const Record<ESXRecordT>& record) const override
{ {
if (!record.get().mTeleport)
return QVariant();
return QString::fromUtf8(record.get().mDestCell.c_str()); return QString::fromUtf8(record.get().mDestCell.c_str());
} }
@ -1179,6 +1184,26 @@ namespace CSMWorld
bool isUserEditable() const override { return true; } bool isUserEditable() const override { return true; }
}; };
template <typename ESXRecordT>
struct IsLockedColumn : public Column<ESXRecordT>
{
IsLockedColumn(int flags)
: Column<ESXRecordT>(Columns::ColumnId_IsLocked, ColumnBase::Display_Boolean, flags)
{
}
QVariant get(const Record<ESXRecordT>& record) const override { return record.get().mIsLocked; }
void set(Record<ESXRecordT>& record, const QVariant& data) override
{
ESXRecordT record2 = record.get();
record2.mIsLocked = data.toBool();
record.setModified(record2);
}
bool isEditable() const override { return true; }
};
template <typename ESXRecordT> template <typename ESXRecordT>
struct LockLevelColumn : public Column<ESXRecordT> struct LockLevelColumn : public Column<ESXRecordT>
{ {
@ -1187,7 +1212,12 @@ namespace CSMWorld
{ {
} }
QVariant get(const Record<ESXRecordT>& record) const override { return record.get().mLockLevel; } QVariant get(const Record<ESXRecordT>& record) const override
{
if (record.get().mIsLocked)
return record.get().mLockLevel;
return QVariant();
}
void set(Record<ESXRecordT>& record, const QVariant& data) override void set(Record<ESXRecordT>& record, const QVariant& data) override
{ {
@ -1209,7 +1239,9 @@ namespace CSMWorld
QVariant get(const Record<ESXRecordT>& record) const override QVariant get(const Record<ESXRecordT>& 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<ESXRecordT>& record, const QVariant& data) override void set(Record<ESXRecordT>& record, const QVariant& data) override
@ -1279,17 +1311,21 @@ namespace CSMWorld
{ {
ESM::Position ESXRecordT::*mPosition; ESM::Position ESXRecordT::*mPosition;
int mIndex; int mIndex;
bool mIsDoor;
PosColumn(ESM::Position ESXRecordT::*position, int index, bool door) PosColumn(ESM::Position ESXRecordT::*position, int index, bool door)
: Column<ESXRecordT>((door ? Columns::ColumnId_DoorPositionXPos : Columns::ColumnId_PositionXPos) + index, : Column<ESXRecordT>((door ? Columns::ColumnId_DoorPositionXPos : Columns::ColumnId_PositionXPos) + index,
ColumnBase::Display_Float) ColumnBase::Display_Float)
, mPosition(position) , mPosition(position)
, mIndex(index) , mIndex(index)
, mIsDoor(door)
{ {
} }
QVariant get(const Record<ESXRecordT>& record) const override QVariant get(const Record<ESXRecordT>& record) const override
{ {
if (!record.get().mTeleport && mIsDoor)
return QVariant();
const ESM::Position& position = record.get().*mPosition; const ESM::Position& position = record.get().*mPosition;
return position.pos[mIndex]; return position.pos[mIndex];
} }
@ -1313,17 +1349,21 @@ namespace CSMWorld
{ {
ESM::Position ESXRecordT::*mPosition; ESM::Position ESXRecordT::*mPosition;
int mIndex; int mIndex;
bool mIsDoor;
RotColumn(ESM::Position ESXRecordT::*position, int index, bool door) RotColumn(ESM::Position ESXRecordT::*position, int index, bool door)
: Column<ESXRecordT>((door ? Columns::ColumnId_DoorPositionXRot : Columns::ColumnId_PositionXRot) + index, : Column<ESXRecordT>((door ? Columns::ColumnId_DoorPositionXRot : Columns::ColumnId_PositionXRot) + index,
ColumnBase::Display_Double) ColumnBase::Display_Double)
, mPosition(position) , mPosition(position)
, mIndex(index) , mIndex(index)
, mIsDoor(door)
{ {
} }
QVariant get(const Record<ESXRecordT>& record) const override QVariant get(const Record<ESXRecordT>& record) const override
{ {
if (!record.get().mTeleport && mIsDoor)
return QVariant();
const ESM::Position& position = record.get().*mPosition; const ESM::Position& position = record.get().*mPosition;
return osg::RadiansToDegrees(position.rot[mIndex]); return osg::RadiansToDegrees(position.rot[mIndex]);
} }
@ -2049,6 +2089,26 @@ namespace CSMWorld
bool isEditable() const override { return true; } bool isEditable() const override { return true; }
}; };
template <typename ESXRecordT>
struct ProjectileSpeedColumn : public Column<ESXRecordT>
{
ProjectileSpeedColumn()
: Column<ESXRecordT>(Columns::ColumnId_ProjectileSpeed, ColumnBase::Display_Float)
{
}
QVariant get(const Record<ESXRecordT>& record) const override { return record.get().mData.mSpeed; }
void set(Record<ESXRecordT>& record, const QVariant& data) override
{
ESXRecordT record2 = record.get();
record2.mData.mSpeed = data.toFloat();
record.setModified(record2);
}
bool isEditable() const override { return true; }
};
template <typename ESXRecordT> template <typename ESXRecordT>
struct SchoolColumn : public Column<ESXRecordT> struct SchoolColumn : public Column<ESXRecordT>
{ {

@ -57,8 +57,10 @@ namespace CSMWorld
{ ColumnId_Charges, "Charges" }, { ColumnId_Charges, "Charges" },
{ ColumnId_Enchantment, "Enchantment" }, { ColumnId_Enchantment, "Enchantment" },
{ ColumnId_StackCount, "Count" }, { ColumnId_StackCount, "Count" },
{ ColumnId_GoldValue, "Value" },
{ ColumnId_Teleport, "Teleport" }, { ColumnId_Teleport, "Teleport" },
{ ColumnId_TeleportCell, "Teleport Cell" }, { ColumnId_TeleportCell, "Teleport Cell" },
{ ColumnId_IsLocked, "Locked" },
{ ColumnId_LockLevel, "Lock Level" }, { ColumnId_LockLevel, "Lock Level" },
{ ColumnId_Key, "Key" }, { ColumnId_Key, "Key" },
{ ColumnId_Trap, "Trap" }, { ColumnId_Trap, "Trap" },
@ -235,6 +237,7 @@ namespace CSMWorld
{ ColumnId_RegionSounds, "Sounds" }, { ColumnId_RegionSounds, "Sounds" },
{ ColumnId_SoundName, "Sound Name" }, { ColumnId_SoundName, "Sound Name" },
{ ColumnId_SoundChance, "Chance" }, { ColumnId_SoundChance, "Chance" },
{ ColumnId_SoundProbability, "Probability" },
{ ColumnId_FactionReactions, "Reactions" }, { ColumnId_FactionReactions, "Reactions" },
{ ColumnId_FactionRanks, "Ranks" }, { ColumnId_FactionRanks, "Ranks" },
@ -321,7 +324,6 @@ namespace CSMWorld
{ ColumnId_MaxAttack, "Max Attack" }, { ColumnId_MaxAttack, "Max Attack" },
{ ColumnId_CreatureMisc, "Creature Misc" }, { ColumnId_CreatureMisc, "Creature Misc" },
{ ColumnId_Idle1, "Idle 1" },
{ ColumnId_Idle2, "Idle 2" }, { ColumnId_Idle2, "Idle 2" },
{ ColumnId_Idle3, "Idle 3" }, { ColumnId_Idle3, "Idle 3" },
{ ColumnId_Idle4, "Idle 4" }, { ColumnId_Idle4, "Idle 4" },
@ -329,6 +331,7 @@ namespace CSMWorld
{ ColumnId_Idle6, "Idle 6" }, { ColumnId_Idle6, "Idle 6" },
{ ColumnId_Idle7, "Idle 7" }, { ColumnId_Idle7, "Idle 7" },
{ ColumnId_Idle8, "Idle 8" }, { ColumnId_Idle8, "Idle 8" },
{ ColumnId_Idle9, "Idle 9" },
{ ColumnId_RegionWeather, "Weather" }, { ColumnId_RegionWeather, "Weather" },
{ ColumnId_WeatherName, "Type" }, { ColumnId_WeatherName, "Type" },
@ -376,6 +379,7 @@ namespace CSMWorld
{ ColumnId_Blocked, "Blocked" }, { ColumnId_Blocked, "Blocked" },
{ ColumnId_LevelledCreatureId, "Levelled Creature" }, { ColumnId_LevelledCreatureId, "Levelled Creature" },
{ ColumnId_ProjectileSpeed, "Projectile Speed" },
// end marker // end marker
{ -1, 0 }, { -1, 0 },

@ -310,14 +310,14 @@ namespace CSMWorld
ColumnId_MaxAttack = 284, ColumnId_MaxAttack = 284,
ColumnId_CreatureMisc = 285, ColumnId_CreatureMisc = 285,
ColumnId_Idle1 = 286, ColumnId_Idle2 = 286,
ColumnId_Idle2 = 287, ColumnId_Idle3 = 287,
ColumnId_Idle3 = 288, ColumnId_Idle4 = 288,
ColumnId_Idle4 = 289, ColumnId_Idle5 = 289,
ColumnId_Idle5 = 290, ColumnId_Idle6 = 290,
ColumnId_Idle6 = 291, ColumnId_Idle7 = 291,
ColumnId_Idle7 = 292, ColumnId_Idle8 = 292,
ColumnId_Idle8 = 293, ColumnId_Idle9 = 293,
ColumnId_RegionWeather = 294, ColumnId_RegionWeather = 294,
ColumnId_WeatherName = 295, ColumnId_WeatherName = 295,
@ -349,6 +349,14 @@ namespace CSMWorld
ColumnId_SelectionGroupObjects = 316, 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 // Allocated to a separate value range, so we don't get a collision should we ever need
// to extend the number of use values. // to extend the number of use values.
ColumnId_UseValue1 = 0x10000, ColumnId_UseValue1 = 0x10000,

@ -161,7 +161,7 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data
defines["radialFog"] = "0"; defines["radialFog"] = "0";
defines["lightingModel"] = "0"; defines["lightingModel"] = "0";
defines["reverseZ"] = "0"; defines["reverseZ"] = "0";
defines["refraction_enabled"] = "0"; defines["waterRefraction"] = "0";
for (const auto& define : shadowDefines) for (const auto& define : shadowDefines)
defines[define.first] = define.second; defines[define.first] = define.second;
mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(defines); mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(defines);
@ -301,8 +301,8 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data
mRegions.addColumn(new NestedParentColumn<ESM::Region>(Columns::ColumnId_RegionWeather)); mRegions.addColumn(new NestedParentColumn<ESM::Region>(Columns::ColumnId_RegionWeather));
index = mRegions.getColumns() - 1; index = mRegions.getColumns() - 1;
mRegions.addAdapter(std::make_pair(&mRegions.getColumn(index), new RegionWeatherAdapter())); mRegions.addAdapter(std::make_pair(&mRegions.getColumn(index), new RegionWeatherAdapter()));
mRegions.getNestableColumn(index)->addColumn( mRegions.getNestableColumn(index)->addColumn(new NestedChildColumn(
new NestedChildColumn(Columns::ColumnId_WeatherName, ColumnBase::Display_String, false)); Columns::ColumnId_WeatherName, ColumnBase::Display_String, ColumnBase::Flag_Dialogue, false));
mRegions.getNestableColumn(index)->addColumn( mRegions.getNestableColumn(index)->addColumn(
new NestedChildColumn(Columns::ColumnId_WeatherChance, ColumnBase::Display_UnsignedInteger8)); new NestedChildColumn(Columns::ColumnId_WeatherChance, ColumnBase::Display_UnsignedInteger8));
// Region Sounds // Region Sounds
@ -313,6 +313,8 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data
new NestedChildColumn(Columns::ColumnId_SoundName, ColumnBase::Display_Sound)); new NestedChildColumn(Columns::ColumnId_SoundName, ColumnBase::Display_Sound));
mRegions.getNestableColumn(index)->addColumn( mRegions.getNestableColumn(index)->addColumn(
new NestedChildColumn(Columns::ColumnId_SoundChance, ColumnBase::Display_UnsignedInteger8)); 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<ESM::BirthSign>); mBirthsigns.addColumn(new StringIdColumn<ESM::BirthSign>);
mBirthsigns.addColumn(new RecordStateColumn<ESM::BirthSign>); mBirthsigns.addColumn(new RecordStateColumn<ESM::BirthSign>);
@ -500,6 +502,7 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data
mMagicEffects.addColumn(new FixedRecordTypeColumn<ESM::MagicEffect>(UniversalId::Type_MagicEffect)); mMagicEffects.addColumn(new FixedRecordTypeColumn<ESM::MagicEffect>(UniversalId::Type_MagicEffect));
mMagicEffects.addColumn(new SchoolColumn<ESM::MagicEffect>); mMagicEffects.addColumn(new SchoolColumn<ESM::MagicEffect>);
mMagicEffects.addColumn(new BaseCostColumn<ESM::MagicEffect>); mMagicEffects.addColumn(new BaseCostColumn<ESM::MagicEffect>);
mMagicEffects.addColumn(new ProjectileSpeedColumn<ESM::MagicEffect>);
mMagicEffects.addColumn(new EffectTextureColumn<ESM::MagicEffect>(Columns::ColumnId_Icon)); mMagicEffects.addColumn(new EffectTextureColumn<ESM::MagicEffect>(Columns::ColumnId_Icon));
mMagicEffects.addColumn(new EffectTextureColumn<ESM::MagicEffect>(Columns::ColumnId_Particle)); mMagicEffects.addColumn(new EffectTextureColumn<ESM::MagicEffect>(Columns::ColumnId_Particle));
mMagicEffects.addColumn(new EffectObjectColumn<ESM::MagicEffect>(Columns::ColumnId_CastingObject)); mMagicEffects.addColumn(new EffectObjectColumn<ESM::MagicEffect>(Columns::ColumnId_CastingObject));
@ -510,6 +513,7 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data
mMagicEffects.addColumn(new EffectSoundColumn<ESM::MagicEffect>(Columns::ColumnId_HitSound)); mMagicEffects.addColumn(new EffectSoundColumn<ESM::MagicEffect>(Columns::ColumnId_HitSound));
mMagicEffects.addColumn(new EffectSoundColumn<ESM::MagicEffect>(Columns::ColumnId_AreaSound)); mMagicEffects.addColumn(new EffectSoundColumn<ESM::MagicEffect>(Columns::ColumnId_AreaSound));
mMagicEffects.addColumn(new EffectSoundColumn<ESM::MagicEffect>(Columns::ColumnId_BoltSound)); mMagicEffects.addColumn(new EffectSoundColumn<ESM::MagicEffect>(Columns::ColumnId_BoltSound));
mMagicEffects.addColumn( mMagicEffects.addColumn(
new FlagColumn<ESM::MagicEffect>(Columns::ColumnId_AllowSpellmaking, ESM::MagicEffect::AllowSpellmaking)); new FlagColumn<ESM::MagicEffect>(Columns::ColumnId_AllowSpellmaking, ESM::MagicEffect::AllowSpellmaking));
mMagicEffects.addColumn( mMagicEffects.addColumn(
@ -589,7 +593,8 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data
mRefs.addColumn(new ChargesColumn<CellRef>); mRefs.addColumn(new ChargesColumn<CellRef>);
mRefs.addColumn(new EnchantmentChargesColumn<CellRef>); mRefs.addColumn(new EnchantmentChargesColumn<CellRef>);
mRefs.addColumn(new StackSizeColumn<CellRef>); mRefs.addColumn(new StackSizeColumn<CellRef>);
mRefs.addColumn(new TeleportColumn<CellRef>); mRefs.addColumn(new TeleportColumn<CellRef>(
ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh));
mRefs.addColumn(new TeleportCellColumn<CellRef>); mRefs.addColumn(new TeleportCellColumn<CellRef>);
mRefs.addColumn(new PosColumn<CellRef>(&CellRef::mDoorDest, 0, true)); mRefs.addColumn(new PosColumn<CellRef>(&CellRef::mDoorDest, 0, true));
mRefs.addColumn(new PosColumn<CellRef>(&CellRef::mDoorDest, 1, true)); mRefs.addColumn(new PosColumn<CellRef>(&CellRef::mDoorDest, 1, true));
@ -597,6 +602,8 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data
mRefs.addColumn(new RotColumn<CellRef>(&CellRef::mDoorDest, 0, true)); mRefs.addColumn(new RotColumn<CellRef>(&CellRef::mDoorDest, 0, true));
mRefs.addColumn(new RotColumn<CellRef>(&CellRef::mDoorDest, 1, true)); mRefs.addColumn(new RotColumn<CellRef>(&CellRef::mDoorDest, 1, true));
mRefs.addColumn(new RotColumn<CellRef>(&CellRef::mDoorDest, 2, true)); mRefs.addColumn(new RotColumn<CellRef>(&CellRef::mDoorDest, 2, true));
mRefs.addColumn(new IsLockedColumn<CellRef>(
ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh));
mRefs.addColumn(new LockLevelColumn<CellRef>); mRefs.addColumn(new LockLevelColumn<CellRef>);
mRefs.addColumn(new KeyColumn<CellRef>); mRefs.addColumn(new KeyColumn<CellRef>);
mRefs.addColumn(new TrapColumn<CellRef>); mRefs.addColumn(new TrapColumn<CellRef>);

@ -63,9 +63,18 @@ bool CSMWorld::IdTableProxyModel::filterAcceptsRow(int sourceRow, const QModelIn
CSMWorld::IdTableProxyModel::IdTableProxyModel(QObject* parent) CSMWorld::IdTableProxyModel::IdTableProxyModel(QObject* parent)
: QSortFilterProxyModel(parent) : QSortFilterProxyModel(parent)
, mFilterTimer{ new QTimer(this) }
, mSourceModel(nullptr) , mSourceModel(nullptr)
{ {
setSortCaseSensitivity(Qt::CaseInsensitive); 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 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<CSMFilter::Node>& filter) void CSMWorld::IdTableProxyModel::setFilter(const std::shared_ptr<CSMFilter::Node>& filter)
{ {
beginResetModel(); mAwaitingFilter = filter;
mFilter = filter; mFilterTimer->start();
updateColumnMap();
endResetModel();
} }
bool CSMWorld::IdTableProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right) const 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) void CSMWorld::IdTableProxyModel::sourceRowsInserted(const QModelIndex& parent, int /*start*/, int end)
{ {
refreshFilter(); refreshFilter();

@ -10,6 +10,9 @@
#include <QModelIndex> #include <QModelIndex>
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
#include <QString> #include <QString>
#include <QTimer>
#include "../prefs/state.hpp"
#include "columns.hpp" #include "columns.hpp"
@ -29,6 +32,8 @@ namespace CSMWorld
Q_OBJECT Q_OBJECT
std::shared_ptr<CSMFilter::Node> mFilter; std::shared_ptr<CSMFilter::Node> mFilter;
std::unique_ptr<QTimer> mFilterTimer;
std::shared_ptr<CSMFilter::Node> mAwaitingFilter;
std::map<int, int> mColumnMap; // column ID, column index in this model (or -1) std::map<int, int> mColumnMap; // column ID, column index in this model (or -1)
// Cache of enum values for enum columns (e.g. Modified, Record Type). // 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); virtual void sourceDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight);
void timerTimeout();
void settingChanged(const CSMPrefs::Setting* setting);
signals: signals:
void rowAdded(const std::string& id); void rowAdded(const std::string& id);

File diff suppressed because it is too large Load Diff

@ -7,133 +7,13 @@
#include <components/esm3/loadinfo.hpp> #include <components/esm3/loadinfo.hpp>
namespace ESM #include <QVariant>
{
class Variant;
}
namespace CSMWorld 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 class ConstInfoSelectWrapper
{ {
public: 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 enum ComparisonType
{ {
Comparison_Boolean, Comparison_Boolean,
@ -143,25 +23,13 @@ namespace CSMWorld
Comparison_None 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* FunctionEnumStrings[];
static const char* RelationEnumStrings[]; 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; ESM::DialogueCondition::Function getFunctionName() const;
RelationType getRelationType() const; ESM::DialogueCondition::Comparison getRelationType() const;
ComparisonType getComparisonType() const; ComparisonType getComparisonType() const;
bool hasVariable() const; bool hasVariable() const;
@ -169,17 +37,12 @@ namespace CSMWorld
bool conditionIsAlwaysTrue() const; bool conditionIsAlwaysTrue() const;
bool conditionIsNeverTrue() const; bool conditionIsNeverTrue() const;
bool variantTypeIsValid() const;
const ESM::Variant& getVariant() const; QVariant getValue() const;
std::string toString() const; std::string toString() const;
protected: protected:
void readRule();
void readFunctionName();
void readRelationType();
void readVariableName();
void updateHasVariable(); void updateHasVariable();
void updateComparisonType(); void updateComparisonType();
@ -207,38 +70,29 @@ namespace CSMWorld
template <typename Type1, typename Type2> template <typename Type1, typename Type2>
bool conditionIsNeverTrue(std::pair<Type1, Type1> conditionRange, std::pair<Type2, Type2> validRange) const; bool conditionIsNeverTrue(std::pair<Type1, Type1> conditionRange, std::pair<Type2, Type2> validRange) const;
FunctionName mFunctionName;
RelationType mRelationType;
ComparisonType mComparisonType; ComparisonType mComparisonType;
bool mHasVariable; bool mHasVariable;
std::string mVariableName;
private: 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 class InfoSelectWrapper : public ConstInfoSelectWrapper
{ {
public: public:
InfoSelectWrapper(ESM::DialInfo::SelectStruct& select); InfoSelectWrapper(ESM::DialogueCondition& select);
// Wrapped SelectStruct will not be modified until update() is called // Wrapped SelectStruct will not be modified until update() is called
void setFunctionName(FunctionName name); void setFunctionName(ESM::DialogueCondition::Function name);
void setRelationType(RelationType type); void setRelationType(ESM::DialogueCondition::Comparison type);
void setVariableName(const std::string& name); void setVariableName(const std::string& name);
void setValue(int value);
// Modified wrapped SelectStruct void setValue(float value);
void update();
// This sets properties based on the function name to its defaults and updates the wrapped object
void setDefaults();
ESM::Variant& getVariant();
private: private:
ESM::DialInfo::SelectStruct& mSelect; ESM::DialogueCondition& mSelect;
void writeRule(); void writeRule();
}; };

@ -38,7 +38,6 @@ namespace CSMWorld
point.mZ = 0; point.mZ = 0;
point.mAutogenerated = 0; point.mAutogenerated = 0;
point.mConnectionNum = 0; point.mConnectionNum = 0;
point.mUnknown = 0;
points.insert(points.begin() + position, point); points.insert(points.begin() + position, point);
pathgrid.mData.mPoints = pathgrid.mPoints.size(); pathgrid.mData.mPoints = pathgrid.mPoints.size();
@ -414,20 +413,32 @@ namespace CSMWorld
QVariant RegionSoundListAdapter::getData(const Record<ESM::Region>& record, int subRowIndex, int subColIndex) const QVariant RegionSoundListAdapter::getData(const Record<ESM::Region>& record, int subRowIndex, int subColIndex) const
{ {
ESM::Region region = record.get(); const ESM::Region& region = record.get();
std::vector<ESM::Region::SoundRef>& soundList = region.mSoundList; const std::vector<ESM::Region::SoundRef>& soundList = region.mSoundList;
if (subRowIndex < 0 || subRowIndex >= static_cast<int>(soundList.size())) const size_t index = static_cast<size_t>(subRowIndex);
if (subRowIndex < 0 || index >= soundList.size())
throw std::runtime_error("index out of range"); throw std::runtime_error("index out of range");
ESM::Region::SoundRef soundRef = soundList[subRowIndex]; const ESM::Region::SoundRef& soundRef = soundList[subRowIndex];
switch (subColIndex) switch (subColIndex)
{ {
case 0: case 0:
return QString(soundRef.mSound.getRefIdString().c_str()); return QString(soundRef.mSound.getRefIdString().c_str());
case 1: case 1:
return soundRef.mChance; 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: default:
throw std::runtime_error("Region sounds subcolumn index out of range"); throw std::runtime_error("Region sounds subcolumn index out of range");
} }
@ -463,7 +474,7 @@ namespace CSMWorld
int RegionSoundListAdapter::getColumnsCount(const Record<ESM::Region>& record) const int RegionSoundListAdapter::getColumnsCount(const Record<ESM::Region>& record) const
{ {
return 2; return 3;
} }
int RegionSoundListAdapter::getRowsCount(const Record<ESM::Region>& record) const int RegionSoundListAdapter::getRowsCount(const Record<ESM::Region>& record) const
@ -527,13 +538,11 @@ namespace CSMWorld
{ {
Info info = record.get(); Info info = record.get();
std::vector<ESM::DialInfo::SelectStruct>& conditions = info.mSelects; auto& conditions = info.mSelects;
// default row // default row
ESM::DialInfo::SelectStruct condStruct; ESM::DialogueCondition condStruct;
condStruct.mSelectRule = "01000"; condStruct.mIndex = conditions.size();
condStruct.mValue = ESM::Variant();
condStruct.mValue.setType(ESM::VT_Int);
conditions.insert(conditions.begin() + position, condStruct); conditions.insert(conditions.begin() + position, condStruct);
@ -544,7 +553,7 @@ namespace CSMWorld
{ {
Info info = record.get(); Info info = record.get();
std::vector<ESM::DialInfo::SelectStruct>& conditions = info.mSelects; auto& conditions = info.mSelects;
if (rowToRemove < 0 || rowToRemove >= static_cast<int>(conditions.size())) if (rowToRemove < 0 || rowToRemove >= static_cast<int>(conditions.size()))
throw std::runtime_error("index out of range"); throw std::runtime_error("index out of range");
@ -558,8 +567,8 @@ namespace CSMWorld
{ {
Info info = record.get(); Info info = record.get();
info.mSelects = static_cast<const NestedTableWrapper<std::vector<ESM::DialInfo::SelectStruct>>&>(nestedTable) info.mSelects
.mNestedTable; = static_cast<const NestedTableWrapper<std::vector<ESM::DialogueCondition>>&>(nestedTable).mNestedTable;
record.setModified(info); record.setModified(info);
} }
@ -567,14 +576,14 @@ namespace CSMWorld
NestedTableWrapperBase* InfoConditionAdapter::table(const Record<Info>& record) const NestedTableWrapperBase* InfoConditionAdapter::table(const Record<Info>& record) const
{ {
// deleted by dtor of NestedTableStoring // deleted by dtor of NestedTableStoring
return new NestedTableWrapper<std::vector<ESM::DialInfo::SelectStruct>>(record.get().mSelects); return new NestedTableWrapper<std::vector<ESM::DialogueCondition>>(record.get().mSelects);
} }
QVariant InfoConditionAdapter::getData(const Record<Info>& record, int subRowIndex, int subColIndex) const QVariant InfoConditionAdapter::getData(const Record<Info>& record, int subRowIndex, int subColIndex) const
{ {
Info info = record.get(); Info info = record.get();
std::vector<ESM::DialInfo::SelectStruct>& conditions = info.mSelects; auto& conditions = info.mSelects;
if (subRowIndex < 0 || subRowIndex >= static_cast<int>(conditions.size())) if (subRowIndex < 0 || subRowIndex >= static_cast<int>(conditions.size()))
throw std::runtime_error("index out of range"); throw std::runtime_error("index out of range");
@ -596,23 +605,11 @@ namespace CSMWorld
} }
case 2: case 2:
{ {
return infoSelectWrapper.getRelationType(); return infoSelectWrapper.getRelationType() - ESM::DialogueCondition::Comp_Eq;
} }
case 3: case 3:
{ {
switch (infoSelectWrapper.getVariant().getType()) return infoSelectWrapper.getValue();
{
case ESM::VT_Int:
{
return infoSelectWrapper.getVariant().getInteger();
}
case ESM::VT_Float:
{
return infoSelectWrapper.getVariant().getFloat();
}
default:
return QVariant();
}
} }
default: default:
throw std::runtime_error("Info condition subcolumn index out of range"); throw std::runtime_error("Info condition subcolumn index out of range");
@ -624,7 +621,7 @@ namespace CSMWorld
{ {
Info info = record.get(); Info info = record.get();
std::vector<ESM::DialInfo::SelectStruct>& conditions = info.mSelects; auto& conditions = info.mSelects;
if (subRowIndex < 0 || subRowIndex >= static_cast<int>(conditions.size())) if (subRowIndex < 0 || subRowIndex >= static_cast<int>(conditions.size()))
throw std::runtime_error("index out of range"); throw std::runtime_error("index out of range");
@ -636,27 +633,18 @@ namespace CSMWorld
{ {
case 0: // Function case 0: // Function
{ {
infoSelectWrapper.setFunctionName(static_cast<ConstInfoSelectWrapper::FunctionName>(value.toInt())); infoSelectWrapper.setFunctionName(static_cast<ESM::DialogueCondition::Function>(value.toInt()));
if (infoSelectWrapper.getComparisonType() != ConstInfoSelectWrapper::Comparison_Numeric
&& infoSelectWrapper.getVariant().getType() != ESM::VT_Int)
{
infoSelectWrapper.getVariant().setType(ESM::VT_Int);
}
infoSelectWrapper.update();
break; break;
} }
case 1: // Variable case 1: // Variable
{ {
infoSelectWrapper.setVariableName(value.toString().toUtf8().constData()); infoSelectWrapper.setVariableName(value.toString().toUtf8().constData());
infoSelectWrapper.update();
break; break;
} }
case 2: // Relation case 2: // Relation
{ {
infoSelectWrapper.setRelationType(static_cast<ConstInfoSelectWrapper::RelationType>(value.toInt())); infoSelectWrapper.setRelationType(
infoSelectWrapper.update(); static_cast<ESM::DialogueCondition::Comparison>(value.toInt() + ESM::DialogueCondition::Comp_Eq));
break; break;
} }
case 3: // Value case 3: // Value
@ -668,13 +656,11 @@ namespace CSMWorld
// QVariant seems to have issues converting 0 // QVariant seems to have issues converting 0
if ((value.toInt(&conversionResult) && conversionResult) || value.toString().compare("0") == 0) if ((value.toInt(&conversionResult) && conversionResult) || value.toString().compare("0") == 0)
{ {
infoSelectWrapper.getVariant().setType(ESM::VT_Int); infoSelectWrapper.setValue(value.toInt());
infoSelectWrapper.getVariant().setInteger(value.toInt());
} }
else if (value.toFloat(&conversionResult) && conversionResult) else if (value.toFloat(&conversionResult) && conversionResult)
{ {
infoSelectWrapper.getVariant().setType(ESM::VT_Float); infoSelectWrapper.setValue(value.toFloat());
infoSelectWrapper.getVariant().setFloat(value.toFloat());
} }
break; break;
} }
@ -683,8 +669,7 @@ namespace CSMWorld
{ {
if ((value.toInt(&conversionResult) && conversionResult) || value.toString().compare("0") == 0) if ((value.toInt(&conversionResult) && conversionResult) || value.toString().compare("0") == 0)
{ {
infoSelectWrapper.getVariant().setType(ESM::VT_Int); infoSelectWrapper.setValue(value.toInt());
infoSelectWrapper.getVariant().setInteger(value.toInt());
} }
break; break;
} }
@ -996,7 +981,10 @@ namespace CSMWorld
case 5: case 5:
{ {
if (isInterior && interiorWater) if (isInterior && interiorWater)
{
cell.mWater = value.toFloat(); cell.mWater = value.toFloat();
cell.setHasWaterHeightSub(true);
}
else else
return; // return without saving return; // return without saving
break; break;

@ -255,20 +255,22 @@ namespace CSMWorld
{ {
ESXRecordT magic = record.get(); ESXRecordT magic = record.get();
std::vector<ESM::ENAMstruct>& effectsList = magic.mEffects.mList; std::vector<ESM::IndexedENAMstruct>& effectsList = magic.mEffects.mList;
// blank row // blank row
ESM::ENAMstruct effect; ESM::IndexedENAMstruct effect;
effect.mEffectID = 0; effect.mIndex = position;
effect.mSkill = -1; effect.mData.mEffectID = 0;
effect.mAttribute = -1; effect.mData.mSkill = -1;
effect.mRange = 0; effect.mData.mAttribute = -1;
effect.mArea = 0; effect.mData.mRange = 0;
effect.mDuration = 0; effect.mData.mArea = 0;
effect.mMagnMin = 0; effect.mData.mDuration = 0;
effect.mMagnMax = 0; effect.mData.mMagnMin = 0;
effect.mData.mMagnMax = 0;
effectsList.insert(effectsList.begin() + position, effect); effectsList.insert(effectsList.begin() + position, effect);
magic.mEffects.updateIndexes();
record.setModified(magic); record.setModified(magic);
} }
@ -277,12 +279,13 @@ namespace CSMWorld
{ {
ESXRecordT magic = record.get(); ESXRecordT magic = record.get();
std::vector<ESM::ENAMstruct>& effectsList = magic.mEffects.mList; std::vector<ESM::IndexedENAMstruct>& effectsList = magic.mEffects.mList;
if (rowToRemove < 0 || rowToRemove >= static_cast<int>(effectsList.size())) if (rowToRemove < 0 || rowToRemove >= static_cast<int>(effectsList.size()))
throw std::runtime_error("index out of range"); throw std::runtime_error("index out of range");
effectsList.erase(effectsList.begin() + rowToRemove); effectsList.erase(effectsList.begin() + rowToRemove);
magic.mEffects.updateIndexes();
record.setModified(magic); record.setModified(magic);
} }
@ -292,7 +295,7 @@ namespace CSMWorld
ESXRecordT magic = record.get(); ESXRecordT magic = record.get();
magic.mEffects.mList magic.mEffects.mList
= static_cast<const NestedTableWrapper<std::vector<ESM::ENAMstruct>>&>(nestedTable).mNestedTable; = static_cast<const NestedTableWrapper<std::vector<ESM::IndexedENAMstruct>>&>(nestedTable).mNestedTable;
record.setModified(magic); record.setModified(magic);
} }
@ -300,19 +303,19 @@ namespace CSMWorld
NestedTableWrapperBase* table(const Record<ESXRecordT>& record) const override NestedTableWrapperBase* table(const Record<ESXRecordT>& record) const override
{ {
// deleted by dtor of NestedTableStoring // deleted by dtor of NestedTableStoring
return new NestedTableWrapper<std::vector<ESM::ENAMstruct>>(record.get().mEffects.mList); return new NestedTableWrapper<std::vector<ESM::IndexedENAMstruct>>(record.get().mEffects.mList);
} }
QVariant getData(const Record<ESXRecordT>& record, int subRowIndex, int subColIndex) const override QVariant getData(const Record<ESXRecordT>& record, int subRowIndex, int subColIndex) const override
{ {
ESXRecordT magic = record.get(); ESXRecordT magic = record.get();
std::vector<ESM::ENAMstruct>& effectsList = magic.mEffects.mList; std::vector<ESM::IndexedENAMstruct>& effectsList = magic.mEffects.mList;
if (subRowIndex < 0 || subRowIndex >= static_cast<int>(effectsList.size())) if (subRowIndex < 0 || subRowIndex >= static_cast<int>(effectsList.size()))
throw std::runtime_error("index out of range"); throw std::runtime_error("index out of range");
ESM::ENAMstruct effect = effectsList[subRowIndex]; ESM::ENAMstruct effect = effectsList[subRowIndex].mData;
switch (subColIndex) switch (subColIndex)
{ {
case 0: case 0:
@ -374,12 +377,12 @@ namespace CSMWorld
{ {
ESXRecordT magic = record.get(); ESXRecordT magic = record.get();
std::vector<ESM::ENAMstruct>& effectsList = magic.mEffects.mList; std::vector<ESM::IndexedENAMstruct>& effectsList = magic.mEffects.mList;
if (subRowIndex < 0 || subRowIndex >= static_cast<int>(effectsList.size())) if (subRowIndex < 0 || subRowIndex >= static_cast<int>(effectsList.size()))
throw std::runtime_error("index out of range"); throw std::runtime_error("index out of range");
ESM::ENAMstruct effect = effectsList[subRowIndex]; ESM::ENAMstruct effect = effectsList[subRowIndex].mData;
switch (subColIndex) switch (subColIndex)
{ {
case 0: case 0:
@ -438,7 +441,7 @@ namespace CSMWorld
throw std::runtime_error("Magic Effects subcolumn index out of range"); 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); record.setModified(magic);
} }

@ -19,6 +19,11 @@ namespace CSMWorld
State mState; State mState;
explicit RecordBase(State state)
: mState(state)
{
}
virtual ~RecordBase() = default; virtual ~RecordBase() = default;
virtual std::unique_ptr<RecordBase> clone() const = 0; virtual std::unique_ptr<RecordBase> clone() const = 0;
@ -69,21 +74,18 @@ namespace CSMWorld
template <typename ESXRecordT> template <typename ESXRecordT>
Record<ESXRecordT>::Record() Record<ESXRecordT>::Record()
: mBase() : RecordBase(State_BaseOnly)
, mBase()
, mModified() , mModified()
{ {
} }
template <typename ESXRecordT> template <typename ESXRecordT>
Record<ESXRecordT>::Record(State state, const ESXRecordT* base, const ESXRecordT* modified) Record<ESXRecordT>::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 <typename ESXRecordT> template <typename ESXRecordT>

@ -33,7 +33,7 @@ QVariant CSMWorld::PotionRefIdAdapter::getData(const RefIdColumn* column, const
data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Potion))); data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Potion)));
if (column == mAutoCalc) 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() // to show nested tables in dialogue subview, see IdTree::hasChildren()
if (column == mColumns.mEffects) if (column == mColumns.mEffects)
@ -51,7 +51,7 @@ void CSMWorld::PotionRefIdAdapter::setData(
ESM::Potion potion = record.get(); ESM::Potion potion = record.get();
if (column == mAutoCalc) if (column == mAutoCalc)
potion.mData.mAutoCalc = value.toInt(); potion.mData.mFlags = value.toBool();
else else
{ {
InventoryRefIdAdapter<ESM::Potion>::setData(column, data, index, value); InventoryRefIdAdapter<ESM::Potion>::setData(column, data, index, value);

@ -97,7 +97,7 @@ CSMWorld::RefIdCollection::RefIdCollection()
inventoryColumns.mIcon = &mColumns.back(); inventoryColumns.mIcon = &mColumns.back();
mColumns.emplace_back(Columns::ColumnId_Weight, ColumnBase::Display_Float); mColumns.emplace_back(Columns::ColumnId_Weight, ColumnBase::Display_Float);
inventoryColumns.mWeight = &mColumns.back(); 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(); inventoryColumns.mValue = &mColumns.back();
IngredientColumns ingredientColumns(inventoryColumns); 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_AiDuration, CSMWorld::ColumnBase::Display_Integer));
mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_AiWanderToD, 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_Idle2, CSMWorld::ColumnBase::Display_Integer));
mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle3, 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)); 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_Idle6, CSMWorld::ColumnBase::Display_Integer));
mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle7, 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_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(new RefIdColumn(Columns::ColumnId_AiWanderRepeat, CSMWorld::ColumnBase::Display_Boolean));
mColumns.back().addColumn( mColumns.back().addColumn(

@ -1,7 +1,9 @@
#include "regionmap.hpp" #include "regionmap.hpp"
#include <QApplication>
#include <QBrush> #include <QBrush>
#include <QModelIndex> #include <QModelIndex>
#include <QPalette>
#include <QSize> #include <QSize>
#include <QVariant> #include <QVariant>
@ -21,20 +23,33 @@
#include "data.hpp" #include "data.hpp"
#include "universalid.hpp" #include "universalid.hpp"
CSMWorld::RegionMap::CellDescription::CellDescription() namespace CSMWorld
: mDeleted(false)
{ {
float getLandHeight(const CSMWorld::Cell& cell, CSMWorld::Data& data)
{
const IdCollection<Land>& 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>& cell) CSMWorld::RegionMap::CellDescription::CellDescription(const Record<Cell>& cell, float landHeight)
{ {
const Cell& cell2 = cell.get(); const Cell& cell2 = cell.get();
if (!cell2.isExterior()) if (!cell2.isExterior())
throw std::logic_error("Interior cell in region map"); throw std::logic_error("Interior cell in region map");
mMaxLandHeight = landHeight;
mDeleted = cell.isDeleted(); mDeleted = cell.isDeleted();
mRegion = cell2.mRegion; mRegion = cell2.mRegion;
mName = cell2.mName; mName = cell2.mName;
} }
@ -92,7 +107,7 @@ void CSMWorld::RegionMap::buildMap()
if (cell2.isExterior()) if (cell2.isExterior())
{ {
CellDescription description(cell); CellDescription description(cell, getLandHeight(cell2, mData));
CellCoordinates index = getIndex(cell2); CellCoordinates index = getIndex(cell2);
@ -140,7 +155,7 @@ void CSMWorld::RegionMap::addCells(int start, int end)
{ {
CellCoordinates index = getIndex(cell2); CellCoordinates index = getIndex(cell2);
CellDescription description(cell); CellDescription description(cell, getLandHeight(cell.get(), mData));
addCell(index, description); addCell(index, description);
} }
@ -335,10 +350,11 @@ QVariant CSMWorld::RegionMap::data(const QModelIndex& index, int role) const
auto iter = mColours.find(cell->second.mRegion); auto iter = mColours.find(cell->second.mRegion);
if (iter != mColours.end()) 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()) if (cell->second.mRegion.empty()) // no region
return QBrush(Qt::Dense6Pattern); // no region return QBrush(cell->second.mMaxLandHeight > 0 ? Qt::Dense3Pattern : Qt::Dense6Pattern);
return QBrush(Qt::red, Qt::Dense6Pattern); // invalid region return QBrush(Qt::red, Qt::Dense6Pattern); // invalid region
} }

@ -40,13 +40,12 @@ namespace CSMWorld
private: private:
struct CellDescription struct CellDescription
{ {
float mMaxLandHeight;
bool mDeleted; bool mDeleted;
ESM::RefId mRegion; ESM::RefId mRegion;
std::string mName; std::string mName;
CellDescription(); CellDescription(const Record<Cell>& cell, float landHeight);
CellDescription(const Record<Cell>& cell);
}; };
Data& mData; Data& mData;

@ -28,7 +28,7 @@ void CSMWorld::Resources::recreate(const VFS::Manager* vfs, const char* const* e
size_t baseSize = mBaseDirectory.size(); size_t baseSize = mBaseDirectory.size();
for (const auto& filepath : vfs->getRecursiveDirectoryIterator("")) for (const auto& filepath : vfs->getRecursiveDirectoryIterator())
{ {
const std::string_view view = filepath.view(); const std::string_view view = filepath.view();
if (view.size() < baseSize + 1 || !view.starts_with(mBaseDirectory) || view[baseSize] != '/') if (view.size() < baseSize + 1 || !view.starts_with(mBaseDirectory) || view[baseSize] != '/')

@ -58,7 +58,7 @@ std::string CSMWorld::TableMimeData::getIcon() const
if (tmpIcon != id.getIcon()) if (tmpIcon != id.getIcon())
{ {
return ":/multitype.png"; // icon stolen from gnome TODO: get new icon return ":multitype";
} }
tmpIcon = id.getIcon(); tmpIcon = id.getIcon();

@ -23,158 +23,137 @@ namespace
constexpr TypeData sNoArg[] = { constexpr TypeData sNoArg[] = {
{ CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, "-", ":placeholder" }, { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, "-", ":placeholder" },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Globals, "Global Variables", { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Globals, "Global Variables",
":./global-variable.png" }, ":global-variable" },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Gmsts, "Game Settings", ":./gmst.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Gmsts, "Game Settings", ":gmst" },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Skills, "Skills", ":./skill.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Skills, "Skills", ":skill" },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Classes, "Classes", ":./class.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Classes, "Classes", ":class" },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Factions, "Factions", ":./faction.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Factions, "Factions", ":faction" },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Races, "Races", ":./race.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Races, "Races", ":race" },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Sounds, "Sounds", ":./sound.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Sounds, "Sounds", ":sound" },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Scripts, "Scripts", ":./script.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Scripts, "Scripts", ":script" },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Regions, "Regions", ":./region.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Regions, "Regions", ":region" },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Birthsigns, "Birthsigns", { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Birthsigns, "Birthsigns", ":birthsign" },
":./birthsign.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Spells, "Spells", ":spell" },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Spells, "Spells", ":./spell.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Topics, "Topics", ":dialogue-topics" },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Topics, "Topics",
":./dialogue-topics.png" },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Journals, "Journals", { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Journals, "Journals",
":./journal-topics.png" }, ":journal-topics" },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_TopicInfos, "Topic Infos", { 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", { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_JournalInfos, "Journal Infos",
":./journal-topic-infos.png" }, ":journal-topic-infos" },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Cells, "Cells", ":./cell.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Cells, "Cells", ":cell" },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Enchantments, "Enchantments", { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Enchantments, "Enchantments",
":./enchantment.png" }, ":enchantment" },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_BodyParts, "Body Parts", { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_BodyParts, "Body Parts", ":body-part" },
":./body-part.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Referenceables, "Objects", ":object" },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Referenceables, "Objects", { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_References, "Instances", ":instance" },
":./object.png" }, { CSMWorld::UniversalId::Class_NonRecord, CSMWorld::UniversalId::Type_RegionMap, "Region Map", ":region-map" },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_References, "Instances", { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Filters, "Filters", ":filter" },
":./instance.png" }, { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Meshes, "Meshes", ":resources-mesh" },
{ CSMWorld::UniversalId::Class_NonRecord, CSMWorld::UniversalId::Type_RegionMap, "Region Map", { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Icons, "Icons", ":resources-icon" },
":./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" },
{ CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Musics, "Music Files", { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Musics, "Music Files",
":./resources-music" }, ":resources-music" },
{ CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_SoundsRes, "Sound Files", { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_SoundsRes, "Sound Files",
":resources-sound" }, ":resources-sound" },
{ CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Textures, "Textures", { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Textures, "Textures",
":./resources-texture" }, ":resources-texture" },
{ CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Videos, "Videos", { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Videos, "Videos", ":resources-video" },
":./resources-video" },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_DebugProfiles, "Debug Profiles", { 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_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", { 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", { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_MagicEffects, "Magic Effects",
":./magic-effect.png" }, ":magic-effect" },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Lands, "Lands", { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Lands, "Lands", ":land-heightmap" },
":./land-heightmap.png" },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_LandTextures, "Land Textures", { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_LandTextures, "Land Textures",
":./land-texture.png" }, ":land-texture" },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Pathgrids, "Pathgrids", { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Pathgrids, "Pathgrids", ":pathgrid" },
":./pathgrid.png" },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_StartScripts, "Start Scripts", { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_StartScripts, "Start Scripts",
":./start-script.png" }, ":start-script" },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_MetaDatas, "Metadata", { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_MetaDatas, "Metadata", ":metadata" },
":./metadata.png" },
}; };
constexpr TypeData sIdArg[] = { constexpr TypeData sIdArg[] = {
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Global, "Global Variable", { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Global, "Global Variable",
":./global-variable.png" }, ":global-variable" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Gmst, "Game Setting", ":./gmst.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Gmst, "Game Setting", ":gmst" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Skill, "Skill", ":./skill.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Skill, "Skill", ":skill" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Class, "Class", ":./class.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Class, "Class", ":class" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Faction, "Faction", ":./faction.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Faction, "Faction", ":faction" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Race, "Race", ":./race.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Race, "Race", ":race" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Sound, "Sound", ":./sound.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Sound, "Sound", ":sound" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Script, "Script", ":./script.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Script, "Script", ":script" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Region, "Region", ":./region.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Region, "Region", ":region" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Birthsign, "Birthsign", ":./birthsign.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Birthsign, "Birthsign", ":birthsign" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Spell, "Spell", ":./spell.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Spell, "Spell", ":spell" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Topic, "Topic", ":./dialogue-topics.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Topic, "Topic", ":dialogue-topics" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Journal, "Journal", { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Journal, "Journal", ":journal-topics" },
":./journal-topics.png" },
{ CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_TopicInfo, "TopicInfo", { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_TopicInfo, "TopicInfo",
":./dialogue-topic-infos.png" }, ":dialogue-info" },
{ CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_JournalInfo, "JournalInfo", { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_JournalInfo, "JournalInfo",
":./journal-topic-infos.png" }, ":journal-topic-infos" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell, "Cell", ":./cell.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell, "Cell", ":cell" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell_Missing, "Cell", ":./cell.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell_Missing, "Cell", ":cell" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Referenceable, "Object", ":./object.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Referenceable, "Object", ":object" },
{ CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Activator, "Activator", { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Activator, "Activator", ":activator" },
":./activator.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Potion, "Potion", ":potion" },
{ CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Potion, "Potion", ":./potion.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Apparatus, "Apparatus", ":apparatus" },
{ CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Apparatus, "Apparatus", { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Armor, "Armor", ":armor" },
":./apparatus.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Book, "Book", ":book" },
{ CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Armor, "Armor", ":./armor.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Clothing, "Clothing", ":clothing" },
{ CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Book, "Book", ":./book.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Container, "Container", ":container" },
{ CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Clothing, "Clothing", ":./clothing.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Creature, "Creature", ":creature" },
{ CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Container, "Container", { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Door, "Door", ":door" },
":./container.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Ingredient, "Ingredient", ":ingredient" },
{ 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" },
{ CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_CreatureLevelledList, { 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", { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_ItemLevelledList, "Item Levelled List",
":./levelled-item.png" }, ":levelled-item" },
{ CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Light, "Light", ":./light.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Light, "Light", ":light" },
{ CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Lockpick, "Lockpick", ":./lockpick.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Lockpick, "Lockpick", ":lockpick" },
{ CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Miscellaneous, "Miscellaneous", { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Miscellaneous, "Miscellaneous",
":./miscellaneous.png" }, ":miscellaneous" },
{ CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Npc, "NPC", ":./npc.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Npc, "NPC", ":npc" },
{ CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Probe, "Probe", ":./probe.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Probe, "Probe", ":probe" },
{ CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Repair, "Repair", ":./repair.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Repair, "Repair", ":repair" },
{ CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Static, "Static", ":./static.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Static, "Static", ":static" },
{ CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Weapon, "Weapon", ":./weapon.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Weapon, "Weapon", ":weapon" },
{ CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_Reference, "Instance", { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_Reference, "Instance", ":instance" },
":./instance.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Filter, "Filter", ":filter" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Filter, "Filter", ":./filter.png" }, { CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Scene, "Scene", ":scene" },
{ CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Scene, "Scene", ":./scene.png" }, { CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Preview, "Preview", ":edit-preview" },
{ CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Preview, "Preview", { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Enchantment, "Enchantment", ":enchantment" },
":./record-preview.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_BodyPart, "Body Part", ":body-part" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Enchantment, "Enchantment", { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Mesh, "Mesh", ":resources-mesh" },
":./enchantment.png" }, { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Icon, "Icon", ":resources-icon" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_BodyPart, "Body Part", ":./body-part.png" }, { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Music, "Music", ":resources-music" },
{ 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", { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_SoundRes, "Sound File",
":./resources-sound" }, ":resources-sound" },
{ CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Texture, "Texture", { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Texture, "Texture", ":resources-texture" },
":./resources-texture" }, { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Video, "Video", ":resources-video" },
{ CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Video, "Video", ":./resources-video" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_DebugProfile, "Debug Profile", { 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", { 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", { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_MagicEffect, "Magic Effect",
":./magic-effect.png" }, ":magic-effect" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Land, "Land", ":./land-heightmap.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Land, "Land", ":land-heightmap" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_LandTexture, "Land Texture", { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_LandTexture, "Land Texture",
":./land-texture.png" }, ":land-texture" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Pathgrid, "Pathgrid", ":./pathgrid.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Pathgrid, "Pathgrid", ":pathgrid" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_StartScript, "Start Script", { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_StartScript, "Start Script",
":./start-script.png" }, ":start-script" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_MetaData, "Metadata", ":./metadata.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_MetaData, "Metadata", ":metadata" },
}; };
constexpr TypeData sIndexArg[] = { constexpr TypeData sIndexArg[] = {
{ CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_VerificationResults, { 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", { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_LoadErrorLog, "Load Error Log",
":./error-log.png" }, ":error-log" },
{ CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_Search, "Global Search", { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_Search, "Global Search", ":menu-search" },
":./menu-search.png" },
}; };
struct WriteToStream struct WriteToStream

@ -10,7 +10,7 @@
#include <QScreen> #include <QScreen>
#include <QVBoxLayout> #include <QVBoxLayout>
QPushButton* CSVDoc::StartupDialogue::addButton(const QString& label, const QIcon& icon) QPushButton* CSVDoc::StartupDialogue::addButton(const QString& label, const QString& icon)
{ {
int column = mColumn--; int column = mColumn--;
@ -39,13 +39,13 @@ QWidget* CSVDoc::StartupDialogue::createButtons()
mLayout = new QGridLayout(widget); mLayout = new QGridLayout(widget);
/// \todo add icons /// \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); 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); 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); connect(createGame, &QPushButton::clicked, this, &StartupDialogue::createGame);
for (int i = 0; i < 3; ++i) for (int i = 0; i < 3; ++i)

@ -20,7 +20,7 @@ namespace CSVDoc
int mColumn; int mColumn;
QGridLayout* mLayout; QGridLayout* mLayout;
QPushButton* addButton(const QString& label, const QIcon& icon); QPushButton* addButton(const QString& label, const QString& icon);
QWidget* createButtons(); QWidget* createButtons();

@ -71,30 +71,30 @@ void CSVDoc::View::setupFileMenu()
{ {
QMenu* file = menuBar()->addMenu(tr("File")); 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); 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); 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); 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); connect(save, &QAction::triggered, this, &View::save);
mSave = save; mSave = save;
file->addSeparator(); 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); connect(verify, &QAction::triggered, this, &View::verify);
mVerify = 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); connect(merge, &QAction::triggered, this, &View::merge);
mMerge = 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); connect(loadErrors, &QAction::triggered, this, &View::loadErrorLog);
QAction* meta = createMenuEntry(CSMWorld::UniversalId::Type_MetaDatas, file, "document-file-metadata"); QAction* meta = createMenuEntry(CSMWorld::UniversalId::Type_MetaDatas, file, "document-file-metadata");
@ -102,10 +102,10 @@ void CSVDoc::View::setupFileMenu()
file->addSeparator(); 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); 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(exit, &QAction::triggered, this, &View::exit);
connect(this, &View::exitApplicationRequest, &mViewManager, &ViewManager::exitApplication); connect(this, &View::exitApplicationRequest, &mViewManager, &ViewManager::exitApplication);
@ -140,17 +140,16 @@ void CSVDoc::View::setupEditMenu()
mUndo = mDocument->getUndoStack().createUndoAction(this, tr("Undo")); mUndo = mDocument->getUndoStack().createUndoAction(this, tr("Undo"));
setupShortcut("document-edit-undo", mUndo); setupShortcut("document-edit-undo", mUndo);
connect(mUndo, &QAction::changed, this, &View::undoActionChanged); 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); edit->addAction(mUndo);
mRedo = mDocument->getUndoStack().createRedoAction(this, tr("Redo")); mRedo = mDocument->getUndoStack().createRedoAction(this, tr("Redo"));
connect(mRedo, &QAction::changed, this, &View::redoActionChanged); connect(mRedo, &QAction::changed, this, &View::redoActionChanged);
setupShortcut("document-edit-redo", mRedo); setupShortcut("document-edit-redo", mRedo);
mRedo->setIcon(QIcon(QString::fromStdString(":./menu-redo.png"))); mRedo->setIcon(QIcon(QString::fromStdString(":menu-redo")));
edit->addAction(mRedo); edit->addAction(mRedo);
QAction* userSettings QAction* userSettings = createMenuEntry("Preferences", ":menu-preferences", edit, "document-edit-preferences");
= createMenuEntry("Preferences", ":./menu-preferences.png", edit, "document-edit-preferences");
connect(userSettings, &QAction::triggered, this, &View::editSettingsRequest); connect(userSettings, &QAction::triggered, this, &View::editSettingsRequest);
QAction* search = createMenuEntry(CSMWorld::UniversalId::Type_Search, edit, "document-edit-search"); 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")); 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); 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); connect(mShowStatusBar, &QAction::toggled, this, &View::toggleShowStatusBar);
mShowStatusBar->setCheckable(true); mShowStatusBar->setCheckable(true);
mShowStatusBar->setChecked(CSMPrefs::get()["Windows"]["show-statusbar"].isTrue()); mShowStatusBar->setChecked(CSMPrefs::get()["Windows"]["show-statusbar"].isTrue());
@ -289,7 +288,7 @@ void CSVDoc::View::setupAssetsMenu()
{ {
QMenu* assets = menuBar()->addMenu(tr("Assets")); 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); connect(reload, &QAction::triggered, &mDocument->getData(), &CSMWorld::Data::assetsChanged);
assets->addSeparator(); assets->addSeparator();
@ -341,9 +340,9 @@ void CSVDoc::View::setupDebugMenu()
QAction* runDebug = debug->addMenu(mGlobalDebugProfileMenu); QAction* runDebug = debug->addMenu(mGlobalDebugProfileMenu);
runDebug->setText(tr("Run OpenMW")); runDebug->setText(tr("Run OpenMW"));
setupShortcut("document-debug-run", runDebug); 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); connect(stopDebug, &QAction::triggered, this, &View::stop);
mStopDebug = stopDebug; mStopDebug = stopDebug;
@ -355,16 +354,16 @@ void CSVDoc::View::setupHelpMenu()
{ {
QMenu* help = menuBar()->addMenu(tr("Help")); 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); 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); 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); 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); connect(aboutQt, &QAction::triggered, this, &View::infoAboutQt);
} }
@ -1111,7 +1110,16 @@ void CSVDoc::View::updateWidth(bool isGrowLimit, int minSubViewWidth)
{ {
QRect rect; QRect rect;
if (isGrowLimit) 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 else
rect = desktopRect(); rect = desktopRect();

@ -44,7 +44,7 @@ CSVFilter::EditWidget::EditWidget(CSMWorld::Data& data, QWidget* parent)
mHelpAction = new QAction(tr("Help"), this); mHelpAction = new QAction(tr("Help"), this);
connect(mHelpAction, &QAction::triggered, this, &EditWidget::openHelp); connect(mHelpAction, &QAction::triggered, this, &EditWidget::openHelp);
mHelpAction->setIcon(QIcon(":/info.png")); mHelpAction->setIcon(QIcon(":info"));
addAction(mHelpAction); addAction(mHelpAction);
auto* openHelpShortcut = new CSMPrefs::Shortcut("help", this); auto* openHelpShortcut = new CSMPrefs::Shortcut("help", this);
openHelpShortcut->associateAction(mHelpAction); openHelpShortcut->associateAction(mHelpAction);

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

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

@ -8,7 +8,7 @@
class QWidget; class QWidget;
CSVRender::InstanceMoveMode::InstanceMoveMode(QWidget* parent) CSVRender::InstanceMoveMode::InstanceMoveMode(QWidget* parent)
: ModeButton(QIcon(QPixmap(":scenetoolbar/transform-move")), : ModeButton(QIcon(":scenetoolbar/transform-move"),
"Move selected instances" "Move selected instances"
"<ul><li>Use {scene-edit-primary} to move instances around freely</li>" "<ul><li>Use {scene-edit-primary} to move instances around freely</li>"
"<li>Use {scene-edit-secondary} to move instances around within the grid</li>" "<li>Use {scene-edit-secondary} to move instances around within the grid</li>"

@ -58,7 +58,8 @@ namespace CSVRender
InstanceSelectionMode::~InstanceSelectionMode() InstanceSelectionMode::~InstanceSelectionMode()
{ {
mParentNode->removeChild(mBaseNode); if (mBaseNode)
mParentNode->removeChild(mBaseNode);
} }
void InstanceSelectionMode::setDragStart(const osg::Vec3d& dragStart) void InstanceSelectionMode::setDragStart(const osg::Vec3d& dragStart)

@ -12,11 +12,10 @@ namespace CSVRender
{ {
// elements that are part of the actual scene // elements that are part of the actual scene
Mask_Hidden = 0x0, Mask_Hidden = 0x0,
Mask_Reference = 0x2, Mask_Reference = 0x1,
Mask_Pathgrid = 0x4, Mask_Pathgrid = 0x2,
Mask_Water = 0x8, Mask_Water = 0x4,
Mask_Fog = 0x10, Mask_Terrain = 0x8,
Mask_Terrain = 0x20,
// used within models // used within models
Mask_ParticleSystem = 0x100, Mask_ParticleSystem = 0x100,

@ -8,7 +8,7 @@
#include <osg/Vec4f> #include <osg/Vec4f>
#include <osg/ref_ptr> #include <osg/ref_ptr>
#include <components/esm/defs.hpp> #include <components/esm/position.hpp>
#include <components/esm/refid.hpp> #include <components/esm/refid.hpp>
#include "tagbase.hpp" #include "tagbase.hpp"

@ -160,7 +160,6 @@ void CSVRender::PagedWorldspaceWidget::addVisibilitySelectorButtons(CSVWidget::S
{ {
WorldspaceWidget::addVisibilitySelectorButtons(tool); WorldspaceWidget::addVisibilitySelectorButtons(tool);
tool->addButton(Button_Terrain, Mask_Terrain, "Terrain"); tool->addButton(Button_Terrain, Mask_Terrain, "Terrain");
tool->addButton(Button_Fog, Mask_Fog, "Fog", "", true);
} }
void CSVRender::PagedWorldspaceWidget::addEditModeSelectorButtons(CSVWidget::SceneToolMode* tool) void CSVRender::PagedWorldspaceWidget::addEditModeSelectorButtons(CSVWidget::SceneToolMode* tool)
@ -170,9 +169,10 @@ void CSVRender::PagedWorldspaceWidget::addEditModeSelectorButtons(CSVWidget::Sce
/// \todo replace EditMode with suitable subclasses /// \todo replace EditMode with suitable subclasses
tool->addButton(new TerrainShapeMode(this, mRootNode, tool), "terrain-shape"); tool->addButton(new TerrainShapeMode(this, mRootNode, tool), "terrain-shape");
tool->addButton(new TerrainTextureMode(this, mRootNode, tool), "terrain-texture"); tool->addButton(new TerrainTextureMode(this, mRootNode, tool), "terrain-texture");
tool->addButton( const QIcon vertexIcon = QIcon(":scenetoolbar/editing-terrain-vertex-paint");
new EditMode(this, QIcon(":placeholder"), Mask_Reference, "Terrain vertex paint editing"), "terrain-vertex"); const QIcon movementIcon = QIcon(":scenetoolbar/editing-terrain-movement");
tool->addButton(new EditMode(this, QIcon(":placeholder"), Mask_Reference, "Terrain movement"), "terrain-move"); 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) void CSVRender::PagedWorldspaceWidget::handleInteractionPress(const WorldspaceHitResult& hit, InteractionType type)

@ -36,8 +36,8 @@ class QWidget;
namespace CSVRender namespace CSVRender
{ {
PathgridMode::PathgridMode(WorldspaceWidget* worldspaceWidget, QWidget* parent) PathgridMode::PathgridMode(WorldspaceWidget* worldspaceWidget, QWidget* parent)
: EditMode(worldspaceWidget, QIcon(":placeholder"), Mask_Pathgrid | Mask_Terrain | Mask_Reference, getTooltip(), : EditMode(worldspaceWidget, QIcon(":scenetoolbar/editing-pathgrid"),
parent) Mask_Pathgrid | Mask_Terrain | Mask_Reference, getTooltip(), parent)
, mDragMode(DragMode_None) , mDragMode(DragMode_None)
, mFromNode(0) , mFromNode(0)
, mSelectionMode(nullptr) , mSelectionMode(nullptr)

@ -48,6 +48,7 @@
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include <components/resource/resourcesystem.hpp> #include <components/resource/resourcesystem.hpp>
#include <components/resource/scenemanager.hpp> #include <components/resource/scenemanager.hpp>
#include <components/sceneutil/glextensions.hpp>
#include <components/sceneutil/lightmanager.hpp> #include <components/sceneutil/lightmanager.hpp>
#include "../widget/scenetoolmode.hpp" #include "../widget/scenetoolmode.hpp"
@ -76,6 +77,8 @@ namespace CSVRender
= new osgViewer::GraphicsWindowEmbedded(0, 0, width(), height()); = new osgViewer::GraphicsWindowEmbedded(0, 0, width(), height());
mWidget->setGraphicsWindowEmbedded(window); mWidget->setGraphicsWindowEmbedded(window);
mRenderer->setRealizeOperation(new SceneUtil::GetGLExtensionsOperation());
int frameRateLimit = CSMPrefs::get()["Rendering"]["framerate-limit"].toInt(); int frameRateLimit = CSMPrefs::get()["Rendering"]["framerate-limit"].toInt();
mRenderer->setRunMaxFrameRate(frameRateLimit); mRenderer->setRunMaxFrameRate(frameRateLimit);
mRenderer->setUseConfigureAffinity(false); mRenderer->setUseConfigureAffinity(false);

@ -347,7 +347,6 @@ void CSVRender::UnpagedWorldspaceWidget::addVisibilitySelectorButtons(CSVWidget:
{ {
WorldspaceWidget::addVisibilitySelectorButtons(tool); WorldspaceWidget::addVisibilitySelectorButtons(tool);
tool->addButton(Button_Terrain, Mask_Terrain, "Terrain", "", true); tool->addButton(Button_Terrain, Mask_Terrain, "Terrain", "", true);
tool->addButton(Button_Fog, Mask_Fog, "Fog");
} }
std::string CSVRender::UnpagedWorldspaceWidget::getStartupInstruction() std::string CSVRender::UnpagedWorldspaceWidget::getStartupInstruction()

@ -222,8 +222,7 @@ namespace CSVRender
Button_Reference = 0x1, Button_Reference = 0x1,
Button_Pathgrid = 0x2, Button_Pathgrid = 0x2,
Button_Water = 0x4, Button_Water = 0x4,
Button_Fog = 0x8, Button_Terrain = 0x8
Button_Terrain = 0x10
}; };
virtual void addVisibilitySelectorButtons(CSVWidget::SceneToolToggle2* tool); virtual void addVisibilitySelectorButtons(CSVWidget::SceneToolToggle2* tool);

@ -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) 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); addButton(button, id);
} }

@ -60,10 +60,10 @@ CSVWidget::ShapeBrushWindow::ShapeBrushWindow(CSMDoc::Document& document, QWidge
: QFrame(parent, Qt::Popup) : QFrame(parent, Qt::Popup)
, mDocument(document) , mDocument(document)
{ {
mButtonPoint = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-point")), "", this); mButtonPoint = new QPushButton(QIcon(":scenetoolbar/brush-point"), "", this);
mButtonSquare = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-square")), "", this); mButtonSquare = new QPushButton(QIcon(":scenetoolbar/brush-square"), "", this);
mButtonCircle = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-circle")), "", this); mButtonCircle = new QPushButton(QIcon(":scenetoolbar/brush-circle"), "", this);
mButtonCustom = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-custom")), "", this); mButtonCustom = new QPushButton(QIcon(":scenetoolbar/brush-custom"), "", this);
mSizeSliders = new ShapeBrushSizeControls("Brush size", this); mSizeSliders = new ShapeBrushSizeControls("Brush size", this);
@ -201,25 +201,25 @@ void CSVWidget::SceneToolShapeBrush::setButtonIcon(CSVWidget::BrushShape brushSh
{ {
case BrushShape_Point: case BrushShape_Point:
setIcon(QIcon(QPixmap(":scenetoolbar/brush-point"))); setIcon(QIcon(":scenetoolbar/brush-point"));
tooltip += mShapeBrushWindow->toolTipPoint; tooltip += mShapeBrushWindow->toolTipPoint;
break; break;
case BrushShape_Square: case BrushShape_Square:
setIcon(QIcon(QPixmap(":scenetoolbar/brush-square"))); setIcon(QIcon(":scenetoolbar/brush-square"));
tooltip += mShapeBrushWindow->toolTipSquare; tooltip += mShapeBrushWindow->toolTipSquare;
break; break;
case BrushShape_Circle: case BrushShape_Circle:
setIcon(QIcon(QPixmap(":scenetoolbar/brush-circle"))); setIcon(QIcon(":scenetoolbar/brush-circle"));
tooltip += mShapeBrushWindow->toolTipCircle; tooltip += mShapeBrushWindow->toolTipCircle;
break; break;
case BrushShape_Custom: case BrushShape_Custom:
setIcon(QIcon(QPixmap(":scenetoolbar/brush-custom"))); setIcon(QIcon(":scenetoolbar/brush-custom"));
tooltip += mShapeBrushWindow->toolTipCustom; tooltip += mShapeBrushWindow->toolTipCustom;
break; break;
} }

@ -90,10 +90,10 @@ CSVWidget::TextureBrushWindow::TextureBrushWindow(CSMDoc::Document& document, QW
mSelectedBrush = new QLabel(QString::fromStdString(mBrushTextureLabel)); mSelectedBrush = new QLabel(QString::fromStdString(mBrushTextureLabel));
} }
mButtonPoint = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-point")), "", this); mButtonPoint = new QPushButton(QIcon(":scenetoolbar/brush-point"), "", this);
mButtonSquare = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-square")), "", this); mButtonSquare = new QPushButton(QIcon(":scenetoolbar/brush-square"), "", this);
mButtonCircle = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-circle")), "", this); mButtonCircle = new QPushButton(QIcon(":scenetoolbar/brush-circle"), "", this);
mButtonCustom = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-custom")), "", this); mButtonCustom = new QPushButton(QIcon(":scenetoolbar/brush-custom"), "", this);
mSizeSliders = new BrushSizeControls("Brush size", this); mSizeSliders = new BrushSizeControls("Brush size", this);
@ -282,25 +282,25 @@ void CSVWidget::SceneToolTextureBrush::setButtonIcon(CSVWidget::BrushShape brush
{ {
case BrushShape_Point: case BrushShape_Point:
setIcon(QIcon(QPixmap(":scenetoolbar/brush-point"))); setIcon(QIcon(":scenetoolbar/brush-point"));
tooltip += mTextureBrushWindow->toolTipPoint; tooltip += mTextureBrushWindow->toolTipPoint;
break; break;
case BrushShape_Square: case BrushShape_Square:
setIcon(QIcon(QPixmap(":scenetoolbar/brush-square"))); setIcon(QIcon(":scenetoolbar/brush-square"));
tooltip += mTextureBrushWindow->toolTipSquare; tooltip += mTextureBrushWindow->toolTipSquare;
break; break;
case BrushShape_Circle: case BrushShape_Circle:
setIcon(QIcon(QPixmap(":scenetoolbar/brush-circle"))); setIcon(QIcon(":scenetoolbar/brush-circle"));
tooltip += mTextureBrushWindow->toolTipCircle; tooltip += mTextureBrushWindow->toolTipCircle;
break; break;
case BrushShape_Custom: case BrushShape_Custom:
setIcon(QIcon(QPixmap(":scenetoolbar/brush-custom"))); setIcon(QIcon(":scenetoolbar/brush-custom"));
tooltip += mTextureBrushWindow->toolTipCustom; tooltip += mTextureBrushWindow->toolTipCustom;
break; break;
} }

@ -88,7 +88,7 @@ void CSVWidget::SceneToolToggle2::addButton(
stream << mSingleIcon << id; stream << mSingleIcon << id;
PushButton* button = new PushButton( 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->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
button->setIconSize(QSize(mIconSize, mIconSize)); button->setIconSize(QSize(mIconSize, mIconSize));

@ -29,7 +29,7 @@ void CSVWorld::DragRecordTable::startDragFromTable(const CSVWorld::DragRecordTab
mime->setIndexAtDragStart(index); mime->setIndexAtDragStart(index);
QDrag* drag = new QDrag(this); QDrag* drag = new QDrag(this);
drag->setMimeData(mime); 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); drag->exec(Qt::CopyAction);
} }

@ -46,41 +46,41 @@ QWidget* CSVWorld::IdCompletionDelegate::createEditor(QWidget* parent, const QSt
switch (conditionFunction) switch (conditionFunction)
{ {
case CSMWorld::ConstInfoSelectWrapper::Function_Global: case ESM::DialogueCondition::Function_Global:
{ {
return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_GlobalVariable); 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); 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); return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Referenceable);
} }
case CSMWorld::ConstInfoSelectWrapper::Function_Dead: case ESM::DialogueCondition::Function_Dead:
case CSMWorld::ConstInfoSelectWrapper::Function_NotId: case ESM::DialogueCondition::Function_NotId:
{ {
return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Referenceable); 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); 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); 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); 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); return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Cell);
} }
case CSMWorld::ConstInfoSelectWrapper::Function_Local: case ESM::DialogueCondition::Function_Local:
case CSMWorld::ConstInfoSelectWrapper::Function_NotLocal: case ESM::DialogueCondition::Function_NotLocal:
{ {
return new CSVWidget::DropLineEdit(display, parent); return new CSVWidget::DropLineEdit(display, parent);
} }

@ -2,17 +2,6 @@
#include <components/misc/strings/lower.hpp> #include <components/misc/strings/lower.hpp>
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) CSVWorld::IdValidator::IdValidator(bool relaxed, QObject* parent)
: QValidator(parent) : QValidator(parent)
, mRelaxed(relaxed) , mRelaxed(relaxed)
@ -92,7 +81,7 @@ QValidator::State CSVWorld::IdValidator::validate(QString& input, int& pos) cons
{ {
prevScope = false; prevScope = false;
if (!isValid(*iter, first)) if (!iter->isPrint())
return QValidator::Invalid; return QValidator::Invalid;
} }
} }

@ -13,9 +13,6 @@ namespace CSVWorld
std::string mNamespace; std::string mNamespace;
mutable std::string mError; mutable std::string mError;
private:
bool isValid(const QChar& c, bool first) const;
public: public:
IdValidator(bool relaxed = false, QObject* parent = nullptr); IdValidator(bool relaxed = false, QObject* parent = nullptr);
///< \param relaxed Relaxed rules for IDs that also functino as user visible text ///< \param relaxed Relaxed rules for IDs that also functino as user visible text

@ -92,7 +92,7 @@ CSVWorld::RecordButtonBar::RecordButtonBar(const CSMWorld::UniversalId& id, CSMW
if (mTable.getFeatures() & CSMWorld::IdTable::Feature_View) if (mTable.getFeatures() & CSMWorld::IdTable::Feature_View)
{ {
QToolButton* viewButton = new QToolButton(this); 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"); viewButton->setToolTip("Open a scene view of the cell this record is located in");
buttonsLayout->addWidget(viewButton); buttonsLayout->addWidget(viewButton);
connect(viewButton, &QToolButton::clicked, this, &RecordButtonBar::viewRecord); connect(viewButton, &QToolButton::clicked, this, &RecordButtonBar::viewRecord);

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

Loading…
Cancel
Save