Merge branch 'feature/pplLauncherSetting' of git@gitlab.com:BPengu1n/openmw.git

i-have-no-land-and-i-must-scream
Benjamin Y 8 months ago
commit 89ad315f65

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

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

@ -33,6 +33,8 @@
Bug #5977: Fatigueless NPCs' corpse underwater changes animation on game load
Bug #6025: Subrecords cannot overlap records
Bug #6027: Collisionshape becomes spiderweb-like when the mesh is too complex
Bug #6146: Lua command `actor:setEquipment` doesn't trigger mwscripts when equipping or unequipping a scripted item
Bug #6156: 1ft Charm or Sound magic effect vfx doesn't work properly
Bug #6190: Unintuitive sun specularity time of day dependence
Bug #6222: global map cell size can crash openmw if set to too high a value
Bug #6313: Followers with high Fight can turn hostile
@ -44,9 +46,11 @@
Bug #6657: Distant terrain tiles become black when using FWIW mod
Bug #6661: Saved games that have no preview screenshot cause issues or crashes
Bug #6716: mwscript comparison operator handling is too restrictive
Bug #6723: "Turn to movement direction" makes the player rotate wildly with COLLADA
Bug #6754: Beast to Non-beast transformation mod is not working on OpenMW
Bug #6758: Main menu background video can be stopped by opening the options menu
Bug #6807: Ultimate Galleon is not working properly
Bug #6846: Launcher only works with default config paths
Bug #6893: Lua: Inconsistent behavior with actors affected by Disable and SetDelete commands
Bug #6894: Added item combines with equipped stack instead of creating a new unequipped stack
Bug #6932: Creatures flee from my followers and we have to chase after them
@ -56,6 +60,7 @@
Bug #6973: Fade in happens after the scene load and is shown
Bug #6974: Only harmful effects are reflected
Bug #6977: Sun damage implementation does not match research
Bug #6985: Issues with Magic Cards numbers readability
Bug #6986: Sound magic effect does not make noise
Bug #6987: Set/Mod Blindness should not darken the screen
Bug #6992: Crossbow reloading doesn't look the same as in Morrowind
@ -71,12 +76,15 @@
Bug #7084: Resurrecting an actor doesn't take into account base record changes
Bug #7088: Deleting last save game of last character doesn't clear character name/details
Bug #7092: BSA archives from higher priority directories don't take priority
Bug #7102: Some HQ Creatures mod models can hit the 8 texture slots limit with 0.48
Bug #7103: Multiple paths pointing to the same plugin but with different cases lead to automatically removed config entries
Bug #7122: Teleportation to underwater should cancel active water walking effect
Bug #7131: MyGUI log spam when post processing HUD is open
Bug #7134: Saves with an invalid last generated RefNum can be loaded
Bug #7163: Myar Aranath: Wheat breaks the GUI
Bug #7168: Fix average scene luminance
Bug #7172: Current music playlist continues playing indefinitely if next playlist is empty
Bug #7202: Post-processing normals for terrain, water randomly stop rendering
Bug #7204: Missing actor scripts freeze the game
Bug #7229: Error marker loading failure is not handled
Bug #7243: Supporting loading external files from VFS from esm files
@ -93,24 +101,29 @@
Bug #7415: Unbreakable lock discrepancies
Bug #7416: Modpccrimelevel is different from vanilla
Bug #7428: AutoCalc flag is not used to calculate enchantment costs
Bug #7447: OpenMW-CS: Dragging a cell of a different type (from the initial type) into the 3D view crashes OpenMW-CS
Bug #7450: Evading obstacles does not work for actors missing certain animations
Bug #7459: Icons get stacked on the cursor when picking up multiple items simultaneously
Bug #7472: Crash when enchanting last projectiles
Bug #7475: Equipping a constant effect item doesn't update the magic menu
Bug #7502: Data directories dialog (0.48.0) forces adding subdirectory instead of intended directory
Bug #7505: Distant terrain does not support sample size greater than cell size
Bug #7535: Bookart paths for textures in OpenMW vs vanilla Morrowind
Bug #7553: Faction reaction loading is incorrect
Bug #7557: Terrain::ChunkManager::createChunk is called twice for the same position, lod on initial loading
Bug #7573: Drain Fatigue can't bring fatigue below zero by default
Bug #7585: Difference in interior lighting between OpenMW with legacy lighting method enabled and vanilla Morrowind
Bug #7587: Quick load related crash
Bug #7603: Scripts menu size is not updated properly
Bug #7604: Goblins Grunt becomes idle once injured
Bug #7609: ForceGreeting should not open dialogue for werewolves
Bug #7611: Beast races' idle animations slide after turning or jumping in place
Bug #7617: The death prompt asks the player if they wanted to load the character's last created save
Bug #7619: Long map notes may get cut off
Bug #7623: Incorrect placement of the script info in the engraved ring of healing tooltip
Bug #7630: Charm can be cast on creatures
Bug #7631: Cannot trade with/talk to Creeper or Mudcrab Merchant when they're fleeing
Bug #7633: Groundcover should ignore non-geometry Drawables
Bug #7636: Animations bug out when switching between 1st and 3rd person, while playing a scripted animation
Bug #7637: Actors can sometimes move while playing scripted animations
Bug #7639: NPCs don't use hand-to-hand if their other melee skills were damaged during combat
@ -128,30 +141,51 @@
Bug #7679: Scene luminance value flashes when toggling shaders
Bug #7685: Corky sometimes doesn't follow Llovyn Andus
Bug #7712: Casting doesn't support spells and enchantments with no effects
Bug #7721: CS: Special Chars Not Allowed in IDs
Bug #7723: Assaulting vampires and werewolves shouldn't be a crime
Bug #7724: Guards don't help vs werewolves
Bug #7733: Launcher shows incorrect data paths when there's two plugins with the same name
Bug #7742: Governing attribute training limit should use the modified attribute
Bug #7753: Editor: Actors Don't Scale According to Their Race
Bug #7758: Water walking is not taken into account to compute path cost on the water
Bug #7761: Rain and ambient loop sounds are mutually exclusive
Bug #7763: Bullet shape loading problems, assorted
Bug #7765: OpenMW-CS: Touch Record option is broken
Bug #7769: Sword of the Perithia: Broken NPCs
Bug #7770: Sword of the Perithia: Script execution failure
Bug #7780: Non-ASCII texture paths in NIF files don't work
Bug #7785: OpenMW-CS initialising Skill and Attribute fields to 0 instead of -1 on non-FortifyStat spells
Bug #7794: Fleeing NPCs name tooltip doesn't appear
Bug #7796: Absorbed enchantments don't restore magicka
Bug #7823: Game crashes when launching it.
Bug #7832: Ingredient tooltips show magnitude for Fortify Maximum Magicka effect
Bug #7840: First run of the launcher doesn't save viewing distance as the default value
Bug #7841: Editor: "Dirty" water heights are saved in modified CELLs
Bug #7859: AutoCalc flag is not used to calculate potion value
Bug #7861: OpenMW-CS: Incorrect DIAL's type in INFO records
Bug #7872: Region sounds use wrong odds
Bug #7886: Equip and unequip animations can't share the animation track section
Bug #7887: Editor: Mismatched reported script data size and actual data size causes a crash during save
Bug #7898: Editor: Invalid reference scales are allowed
Bug #7899: Editor: Doors can't be unlocked
Bug #7901: Editor: Teleport-related fields shouldn't be editable if a ref does not teleport
Bug #7908: Key bindings names in the settings menu are layout-specific
Bug #7943: Using "addSoulGem" and "dropSoulGem" commands to creatures works only with "Weapon & Shield" flagged ones
Feature #2566: Handle NAM9 records for manual cell references
Feature #3537: Shader-based water ripples
Feature #5173: Support for NiFogProperty
Feature #5492: Let rain and snow collide with statics
Feature #6149: Dehardcode Lua API_REVISION
Feature #5926: Refraction based on water depth
Feature #5944: Option to use camera as sound listener
Feature #6152: Playing music via lua scripts
Feature #6188: Specular lighting from point light sources
Feature #6411: Support translations in openmw-launcher
Feature #6447: Add LOD support to Object Paging
Feature #6491: Add support for Qt6
Feature #6556: Lua API for sounds
Feature #6679: Design a custom Input Action API
Feature #6726: Lua API for creating new objects
Feature #6727: Lua API for records of all object types
Feature #6864: Lua file access API
Feature #6922: Improve launcher appearance
Feature #6933: Support high-resolution cursor textures
@ -164,16 +198,19 @@
Feature #7125: Remembering console commands between sessions
Feature #7129: Add support for non-adaptive VSync
Feature #7130: Ability to set MyGUI logging verbosity
Feature #7142: MWScript Lua API
Feature #7148: Optimize string literal lookup in mwscript
Feature #7161: OpenMW-CS: Make adding and filtering TopicInfos easier
Feature #7194: Ori to show texture paths
Feature #7214: Searching in the in-game console
Feature #7284: Searching in the console with regex and toggleable case-sensitivity
Feature #7248: Searching in the console with regex and toggleable case-sensitivity
Feature #7468: Factions API for Lua
Feature #7477: NegativeLight Magic Effect flag
Feature #7499: OpenMW-CS: Generate record filters by drag & dropping cell content to the filters field
Feature #7546: Start the game on Fredas
Feature #7554: Controller binding for tab for menu navigation
Feature #7568: Uninterruptable scripted music
Feature #7590: [Lua] Ability to deserialize YAML data from scripts
Feature #7606: Launcher: allow Shift-select in Archives tab
Feature #7608: Make the missing dependencies warning when loading a savegame more helpful
Feature #7618: Show the player character's health in the save details
@ -183,11 +220,20 @@
Feature #7652: Sort inactive post processing shaders list properly
Feature #7698: Implement sAbsorb, sDamage, sDrain, sFortify and sRestore
Feature #7709: Improve resolution selection in Launcher
Feature #7777: Support external Bethesda material files (BGSM/BGEM)
Feature #7792: Support Timescale Clouds
Feature #7795: Support MaxNumberRipples INI setting
Feature #7805: Lua Menu context
Feature #7860: Lua: Expose NPC AI settings (fight, alarm, flee)
Feature #7875: Disable MyGUI windows snapping
Feature #7914: Do not allow to move GUI windows out of screen
Feature #7923: Don't show non-existent higher ranks for factions with fewer than 9 ranks
Feature #7932: Support two-channel normal maps
Task #5896: Do not use deprecated MyGUI properties
Task #6085: Replace boost::filesystem with std::filesystem
Task #6149: Dehardcode Lua API_REVISION
Task #6624: Drop support for saves made prior to 0.45
Task #7048: Get rid of std::bind
Task #7113: Move from std::atoi to std::from_char
Task #7117: Replace boost::scoped_array with std::vector
Task #7151: Do not use std::strerror to get errno error message

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

@ -347,6 +347,26 @@ add_qt_style_dlls() {
QT_STYLES[$CONFIG]="${QT_STYLES[$CONFIG]} $@"
}
declare -A QT_IMAGEFORMATS
QT_IMAGEFORMATS["Release"]=""
QT_IMAGEFORMATS["Debug"]=""
QT_IMAGEFORMATS["RelWithDebInfo"]=""
add_qt_image_dlls() {
local CONFIG=$1
shift
QT_IMAGEFORMATS[$CONFIG]="${QT_IMAGEFORMATS[$CONFIG]} $@"
}
declare -A QT_ICONENGINES
QT_ICONENGINES["Release"]=""
QT_ICONENGINES["Debug"]=""
QT_ICONENGINES["RelWithDebInfo"]=""
add_qt_icon_dlls() {
local CONFIG=$1
shift
QT_ICONENGINES[$CONFIG]="${QT_ICONENGINES[$CONFIG]} $@"
}
if [ -z $PLATFORM ]; then
PLATFORM="$(uname -m)"
fi
@ -528,8 +548,12 @@ if ! [ -z $UNITY_BUILD ]; then
add_cmake_opts "-DOPENMW_UNITY_BUILD=True"
fi
if ! [ -z $USE_CCACHE ]; then
add_cmake_opts "-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache"
if [ -n "$USE_CCACHE" ]; then
if [ -n "$NMAKE" ] || [ -n "$NINJA" ]; then
add_cmake_opts "-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DPRECOMPILE_HEADERS_WITH_MSVC=OFF"
else
echo "Ignoring -C (CCache) as it is incompatible with Visual Studio CMake generators"
fi
fi
# turn on LTO by default
@ -549,7 +573,7 @@ ICU_VER="70_1"
LUAJIT_VER="v2.1.0-beta3-452-g7a0cf5fd"
LZ4_VER="1.9.2"
OPENAL_VER="1.23.0"
QT_VER="5.15.2"
QT_VER="6.6.2"
OSG_ARCHIVE_NAME="OSGoS 3.6.5"
OSG_ARCHIVE="OSGoS-3.6.5-123-g68c5c573d-msvc${OSG_MSVC_YEAR}-win${BITS}"
@ -880,7 +904,7 @@ printf "Qt ${QT_VER}... "
printf "Exists. "
elif [ -z $SKIP_EXTRACT ]; then
pushd "$DEPS" > /dev/null
AQT_VERSION="v3.1.7"
AQT_VERSION="v3.1.12"
if ! [ -f "aqt_x64-${AQT_VERSION}.exe" ]; then
download "aqt ${AQT_VERSION}"\
"https://github.com/miurahr/aqtinstall/releases/download/${AQT_VERSION}/aqt_x64.exe" \
@ -901,6 +925,9 @@ printf "Qt ${QT_VER}... "
echo Done.
fi
QT_MAJOR_VER=$(echo "${QT_VER}" | awk -F '[.]' '{printf "%d", $1}')
QT_MINOR_VER=$(echo "${QT_VER}" | awk -F '[.]' '{printf "%d", $2}')
cd $QT_SDK
for CONFIGURATION in ${CONFIGURATIONS[@]}; do
if [ $CONFIGURATION == "Debug" ]; then
@ -908,13 +935,24 @@ printf "Qt ${QT_VER}... "
else
DLLSUFFIX=""
fi
if [ "${QT_VER:0:1}" -eq "6" ]; then
add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt${QT_VER:0:1}"{Core,Gui,Network,OpenGL,OpenGLWidgets,Widgets}${DLLSUFFIX}.dll
if [ "${QT_MAJOR_VER}" -eq 6 ]; then
add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt${QT_MAJOR_VER}"{Core,Gui,Network,OpenGL,OpenGLWidgets,Widgets,Svg}${DLLSUFFIX}.dll
# Since Qt 6.7.0 plugin is called "qmodernwindowsstyle"
if [ "${QT_MINOR_VER}" -ge 7 ]; then
add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qmodernwindowsstyle${DLLSUFFIX}.dll"
else
add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll"
fi
else
add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt${QT_VER:0:1}"{Core,Gui,Network,OpenGL,Widgets}${DLLSUFFIX}.dll
add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt${QT_MAJOR_VER}"{Core,Gui,Network,OpenGL,Widgets,Svg}${DLLSUFFIX}.dll
add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll"
fi
add_qt_platform_dlls $CONFIGURATION "$(pwd)/plugins/platforms/qwindows${DLLSUFFIX}.dll"
add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll"
add_qt_image_dlls $CONFIGURATION "$(pwd)/plugins/imageformats/qsvg${DLLSUFFIX}.dll"
add_qt_icon_dlls $CONFIGURATION "$(pwd)/plugins/iconengines/qsvgicon${DLLSUFFIX}.dll"
done
echo Done.
}
@ -1109,6 +1147,20 @@ fi
cp "$DLL" "${DLL_PREFIX}styles"
done
echo
echo "- Qt Image Format DLLs..."
mkdir -p ${DLL_PREFIX}imageformats
for DLL in ${QT_IMAGEFORMATS[$CONFIGURATION]}; do
echo " $(basename $DLL)"
cp "$DLL" "${DLL_PREFIX}imageformats"
done
echo
echo "- Qt Icon Engine DLLs..."
mkdir -p ${DLL_PREFIX}iconengines
for DLL in ${QT_ICONENGINES[$CONFIGURATION]}; do
echo " $(basename $DLL)"
cp "$DLL" "${DLL_PREFIX}iconengines"
done
echo
done
#fi

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

@ -20,6 +20,7 @@ apps/openmw_test_suite/lua/test_storage.cpp
apps/openmw_test_suite/lua/test_ui_content.cpp
apps/openmw_test_suite/lua/test_utilpackage.cpp
apps/openmw_test_suite/lua/test_inputactions.cpp
apps/openmw_test_suite/lua/test_yaml.cpp
apps/openmw_test_suite/misc/test_endianness.cpp
apps/openmw_test_suite/misc/test_resourcehelpers.cpp
apps/openmw_test_suite/misc/test_stringops.cpp

@ -36,7 +36,7 @@ declare -rA GROUPED_DEPS=(
libsdl2-dev libqt5opengl5-dev qttools5-dev qttools5-dev-tools libopenal-dev
libunshield-dev libtinyxml-dev libbullet-dev liblz4-dev libpng-dev libjpeg-dev
libluajit-5.1-dev librecast-dev libsqlite3-dev ca-certificates libicu-dev
libyaml-cpp-dev
libyaml-cpp-dev libqt5svg5 libqt5svg5-dev
"
# These dependencies can alternatively be built and linked statically.

@ -19,6 +19,14 @@ if(OPENMW_GL4ES_MANUAL_INIT)
add_definitions(-DOPENMW_GL4ES_MANUAL_INIT)
endif()
if (APPLE OR WIN32)
set(DEPLOY_QT_TRANSLATIONS_DEFAULT ON)
else ()
set(DEPLOY_QT_TRANSLATIONS_DEFAULT OFF)
endif ()
option(DEPLOY_QT_TRANSLATIONS "Deploy standard Qt translations to resources folder. Needed when OpenMW applications are deployed with Qt libraries" ${DEPLOY_QT_TRANSLATIONS_DEFAULT})
# Apps and tools
option(BUILD_OPENMW "Build OpenMW" ON)
option(BUILD_LAUNCHER "Build Launcher" ON)
@ -36,6 +44,7 @@ option(BUILD_BENCHMARKS "Build benchmarks with Google Benchmark" OFF)
option(BUILD_NAVMESHTOOL "Build navmesh tool" ON)
option(BUILD_BULLETOBJECTTOOL "Build Bullet object tool" ON)
option(BUILD_OPENCS_TESTS "Build OpenMW Construction Set tests" OFF)
option(PRECOMPILE_HEADERS_WITH_MSVC "Precompile most common used headers with MSVC (alternative to ccache)" ON)
set(OpenGL_GL_PREFERENCE LEGACY) # Use LEGACY as we use GL2; GLNVD is for GL3 and up.
@ -72,7 +81,7 @@ message(STATUS "Configuring OpenMW...")
set(OPENMW_VERSION_MAJOR 0)
set(OPENMW_VERSION_MINOR 49)
set(OPENMW_VERSION_RELEASE 0)
set(OPENMW_LUA_API_REVISION 53)
set(OPENMW_LUA_API_REVISION 59)
set(OPENMW_POSTPROCESSING_API_REVISION 1)
set(OPENMW_VERSION_COMMITHASH "")
@ -81,7 +90,7 @@ set(OPENMW_VERSION_COMMITDATE "")
set(OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}")
set(OPENMW_DOC_BASEURL "https://openmw.readthedocs.io/en/stable/")
set(OPENMW_DOC_BASEURL "https://openmw.readthedocs.io/en/")
set(GIT_CHECKOUT FALSE)
if(EXISTS ${PROJECT_SOURCE_DIR}/.git)
@ -182,6 +191,22 @@ if (MSVC)
add_compile_options(/bigobj)
add_compile_options(/Zc:__cplusplus)
if (CMAKE_CXX_COMPILER_LAUNCHER OR CMAKE_C_COMPILER_LAUNCHER)
if (CMAKE_GENERATOR MATCHES "Visual Studio")
message(STATUS "A compiler launcher was specified, but will be unused by the current generator (${CMAKE_GENERATOR})")
else()
foreach (config_lower ${CMAKE_CONFIGURATION_TYPES})
string(TOUPPER "${config_lower}" config)
if (CMAKE_C_COMPILER_LAUNCHER STREQUAL "ccache")
string(REPLACE "/Zi" "/Z7" CMAKE_C_FLAGS_${config} "${CMAKE_C_FLAGS_${config}}")
endif()
if (CMAKE_CXX_COMPILER_LAUNCHER STREQUAL "ccache")
string(REPLACE "/Zi" "/Z7" CMAKE_CXX_FLAGS_${config} "${CMAKE_CXX_FLAGS_${config}}")
endif()
endforeach()
endif()
endif()
endif()
# Set up common paths
@ -209,7 +234,7 @@ else()
endif(APPLE)
if (WIN32)
option(USE_DEBUG_CONSOLE "whether a debug console should be enabled for debug builds, if false debug output is redirected to Visual Studio output" ON)
option(USE_DEBUG_CONSOLE "Whether a console should be displayed if OpenMW isn't launched from the command line. Does not affect the Release configuration." ON)
endif()
if(MSVC)
@ -224,9 +249,9 @@ find_package(LZ4 REQUIRED)
if (USE_QT)
find_package(QT REQUIRED COMPONENTS Core NAMES Qt6 Qt5)
if (QT_VERSION_MAJOR VERSION_EQUAL 5)
find_package(Qt5 5.15 COMPONENTS Core Widgets Network OpenGL LinguistTools REQUIRED)
find_package(Qt5 5.15 COMPONENTS Core Widgets Network OpenGL LinguistTools Svg REQUIRED)
else()
find_package(Qt6 COMPONENTS Core Widgets Network OpenGL OpenGLWidgets LinguistTools REQUIRED)
find_package(Qt6 COMPONENTS Core Widgets Network OpenGL OpenGLWidgets LinguistTools Svg REQUIRED)
endif()
message(STATUS "Using Qt${QT_VERSION}")
endif()
@ -685,9 +710,8 @@ if (WIN32)
if (USE_DEBUG_CONSOLE AND BUILD_OPENMW)
set_target_properties(openmw PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE")
set_target_properties(openmw PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:CONSOLE")
set_target_properties(openmw PROPERTIES COMPILE_DEFINITIONS $<$<CONFIG:Debug>:_CONSOLE>)
elseif (BUILD_OPENMW)
# Turn off debug console, debug output will be written to visual studio output instead
# Turn off implicit console, you won't see stdout unless launching OpenMW from a command line shell or look at openmw.log
set_target_properties(openmw PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:WINDOWS")
set_target_properties(openmw PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:WINDOWS")
endif()
@ -710,67 +734,66 @@ if (WIN32)
)
foreach(d ${WARNINGS_DISABLE})
set(WARNINGS "${WARNINGS} /wd${d}")
list(APPEND WARNINGS "/wd${d}")
endforeach(d)
if(OPENMW_MSVC_WERROR)
set(WARNINGS "${WARNINGS} /WX")
list(APPEND WARNINGS "/WX")
endif()
set_target_properties(components PROPERTIES COMPILE_FLAGS "${WARNINGS}")
set_target_properties(osg-ffmpeg-videoplayer PROPERTIES COMPILE_FLAGS "${WARNINGS}")
target_compile_options(components PRIVATE ${WARNINGS})
target_compile_options(osg-ffmpeg-videoplayer PRIVATE ${WARNINGS})
if (MSVC_VERSION GREATER_EQUAL 1915 AND MSVC_VERSION LESS 1920)
target_compile_definitions(components INTERFACE _ENABLE_EXTENDED_ALIGNED_STORAGE)
endif()
if (BUILD_BSATOOL)
set_target_properties(bsatool PROPERTIES COMPILE_FLAGS "${WARNINGS}")
target_compile_options(bsatool PRIVATE ${WARNINGS})
endif()
if (BUILD_ESMTOOL)
set_target_properties(esmtool PROPERTIES COMPILE_FLAGS "${WARNINGS}")
target_compile_options(esmtool PRIVATE ${WARNINGS})
endif()
if (BUILD_ESSIMPORTER)
set_target_properties(openmw-essimporter PROPERTIES COMPILE_FLAGS "${WARNINGS}")
target_compile_options(openmw-essimporter PRIVATE ${WARNINGS})
endif()
if (BUILD_LAUNCHER)
set_target_properties(openmw-launcher PROPERTIES COMPILE_FLAGS "${WARNINGS}")
target_compile_options(openmw-launcher PRIVATE ${WARNINGS})
endif()
if (BUILD_MWINIIMPORTER)
set_target_properties(openmw-iniimporter PROPERTIES COMPILE_FLAGS "${WARNINGS}")
target_compile_options(openmw-iniimporter PRIVATE ${WARNINGS})
endif()
if (BUILD_OPENCS)
set_target_properties(openmw-cs PROPERTIES COMPILE_FLAGS "${WARNINGS}")
target_compile_options(openmw-cs PRIVATE ${WARNINGS})
endif()
if (BUILD_OPENMW)
set_target_properties(openmw PROPERTIES COMPILE_FLAGS "${WARNINGS}")
target_compile_options(openmw PRIVATE ${WARNINGS})
endif()
if (BUILD_WIZARD)
set_target_properties(openmw-wizard PROPERTIES COMPILE_FLAGS "${WARNINGS}")
target_compile_options(openmw-wizard PRIVATE ${WARNINGS})
endif()
if (BUILD_UNITTESTS)
set_target_properties(openmw_test_suite PROPERTIES COMPILE_FLAGS "${WARNINGS}")
target_compile_options(openmw_test_suite PRIVATE ${WARNINGS})
endif()
if (BUILD_BENCHMARKS)
set_target_properties(openmw_detournavigator_navmeshtilescache_benchmark PROPERTIES COMPILE_FLAGS "${WARNINGS}")
target_compile_options(openmw_detournavigator_navmeshtilescache_benchmark PRIVATE ${WARNINGS})
endif()
if (BUILD_NAVMESHTOOL)
set_target_properties(openmw-navmeshtool PROPERTIES COMPILE_FLAGS "${WARNINGS}")
target_compile_options(openmw-navmeshtool PRIVATE ${WARNINGS})
endif()
if (BUILD_BULLETOBJECTTOOL)
set(WARNINGS "${WARNINGS} ${MT_BUILD}")
set_target_properties(openmw-bulletobjecttool PROPERTIES COMPILE_FLAGS "${WARNINGS}")
target_compile_options(openmw-bulletobjecttool PRIVATE ${WARNINGS} ${MT_BUILD})
endif()
endif(MSVC)
@ -802,6 +825,18 @@ if (OPENMW_OSX_DEPLOYMENT AND APPLE)
get_filename_component(QT_QMACSTYLE_PLUGIN_NAME "${QT_QMACSTYLE_PLUGIN_PATH}" NAME)
configure_file("${QT_QMACSTYLE_PLUGIN_PATH}" "${APP_BUNDLE_DIR}/Contents/PlugIns/${QT_QMACSTYLE_PLUGIN_GROUP}/${QT_QMACSTYLE_PLUGIN_NAME}" COPYONLY)
get_property(QT_QSVG_PLUGIN_PATH TARGET Qt${QT_VERSION_MAJOR}::QSvgPlugin PROPERTY LOCATION_RELEASE)
get_filename_component(QT_QSVG_PLUGIN_DIR "${QT_QSVG_PLUGIN_PATH}" DIRECTORY)
get_filename_component(QT_QSVG_PLUGIN_GROUP "${QT_QSVG_PLUGIN_DIR}" NAME)
get_filename_component(QT_QSVG_PLUGIN_NAME "${QT_QSVG_PLUGIN_PATH}" NAME)
configure_file("${QT_QSVG_PLUGIN_PATH}" "${APP_BUNDLE_DIR}/Contents/PlugIns/${QT_QSVG_PLUGIN_GROUP}/${QT_QSVG_PLUGIN_NAME}" COPYONLY)
get_property(QT_QSVG_ICON_PLUGIN_PATH TARGET Qt${QT_VERSION_MAJOR}::QSvgIconPlugin PROPERTY LOCATION_RELEASE)
get_filename_component(QT_QSVG_ICON_PLUGIN_DIR "${QT_QSVG_ICON_PLUGIN_PATH}" DIRECTORY)
get_filename_component(QT_QSVG_ICON_PLUGIN_GROUP "${QT_QSVG_ICON_PLUGIN_DIR}" NAME)
get_filename_component(QT_QSVG_ICON_PLUGIN_NAME "${QT_QSVG_ICON_PLUGIN_PATH}" NAME)
configure_file("${QT_QSVG_ICON_PLUGIN_PATH}" "${APP_BUNDLE_DIR}/Contents/PlugIns/${QT_QSVG_ICON_PLUGIN_GROUP}/${QT_QSVG_ICON_PLUGIN_NAME}" COPYONLY)
configure_file("${OpenMW_SOURCE_DIR}/files/mac/qt.conf" "${APP_BUNDLE_DIR}/Contents/Resources/qt.conf" COPYONLY)
if (BUILD_OPENCS)
@ -809,6 +844,8 @@ if (OPENMW_OSX_DEPLOYMENT AND APPLE)
set(OPENCS_BUNDLE_NAME "${OPENCS_BUNDLE_NAME_TMP}.app")
configure_file("${QT_COCOA_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/PlugIns/${QT_COCOA_PLUGIN_GROUP}/${QT_COCOA_PLUGIN_NAME}" COPYONLY)
configure_file("${QT_QMACSTYLE_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/PlugIns/${QT_QMACSTYLE_PLUGIN_GROUP}/${QT_QMACSTYLE_PLUGIN_NAME}" COPYONLY)
configure_file("${QT_QSVG_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/PlugIns/${QT_QSVG_PLUGIN_GROUP}/${QT_QSVG_PLUGIN_NAME}" COPYONLY)
configure_file("${QT_QSVG_ICON_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/PlugIns/${QT_QSVG_ICON_PLUGIN_GROUP}/${QT_QSVG_ICON_PLUGIN_NAME}" COPYONLY)
configure_file("${OpenMW_SOURCE_DIR}/files/mac/qt.conf" "${OPENCS_BUNDLE_NAME}/Contents/Resources/qt.conf" COPYONLY)
endif ()
@ -1082,30 +1119,30 @@ if (USE_QT)
file(GLOB COMPONENTS_TS_FILES ${CMAKE_SOURCE_DIR}/files/lang/components_*.ts)
get_target_property(QT_LUPDATE_EXECUTABLE Qt::lupdate IMPORTED_LOCATION)
add_custom_target(translations
COMMAND ${QT_LUPDATE_EXECUTABLE} ${CMAKE_SOURCE_DIR}/components/contentselector ${CMAKE_SOURCE_DIR}/components/process -ts ${COMPONENTS_TS_FILES}
COMMAND ${QT_LUPDATE_EXECUTABLE} -locations none ${CMAKE_SOURCE_DIR}/components/contentselector ${CMAKE_SOURCE_DIR}/components/process -ts ${COMPONENTS_TS_FILES}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/components
VERBATIM
COMMAND_EXPAND_LISTS
COMMAND ${QT_LUPDATE_EXECUTABLE} ${CMAKE_SOURCE_DIR}/apps/wizard -ts ${WIZARD_TS_FILES}
COMMAND ${QT_LUPDATE_EXECUTABLE} -locations none ${CMAKE_SOURCE_DIR}/apps/wizard -ts ${WIZARD_TS_FILES}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/apps/wizard
VERBATIM
COMMAND_EXPAND_LISTS
COMMAND ${QT_LUPDATE_EXECUTABLE} ${CMAKE_SOURCE_DIR}/apps/launcher -ts ${LAUNCHER_TS_FILES}
COMMAND ${QT_LUPDATE_EXECUTABLE} -locations none ${CMAKE_SOURCE_DIR}/apps/launcher -ts ${LAUNCHER_TS_FILES}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/apps/launcher
VERBATIM
COMMAND_EXPAND_LISTS)
if (BUILD_LAUNCHER OR BUILD_WIZARD)
if (APPLE)
set(QT_TRANSLATIONS_PATH "${APP_BUNDLE_DIR}/Contents/Resources/resources/translations")
set(QT_OPENMW_TRANSLATIONS_PATH "${APP_BUNDLE_DIR}/Contents/Resources/resources/translations")
else ()
get_generator_is_multi_config(multi_config)
if (multi_config)
set(QT_TRANSLATIONS_PATH "${OpenMW_BINARY_DIR}/$<CONFIG>/resources/translations")
set(QT_OPENMW_TRANSLATIONS_PATH "${OpenMW_BINARY_DIR}/$<CONFIG>/resources/translations")
else ()
set(QT_TRANSLATIONS_PATH "${OpenMW_BINARY_DIR}/resources/translations")
set(QT_OPENMW_TRANSLATIONS_PATH "${OpenMW_BINARY_DIR}/resources/translations")
endif ()
endif ()
@ -1121,9 +1158,30 @@ if (USE_QT)
qt_add_translation(QM_FILES ${TS_FILES} OPTIONS -silent)
if (DEPLOY_QT_TRANSLATIONS)
# Once we set a Qt 6.2.0 as a minimum required version, we may use "qtpaths --qt-query" instead.
get_target_property(QT_QMAKE_EXECUTABLE Qt::qmake IMPORTED_LOCATION)
execute_process(COMMAND "${QT_QMAKE_EXECUTABLE}" -query QT_INSTALL_TRANSLATIONS
OUTPUT_VARIABLE QT_TRANSLATIONS_DIR OUTPUT_STRIP_TRAILING_WHITESPACE)
foreach(QM_FILE ${QM_FILES})
get_filename_component(QM_BASENAME ${QM_FILE} NAME)
string(REGEX REPLACE "[^_]+_(.*)\\.qm" "\\1" LANG_NAME ${QM_BASENAME})
if (EXISTS "${QT_TRANSLATIONS_DIR}/qtbase_${LANG_NAME}.qm")
set(QM_FILES ${QM_FILES} "${QT_TRANSLATIONS_DIR}/qtbase_${LANG_NAME}.qm")
elseif (EXISTS "${QT_TRANSLATIONS_DIR}/qt_${LANG_NAME}.qm")
set(QM_FILES ${QM_FILES} "${QT_TRANSLATIONS_DIR}/qt_${LANG_NAME}.qm")
else ()
message(FATAL_ERROR "Qt translations for '${LANG_NAME}' locale are not found in the '${QT_TRANSLATIONS_DIR}' folder.")
endif ()
endforeach(QM_FILE)
list(REMOVE_DUPLICATES QM_FILES)
endif ()
add_custom_target(qm-files
COMMAND ${CMAKE_COMMAND} -E make_directory ${QT_TRANSLATIONS_PATH}
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${QM_FILES} ${QT_TRANSLATIONS_PATH}
COMMAND ${CMAKE_COMMAND} -E make_directory ${QT_OPENMW_TRANSLATIONS_PATH}
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${QM_FILES} ${QT_OPENMW_TRANSLATIONS_PATH}
DEPENDS ${QM_FILES}
COMMENT "Copy *.qm files to resources folder")
endif ()

@ -20,7 +20,7 @@ Font Licenses:
Current Status
--------------
The main quests in Morrowind, Tribunal and Bloodmoon are all completable. Some issues with side quests are to be expected (but rare). Check the [bug tracker](https://gitlab.com/OpenMW/openmw/issues?label_name%5B%5D=1.0) for a list of issues we need to resolve before the "1.0" release. Even before the "1.0" release however, OpenMW boasts some new [features](https://wiki.openmw.org/index.php?title=Features), such as improved graphics and user interfaces.
The main quests in Morrowind, Tribunal and Bloodmoon are all completable. Some issues with side quests are to be expected (but rare). Check the [bug tracker](https://gitlab.com/OpenMW/openmw/-/issues/?milestone_title=openmw-1.0) for a list of issues we need to resolve before the "1.0" release. Even before the "1.0" release however, OpenMW boasts some new [features](https://wiki.openmw.org/index.php?title=Features), such as improved graphics and user interfaces.
Pre-existing modifications created for the original Morrowind engine can be hit-and-miss. The OpenMW script compiler performs more thorough error-checking than Morrowind does, meaning that a mod created for Morrowind may not necessarily run in OpenMW. Some mods also rely on quirky behaviour or engine bugs in order to work. We are considering such compatibility issues on a case-by-case basis - in some cases adding a workaround to OpenMW may be feasible, in other cases fixing the mod will be the only option. If you know of any mods that work or don't work, feel free to add them to the [Mod status](https://wiki.openmw.org/index.php?title=Mod_status) wiki page.

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

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

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

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

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

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

@ -329,17 +329,19 @@ int main(int argc, char** argv)
switch (bsaVersion)
{
case Bsa::BSAVER_COMPRESSED:
case Bsa::BsaVersion::Unknown:
break;
case Bsa::BsaVersion::Uncompressed:
return call<Bsa::BSAFile>(info);
case Bsa::BsaVersion::Compressed:
return call<Bsa::CompressedBSAFile>(info);
case Bsa::BSAVER_BA2_GNRL:
case Bsa::BsaVersion::BA2GNRL:
return call<Bsa::BA2GNRLFile>(info);
case Bsa::BSAVER_BA2_DX10:
case Bsa::BsaVersion::BA2DX10:
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)
{

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

@ -12,6 +12,7 @@
#include <components/files/multidircollection.hpp>
#include <components/misc/strings/conversion.hpp>
#include <components/platform/platform.hpp>
#include <components/resource/bgsmfilemanager.hpp>
#include <components/resource/bulletshape.hpp>
#include <components/resource/bulletshapemanager.hpp>
#include <components/resource/foreachbulletobject.hpp>
@ -146,7 +147,9 @@ namespace
dataDirs.insert(dataDirs.begin(), resDir / "vfs");
const Files::Collections fileCollections(dataDirs);
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);
@ -171,7 +174,8 @@ namespace
constexpr double expiryDelay = 0;
Resource::ImageManager imageManager(&vfs, expiryDelay);
Resource::NifFileManager nifFileManager(&vfs, &encoder.getStatelessEncoder());
Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, expiryDelay);
Resource::BgsmFileManager bgsmFileManager(&vfs, expiryDelay);
Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, &bgsmFileManager, expiryDelay);
Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay);
Resource::forEachBulletObject(

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

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

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

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

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

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

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

@ -135,7 +135,7 @@ namespace ESSImport
sub.mFileOffset = esm.getFileOffset();
sub.mName = esm.retSubName().toString();
sub.mData.resize(esm.getSubSize());
esm.getExact(&sub.mData[0], sub.mData.size());
esm.getExact(sub.mData.data(), sub.mData.size());
rec.mSubrecords.push_back(sub);
}
file.mRecords.push_back(rec);

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

@ -142,7 +142,7 @@ Launcher::DataFilesPage::DataFilesPage(const Files::ConfigurationManager& cfg, C
ui.setupUi(this);
setObjectName("DataFilesPage");
mSelector = new ContentSelectorView::ContentSelector(ui.contentSelectorWidget, /*showOMWScripts=*/true);
const QString encoding = mGameSettings.value("encoding", "win1252");
const QString encoding = mGameSettings.value("encoding", { "win1252" }).value;
mSelector->setEncoding(encoding);
QVector<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.directoryUpButton, &QPushButton::released, this, [this]() { this->moveDirectory(-1); });
connect(ui.directoryDownButton, &QPushButton::released, this, [this]() { this->moveDirectory(1); });
connect(ui.directoryRemoveButton, &QPushButton::released, this, [this]() { this->removeDirectory(); });
connect(ui.directoryRemoveButton, &QPushButton::released, this, &DataFilesPage::removeDirectory);
connect(ui.archiveUpButton, &QPushButton::released, this, [this]() { this->moveArchives(-1); });
connect(ui.archiveDownButton, &QPushButton::released, this, [this]() { this->moveArchives(1); });
connect(
ui.directoryListWidget->model(), &QAbstractItemModel::rowsMoved, this, [this]() { this->sortDirectories(); });
connect(ui.directoryListWidget->model(), &QAbstractItemModel::rowsMoved, this, &DataFilesPage::sortDirectories);
connect(ui.archiveListWidget->model(), &QAbstractItemModel::rowsMoved, this, &DataFilesPage::sortArchives);
buildView();
loadSettings();
@ -271,65 +271,79 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
ui.archiveListWidget->clear();
ui.directoryListWidget->clear();
QStringList directories = mLauncherSettings.getDataDirectoryList(contentModelName);
if (directories.isEmpty())
directories = mGameSettings.getDataDirs();
QList<Config::SettingValue> directories = mGameSettings.getDataDirs();
QStringList contentModelDirectories = mLauncherSettings.getDataDirectoryList(contentModelName);
if (!contentModelDirectories.isEmpty())
{
directories.erase(std::remove_if(directories.begin(), directories.end(),
[&](const Config::SettingValue& dir) { return mGameSettings.isUserSetting(dir); }),
directories.end());
for (const auto& dir : contentModelDirectories)
directories.push_back({ dir });
}
mDataLocal = mGameSettings.getDataLocal();
if (!mDataLocal.isEmpty())
directories.insert(0, mDataLocal);
directories.insert(0, { mDataLocal });
const auto& globalDataDir = mGameSettings.getGlobalDataDir();
if (!globalDataDir.empty())
directories.insert(0, Files::pathToQString(globalDataDir));
const auto& resourcesVfs = mGameSettings.getResourcesVfs();
if (!resourcesVfs.isEmpty())
directories.insert(0, { resourcesVfs });
std::unordered_set<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
const QString canonicalDirPath = QDir(QDir::cleanPath(currentDir)).canonicalPath();
if (!visitedDirectories.insert(canonicalDirPath).second)
if (!visitedDirectories.insert(currentDir.value).second)
continue;
// add new achives files presents in current directory
addArchivesFromDir(currentDir);
addArchivesFromDir(currentDir.value);
QString tooltip;
QStringList tooltip;
// add content files presents in current directory
mSelector->addFiles(currentDir, mNewDataDirs.contains(canonicalDirPath));
mSelector->addFiles(currentDir.value, mNewDataDirs.contains(currentDir.value));
// add current directory to list
ui.directoryListWidget->addItem(currentDir);
ui.directoryListWidget->addItem(currentDir.originalRepresentation);
auto row = ui.directoryListWidget->count() - 1;
auto* item = ui.directoryListWidget->item(row);
item->setData(Qt::UserRole, QVariant::fromValue(currentDir));
if (currentDir.value != currentDir.originalRepresentation)
tooltip << tr("Resolved as %1").arg(currentDir.value);
// Display new content with custom formatting
if (mNewDataDirs.contains(canonicalDirPath))
if (mNewDataDirs.contains(currentDir.value))
{
tooltip += tr("Will be added to the current profile");
tooltip << tr("Will be added to the current profile");
QFont font = item->font();
font.setBold(true);
font.setItalic(true);
item->setFont(font);
}
// deactivate data-local and global data directory: they are always included
if (currentDir == mDataLocal || Files::pathFromQString(currentDir) == globalDataDir)
// deactivate data-local and resources/vfs: they are always included
// same for ones from non-user config files
if (currentDir.value == mDataLocal || currentDir.value == resourcesVfs
|| !mGameSettings.isUserSetting(currentDir))
{
auto flags = item->flags();
item->setFlags(flags & ~(Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled));
if (currentDir.value == mDataLocal)
tooltip << tr("This is the data-local directory and cannot be disabled");
else if (currentDir.value == resourcesVfs)
tooltip << tr("This directory is part of OpenMW and cannot be disabled");
else
tooltip << tr("This directory is enabled in an openmw.cfg other than the user one");
}
// Add a "data file" icon if the directory contains a content file
if (mSelector->containsDataFiles(currentDir))
if (mSelector->containsDataFiles(currentDir.value))
{
item->setIcon(QIcon(":/images/openmw-plugin.png"));
if (!tooltip.isEmpty())
tooltip += "\n";
tooltip += tr("Contains content file(s)");
tooltip << tr("Contains content file(s)");
}
else
{
@ -339,19 +353,26 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
auto emptyIcon = QIcon(pixmap);
item->setIcon(emptyIcon);
}
item->setToolTip(tooltip);
item->setToolTip(tooltip.join('\n'));
}
mSelector->sortFiles();
QStringList selectedArchives = mLauncherSettings.getArchiveList(contentModelName);
if (selectedArchives.isEmpty())
selectedArchives = mGameSettings.getArchiveList();
QList<Config::SettingValue> selectedArchives = mGameSettings.getArchiveList();
QStringList contentModelSelectedArchives = mLauncherSettings.getArchiveList(contentModelName);
if (contentModelSelectedArchives.isEmpty())
{
selectedArchives.erase(std::remove_if(selectedArchives.begin(), selectedArchives.end(),
[&](const Config::SettingValue& dir) { return mGameSettings.isUserSetting(dir); }),
selectedArchives.end());
for (const auto& dir : contentModelSelectedArchives)
selectedArchives.push_back({ dir });
}
// sort and tick BSA according to profile
int row = 0;
for (const auto& archive : selectedArchives)
{
const auto match = ui.archiveListWidget->findItems(archive, Qt::MatchExactly);
const auto match = ui.archiveListWidget->findItems(archive.value, Qt::MatchExactly);
if (match.isEmpty())
continue;
const auto name = match[0]->text();
@ -359,9 +380,25 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
ui.archiveListWidget->takeItem(oldrow);
ui.archiveListWidget->insertItem(row, name);
ui.archiveListWidget->item(row)->setCheckState(Qt::Checked);
ui.archiveListWidget->item(row)->setData(Qt::UserRole, QVariant::fromValue(archive));
if (!mGameSettings.isUserSetting(archive))
{
auto flags = ui.archiveListWidget->item(row)->flags();
ui.archiveListWidget->item(row)->setFlags(
flags & ~(Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled));
ui.archiveListWidget->item(row)->setToolTip(
tr("This archive is enabled in an openmw.cfg other than the user one"));
}
row++;
}
QStringList nonUserContent;
for (const auto& content : mGameSettings.getContentList())
{
if (!mGameSettings.isUserSetting(content))
nonUserContent.push_back(content.value);
}
mSelector->setNonUserContent(nonUserContent);
mSelector->setProfileContent(mLauncherSettings.getContentListFiles(contentModelName));
}
@ -389,7 +426,19 @@ void Launcher::DataFilesPage::saveSettings(const QString& profile)
{
fileNames.append(item->fileName());
}
mLauncherSettings.setContentList(profileName, dirList, selectedArchivePaths(), fileNames);
QStringList dirNames;
for (const auto& dir : dirList)
{
if (mGameSettings.isUserSetting(dir))
dirNames.push_back(dir.originalRepresentation);
}
QStringList archiveNames;
for (const auto& archive : selectedArchivePaths())
{
if (mGameSettings.isUserSetting(archive))
archiveNames.push_back(archive.originalRepresentation);
}
mLauncherSettings.setContentList(profileName, dirNames, archiveNames, fileNames);
mGameSettings.setContentList(dirList, selectedArchivePaths(), fileNames);
QString language(mSelector->languageBox()->currentData().toString());
@ -398,38 +447,38 @@ void Launcher::DataFilesPage::saveSettings(const QString& profile)
if (language == QLatin1String("Polish"))
{
mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1250"));
mGameSettings.setValue(QLatin1String("encoding"), { "win1250" });
}
else if (language == QLatin1String("Russian"))
{
mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1251"));
mGameSettings.setValue(QLatin1String("encoding"), { "win1251" });
}
else
{
mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1252"));
mGameSettings.setValue(QLatin1String("encoding"), { "win1252" });
}
}
QStringList Launcher::DataFilesPage::selectedDirectoriesPaths() const
QList<Config::SettingValue> Launcher::DataFilesPage::selectedDirectoriesPaths() const
{
QStringList dirList;
QList<Config::SettingValue> dirList;
for (int i = 0; i < ui.directoryListWidget->count(); ++i)
{
const QListWidgetItem* item = ui.directoryListWidget->item(i);
if (item->flags() & Qt::ItemIsEnabled)
dirList.append(item->text());
dirList.append(qvariant_cast<Config::SettingValue>(item->data(Qt::UserRole)));
}
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)
{
const QListWidgetItem* item = ui.archiveListWidget->item(i);
if (item->checkState() == Qt::Checked)
archiveList.append(item->text());
archiveList.append(qvariant_cast<Config::SettingValue>(item->data(Qt::UserRole)));
}
return archiveList;
}
@ -583,7 +632,20 @@ void Launcher::DataFilesPage::on_cloneProfileAction_triggered()
if (profile.isEmpty())
return;
mLauncherSettings.setContentList(profile, selectedDirectoriesPaths(), selectedArchivePaths(), selectedFilePaths());
const auto& dirList = selectedDirectoriesPaths();
QStringList dirNames;
for (const auto& dir : dirList)
{
if (mGameSettings.isUserSetting(dir))
dirNames.push_back(dir.originalRepresentation);
}
QStringList archiveNames;
for (const auto& archive : selectedArchivePaths())
{
if (mGameSettings.isUserSetting(archive))
archiveNames.push_back(archive.originalRepresentation);
}
mLauncherSettings.setContentList(profile, dirNames, archiveNames, selectedFilePaths());
addProfile(profile, true);
}
@ -650,6 +712,9 @@ void Launcher::DataFilesPage::addSubdirectories(bool append)
if (!ui.directoryListWidget->findItems(rootPath, Qt::MatchFixedString).isEmpty())
return;
ui.directoryListWidget->addItem(rootPath);
auto row = ui.directoryListWidget->count() - 1;
auto* item = ui.directoryListWidget->item(row);
item->setData(Qt::UserRole, QVariant::fromValue(Config::SettingValue{ rootPath }));
mNewDataDirs.push_back(rootPath);
refreshDataFilesView();
return;
@ -679,8 +744,11 @@ void Launcher::DataFilesPage::addSubdirectories(bool append)
const auto* dir = select.dirListWidget->item(i);
if (dir->checkState() == Qt::Checked)
{
ui.directoryListWidget->insertItem(selectedRow++, dir->text());
ui.directoryListWidget->insertItem(selectedRow, dir->text());
auto* item = ui.directoryListWidget->item(selectedRow);
item->setData(Qt::UserRole, QVariant::fromValue(Config::SettingValue{ dir->text() }));
mNewDataDirs.push_back(dir->text());
++selectedRow;
}
}
@ -702,6 +770,21 @@ void Launcher::DataFilesPage::sortDirectories()
}
}
void Launcher::DataFilesPage::sortArchives()
{
// Ensure disabled entries (aka ones from non-user config files) are always at the top.
for (auto i = 1; i < ui.archiveListWidget->count(); ++i)
{
if (!(ui.archiveListWidget->item(i)->flags() & Qt::ItemIsEnabled)
&& (ui.archiveListWidget->item(i - 1)->flags() & Qt::ItemIsEnabled))
{
const auto item = ui.archiveListWidget->takeItem(i);
ui.archiveListWidget->insertItem(i - 1, item);
ui.archiveListWidget->setCurrentRow(i);
}
}
}
void Launcher::DataFilesPage::moveDirectory(int step)
{
int selectedRow = ui.directoryListWidget->currentRow();
@ -784,9 +867,8 @@ bool Launcher::DataFilesPage::moveArchive(QListWidgetItem* listItem, int step)
if (selectedRow == -1 || newRow < 0 || newRow > ui.archiveListWidget->count() - 1)
return false;
const QListWidgetItem* item = ui.archiveListWidget->takeItem(selectedRow);
addArchive(item->text(), item->checkState(), newRow);
QListWidgetItem* item = ui.archiveListWidget->takeItem(selectedRow);
ui.archiveListWidget->insertItem(newRow, item);
ui.archiveListWidget->setCurrentRow(newRow);
return true;
}
@ -797,6 +879,7 @@ void Launcher::DataFilesPage::addArchive(const QString& name, Qt::CheckState sel
row = ui.archiveListWidget->count();
ui.archiveListWidget->insertItem(row, name);
ui.archiveListWidget->item(row)->setCheckState(selected);
ui.archiveListWidget->item(row)->setData(Qt::UserRole, QVariant::fromValue(Config::SettingValue{ name }));
if (mKnownArchives.filter(name).isEmpty()) // XXX why contains doesn't work here ???
{
auto item = ui.archiveListWidget->item(row);
@ -819,7 +902,7 @@ void Launcher::DataFilesPage::addArchivesFromDir(const QString& path)
for (const auto& fileinfo : dir.entryInfoList(archiveFilter))
{
const auto absPath = fileinfo.absoluteFilePath();
if (Bsa::BSAFile::detectVersion(Files::pathFromQString(absPath)) == Bsa::BSAVER_UNKNOWN)
if (Bsa::BSAFile::detectVersion(Files::pathFromQString(absPath)) == Bsa::BsaVersion::Unknown)
continue;
const auto fileName = fileinfo.fileName();

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

@ -37,9 +37,9 @@ Launcher::ImportPage::ImportPage(const Files::ConfigurationManager& cfg, Config:
// Detect Morrowind configuration files
QStringList iniPaths;
for (const QString& path : mGameSettings.getDataDirs())
for (const auto& path : mGameSettings.getDataDirs())
{
QDir dir(path);
QDir dir(path.value);
dir.setPath(dir.canonicalPath()); // Resolve symlinks
if (dir.exists(QString("Morrowind.ini")))
@ -125,7 +125,7 @@ void Launcher::ImportPage::on_importerButton_clicked()
arguments.append(QString("--fonts"));
arguments.append(QString("--encoding"));
arguments.append(mGameSettings.value(QString("encoding"), QString("win1252")));
arguments.append(mGameSettings.value(QString("encoding"), { "win1252" }).value);
arguments.append(QString("--ini"));
arguments.append(settingsComboBox->currentText());
arguments.append(QString("--cfg"));

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

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

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

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

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

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

@ -54,7 +54,7 @@
<item row="0" column="0">
<widget class="QLabel" name="importerLabel">
<property name="text">
<string>File to import settings from:</string>
<string>File to Import Settings From:</string>
</property>
</widget>
</item>
@ -73,7 +73,7 @@
<item>
<widget class="QCheckBox" name="addonsCheckBox">
<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 name="checked">
<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>
</property>
<property name="text">
<string>Import bitmap fonts setup</string>
<string>Import Bitmap Fonts</string>
</property>
<property name="checked">
<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>
</property>
<property name="text">
<string>Always allow actors to follow over water</string>
<string>Always Allow Actors to Follow over Water</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Permanent barter disposition changes</string>
<string>Permanent Barter Disposition Changes</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Racial variation in speed fix</string>
<string>Racial Variation in Speed Fix</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Classic Calm spells behavior</string>
<string>Classic Calm Spells Behavior</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>NPCs avoid collisions</string>
<string>NPCs Avoid Collisions</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Soulgem values rebalance</string>
<string>Soulgem Values Rebalance</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Day night switch nodes</string>
<string>Day Night Switch Nodes</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Followers defend immediately</string>
<string>Followers Defend Immediately</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Use navigation mesh for pathfinding</string>
<string>Use Navigation Mesh for Pathfinding</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Only magical ammo bypass resistance</string>
<string>Only Magical Ammo Bypass Resistance</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Graphic herbalism</string>
<string>Graphic Herbalism</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Swim upward correction</string>
<string>Swim Upward Correction</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Enchanted weapons are magical</string>
<string>Enchanted Weapons Are Magical</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Merchant equipping fix</string>
<string>Merchant Equipping Fix</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Trainers choose offered skills by base value</string>
<string>Trainers Choose Offered Skills by Base Value</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Can loot during death animation</string>
<string>Can Loot During Death Animation</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Steal from knocked out actors in combat</string>
<string>Steal from Knocked out Actors in Combat</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Classic reflected Absorb spells behavior</string>
<string>Classic Reflected Absorb Spells Behavior</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Unarmed creature attacks damage armor</string>
<string>Unarmed Creature Attacks Damage Armor</string>
</property>
</widget>
</item>
@ -230,7 +230,7 @@
<item row="0" column="0">
<widget class="QLabel" name="unarmedFactorsStrengthLabel">
<property name="text">
<string>Factor strength into hand-to-hand combat</string>
<string>Factor Strength into Hand-to-Hand Combat</string>
</property>
</widget>
</item>
@ -246,12 +246,12 @@
</item>
<item>
<property name="text">
<string>Affect werewolves</string>
<string>Affect Werewolves</string>
</property>
</item>
<item>
<property name="text">
<string>Do not affect werewolves</string>
<string>Do Not Affect Werewolves</string>
</property>
</item>
</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>
</property>
<property name="text">
<string>Background physics threads</string>
<string>Background Physics Threads</string>
</property>
</widget>
</item>
@ -272,7 +272,7 @@
<item row="2" column="0">
<widget class="QLabel" name="labelActorCollisionShapeType">
<property name="text">
<string>Actor collision shape type</string>
<string>Actor Collision Shape Type</string>
</property>
</widget>
</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>
</property>
<property name="currentText">
<string>Axis-aligned bounding box</string>
<string>Axis-Aligned Bounding Box</string>
</property>
<item>
<property name="text">
<string>Axis-aligned bounding box</string>
<string>Axis-Aligned Bounding Box</string>
</property>
</item>
<item>
<property name="text">
<string>Rotating box</string>
<string>Rotating Box</string>
</property>
</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>
</property>
<property name="text">
<string>Smooth movement</string>
<string>Smooth Movement</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Use additional animation sources</string>
<string>Use Additional Animation Sources</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Turn to movement direction</string>
<string>Turn to Movement Direction</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Weapon sheathing</string>
<string>Weapon Sheathing</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Shield sheathing</string>
<string>Shield Sheathing</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Player movement ignores animation</string>
<string>Player Movement Ignores Animation</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Use magic item animation</string>
<string>Use Magic Item Animation</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Auto use object normal maps</string>
<string>Auto Use Object Normal Maps</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Soft particles</string>
<string>Soft Particles</string>
</property>
</widget>
</item>
@ -471,7 +471,7 @@
(.osg file, not supported in .nif files). Affects objects.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Auto use object specular maps</string>
<string>Auto Use Object Specular Maps</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Auto use terrain normal maps</string>
<string>Auto Use Terrain Normal Maps</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Auto use terrain specular maps</string>
<string>Auto Use Terrain Specular Maps</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Adjust coverage for alpha test</string>
<string>Adjust Coverage for Alpha Test</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Use anti-alias alpha testing</string>
<string>Use Anti-Aliased Alpha Testing</string>
</property>
</widget>
</item>
@ -537,7 +537,7 @@
&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Bump/reflect map local lighting</string>
<string>Bump/Reflect Map Local Lighting</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Weather particle occlusion</string>
<string>Weather Particle Occlusion</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Exponential fog</string>
<string>Exponential Fog</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Radial fog</string>
<string>Radial Fog</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Sky blending start</string>
<string>Sky Blending Start</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Sky blending</string>
<string>Sky Blending</string>
</property>
</widget>
</item>
@ -652,12 +652,35 @@
<item>
<layout class="QGridLayout" name="terrainLayout" columnstretch="0,0">
<item row="4" column="0">
<widget class="QCheckBox" name="distantLandCheckBox">
<widget class="QLabel" name="objectPagingMinSizeLabel">
<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 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">
<string>Distant land</string>
<string>Viewing Distance</string>
</property>
</widget>
</item>
@ -666,15 +689,18 @@
<property name="suffix">
<string> cells</string>
</property>
<property name="decimals">
<number>3</number>
</property>
<property name="minimum">
<double>0.000000000000000</double>
</property>
<property name="singleStep">
<double>0.500000000000000</double>
<double>0.125000000000000</double>
</property>
</widget>
</item>
<item row="6" column="1">
<item row="7" column="1">
<spacer name="verticalSpacer_15">
<property name="orientation">
<enum>Qt::Vertical</enum>
@ -688,45 +714,22 @@
</spacer>
</item>
<item row="3" column="0">
<widget class="QLabel" name="objectPagingMinSizeLabel">
<widget class="QCheckBox" name="distantLandCheckBox">
<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>
</property>
<property name="text">
<string>Object paging min size</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>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="viewingDistanceLabel">
<property name="text">
<string>Viewing distance</string>
<string>Distant Land</string>
</property>
</widget>
</item>
<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">
<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>
</property>
<property name="text">
<string>Active grid object paging</string>
<string>Active Grid Object Paging</string>
</property>
</widget>
</item>
@ -741,16 +744,22 @@
<layout class="QHBoxLayout" name="horizontalPostProcessLayout">
<item>
<layout class="QGridLayout" name="postProcessLayout" columnstretch="0,0">
<item row="5" column="0">
<widget class="QLabel" name="postprocessHDRTimeLabel">
<item row="5" column="1">
<widget class="QDoubleSpinBox" name="postprocessHDRTimeComboBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<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 name="decimals">
<number>3</number>
</property>
<property name="text">
<string>Auto exposure speed</string>
<property name="minimum">
<double>0.010000000000000</double>
</property>
<property name="maximum">
<double>10.000000000000000</double>
</property>
<property name="singleStep">
<double>0.001000000000000</double>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Transparent postpass</string>
<string>Transparent Postpass</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QDoubleSpinBox" name="postprocessHDRTimeComboBox">
<item row="5" column="0">
<widget class="QLabel" name="postprocessHDRTimeLabel">
<property name="enabled">
<bool>false</bool>
</property>
<property name="decimals">
<number>3</number>
</property>
<property name="minimum">
<double>0.010000000000000</double>
</property>
<property name="maximum">
<double>10.000000000000000</double>
<property name="toolTip">
<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="singleStep">
<double>0.001000000000000</double>
<property name="text">
<string>Auto Exposure Speed</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Enable post processing</string>
<string>Enable Post Processing</string>
</property>
</widget>
</item>
@ -824,17 +827,17 @@
<widget class="QComboBox" name="shadowComputeSceneBoundsComboBox">
<item>
<property name="text">
<string>bounds</string>
<string>Bounds</string>
</property>
</item>
<item>
<property name="text">
<string>primitives</string>
<string>Primitives</string>
</property>
</item>
<item>
<property name="text">
<string>none</string>
<string>None</string>
</property>
</item>
</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>
</property>
<property name="text">
<string>Shadow planes computation method</string>
<string>Shadow Planes Computation Method</string>
</property>
</widget>
</item>
@ -866,6 +869,9 @@
<property name="maximum">
<number>81920</number>
</property>
<property name="singleStep">
<number>128</number>
</property>
<property name="value">
<number>8192</number>
</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>
</property>
<property name="text">
<string>Enable actor shadows</string>
<string>Enable Actor Shadows</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Fade start multiplier</string>
<string>Fade Start Multiplier</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Enable player shadows</string>
<string>Enable Player Shadows</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Shadow map resolution</string>
<string>Shadow Map Resolution</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Shadow distance limit:</string>
<string>Shadow Distance Limit:</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Enable object shadows</string>
<string>Enable Object Shadows</string>
</property>
</widget>
</item>
@ -969,6 +975,9 @@
<property name="maximum">
<double>1.000000000000000</double>
</property>
<property name="singleStep">
<double>0.010000000000000</double>
</property>
<property name="value">
<double>0.900000000000000</double>
</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>
</property>
<property name="text">
<string>Enable indoor shadows</string>
<string>Enable Indoor Shadows</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Enable terrain shadows</string>
<string>Enable Terrain Shadows</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Lights maximum distance</string>
<string>Maximum Light Distance</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Max light sources</string>
<string>Max Lights</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Lights fade multiplier</string>
<string>Fade Start Multiplier</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Lighting method</string>
<string>Lighting Method</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Lights bounding sphere multiplier</string>
<string>Bounding Sphere Multiplier</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Lights minimum interior brightness</string>
<string>Minimum Interior Brightness</string>
</property>
</widget>
</item>
@ -1214,7 +1223,7 @@
<string>Select your preferred audio device.</string>
</property>
<property name="text">
<string>Audio device</string>
<string>Audio Device</string>
</property>
</widget>
</item>
@ -1300,7 +1309,7 @@
<string>Select your preferred HRTF profile.</string>
</property>
<property name="text">
<string>HRTF profile</string>
<string>HRTF Profile</string>
</property>
</widget>
</item>
@ -1330,6 +1339,16 @@
</item>
</layout>
</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>
<spacer>
<property name="orientation">
@ -1374,7 +1393,7 @@
</item>
<item>
<property name="text">
<string>Tooltip and crosshair</string>
<string>Tooltip and Crosshair</string>
</property>
</item>
</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>
</property>
<property name="text">
<string>GUI scaling factor</string>
<string>GUI Scaling Factor</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Show effect duration</string>
<string>Show Effect Duration</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Change dialogue topic color</string>
<string>Change Dialogue Topic Color</string>
</property>
</widget>
</item>
@ -1450,7 +1469,7 @@
<string>Size of characters in game texts.</string>
</property>
<property name="text">
<string>Font size</string>
<string>Font Size</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Can zoom on maps</string>
<string>Can Zoom on Maps</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Show projectile damage</string>
<string>Show Projectile Damage</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Show melee info</string>
<string>Show Melee Info</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Stretch menu background</string>
<string>Stretch Menu Background</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="showOwnedLabel">
<property name="text">
<string>Show owned objects</string>
<string>Show Owned Objects</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Show enchant chance</string>
<string>Show Enchant Chance</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Add &quot;Time Played&quot; to saves</string>
<string>Add &quot;Time Played&quot; to Saves</string>
</property>
</widget>
</item>
@ -1554,7 +1573,7 @@
<item>
<widget class="QLabel" name="maximumQuicksavesLabel">
<property name="text">
<string>Maximum quicksaves</string>
<string>Maximum Quicksaves</string>
</property>
</widget>
</item>
@ -1581,7 +1600,7 @@
<item>
<widget class="QLabel" name="screenshotFormatLabel">
<property name="text">
<string>Screenshot format</string>
<string>Screenshot Format</string>
</property>
</widget>
</item>
@ -1609,7 +1628,7 @@
<item>
<widget class="QCheckBox" name="notifyOnSavedScreenshotCheckBox">
<property name="text">
<string>Notify on saved screenshot</string>
<string>Notify on Saved Screenshot</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>Grab cursor</string>
<string>Grab Cursor</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="skipMenuCheckBox">
<property name="text">
<string>Skip menu and generate default character</string>
<string>Skip Menu and Generate Default Character</string>
</property>
</widget>
</item>
@ -1691,14 +1710,14 @@
<item>
<widget class="QLabel" name="startDefaultCharacterAtLabel">
<property name="text">
<string>Start default character at</string>
<string>Start Default Character at</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="startDefaultCharacterAtField">
<property name="placeholderText">
<string>default cell</string>
<string>Default Cell</string>
</property>
</widget>
</item>
@ -1707,7 +1726,7 @@
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Run script after startup:</string>
<string>Run Script After Startup:</string>
</property>
</widget>
</item>

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

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

@ -19,6 +19,7 @@
#include <components/files/conversion.hpp>
#include <components/files/multidircollection.hpp>
#include <components/platform/platform.hpp>
#include <components/resource/bgsmfilemanager.hpp>
#include <components/resource/bulletshapemanager.hpp>
#include <components/resource/imagemanager.hpp>
#include <components/resource/niffilemanager.hpp>
@ -165,7 +166,9 @@ namespace NavMeshTool
dataDirs.insert(dataDirs.begin(), resDir / "vfs");
const Files::Collections fileCollections(dataDirs);
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>();
if (threadsNumber < 1)
@ -218,7 +221,8 @@ namespace NavMeshTool
Resource::ImageManager imageManager(&vfs, expiryDelay);
Resource::NifFileManager nifFileManager(&vfs, &encoder.getStatelessEncoder());
Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, expiryDelay);
Resource::BgsmFileManager bgsmFileManager(&vfs, expiryDelay);
Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, &bgsmFileManager, expiryDelay);
Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay);
DetourNavigator::RecastGlobalAllocator::init();
DetourNavigator::Settings navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager();

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

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

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

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

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

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

@ -452,7 +452,10 @@ std::shared_ptr<CSMFilter::Node> CSMFilter::Parser::parseText()
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()

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

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

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

@ -60,38 +60,38 @@ void CSMTools::EnchantmentCheckStage::perform(int stage, CSMDoc::Messages& messa
}
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++)
{
const std::string number = std::to_string(i);
// At the time of writing this effects, attributes and skills are hardcoded
if (effect->mEffectID < 0 || effect->mEffectID > 142)
if (effect->mData.mEffectID < 0 || effect->mData.mEffectID > 142)
{
messages.add(id, "Effect #" + number + " is invalid", "", CSMDoc::Message::Severity_Error);
++effect;
continue;
}
if (effect->mSkill < -1 || effect->mSkill > 26)
if (effect->mData.mSkill < -1 || effect->mData.mSkill > 26)
messages.add(
id, "Effect #" + number + " affected skill is invalid", "", CSMDoc::Message::Severity_Error);
if (effect->mAttribute < -1 || effect->mAttribute > 7)
if (effect->mData.mAttribute < -1 || effect->mData.mAttribute > 7)
messages.add(
id, "Effect #" + number + " affected attribute is invalid", "", CSMDoc::Message::Severity_Error);
if (effect->mRange < 0 || effect->mRange > 2)
if (effect->mData.mRange < 0 || effect->mData.mRange > 2)
messages.add(id, "Effect #" + number + " range is invalid", "", CSMDoc::Message::Severity_Error);
if (effect->mArea < 0)
if (effect->mData.mArea < 0)
messages.add(id, "Effect #" + number + " area is negative", "", CSMDoc::Message::Severity_Error);
if (effect->mDuration < 0)
if (effect->mData.mDuration < 0)
messages.add(id, "Effect #" + number + " duration is negative", "", CSMDoc::Message::Severity_Error);
if (effect->mMagnMin < 0)
if (effect->mData.mMagnMin < 0)
messages.add(
id, "Effect #" + number + " minimum magnitude is negative", "", CSMDoc::Message::Severity_Error);
if (effect->mMagnMax < 0)
if (effect->mData.mMagnMax < 0)
messages.add(
id, "Effect #" + number + " maximum magnitude is negative", "", CSMDoc::Message::Severity_Error);
if (effect->mMagnMin > effect->mMagnMax)
if (effect->mData.mMagnMin > effect->mData.mMagnMax)
messages.add(id, "Effect #" + number + " minimum magnitude is higher than maximum magnitude", "",
CSMDoc::Message::Severity_Error);
++effect;

@ -58,7 +58,12 @@ void CSMTools::MagicEffectCheckStage::perform(int stage, CSMDoc::Messages& messa
return;
ESM::MagicEffect effect = record.get();
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_MagicEffect, effect.mId);
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_MagicEffect, CSMWorld::getRecordId(effect));
if (effect.mData.mSpeed <= 0.0f)
{
messages.add(id, "Speed is less than or equal to zero", "", CSMDoc::Message::Severity_Error);
}
if (effect.mDescription.empty())
{

@ -171,10 +171,9 @@ void CSMTools::TopicInfoCheckStage::perform(int stage, CSMDoc::Messages& message
// Check info conditions
for (std::vector<ESM::DialInfo::SelectStruct>::const_iterator it = topicInfo.mSelects.begin();
it != topicInfo.mSelects.end(); ++it)
for (const auto& select : topicInfo.mSelects)
{
verifySelectStruct((*it), id, messages);
verifySelectStruct(select, id, messages);
}
}
@ -308,49 +307,15 @@ bool CSMTools::TopicInfoCheckStage::verifyItem(
}
bool CSMTools::TopicInfoCheckStage::verifySelectStruct(
const ESM::DialInfo::SelectStruct& select, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages)
const ESM::DialogueCondition& select, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages)
{
CSMWorld::ConstInfoSelectWrapper infoCondition(select);
if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_None)
if (select.mFunction == ESM::DialogueCondition::Function_None)
{
messages.add(id, "Invalid condition '" + infoCondition.toString() + "'", "", CSMDoc::Message::Severity_Error);
return false;
}
else if (!infoCondition.variantTypeIsValid())
{
std::ostringstream stream;
stream << "Value of condition '" << infoCondition.toString() << "' has invalid ";
switch (select.mValue.getType())
{
case ESM::VT_None:
stream << "None";
break;
case ESM::VT_Short:
stream << "Short";
break;
case ESM::VT_Int:
stream << "Int";
break;
case ESM::VT_Long:
stream << "Long";
break;
case ESM::VT_Float:
stream << "Float";
break;
case ESM::VT_String:
stream << "String";
break;
default:
stream << "unknown";
break;
}
stream << " type";
messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error);
return false;
}
else if (infoCondition.conditionIsAlwaysTrue())
{
messages.add(
@ -365,48 +330,48 @@ bool CSMTools::TopicInfoCheckStage::verifySelectStruct(
}
// Id checks
if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Global
&& !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mGlobals, id, messages))
if (select.mFunction == ESM::DialogueCondition::Function_Global
&& !verifyId(ESM::RefId::stringRefId(select.mVariable), mGlobals, id, messages))
{
return false;
}
else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Journal
&& !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mJournals, id, messages))
else if (select.mFunction == ESM::DialogueCondition::Function_Journal
&& !verifyId(ESM::RefId::stringRefId(select.mVariable), mJournals, id, messages))
{
return false;
}
else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Item
&& !verifyItem(ESM::RefId::stringRefId(infoCondition.getVariableName()), id, messages))
else if (select.mFunction == ESM::DialogueCondition::Function_Item
&& !verifyItem(ESM::RefId::stringRefId(select.mVariable), id, messages))
{
return false;
}
else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Dead
&& !verifyActor(ESM::RefId::stringRefId(infoCondition.getVariableName()), id, messages))
else if (select.mFunction == ESM::DialogueCondition::Function_Dead
&& !verifyActor(ESM::RefId::stringRefId(select.mVariable), id, messages))
{
return false;
}
else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotId
&& !verifyActor(ESM::RefId::stringRefId(infoCondition.getVariableName()), id, messages))
else if (select.mFunction == ESM::DialogueCondition::Function_NotId
&& !verifyActor(ESM::RefId::stringRefId(select.mVariable), id, messages))
{
return false;
}
else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotFaction
&& !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mFactions, id, messages))
else if (select.mFunction == ESM::DialogueCondition::Function_NotFaction
&& !verifyId(ESM::RefId::stringRefId(select.mVariable), mFactions, id, messages))
{
return false;
}
else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotClass
&& !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mClasses, id, messages))
else if (select.mFunction == ESM::DialogueCondition::Function_NotClass
&& !verifyId(ESM::RefId::stringRefId(select.mVariable), mClasses, id, messages))
{
return false;
}
else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotRace
&& !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mRaces, id, messages))
else if (select.mFunction == ESM::DialogueCondition::Function_NotRace
&& !verifyId(ESM::RefId::stringRefId(select.mVariable), mRaces, id, messages))
{
return false;
}
else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotCell
&& !verifyCell(infoCondition.getVariableName(), id, messages))
else if (select.mFunction == ESM::DialogueCondition::Function_NotCell
&& !verifyCell(select.mVariable, id, messages))
{
return false;
}

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

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

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

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

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

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

@ -161,7 +161,7 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data
defines["radialFog"] = "0";
defines["lightingModel"] = "0";
defines["reverseZ"] = "0";
defines["refraction_enabled"] = "0";
defines["waterRefraction"] = "0";
for (const auto& define : shadowDefines)
defines[define.first] = define.second;
mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(defines);
@ -301,8 +301,8 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data
mRegions.addColumn(new NestedParentColumn<ESM::Region>(Columns::ColumnId_RegionWeather));
index = mRegions.getColumns() - 1;
mRegions.addAdapter(std::make_pair(&mRegions.getColumn(index), new RegionWeatherAdapter()));
mRegions.getNestableColumn(index)->addColumn(
new NestedChildColumn(Columns::ColumnId_WeatherName, ColumnBase::Display_String, false));
mRegions.getNestableColumn(index)->addColumn(new NestedChildColumn(
Columns::ColumnId_WeatherName, ColumnBase::Display_String, ColumnBase::Flag_Dialogue, false));
mRegions.getNestableColumn(index)->addColumn(
new NestedChildColumn(Columns::ColumnId_WeatherChance, ColumnBase::Display_UnsignedInteger8));
// Region Sounds
@ -313,6 +313,8 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data
new NestedChildColumn(Columns::ColumnId_SoundName, ColumnBase::Display_Sound));
mRegions.getNestableColumn(index)->addColumn(
new NestedChildColumn(Columns::ColumnId_SoundChance, ColumnBase::Display_UnsignedInteger8));
mRegions.getNestableColumn(index)->addColumn(new NestedChildColumn(
Columns::ColumnId_SoundProbability, ColumnBase::Display_String, ColumnBase::Flag_Dialogue, false));
mBirthsigns.addColumn(new StringIdColumn<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 SchoolColumn<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_Particle));
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_AreaSound));
mMagicEffects.addColumn(new EffectSoundColumn<ESM::MagicEffect>(Columns::ColumnId_BoltSound));
mMagicEffects.addColumn(
new FlagColumn<ESM::MagicEffect>(Columns::ColumnId_AllowSpellmaking, ESM::MagicEffect::AllowSpellmaking));
mMagicEffects.addColumn(
@ -589,7 +593,8 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data
mRefs.addColumn(new ChargesColumn<CellRef>);
mRefs.addColumn(new EnchantmentChargesColumn<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 PosColumn<CellRef>(&CellRef::mDoorDest, 0, 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, 1, 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 KeyColumn<CellRef>);
mRefs.addColumn(new TrapColumn<CellRef>);

@ -63,9 +63,18 @@ bool CSMWorld::IdTableProxyModel::filterAcceptsRow(int sourceRow, const QModelIn
CSMWorld::IdTableProxyModel::IdTableProxyModel(QObject* parent)
: QSortFilterProxyModel(parent)
, mFilterTimer{ new QTimer(this) }
, mSourceModel(nullptr)
{
setSortCaseSensitivity(Qt::CaseInsensitive);
mFilterTimer->setSingleShot(true);
int intervalSetting = CSMPrefs::State::get()["ID Tables"]["filter-delay"].toInt();
mFilterTimer->setInterval(intervalSetting);
connect(&CSMPrefs::State::get(), &CSMPrefs::State::settingChanged, this,
[this](const CSMPrefs::Setting* setting) { this->settingChanged(setting); });
connect(mFilterTimer.get(), &QTimer::timeout, this, [this]() { this->timerTimeout(); });
}
QModelIndex CSMWorld::IdTableProxyModel::getModelIndex(const std::string& id, int column) const
@ -87,10 +96,8 @@ void CSMWorld::IdTableProxyModel::setSourceModel(QAbstractItemModel* model)
void CSMWorld::IdTableProxyModel::setFilter(const std::shared_ptr<CSMFilter::Node>& filter)
{
beginResetModel();
mFilter = filter;
updateColumnMap();
endResetModel();
mAwaitingFilter = filter;
mFilterTimer->start();
}
bool CSMWorld::IdTableProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right) const
@ -131,6 +138,26 @@ void CSMWorld::IdTableProxyModel::refreshFilter()
}
}
void CSMWorld::IdTableProxyModel::timerTimeout()
{
if (mAwaitingFilter)
{
beginResetModel();
mFilter = mAwaitingFilter;
updateColumnMap();
endResetModel();
mAwaitingFilter.reset();
}
}
void CSMWorld::IdTableProxyModel::settingChanged(const CSMPrefs::Setting* setting)
{
if (*setting == "ID Tables/filter-delay")
{
mFilterTimer->setInterval(setting->toInt());
}
}
void CSMWorld::IdTableProxyModel::sourceRowsInserted(const QModelIndex& parent, int /*start*/, int end)
{
refreshFilter();

@ -10,6 +10,9 @@
#include <QModelIndex>
#include <QSortFilterProxyModel>
#include <QString>
#include <QTimer>
#include "../prefs/state.hpp"
#include "columns.hpp"
@ -29,6 +32,8 @@ namespace CSMWorld
Q_OBJECT
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)
// Cache of enum values for enum columns (e.g. Modified, Record Type).
@ -68,6 +73,10 @@ namespace CSMWorld
virtual void sourceDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight);
void timerTimeout();
void settingChanged(const CSMPrefs::Setting* setting);
signals:
void rowAdded(const std::string& id);

File diff suppressed because it is too large Load Diff

@ -7,133 +7,13 @@
#include <components/esm3/loadinfo.hpp>
namespace ESM
{
class Variant;
}
#include <QVariant>
namespace CSMWorld
{
// ESM::DialInfo::SelectStruct.mSelectRule
// 012345...
// ^^^ ^^
// ||| ||
// ||| |+------------- condition variable string
// ||| +-------------- comparison type, ['0'..'5']; e.g. !=, <, >=, etc
// ||+---------------- function index (encoded, where function == '1')
// |+----------------- function, ['1'..'C']; e.g. Global, Local, Not ID, etc
// +------------------ unknown
//
// Wrapper for DialInfo::SelectStruct
class ConstInfoSelectWrapper
{
public:
// Order matters
enum FunctionName
{
Function_RankLow = 0,
Function_RankHigh,
Function_RankRequirement,
Function_Reputation,
Function_Health_Percent,
Function_PcReputation,
Function_PcLevel,
Function_PcHealthPercent,
Function_PcMagicka,
Function_PcFatigue,
Function_PcStrength,
Function_PcBlock,
Function_PcArmorer,
Function_PcMediumArmor,
Function_PcHeavyArmor,
Function_PcBluntWeapon,
Function_PcLongBlade,
Function_PcAxe,
Function_PcSpear,
Function_PcAthletics,
Function_PcEnchant,
Function_PcDestruction,
Function_PcAlteration,
Function_PcIllusion,
Function_PcConjuration,
Function_PcMysticism,
Function_PcRestoration,
Function_PcAlchemy,
Function_PcUnarmored,
Function_PcSecurity,
Function_PcSneak,
Function_PcAcrobatics,
Function_PcLightArmor,
Function_PcShortBlade,
Function_PcMarksman,
Function_PcMerchantile,
Function_PcSpeechcraft,
Function_PcHandToHand,
Function_PcGender,
Function_PcExpelled,
Function_PcCommonDisease,
Function_PcBlightDisease,
Function_PcClothingModifier,
Function_PcCrimeLevel,
Function_SameSex,
Function_SameRace,
Function_SameFaction,
Function_FactionRankDifference,
Function_Detected,
Function_Alarmed,
Function_Choice,
Function_PcIntelligence,
Function_PcWillpower,
Function_PcAgility,
Function_PcSpeed,
Function_PcEndurance,
Function_PcPersonality,
Function_PcLuck,
Function_PcCorpus,
Function_Weather,
Function_PcVampire,
Function_Level,
Function_Attacked,
Function_TalkedToPc,
Function_PcHealth,
Function_CreatureTarget,
Function_FriendHit,
Function_Fight,
Function_Hello,
Function_Alarm,
Function_Flee,
Function_ShouldAttack,
Function_Werewolf,
Function_PcWerewolfKills = 73,
Function_Global,
Function_Local,
Function_Journal,
Function_Item,
Function_Dead,
Function_NotId,
Function_NotFaction,
Function_NotClass,
Function_NotRace,
Function_NotCell,
Function_NotLocal,
Function_None
};
enum RelationType
{
Relation_Equal,
Relation_NotEqual,
Relation_Greater,
Relation_GreaterOrEqual,
Relation_Less,
Relation_LessOrEqual,
Relation_None
};
enum ComparisonType
{
Comparison_Boolean,
@ -143,25 +23,13 @@ namespace CSMWorld
Comparison_None
};
static const size_t RuleMinSize;
static const size_t FunctionPrefixOffset;
static const size_t FunctionIndexOffset;
static const size_t RelationIndexOffset;
static const size_t VarNameOffset;
static const char* FunctionEnumStrings[];
static const char* RelationEnumStrings[];
static const char* ComparisonEnumStrings[];
static std::string convertToString(FunctionName name);
static std::string convertToString(RelationType type);
static std::string convertToString(ComparisonType type);
ConstInfoSelectWrapper(const ESM::DialInfo::SelectStruct& select);
ConstInfoSelectWrapper(const ESM::DialogueCondition& select);
FunctionName getFunctionName() const;
RelationType getRelationType() const;
ESM::DialogueCondition::Function getFunctionName() const;
ESM::DialogueCondition::Comparison getRelationType() const;
ComparisonType getComparisonType() const;
bool hasVariable() const;
@ -169,17 +37,12 @@ namespace CSMWorld
bool conditionIsAlwaysTrue() const;
bool conditionIsNeverTrue() const;
bool variantTypeIsValid() const;
const ESM::Variant& getVariant() const;
QVariant getValue() const;
std::string toString() const;
protected:
void readRule();
void readFunctionName();
void readRelationType();
void readVariableName();
void updateHasVariable();
void updateComparisonType();
@ -207,38 +70,29 @@ namespace CSMWorld
template <typename Type1, typename Type2>
bool conditionIsNeverTrue(std::pair<Type1, Type1> conditionRange, std::pair<Type2, Type2> validRange) const;
FunctionName mFunctionName;
RelationType mRelationType;
ComparisonType mComparisonType;
bool mHasVariable;
std::string mVariableName;
private:
const ESM::DialInfo::SelectStruct& mConstSelect;
const ESM::DialogueCondition& mConstSelect;
};
// Wrapper for DialInfo::SelectStruct that can modify the wrapped select struct
// Wrapper for DialogueCondition that can modify the wrapped select struct
class InfoSelectWrapper : public ConstInfoSelectWrapper
{
public:
InfoSelectWrapper(ESM::DialInfo::SelectStruct& select);
InfoSelectWrapper(ESM::DialogueCondition& select);
// Wrapped SelectStruct will not be modified until update() is called
void setFunctionName(FunctionName name);
void setRelationType(RelationType type);
void setFunctionName(ESM::DialogueCondition::Function name);
void setRelationType(ESM::DialogueCondition::Comparison type);
void setVariableName(const std::string& name);
// Modified wrapped SelectStruct
void update();
// This sets properties based on the function name to its defaults and updates the wrapped object
void setDefaults();
ESM::Variant& getVariant();
void setValue(int value);
void setValue(float value);
private:
ESM::DialInfo::SelectStruct& mSelect;
ESM::DialogueCondition& mSelect;
void writeRule();
};

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

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

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

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

@ -97,7 +97,7 @@ CSMWorld::RefIdCollection::RefIdCollection()
inventoryColumns.mIcon = &mColumns.back();
mColumns.emplace_back(Columns::ColumnId_Weight, ColumnBase::Display_Float);
inventoryColumns.mWeight = &mColumns.back();
mColumns.emplace_back(Columns::ColumnId_StackCount, ColumnBase::Display_Integer);
mColumns.emplace_back(Columns::ColumnId_GoldValue, ColumnBase::Display_Integer);
inventoryColumns.mValue = &mColumns.back();
IngredientColumns ingredientColumns(inventoryColumns);
@ -210,7 +210,6 @@ CSMWorld::RefIdCollection::RefIdCollection()
mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_AiDuration, CSMWorld::ColumnBase::Display_Integer));
mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_AiWanderToD, CSMWorld::ColumnBase::Display_Integer));
mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle1, CSMWorld::ColumnBase::Display_Integer));
mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle2, CSMWorld::ColumnBase::Display_Integer));
mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle3, CSMWorld::ColumnBase::Display_Integer));
mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle4, CSMWorld::ColumnBase::Display_Integer));
@ -218,6 +217,7 @@ CSMWorld::RefIdCollection::RefIdCollection()
mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle6, CSMWorld::ColumnBase::Display_Integer));
mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle7, CSMWorld::ColumnBase::Display_Integer));
mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle8, CSMWorld::ColumnBase::Display_Integer));
mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle9, CSMWorld::ColumnBase::Display_Integer));
mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_AiWanderRepeat, CSMWorld::ColumnBase::Display_Boolean));
mColumns.back().addColumn(

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

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

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

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

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

@ -10,7 +10,7 @@
#include <QScreen>
#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--;
@ -39,13 +39,13 @@ QWidget* CSVDoc::StartupDialogue::createButtons()
mLayout = new QGridLayout(widget);
/// \todo add icons
QPushButton* loadDocument = addButton("Edit A Content File", QIcon(":startup/edit-content"));
QPushButton* loadDocument = addButton("Edit A Content File", ":startup/edit-content");
connect(loadDocument, &QPushButton::clicked, this, &StartupDialogue::loadDocument);
QPushButton* createAddon = addButton("Create A New Addon", QIcon(":startup/create-addon"));
QPushButton* createAddon = addButton("Create A New Addon", ":startup/create-addon");
connect(createAddon, &QPushButton::clicked, this, &StartupDialogue::createAddon);
QPushButton* createGame = addButton("Create A New Game", QIcon(":startup/create-game"));
QPushButton* createGame = addButton("Create A New Game", ":startup/create-game");
connect(createGame, &QPushButton::clicked, this, &StartupDialogue::createGame);
for (int i = 0; i < 3; ++i)

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

@ -71,30 +71,30 @@ void CSVDoc::View::setupFileMenu()
{
QMenu* file = menuBar()->addMenu(tr("File"));
QAction* newGame = createMenuEntry("New Game", ":./menu-new-game.png", file, "document-file-newgame");
QAction* newGame = createMenuEntry("New Game", ":menu-new-game", file, "document-file-newgame");
connect(newGame, &QAction::triggered, this, &View::newGameRequest);
QAction* newAddon = createMenuEntry("New Addon", ":./menu-new-addon.png", file, "document-file-newaddon");
QAction* newAddon = createMenuEntry("New Addon", ":menu-new-addon", file, "document-file-newaddon");
connect(newAddon, &QAction::triggered, this, &View::newAddonRequest);
QAction* open = createMenuEntry("Open", ":./menu-open.png", file, "document-file-open");
QAction* open = createMenuEntry("Open", ":menu-open", file, "document-file-open");
connect(open, &QAction::triggered, this, &View::loadDocumentRequest);
QAction* save = createMenuEntry("Save", ":./menu-save.png", file, "document-file-save");
QAction* save = createMenuEntry("Save", ":menu-save", file, "document-file-save");
connect(save, &QAction::triggered, this, &View::save);
mSave = save;
file->addSeparator();
QAction* verify = createMenuEntry("Verify", ":./menu-verify.png", file, "document-file-verify");
QAction* verify = createMenuEntry("Verify", ":menu-verify", file, "document-file-verify");
connect(verify, &QAction::triggered, this, &View::verify);
mVerify = verify;
QAction* merge = createMenuEntry("Merge", ":./menu-merge.png", file, "document-file-merge");
QAction* merge = createMenuEntry("Merge", ":menu-merge", file, "document-file-merge");
connect(merge, &QAction::triggered, this, &View::merge);
mMerge = merge;
QAction* loadErrors = createMenuEntry("Error Log", ":./error-log.png", file, "document-file-errorlog");
QAction* loadErrors = createMenuEntry("Error Log", ":error-log", file, "document-file-errorlog");
connect(loadErrors, &QAction::triggered, this, &View::loadErrorLog);
QAction* meta = createMenuEntry(CSMWorld::UniversalId::Type_MetaDatas, file, "document-file-metadata");
@ -102,10 +102,10 @@ void CSVDoc::View::setupFileMenu()
file->addSeparator();
QAction* close = createMenuEntry("Close", ":./menu-close.png", file, "document-file-close");
QAction* close = createMenuEntry("Close", ":menu-close", file, "document-file-close");
connect(close, &QAction::triggered, this, &View::close);
QAction* exit = createMenuEntry("Exit", ":./menu-exit.png", file, "document-file-exit");
QAction* exit = createMenuEntry("Exit", ":menu-exit", file, "document-file-exit");
connect(exit, &QAction::triggered, this, &View::exit);
connect(this, &View::exitApplicationRequest, &mViewManager, &ViewManager::exitApplication);
@ -140,17 +140,16 @@ void CSVDoc::View::setupEditMenu()
mUndo = mDocument->getUndoStack().createUndoAction(this, tr("Undo"));
setupShortcut("document-edit-undo", mUndo);
connect(mUndo, &QAction::changed, this, &View::undoActionChanged);
mUndo->setIcon(QIcon(QString::fromStdString(":./menu-undo.png")));
mUndo->setIcon(QIcon(QString::fromStdString(":menu-undo")));
edit->addAction(mUndo);
mRedo = mDocument->getUndoStack().createRedoAction(this, tr("Redo"));
connect(mRedo, &QAction::changed, this, &View::redoActionChanged);
setupShortcut("document-edit-redo", mRedo);
mRedo->setIcon(QIcon(QString::fromStdString(":./menu-redo.png")));
mRedo->setIcon(QIcon(QString::fromStdString(":menu-redo")));
edit->addAction(mRedo);
QAction* userSettings
= createMenuEntry("Preferences", ":./menu-preferences.png", edit, "document-edit-preferences");
QAction* userSettings = createMenuEntry("Preferences", ":menu-preferences", edit, "document-edit-preferences");
connect(userSettings, &QAction::triggered, this, &View::editSettingsRequest);
QAction* search = createMenuEntry(CSMWorld::UniversalId::Type_Search, edit, "document-edit-search");
@ -161,10 +160,10 @@ void CSVDoc::View::setupViewMenu()
{
QMenu* view = menuBar()->addMenu(tr("View"));
QAction* newWindow = createMenuEntry("New View", ":./menu-new-window.png", view, "document-view-newview");
QAction* newWindow = createMenuEntry("New View", ":menu-new-window", view, "document-view-newview");
connect(newWindow, &QAction::triggered, this, &View::newView);
mShowStatusBar = createMenuEntry("Toggle Status Bar", ":./menu-status-bar.png", view, "document-view-statusbar");
mShowStatusBar = createMenuEntry("Toggle Status Bar", ":menu-status-bar", view, "document-view-statusbar");
connect(mShowStatusBar, &QAction::toggled, this, &View::toggleShowStatusBar);
mShowStatusBar->setCheckable(true);
mShowStatusBar->setChecked(CSMPrefs::get()["Windows"]["show-statusbar"].isTrue());
@ -289,7 +288,7 @@ void CSVDoc::View::setupAssetsMenu()
{
QMenu* assets = menuBar()->addMenu(tr("Assets"));
QAction* reload = createMenuEntry("Reload", ":./menu-reload.png", assets, "document-assets-reload");
QAction* reload = createMenuEntry("Reload", ":menu-reload", assets, "document-assets-reload");
connect(reload, &QAction::triggered, &mDocument->getData(), &CSMWorld::Data::assetsChanged);
assets->addSeparator();
@ -341,9 +340,9 @@ void CSVDoc::View::setupDebugMenu()
QAction* runDebug = debug->addMenu(mGlobalDebugProfileMenu);
runDebug->setText(tr("Run OpenMW"));
setupShortcut("document-debug-run", runDebug);
runDebug->setIcon(QIcon(QString::fromStdString(":./run-openmw.png")));
runDebug->setIcon(QIcon(QString::fromStdString(":run-openmw")));
QAction* stopDebug = createMenuEntry("Stop OpenMW", ":./stop-openmw.png", debug, "document-debug-shutdown");
QAction* stopDebug = createMenuEntry("Stop OpenMW", ":stop-openmw", debug, "document-debug-shutdown");
connect(stopDebug, &QAction::triggered, this, &View::stop);
mStopDebug = stopDebug;
@ -355,16 +354,16 @@ void CSVDoc::View::setupHelpMenu()
{
QMenu* help = menuBar()->addMenu(tr("Help"));
QAction* helpInfo = createMenuEntry("Help", ":/info.png", help, "document-help-help");
QAction* helpInfo = createMenuEntry("Help", ":info", help, "document-help-help");
connect(helpInfo, &QAction::triggered, this, &View::openHelp);
QAction* tutorial = createMenuEntry("Tutorial", ":/info.png", help, "document-help-tutorial");
QAction* tutorial = createMenuEntry("Tutorial", ":info", help, "document-help-tutorial");
connect(tutorial, &QAction::triggered, this, &View::tutorial);
QAction* about = createMenuEntry("About OpenMW-CS", ":./info.png", help, "document-help-about");
QAction* about = createMenuEntry("About OpenMW-CS", ":info", help, "document-help-about");
connect(about, &QAction::triggered, this, &View::infoAbout);
QAction* aboutQt = createMenuEntry("About Qt", ":./qt.png", help, "document-help-qt");
QAction* aboutQt = createMenuEntry("About Qt", ":qt", help, "document-help-qt");
connect(aboutQt, &QAction::triggered, this, &View::infoAboutQt);
}
@ -1111,7 +1110,16 @@ void CSVDoc::View::updateWidth(bool isGrowLimit, int minSubViewWidth)
{
QRect rect;
if (isGrowLimit)
rect = QApplication::screenAt(pos())->geometry();
{
// Widget position can be negative, we should clamp it.
QPoint position = pos();
if (position.x() <= 0)
position.setX(0);
if (position.y() <= 0)
position.setY(0);
rect = QApplication::screenAt(position)->geometry();
}
else
rect = desktopRect();

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

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

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

@ -8,7 +8,7 @@
class QWidget;
CSVRender::InstanceMoveMode::InstanceMoveMode(QWidget* parent)
: ModeButton(QIcon(QPixmap(":scenetoolbar/transform-move")),
: ModeButton(QIcon(":scenetoolbar/transform-move"),
"Move selected instances"
"<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>"

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

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

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

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

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

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

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

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

@ -94,7 +94,7 @@ void CSVWidget::SceneToolMode::showPanel(const QPoint& position)
void CSVWidget::SceneToolMode::addButton(const std::string& icon, const std::string& id, const QString& tooltip)
{
ModeButton* button = new ModeButton(QIcon(QPixmap(icon.c_str())), tooltip, mPanel);
ModeButton* button = new ModeButton(QIcon(icon.c_str()), tooltip, mPanel);
addButton(button, id);
}

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

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

@ -88,7 +88,7 @@ void CSVWidget::SceneToolToggle2::addButton(
stream << mSingleIcon << id;
PushButton* button = new PushButton(
QIcon(QPixmap(stream.str().c_str())), PushButton::Type_Toggle, tooltip.isEmpty() ? name : tooltip, mPanel);
QIcon(stream.str().c_str()), PushButton::Type_Toggle, tooltip.isEmpty() ? name : tooltip, mPanel);
button->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
button->setIconSize(QSize(mIconSize, mIconSize));

@ -29,7 +29,7 @@ void CSVWorld::DragRecordTable::startDragFromTable(const CSVWorld::DragRecordTab
mime->setIndexAtDragStart(index);
QDrag* drag = new QDrag(this);
drag->setMimeData(mime);
drag->setPixmap(QString::fromUtf8(mime->getIcon().c_str()));
drag->setPixmap(QIcon(mime->getIcon().c_str()).pixmap(QSize(16, 16)));
drag->exec(Qt::CopyAction);
}

@ -46,41 +46,41 @@ QWidget* CSVWorld::IdCompletionDelegate::createEditor(QWidget* parent, const QSt
switch (conditionFunction)
{
case CSMWorld::ConstInfoSelectWrapper::Function_Global:
case ESM::DialogueCondition::Function_Global:
{
return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_GlobalVariable);
}
case CSMWorld::ConstInfoSelectWrapper::Function_Journal:
case ESM::DialogueCondition::Function_Journal:
{
return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Journal);
}
case CSMWorld::ConstInfoSelectWrapper::Function_Item:
case ESM::DialogueCondition::Function_Item:
{
return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Referenceable);
}
case CSMWorld::ConstInfoSelectWrapper::Function_Dead:
case CSMWorld::ConstInfoSelectWrapper::Function_NotId:
case ESM::DialogueCondition::Function_Dead:
case ESM::DialogueCondition::Function_NotId:
{
return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Referenceable);
}
case CSMWorld::ConstInfoSelectWrapper::Function_NotFaction:
case ESM::DialogueCondition::Function_NotFaction:
{
return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Faction);
}
case CSMWorld::ConstInfoSelectWrapper::Function_NotClass:
case ESM::DialogueCondition::Function_NotClass:
{
return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Class);
}
case CSMWorld::ConstInfoSelectWrapper::Function_NotRace:
case ESM::DialogueCondition::Function_NotRace:
{
return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Race);
}
case CSMWorld::ConstInfoSelectWrapper::Function_NotCell:
case ESM::DialogueCondition::Function_NotCell:
{
return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Cell);
}
case CSMWorld::ConstInfoSelectWrapper::Function_Local:
case CSMWorld::ConstInfoSelectWrapper::Function_NotLocal:
case ESM::DialogueCondition::Function_Local:
case ESM::DialogueCondition::Function_NotLocal:
{
return new CSVWidget::DropLineEdit(display, parent);
}

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

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

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

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

Loading…
Cancel
Save