Merge branch 'master' into 'LTO-timing'

# Conflicts:
#   CI/before_script.msvc.sh
LTO-timing
psi29a 2 years ago
commit a8d4ba535f

@ -0,0 +1,8 @@
---
Checks: "-*,
boost-*,
portability-*,
"
WarningsAsErrors: ''
HeaderFilterRegex: ''
FormatStyle: none

@ -0,0 +1,87 @@
name: CMake
on:
pull_request:
branches: [ master ]
env:
BUILD_TYPE: RelWithDebInfo
jobs:
Ubuntu:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Add OpenMW PPA Dependancies
run: sudo add-apt-repository ppa:openmw/openmw; sudo apt-get update
- name: Install Building Dependancies
run: sudo CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic
- name: Prime ccache
uses: hendrikmuhs/ccache-action@v1
with:
key: ${{ matrix.os }}-${{ env.BUILD_TYPE }}
max-size: 1000M
- name: Install gtest
run: |
export CONFIGURATION="Release"
export GOOGLETEST_DIR="."
export GENERATOR="Unix Makefiles"
export CC="gcc"
export CXX="g++"
sudo -E CI/build_googletest.sh
- name: Configure
run: cmake -S . -B . -DGTEST_ROOT="$(pwd)/googletest/build" -DGMOCK_ROOT="$(pwd)/googletest/build" -DBUILD_UNITTESTS=ON -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INSTALL_PREFIX=./install -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_FLAGS='-Werror' -DCMAKE_CXX_FLAGS="-Werror -Wno-error=deprecated-declarations -Wno-error=nonnull -Wno-error=deprecated-copy"
- name: Build
run: cmake --build . --config ${{env.BUILD_TYPE}} --parallel 3
- name: Test
run: ./openmw_test_suite
# - name: Install
# shell: bash
# run: cmake --install .
# - name: Create Artifact
# shell: bash
# working-directory: install
# run: |
# ls -laR
# 7z a ../build_artifact.7z .
# - name: Upload Artifact
# uses: actions/upload-artifact@v1
# with:
# path: ./build_artifact.7z
# name: build_artifact.7z
MacOS:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: Install Building Dependancies
run: CI/before_install.osx.sh
- name: Prime ccache
uses: hendrikmuhs/ccache-action@v1
with:
key: ${{ matrix.os }}-${{ env.BUILD_TYPE }}
max-size: 1000M
- name: Configure
run: |
rm -fr build # remove the build directory
CI/before_script.osx.sh
- name: Build
run: |
cd build
make -j $(sysctl -n hw.logicalcpu) package

14
.gitignore vendored

@ -5,7 +5,7 @@ CMakeCache.txt
cmake_install.cmake
Makefile
makefile
build*
build*/
prebuilt
##windows build process
@ -34,7 +34,6 @@ CMakeLists.txt.user*
.vscode
## resources
data
resources
/*.cfg
/*.desktop
@ -72,6 +71,7 @@ components/ui_contentselector.h
docs/mainpage.hpp
docs/Doxyfile
docs/DoxyfilePages
docs/source/reference/lua-scripting/generated_html
moc_*.cxx
*.cxx_parameters
*qrc_launcher.cxx
@ -84,13 +84,3 @@ moc_*.cxx
*.[ao]
*.so
venv/
## recastnavigation unused files
extern/recastnavigation/.travis.yml
extern/recastnavigation/CONTRIBUTING.md
extern/recastnavigation/Docs/
extern/recastnavigation/Doxyfile
extern/recastnavigation/README.md
extern/recastnavigation/RecastDemo/
extern/recastnavigation/Tests/
extern/recastnavigation/appveyor.yml

@ -1,130 +1,402 @@
default:
interruptible: true
# Note: We set `needs` on each job to control the job DAG.
# See https://docs.gitlab.com/ee/ci/yaml/#needs
stages:
- build
- test
# https://blog.nimbleways.com/let-s-make-faster-gitlab-ci-cd-pipelines/
variables:
FF_USE_NEW_SHELL_ESCAPE: "true"
FF_USE_FASTZIP: "true"
# These can be specified per job or per pipeline
ARTIFACT_COMPRESSION_LEVEL: "fast"
CACHE_COMPRESSION_LEVEL: "fast"
.Debian:
.Ubuntu_Image:
tags:
- docker
- linux
image: debian:bullseye
image: ubuntu:focal
rules:
- if: $CI_PIPELINE_SOURCE == "push"
.Ubuntu:
extends: .Ubuntu_Image
cache:
paths:
- apt-cache/
- ccache/
before_script:
- export APT_CACHE_DIR=`pwd`/apt-cache && mkdir -pv $APT_CACHE_DIR
- apt-get update -yq
- apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake build-essential libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-iostreams-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libsdl2-dev libqt5opengl5-dev libopenal-dev libopenscenegraph-dev libunshield-dev libtinyxml-dev libmygui-dev libbullet-dev liblz4-dev ccache git clang
stage: build
script:
- df -h
- export CCACHE_BASEDIR="`pwd`"
- export CCACHE_DIR="`pwd`/ccache" && mkdir -pv "$CCACHE_DIR"
- ccache -z -M "${CCACHE_SIZE}"
- CI/before_script.linux.sh
- cd build
- cmake --build . -- -j $(nproc)
- df -h
- du -sh .
- find . | grep '\.o$' | xargs rm -f
- df -h
- du -sh .
- cmake --install .
- if [[ "${BUILD_TESTS_ONLY}" ]]; then ./openmw_test_suite; fi
- if [[ "${BUILD_TESTS_ONLY}" ]]; then ./openmw_test_suite --gtest_output="xml:tests.xml"; fi
- if [[ "${BUILD_TESTS_ONLY}" && ! "${BUILD_WITH_CODE_COVERAGE}" ]]; then ./openmw_detournavigator_navmeshtilescache_benchmark; fi
- ccache -s
- df -h
- if [[ "${BUILD_WITH_CODE_COVERAGE}" ]]; then gcovr --xml-pretty --exclude-unreachable-branches --print-summary --root "${CI_PROJECT_DIR}" -j $(nproc) -o ../coverage.xml; fi
- ls | grep -v -e '^extern$' -e '^install$' -e '^tests.xml$' | xargs -I '{}' rm -rf './{}'
- cd ..
- df -h
- du -sh build/
- du -sh build/install/
- du -sh apt-cache/
- du -sh ccache/
artifacts:
paths:
- build/install/
Debian_GCC:
extends: .Debian
Coverity:
extends: .Ubuntu_Image
stage: build
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
before_script:
- CI/install_debian_deps.sh clang openmw-deps openmw-deps-dynamic
- curl -o /tmp/cov-analysis-linux64.tgz https://scan.coverity.com/download/linux64 --form project=$COVERITY_SCAN_PROJECT_NAME --form token=$COVERITY_SCAN_TOKEN
- tar xfz /tmp/cov-analysis-linux64.tgz
script:
- CI/before_script.linux.sh
# Remove the specific targets and build everything once we can do it under 3h
- cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build build -- -j $(nproc) openmw esmtool bsatool niftest openmw-wizard openmw-launcher openmw-iniimporter openmw-essimporter openmw-navmeshtool openmw-cs
after_script:
- tar cfz cov-int.tar.gz cov-int
- curl https://scan.coverity.com/builds?project=$COVERITY_SCAN_PROJECT_NAME
--form token=$COVERITY_SCAN_TOKEN --form email=$GITLAB_USER_EMAIL
--form file=@cov-int.tar.gz --form version="$CI_COMMIT_REF_NAME:$CI_COMMIT_SHORT_SHA"
--form description="CI_COMMIT_SHORT_SHA / $CI_COMMIT_TITLE / $CI_COMMIT_REF_NAME:$CI_PIPELINE_ID"
variables:
CC: clang
CXX: clang++
CXXFLAGS: -O0
artifacts:
paths:
- /builds/OpenMW/openmw/cov-int/build-log.txt
Ubuntu_GCC:
extends: .Ubuntu
cache:
key: Debian_GCC.v2
key: Ubuntu_GCC.v3
before_script:
- CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic
variables:
CC: gcc
CXX: g++
CCACHE_SIZE: 3G
CCACHE_SIZE: 4G
# 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
Debian_GCC_tests:
extends: .Debian
.Ubuntu_GCC_tests:
extends: Ubuntu_GCC
cache:
key: Debian_GCC_tests.v2
key: Ubuntu_GCC_tests.v3
variables:
CC: gcc
CXX: g++
CCACHE_SIZE: 1G
BUILD_TESTS_ONLY: 1
artifacts:
paths: []
name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA}
when: always
reports:
junit: build/tests.xml
Debian_Clang:
extends: .Debian
.Ubuntu_GCC_tests_Debug:
extends: Ubuntu_GCC
cache:
key: Debian_Clang.v2
key: Ubuntu_GCC_tests_Debug.v2
variables:
CCACHE_SIZE: 1G
BUILD_TESTS_ONLY: 1
CMAKE_BUILD_TYPE: Debug
CMAKE_CXX_FLAGS_DEBUG: -g -O0 -D_GLIBCXX_ASSERTIONS
artifacts:
paths: []
name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA}
when: always
reports:
junit: build/tests.xml
.Ubuntu_GCC_tests_asan:
extends: Ubuntu_GCC
cache:
key: Ubuntu_GCC_asan.v1
variables:
CCACHE_SIZE: 1G
BUILD_TESTS_ONLY: 1
CMAKE_BUILD_TYPE: Debug
CMAKE_CXX_FLAGS_DEBUG: -g -O1 -fno-omit-frame-pointer -fsanitize=address -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize=leak
CMAKE_EXE_LINKER_FLAGS: -fsanitize=address -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize=leak
ASAN_OPTIONS: halt_on_error=1:strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1
artifacts:
paths: []
name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA}
when: always
reports:
junit: build/tests.xml
.Ubuntu_GCC_tests_ubsan:
extends: Ubuntu_GCC
cache:
key: Ubuntu_GCC_ubsan.v1
variables:
CCACHE_SIZE: 1G
BUILD_TESTS_ONLY: 1
CMAKE_BUILD_TYPE: Debug
CMAKE_CXX_FLAGS_DEBUG: -g -O0 -fsanitize=undefined
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
artifacts:
paths: []
name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA}
when: always
reports:
junit: build/tests.xml
.Ubuntu_GCC_tests_tsan:
extends: Ubuntu_GCC
cache:
key: Ubuntu_GCC_tsan.v1
variables:
CCACHE_SIZE: 1G
BUILD_TESTS_ONLY: 1
CMAKE_BUILD_TYPE: Debug
CMAKE_CXX_FLAGS_DEBUG: -g -O2 -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsanitize=thread -fPIE
CMAKE_EXE_LINKER_FLAGS: -pthread -pie -fsanitize=thread
TSAN_OPTIONS: second_deadlock_stack=1:halt_on_error=1
artifacts:
paths: []
name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA}
when: always
reports:
junit: build/tests.xml
.Ubuntu_GCC_tests_coverage:
extends: .Ubuntu_GCC_tests_Debug
cache:
key: Ubuntu_GCC_tests_coverage.v1
variables:
BUILD_WITH_CODE_COVERAGE: 1
before_script:
- CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic openmw-coverage
coverage: /^\s*lines:\s*\d+.\d+\%/
artifacts:
paths: []
name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA}
when: always
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xml
junit: build/tests.xml
.Ubuntu_Static_Deps:
extends: Ubuntu_Clang
rules:
- if: $CI_PIPELINE_SOURCE == "push"
changes:
- "**/CMakeLists.txt"
- "cmake/**/*"
- "CI/**/*"
- ".gitlab-ci.yml"
cache:
key: Ubuntu_Static_Deps.V1
paths:
- apt-cache/
- ccache/
- build/extern/fetched/
before_script:
- CI/install_debian_deps.sh clang openmw-deps openmw-deps-static
variables:
CI_OPENMW_USE_STATIC_DEPS: 1
CC: clang
CXX: clang++
CCACHE_SIZE: 2G
CXXFLAGS: -O0
timeout: 3h
Debian_Clang_tests:
extends: .Debian
.Ubuntu_Static_Deps_tests:
extends: .Ubuntu_Static_Deps
cache:
key: Debian_Clang_tests.v2
key: Ubuntu_Static_Deps_tests.V1
variables:
CCACHE_SIZE: 1G
BUILD_TESTS_ONLY: 1
CC: clang
CXX: clang++
CXXFLAGS: -O0
artifacts:
paths: []
name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA}
when: always
reports:
junit: build/tests.xml
Ubuntu_Clang:
extends: .Ubuntu
before_script:
- CI/install_debian_deps.sh clang clang-tidy openmw-deps openmw-deps-dynamic
cache:
key: Ubuntu_Clang.v2
variables:
CC: clang
CXX: clang++
CI_CLANG_TIDY: 1
CCACHE_SIZE: 2G
# 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
.Ubuntu_Clang_tests:
extends: Ubuntu_Clang
cache:
key: Ubuntu_Clang_tests.v3
variables:
CCACHE_SIZE: 1G
BUILD_TESTS_ONLY: 1
artifacts:
paths: []
name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA}
when: always
reports:
junit: build/tests.xml
MacOS:
Ubuntu_Clang_tests_Debug:
extends: Ubuntu_Clang
cache:
key: Ubuntu_Clang_tests_Debug.v1
variables:
CCACHE_SIZE: 1G
BUILD_TESTS_ONLY: 1
CMAKE_BUILD_TYPE: Debug
artifacts:
paths: []
name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA}
when: always
reports:
junit: build/tests.xml
Ubuntu_Clang_integration_tests:
extends: .Ubuntu_Image
stage: test
needs:
- Ubuntu_Clang
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
cache:
key: Ubuntu_Clang_integration_tests.v1
paths:
- .cache/pip
- apt-cache/
before_script:
- CI/install_debian_deps.sh openmw-integration-tests
- pip3 install --user numpy matplotlib termtables click
script:
- CI/run_integration_tests.sh
.MacOS:
image: macos-11-xcode-12
tags:
- macos
- shared-macos-amd64
stage: build
only:
variables:
- $CI_PROJECT_ID == "7107382"
cache:
paths:
- ccache/
script:
- rm -fr build/* # remove anything in the build directory
- rm -fr build # remove the build directory
- CI/before_install.osx.sh
- export CCACHE_BASEDIR="$(pwd)"
- export CCACHE_DIR="$(pwd)/ccache"
- mkdir -pv "${CCACHE_DIR}"
- ccache -z -M "${CCACHE_SIZE}"
- CI/before_script.osx.sh
- cd build; make -j2 package
- for dmg in *.dmg; do mv "$dmg" "${dmg%.dmg}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}.dmg"; done
- cd build; make -j $(sysctl -n hw.logicalcpu) package
- for dmg in *.dmg; do mv "$dmg" "${dmg%.dmg}_${CI_COMMIT_REF_NAME##*/}_${CI_JOB_ID}.dmg"; done
- ccache -s
artifacts:
paths:
- build/OpenMW-*.dmg
- "build/**/*.log"
variables: &engine-targets
targets: "openmw,openmw-essimporter,openmw-iniimporter,openmw-launcher,openmw-wizard"
variables: &cs-targets
targets: "openmw-cs,bsatool,esmtool,niftest"
macOS12_Xcode13:
extends: .MacOS
image: macos-12-xcode-13
cache:
key: macOS12_Xcode13.v1
variables:
CCACHE_SIZE: 3G
.Windows_Ninja_Base:
tags:
- windows
rules:
- if: $CI_PIPELINE_SOURCE == "push"
before_script:
- Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1"
- choco source add -n=openmw-proxy -s="https://repo.openmw.org/repository/Chocolatey/" --priority=1
- choco source disable -n=chocolatey
- choco install git --force --params "/GitAndUnixToolsOnPath" -y
- choco install 7zip -y
- choco install ccache -y
- choco install cmake.install --installargs 'ADD_CMAKE_TO_PATH=System' -y
- choco install vswhere -y
- choco install ninja -y
- choco install python -y
- refreshenv
- |
function Make-SafeFileName {
param(
[Parameter(Mandatory=$true)]
[String]
$FileName
)
[IO.Path]::GetInvalidFileNameChars() | ForEach-Object {
$FileName = $FileName.Replace($_, '_')
}
return $FileName
}
stage: build
script:
- $time = (Get-Date -Format "HH:mm:ss")
- echo ${time}
- echo "started by ${GITLAB_USER_NAME}"
- sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -N
- $env:CCACHE_BASEDIR = Get-Location
- $env:CCACHE_DIR = "$(Get-Location)\ccache"
- New-Item -Type Directory -Force -Path $env:CCACHE_DIR
- sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -N -b -t -C $multiview -E
- cd MSVC2019_64_Ninja
- .\ActivateMSVC.ps1
- cmake --build . --config $config --target ($targets.Split(','))
- 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
- Get-ChildItem -Recurse *.ilk | Remove-Item
- |
if (Get-ChildItem -Recurse *.pdb) {
7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb'
7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt
Get-ChildItem -Recurse *.pdb | Remove-Item
}
- 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}.zip '*'
- 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" '*'
- if ($executables) { foreach ($exe in $executables.Split(',')) { & .\$exe } }
after_script:
- Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log
cache:
key: ninja-v2
key: ninja-v3
paths:
- ccache
- deps
- MSVC2019_64_Ninja/deps/Qt
artifacts:
@ -140,81 +412,92 @@ variables: &cs-targets
- MSVC2019_64_Ninja/*/*/*/*/*/*.log
- MSVC2019_64_Ninja/*/*/*/*/*/*/*.log
- MSVC2019_64_Ninja/*/*/*/*/*/*/*/*.log
# When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks.
timeout: 2h
Windows_Ninja_Engine_Release:
.Windows_Ninja_Release:
extends:
- .Windows_Ninja_Base
variables:
<<: *engine-targets
config: "Release"
Windows_Ninja_Engine_Debug:
.Windows_Ninja_Release_MultiView:
extends:
- .Windows_Ninja_Base
variables:
<<: *engine-targets
config: "Debug"
Windows_Ninja_Engine_RelWithDebInfo:
extends:
- .Windows_Ninja_Base
variables:
<<: *engine-targets
config: "RelWithDebInfo"
Windows_Ninja_CS_Release:
extends:
- .Windows_Ninja_Base
variables:
<<: *cs-targets
multiview: "-M"
config: "Release"
Windows_Ninja_CS_Debug:
.Windows_Ninja_Debug:
extends:
- .Windows_Ninja_Base
variables:
<<: *cs-targets
config: "Debug"
Windows_Ninja_CS_RelWithDebInfo:
.Windows_Ninja_RelWithDebInfo:
extends:
- .Windows_Ninja_Base
variables:
<<: *cs-targets
config: "RelWithDebInfo"
# Gitlab can't successfully execute following binaries due to unknown reason
# executables: "openmw_test_suite.exe,openmw_detournavigator_navmeshtilescache_benchmark.exe"
.Windows_MSBuild_Base:
tags:
- windows
rules:
- if: $CI_PIPELINE_SOURCE == "push"
before_script:
- Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1"
- choco source add -n=openmw-proxy -s="https://repo.openmw.org/repository/Chocolatey/" --priority=1
- choco source disable -n=chocolatey
- choco install git --force --params "/GitAndUnixToolsOnPath" -y
- choco install 7zip -y
- choco install ccache -y
- choco install cmake.install --installargs 'ADD_CMAKE_TO_PATH=System' -y
- choco install vswhere -y
- choco install python -y
- refreshenv
- |
function Make-SafeFileName {
param(
[Parameter(Mandatory=$true)]
[String]
$FileName
)
[IO.Path]::GetInvalidFileNameChars() | ForEach-Object {
$FileName = $FileName.Replace($_, '_')
}
return $FileName
}
stage: build
script:
- $time = (Get-Date -Format "HH:mm:ss")
- echo ${time}
- echo "started by ${GITLAB_USER_NAME}"
- sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V
- $env:CCACHE_BASEDIR = Get-Location
- $env:CCACHE_DIR = "$(Get-Location)\ccache"
- New-Item -Type Directory -Force -Path $env:CCACHE_DIR
- sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -b -t -C $multiview -E
- cd MSVC2019_64
- cmake --build . --config $config --target ($targets.Split(','))
- 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
- Get-ChildItem -Recurse *.ilk | Remove-Item
- |
if (Get-ChildItem -Recurse *.pdb) {
7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb'
7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt
Get-ChildItem -Recurse *.pdb | Remove-Item
}
- 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}.zip '*'
- 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" '*'
- if ($executables) { foreach ($exe in $executables.Split(',')) { & .\$exe } }
after_script:
- Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log
cache:
key: msbuild-v2
key: msbuild-v3
paths:
- ccache
- deps
- MSVC2019_64/deps/Qt
artifacts:
@ -230,68 +513,59 @@ Windows_Ninja_CS_RelWithDebInfo:
- MSVC2019_64/*/*/*/*/*/*.log
- MSVC2019_64/*/*/*/*/*/*/*.log
- MSVC2019_64/*/*/*/*/*/*/*/*.log
# When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks.
timeout: 2h
Windows_MSBuild_Engine_Release:
.Windows_MSBuild_Release:
extends:
- .Windows_MSBuild_Base
variables:
<<: *engine-targets
config: "Release"
# temporarily disabled while this isn't the thing we link on the downloads page
# rules:
# # run this for both pushes and schedules so 'latest successful pipeline for branch' always includes it
# - if: $CI_PIPELINE_SOURCE == "push"
# - if: $CI_PIPELINE_SOURCE == "schedule"
Windows_MSBuild_Engine_Debug:
.Windows_MSBuild_Debug:
extends:
- .Windows_MSBuild_Base
variables:
<<: *engine-targets
config: "Debug"
Windows_MSBuild_Engine_RelWithDebInfo:
Windows_MSBuild_RelWithDebInfo:
extends:
- .Windows_MSBuild_Base
variables:
<<: *engine-targets
config: "RelWithDebInfo"
# Gitlab can't successfully execute following binaries due to unknown reason
# executables: "openmw_test_suite.exe,openmw_detournavigator_navmeshtilescache_benchmark.exe"
# temporarily enabled while we're linking these on the downloads page
rules:
# run this for both pushes and schedules so 'latest successful pipeline for branch' always includes it
- if: $CI_PIPELINE_SOURCE == "push"
- if: $CI_PIPELINE_SOURCE == "schedule"
Windows_MSBuild_CS_Release:
extends:
- .Windows_MSBuild_Base
variables:
<<: *cs-targets
config: "Release"
Windows_MSBuild_CS_Debug:
extends:
- .Windows_MSBuild_Base
variables:
<<: *cs-targets
config: "Debug"
Windows_MSBuild_CS_RelWithDebInfo:
extends:
- .Windows_MSBuild_Base
variables:
<<: *cs-targets
config: "RelWithDebInfo"
Debian_AndroidNDK_arm64-v8a:
Ubuntu_AndroidNDK_arm64-v8a:
tags:
- linux
image: debian:bullseye
image: psi29a/android-ndk:focal-ndk22
rules:
- if: $CI_PIPELINE_SOURCE == "push"
variables:
CCACHE_SIZE: 3G
cache:
key: Debian_AndroidNDK_arm64-v8a.v2
key: Ubuntu__Focal_AndroidNDK_r22b_arm64-v8a.v2
paths:
- apt-cache/
- ccache/
- build/extern/fetched/
before_script:
- export APT_CACHE_DIR=`pwd`/apt-cache && mkdir -pv $APT_CACHE_DIR
- echo "deb http://deb.debian.org/debian unstable main contrib" > /etc/apt/sources.list
- echo "google-android-ndk-installer google-android-installers/mirror select https://dl.google.com" | debconf-set-selections
- apt-get update -yq
- apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake ccache curl unzip git build-essential google-android-ndk-installer
- CI/install_debian_deps.sh gcc
stage: build
script:
- df -h
- export CCACHE_BASEDIR="`pwd`"
- export CCACHE_DIR="`pwd`/ccache" && mkdir -pv "$CCACHE_DIR"
- ccache -z -M "${CCACHE_SIZE}"
@ -299,8 +573,35 @@ Debian_AndroidNDK_arm64-v8a:
- CI/before_script.android.sh
- cd build
- cmake --build . -- -j $(nproc)
- cmake --install .
# - cmake --install . # no one uses builds anyway, disable until 'no space left' is resolved
- ccache -s
- df -h
- ls | grep -v -e '^extern$' -e '^install$' | xargs -I '{}' rm -rf './{}'
- cd ..
- df -h
- du -sh build/
# - du -sh build/install/ # no install dir because it's commented out above
- du -sh apt-cache/
- du -sh ccache/
- du -sh build/extern/fetched/
artifacts:
paths:
- build/install/
# When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks.
timeout: 1h30m
.FindMissingMergeRequests:
image: python:latest
stage: build
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
cache:
key: FindMissingMergeRequests.v1
paths:
- .cache/pip
before_script:
- pip3 install --user requests click discord_webhook
script:
- scripts/find_missing_merge_requests.py --project_id=$CI_PROJECT_ID --ignored_mrs_path=$CI_PROJECT_DIR/.resubmitted_merge_requests.txt

@ -0,0 +1,10 @@
version: 2
sphinx:
configuration: docs/source/conf.py
python:
version: 3.8
install:
- requirements: docs/requirements.txt

@ -0,0 +1,8 @@
1471
1450
1420
1314
1216
1172
1160
1051

@ -1,105 +0,0 @@
language: cpp
branches:
only:
- master
- coverity_scan
- /openmw-.*$/
env:
global:
# The next declaration is the encrypted COVERITY_SCAN_TOKEN, created
# via the "travis encrypt" command using the project repo's public key
- secure: "jybGzAdUbqt9vWR/GEnRd96BgAi/7Zd1+2HK68j/i/8+/1YH2XxLOy4Jv/DUBhBlJIkxs/Xv8dRcUlFOclZDHX1d/9Qnsqd3oUVkD7k1y7cTOWy9TBQaE/v/kZo3LpzA3xPwwthrb0BvqIbOfIELi5fS5s8ba85WFRg3AX70wWE="
cache: ccache
addons:
apt:
sources:
- sourceline: 'ppa:openmw/openmw'
# - ubuntu-toolchain-r-test # for GCC-10
packages: [
# Dev
build-essential, cmake, clang-tools, ccache,
# Boost
libboost-filesystem-dev, libboost-iostreams-dev, libboost-program-options-dev, libboost-system-dev,
# FFmpeg
libavcodec-dev, libavformat-dev, libavutil-dev, libswresample-dev, libswscale-dev,
# Audio, Video and Misc. deps
libsdl2-dev, libqt5opengl5-dev, libopenal-dev, libunshield-dev, libtinyxml-dev, liblz4-dev
# The other ones from OpenMW ppa
libbullet-dev, libopenscenegraph-dev, libmygui-dev
]
coverity_scan: # TODO: currently takes too long, disabled openmw/openmw-cs for now.
project:
name: "OpenMW/openmw"
description: "<Your project description here>"
branch_pattern: coverity_scan
notification_email: 1122069+psi29a@users.noreply.github.com
build_command_prepend: "cov-configure --comptype gcc --compiler gcc-5 --template; cmake . -DBUILD_OPENMW=FALSE -DBUILD_OPENCS=FALSE"
build_command: "make VERBOSE=1 -j3"
matrix:
include:
- name: OpenMW (all) on MacOS 10.15 with Xcode 11.6
os: osx
osx_image: xcode11.6
if: branch != coverity_scan
- name: OpenMW (all) on Ubuntu Focal with GCC
os: linux
dist: focal
if: branch != coverity_scan
- name: OpenMW (tests only) on Ubuntu Focal with GCC
os: linux
dist: focal
if: branch != coverity_scan
env:
- BUILD_TESTS_ONLY: 1
- name: OpenMW (openmw) on Ubuntu Focal with Clang's Static Analysis
os: linux
dist: focal
env:
- MATRIX_EVAL="CC=clang && CXX=clang++"
- ANALYZE="scan-build --force-analyze-debug-code --use-cc clang --use-c++ clang++"
if: branch != coverity_scan
compiler: clang
- name: OpenMW Components Coverity Scan
os: linux
dist: focal
if: branch = coverity_scan
# allow_failures:
# - name: OpenMW (openmw) on Ubuntu Focal with GCC-10
# env:
# - MATRIX_EVAL="CC=gcc-10 && CXX=g++-10"
before_install:
- if [ "${TRAVIS_OS_NAME}" = "linux" ]; then eval "${MATRIX_EVAL}"; fi
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ]; then ./CI/before_install.${TRAVIS_OS_NAME}.sh; fi
before_script:
- ccache -z
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ]; then ./CI/before_script.${TRAVIS_OS_NAME}.sh; fi
script:
- cd ./build
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ]; then ${ANALYZE} make -j3; fi
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then ../CI/check_package.osx.sh; fi
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ] && [ "${BUILD_TESTS_ONLY}" ]; then ./openmw_test_suite; fi
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi
- cd "${TRAVIS_BUILD_DIR}"
- ccache -s
deploy:
provider: script
script: ./CI/deploy.osx.sh
skip_cleanup: true
on:
branch: master
condition: "$TRAVIS_EVENT_TYPE = cron && $TRAVIS_OS_NAME = osx"
repo: OpenMW/openmw
notifications:
email:
recipients:
- corrmage+travis-ci@gmail.com
on_success: change
on_failure: always
irc:
channels:
- "chat.freenode.net#openmw"
on_success: change
on_failure: always
use_notice: true

@ -4,7 +4,7 @@ Contributors
The OpenMW project was started in 2008 by Nicolay Korslund.
In the course of years many people have contributed to the project.
If you feel your name is missing from this list, please notify a developer.
If you feel your name is missing from this list, please add it to `AUTHORS.md`.
Programmers
@ -24,11 +24,14 @@ Programmers
Alex McKibben
alexanderkjall
Alexander Nadeau (wareya)
Alexander Olofsson (Ace)
Alexander Olofsson (Ananace)
Alex Rice
Alex S (docwest)
Alexey Yaryshev (skeevert)
Allofich
Andreas Stöckel
Andrei Kortunov (akortunov)
Andrew Appuhamy (andrew-app)
AnyOldName3
Ardekantur
Armin Preiml
@ -42,6 +45,7 @@ Programmers
Austin Salgat (Salgat)
Ben Shealy (bentsherman)
Berulacks
Bo Svensson
Britt Mathis (galdor557)
Capostrophic
Carl Maxwell
@ -49,6 +53,8 @@ Programmers
Cédric Mocquillon
Chris Boyce (slothlife)
Chris Robinson (KittyCat)
Cody Glassman (Wazabear)
Coleman Smith (olcoal)
Cory F. Cohen (cfcohen)
Cris Mihalache (Mirceam)
crussell187
@ -65,16 +71,19 @@ Programmers
David Teviotdale (dteviot)
Diggory Hardy
Dmitry Marakasov (AMDmi3)
Duncan Frost (duncans_pumpkin)
Edmondo Tommasina (edmondo)
Eduard Cot (trombonecot)
Eli2
Emanuel Guével (potatoesmaster)
Eris Caffee (eris)
eroen
escondida
Evgeniy Mineev (sandstranger)
Federico Guerra (FedeWar)
Fil Krynicki (filkry)
Finbar Crago(finbar-crago)
Florent Teppe (Tetramir)
Florian Weber (Florianjw)
Frédéric Chardon (fr3dz10)
Gaëtan Dezeiraud (Brouilles)
@ -86,16 +95,20 @@ Programmers
Haoda Wang (h313)
hristoast
Internecine
Ivan Beloborodov (myrix)
Jackerty
Jacob Essex (Yacoby)
Jacob Turnbull (Tankinfrank)
Jake Westrip (16bitint)
James Carty (MrTopCat)
James Moore (moore.work)
James Stephens (james-h-stephens)
Jan-Peter Nilsson (peppe)
Jan Borsodi (am0s)
JanuarySnow
Jason Hooks (jhooks)
jeaye
jefetienne
Jeffrey Haines (Jyby)
Jengerer
Jiří Kuneš (kunesj)
@ -104,6 +117,7 @@ Programmers
John Blomberg (fstp)
Jordan Ayers
Jordan Milne
Josquin Frei
Josua Grawitter
Jules Blok (Armada651)
julianko
@ -114,6 +128,7 @@ Programmers
Kurnevsky Evgeny (kurnevsky)
Lars Söderberg (Lazaroth)
lazydev
Léo Peltier
Leon Krieg (lkrieg)
Leon Saunders (emoose)
logzero
@ -121,7 +136,6 @@ Programmers
Lordrea
Łukasz Gołębiewski (lukago)
Lukasz Gromanowski (lgro)
Manuel Edelmann (vorenon)
Marc Bouvier (CramitDeFrog)
Marcin Hulist (Gohan)
Mark Siewert (mark76)
@ -130,6 +144,7 @@ Programmers
Martin Otto (MAtahualpa)
Mateusz Kołaczek (PL_kolek)
Mateusz Malisz (malice)
Max Henzerling (SaintMercury)
megaton
Michael Hogan (Xethik)
Michael Mc Donnell
@ -148,9 +163,12 @@ Programmers
Nathan Jeffords (blunted2night)
NeveHanter
Nialsy
Nick Crawford (nighthawk469)
Nikolay Kasyanov (corristo)
Noah Gooder
nobrakal
Nolan Poe (nopoe)
Nurivan Gomez (Nuri-G)
Oleg Chkan (mrcheko)
Paul Cercueil (pcercuei)
Paul McElroy (Greendogo)
@ -165,6 +183,7 @@ Programmers
PlutonicOverkill
Radu-Marius Popovici (rpopovici)
Rafael Moura (dhustkoder)
Randy Davin (Kindi)
rdimesio
rexelion
riothamus
@ -182,6 +201,7 @@ Programmers
sergoz
ShadowRadiance
Siimacore
Simon Meulenbeek (simonmb)
sir_herrbatka
smbas
Sophie Kirschner (pineapplemachine)
@ -195,21 +215,26 @@ Programmers
Sylvain Thesnieres (Garvek)
t6
terrorfisch
Tess (tescoShoppah)
thegriglat
Thomas Luppi (Digmaster)
tlmullis
tri4ng1e
Thoronador
Tobias Tribble (zackogenic)
Tom Lowe (Vulpen)
Tom Mason (wheybags)
Torben Leif Carrington (TorbenC)
unelsson
uramer
viadanna
Vidi_Aquam
Vincent Heuken
Vladimir Panteleev (CyberShadow)
vocollapse
Wang Ryu (bzzt)
Will Herrmann (Thunderforge)
vocollapse
Wolfgang Lieff
xyzz
Yohaulticetl
Yuri Krupenin
@ -228,11 +253,13 @@ Documentation
Joakim Berg (lysol90)
Ryan Tucker (Ravenwing)
sir_herrbatka
David Nagy (zuzaman)
Packagers
---------
Alexander Olofsson (Ace) - Windows
Alexander Olofsson (Ananace) - Windows and Flatpak
Alexey Sokolov (DarthGandalf) - Gentoo Linux
Bret Curtis (psi29a) - Debian and Ubuntu Linux
Edmondo Tommasina (edmondo) - Gentoo Linux
Julian Ospald (hasufell) - Gentoo Linux

@ -1,50 +1,262 @@
0.48.0
------
Bug #1751: Birthsign abilities increase modified attribute values instead of base ones
Bug #1930: Followers are still fighting if a target stops combat with a leader
Bug #2036: SetStat and ModStat instructions aren't implemented the same way as in Morrowind
Bug #3246: ESSImporter: Most NPCs are dead on save load
Bug #3488: AI combat aiming is too slow
Bug #3514: Editing a reference's position after loading an esp file makes the reference disappear
Bug #3737: Scripts from The Underground 2 .esp do not play (all patched versions)
Bug #3792: 1 frame late magicka recalc breaks early scripted magicka reactions to Intelligence change
Bug #3846: Strings starting with "-" fail to compile if not enclosed in quotes
Bug #3855: AI sometimes spams defensive spells
Bug #3867: All followers attack player when one follower enters combat with player
Bug #3905: Great House Dagoth issues
Bug #4203: Resurrecting an actor doesn't close the loot GUI
Bug #4376: Moved actors don't respawn in their original cells
Bug #4389: NPC's lips do not move if his head model has the NiBSAnimationNode root node
Bug #4602: Robert's Bodies: crash inside createInstance()
Bug #4700: Editor: Incorrect command implementation
Bug #4744: Invisible particles aren't always processed
Bug #4949: Incorrect particle lighting
Bug #5054: Non-biped creatures don't use spellcast equip/unequip animations
Bug #5088: Sky abruptly changes direction during certain weather transitions
Bug #5100: Persuasion doesn't always clamp the resulting disposition
Bug #5120: Scripted object spawning updates physics system
Bug #5192: Actor turn rate is too slow
Bug #5207: Loose summons can be present in scene
Bug #5279: Ingame console stops auto-scrolling after clicking output
Bug #5318: Aiescort behaves differently from vanilla
Bug #5371: 'Dead' slaughterfish added by mod are animated/alive
Bug #5377: Console does not appear after using menutest in inventory
Bug #5379: Wandering NPCs falling through cantons
Bug #5394: Windows snapping no longer works
Bug #5434: Pinned windows shouldn't cover breath progress bar
Bug #5453: Magic effect VFX are offset for creatures
Bug #5483: AutoCalc flag is not used to calculate spells cost
Bug #5508: Engine binary links to Qt without using it
Bug #5592: Weapon idle animations do not work properly
Bug #5596: Effects in constant spells should not be merged
Bug #5621: Drained stats cannot be restored
Bug #5766: Active grid object paging - disappearing textures
Bug #5788: Texture editing parses the selected indexes wrongly
Bug #5801: A multi-effect spell with the intervention effects and recall always favors Almsivi intervention
Bug #5842: GetDisposition adds temporary disposition change from different actors
Bug #5858: Visible modal windows and dropdowns crashing game on exit
Bug #5863: GetEffect should return true after the player has teleported
Bug #5913: Failed assertion during Ritual of Trees quest
Bug #5937: Lights always need to be rotated by 90 degrees
Bug #5989: Simple water isn't affected by texture filter settings
Bug #6037: Launcher: Morrowind content language cannot be set to English
Bug #6051: NaN water height in ESM file is not handled gracefully
Bug #6066: Addtopic "return" does not work from within script. No errors thrown
Bug #6067: ESP loader fails for certain subrecord orders
Bug #6087: Bound items added directly to the inventory disappear if their corresponding spell effect ends
Bug #6101: Disarming trapped unlocked owned objects isn't considered a crime
Bug #6107: Fatigue is incorrectly recalculated when fortify effect is applied or removed
Bug #6115: Showmap overzealous matching
Bug #6118: Creature landing sound counts as a footstep
Bug #6123: NPC with broken script freezes the game on hello
Bug #6129: Player avatar not displayed correctly for large window sizes when GUI scaling active
Bug #6131: Item selection in the avatar window not working correctly for large window sizes
Bug #6133: Cannot reliably sneak or steal in the sight of the NPCs siding with player
Bug #6142: Groundcover plugins change cells flags
Bug #6143: Capturing a screenshot renders the engine temporarily unresponsive
Bug #6165: Paralyzed player character can pickup items when the inventory is open
Bug #6168: Weather particles flicker for a frame at start of storms
Bug #6172: Some creatures can't open doors
Bug #6174: Spellmaking and Enchanting sliders differences from vanilla
Bug #6177: Followers of player follower stop following after waiting for a day
Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla
Bug #6191: Encumbrance messagebox timer works incorrectly
Bug #6197: Infinite Casting Loop
Bug #6253: Multiple instances of Reflect stack additively
Bug #6255: Reflect is different from vanilla
Bug #6256: Crash on exit with enabled shadows and statically linked OpenSceneGraph
Bug #6258: Barter menu glitches out when modifying prices
Bug #6273: Respawning NPCs rotation is inconsistent
Bug #6276: Deleted groundcover instances are not deleted in game
Bug #6282: Laura craft doesn't follow the player character
Bug #6283: Avis Dorsey follows you after her death
Bug #6285: Brush template drawing and terrain selection drawing performance is very bad
Bug #6289: Keyword search in dialogues expected the text to be all ASCII characters
Bug #6291: Can't pickup the dead mage's journal from the mysterious hunter mod
Bug #6302: Teleporting disabled actor breaks its disabled state
Bug #6303: After "go to jail" weapon can be stuck in the ready to attack state
Bug #6307: Pathfinding in Infidelities quest from Tribunal addon is broken
Bug #6321: Arrow enchantments should always be applied to the target
Bug #6322: Total sold/cost should reset to 0 when there are no items offered
Bug #6323: Wyrmhaven: Alboin doesn't follower the player character out of his house
Bug #6324: Special Slave Companions: Can't buy the slave companions
Bug #6326: Detect Enchantment/Key should detect items in unresolved containers
Bug #6327: Blocking roots the character in place
Bug #6333: Werewolf stat changes should be implemented as damage/fortifications
Bug #6343: Magic projectile speed doesn't take race weight into account
Bug #6347: PlaceItem/PlaceItemCell/PlaceAt should work with levelled creatures
Bug #6354: SFX abruptly cut off after crossing max distance
Bug #6358: Changeweather command does not report an error when entering non-existent region
Bug #6363: Some scripts in Morrowland fail to work
Bug #6376: Creatures should be able to use torches
Bug #6386: Artifacts in water reflection due to imprecise screen-space coordinate computation
Bug #6389: Maximum light distance setting doesn't affect water reflections
Bug #6395: Translations with longer tab titles may cause tabs to disappear from the options menu
Bug #6396: Inputting certain Unicode characters triggers an assertion
Bug #6416: Morphs are applied to the wrong target
Bug #6417: OpenMW doesn't always use the right node to accumulate movement
Bug #6429: Wyrmhaven: Can't add AI packages to player
Bug #6433: Items bound to Quick Keys sometimes do not appear until the Quick Key menu is opened
Bug #6451: Weapon summoned from Cast When Used item will have the name "None"
Bug #6473: Strings from NIF should be parsed only to first null terminator
Bug #6493: Unlocking owned but not locked or unlocked containers is considered a crime
Bug #6517: Rotations for KeyframeData in NIFs should be optional
Bug #6519: Effects tooltips for ingredients work incorrectly
Bug #6523: Disintegrate Weapon is resisted by Resist Magicka instead of Sanctuary
Bug #6544: Far from world origin objects jitter when camera is still
Bug #6559: Weapon condition inconsistency between melee and ranged critical / sneak / KO attacks
Bug #6579: OpenMW compilation error when using OSG doubles for BoundingSphere
Bug #6606: Quests with multiple IDs cannot always be restarted
Bug #6653: With default settings the in-game console doesn't fit into screen
Bug #6655: Constant effect absorb attribute causes the game to break
Bug #6667: Pressing the Esc key while resting or waiting causes black screen.
Bug #6670: Dialogue order is incorrect
Bug #6672: Garbage object refs in groundcover plugins like Vurt's grass plugins
Bug #6680: object.cpp handles nodetree unsafely, memory access with dangling pointer
Bug #6682: HitOnMe doesn't fire as intended
Bug #6697: Shaders vertex lighting incorrectly clamped
Bug #6711: Log time differs from real time
Bug #6717: Broken script causes interpreter stack corruption
Bug #6718: Throwable weapons cause arrow enchantment effect to be applied to the whole body
Bug #6730: LoopGroup stalls animation after playing :Stop frame until another animation is played
Bug #6753: Info records without a DATA subrecords are loaded incorrectly
Bug #6794: Light sources are attached to mesh bounds centers instead of mesh origins when AttachLight NiNode is missing
Bug #6799: Game crashes if an NPC has no Class attached
Bug #6849: ImageButton texture is not scaled properly
Feature #890: OpenMW-CS: Column filtering
Feature #1465: "Reset" argument for AI functions
Feature #2491: Ability to make OpenMW "portable"
Feature #2554: OpenMW-CS: Modifying an object in the cell view should trigger the instances table to scroll to the corresponding record
Feature #2766: Warn user if their version of Morrowind is not the latest.
Feature #2780: A way to see current OpenMW version in the console
Feature #2858: Add a tab to the launcher for handling datafolders
Feature #3245: Grid and angle snapping for the OpenMW-CS
Feature #3616: Allow Zoom levels on the World Map
Feature #4067: Post Processing
Feature #4297: Implement APPLIED_ONCE flag for magic effects
Feature #4414: Handle duration of EXTRA SPELL magic effect
Feature #4595: Unique object identifier
Feature #4974: Overridable MyGUI layout
Feature #4975: Built-in TrueType fonts
Feature #5198: Implement "Magic effect expired" event
Feature #5454: Clear active spells from actor when he disappears from scene
Feature #5489: MCP: Telekinesis fix for activators
Feature #5701: Convert osgAnimation::RigGeometry to double-buffered custom version
Feature #5737: Handle instance move from one cell to another
Feature #5928: Allow Glow in the Dahrk to be disabled
Feature #5996: Support Lua scripts in OpenMW
Feature #6017: Separate persistent and temporary cell references when saving
Feature #6019: Add antialias alpha test to the launcher or enable by default if possible
Feature #6032: Reverse-z depth buffer
Feature #6128: Soft Particles
Feature #6171: In-game log viewer
Feature #6189: Navigation mesh disk cache
Feature #6199: Support FBO Rendering
Feature #6248: Embedded error marker mesh
Feature #6249: Alpha testing support for Collada
Feature #6251: OpenMW-CS: Set instance movement based on camera zoom
Feature #6288: Preserve the "blocked" record flag for referenceable objects.
Feature #6360: More realistic raindrop ripples
Feature #6380: Treat commas as whitespace in scripts
Feature #6419: Don't grey out topics if they can produce another topic reference
Feature #6443: Support NiStencilProperty
Feature #6496: Handle NCC flag in NIF files
Feature #6534: Shader-based object texture blending
Feature #6541: Gloss-mapping
Feature #6557: Add support for controller gyroscope
Feature #6592: Support for NiTriShape particle emitters
Feature #6600: Support NiSortAdjustNode
Feature #6684: Support NiFltAnimationNode
Feature #6699: Support Ignored flag
Feature #6700: Support windowed fullscreen
Feature #6706: Save the size of the Options window
Feature #6721: [OpenMW-CS] Add option to open records in new window
Feature #6867: Add a way to localize hardcoded strings in GUI
Task #6078: First person should not clear depth buffer
Task #6161: Refactor Sky to use shaders and be GLES/GL3 friendly
Task #6162: Refactor GUI to use shaders and to be GLES and GL3+ friendly
Task #6201: Remove the "Note: No relevant classes found. No output generated" warnings
Task #6264: Remove the old classes in animation.cpp
Task #6553: Simplify interpreter instruction registration
Task #6564: Remove predefined data paths `data="?global?data"`, `data=./data`
Task #6631: Fix ffmpeg avio API usage causing hangs in ffmpeg version 5
Task #6709: Move KeyframeController transformation magic to NifOsg::MatrixTransform
Task #6763: gcc 12.1 multiple compiler warnings
0.47.0
------
Bug #1662: Qt4 and Windows binaries crash if there's a non-ASCII character in a file path/config path
Bug #1901: Actors colliding behaviour is different from vanilla
Bug #1952: Incorrect particle lighting
Bug #2069: Fireflies in Fireflies invade Morrowind look wrong
Bug #2311: Targeted scripts are not properly supported on non-unique RefIDs
Bug #2473: Unable to overstock merchants
Bug #2798: Mutable ESM records
Bug #2976 [reopened]: Issues combining settings from the command line and both config files
Bug #2976: [reopened]: Issues combining settings from the command line and both config files
Bug #3137: Walking into a wall prevents jumping
Bug #3372: Projectiles and magic bolts go through moving targets
Bug #3676: NiParticleColorModifier isn't applied properly
Bug #3714: Savegame fails to load due to conflict between SpellState and MagicEffects
Bug #3789: Crash in visitEffectSources while in battle
Bug #3862: Random container contents behave differently than vanilla
Bug #3929: Leveled list merchant containers respawn on barter
Bug #4021: Attributes and skills are not stored as floats
Bug #4039: Multiple followers should have the same following distance
Bug #4055: Local scripts don't inherit variables from their base record
Bug #4083: Door animation freezes when colliding with actors
Bug #4247: Cannot walk up stairs in Ebonheart docks
Bug #4357: OpenMW-CS: TopicInfos index sorting and rearranging isn't fully functional
Bug #4363: OpenMW-CS: Defect in Clone Function for Dialogue Info records
Bug #4447: Actor collision capsule shape allows looking through some walls
Bug #4465: Collision shape overlapping causes twitching
Bug #4476: Abot Gondoliers: player hangs in air during scenic travel
Bug #4568: Too many actors in one spot can push other actors out of bounds
Bug #4623: Corprus implementation is incorrect
Bug #4631: Setting MSAA level too high doesn't fall back to highest supported level
Bug #4764: Data race in osg ParticleSystem
Bug #4765: Data race in ChunkManager -> Array::setBinding
Bug #4774: Guards are ignorant of an invisible player that tries to attack them
Bug #5026: Data races with rain intensity uniform set by sky and used by water
Bug #5101: Hostile followers travel with the player
Bug #5108: Savegame bloating due to inefficient fog textures format
Bug #5165: Active spells should use real time intead of timestamps
Bug #5300: NPCs don't switch from torch to shield when starting combat
Bug #5358: ForceGreeting always resets the dialogue window completely
Bug #5363: Enchantment autocalc not always 0/1
Bug #5364: Script fails/stops if trying to startscript an unknown script
Bug #5367: Selecting a spell on an enchanted item per hotkey always plays the equip sound
Bug #5369: Spawnpoint in the Grazelands doesn't produce oversized creatures
Bug #5370: Opening an unlocked but trapped door uses the key
Bug #5384: openmw-cs: deleting an instance requires reload of scene window to show in editor
Bug #5384: OpenMW-CS: Deleting an instance requires reload of scene window to show in editor
Bug #5387: Move/MoveWorld don't update the object's cell properly
Bug #5391: Races Redone 1.2 bodies don't show on the inventory
Bug #5397: NPC greeting does not reset if you leave + reenter area
Bug #5400: Editor: Verifier checks race of non-skin bodyparts
Bug #5400: OpenMW-CS: Verifier checks race of non-skin bodyparts
Bug #5403: Enchantment effect doesn't show on an enemy during death animation
Bug #5415: Environment maps in ebony cuirass and HiRez Armors Indoril cuirass don't work
Bug #5416: Junk non-node records before the root node are not handled gracefully
Bug #5422: The player loses all spells when resurrected
Bug #5423: Guar follows actors too closely
Bug #5424: Creatures do not headtrack player
Bug #5425: Poison effect only appears for one frame
Bug #5427: GetDistance unknown ID error is misleading
Bug #5431: Physics performance degradation after a specific number of actors on a scene
Bug #5435: Enemies can't hurt the player when collision is off
Bug #5441: Enemies can't push a player character when in critical strike stance
Bug #5451: Magic projectiles don't disappear with the caster
Bug #5452: Autowalk is being included in savegames
Bug #5469: Local map is reset when re-entering certain cells
Bug #5472: Mistify mod causes CTD in 0.46 on Mac
Bug #5473: OpenMW-CS: Cell border lines don't update properly on terrain change
Bug #5479: NPCs who should be walking around town are standing around without walking
Bug #5484: Zero value items shouldn't be able to be bought or sold for 1 gold
Bug #5485: Intimidate doesn't increase disposition on marginal wins
@ -52,33 +264,98 @@
Bug #5499: Faction advance is available when requirements not met
Bug #5502: Dead zone for analogue stick movement is too small
Bug #5507: Sound volume is not clamped on ingame settings update
Bug #5525: Case-insensitive search in the inventory window does not work with non-ASCII characters
Bug #5531: Actors flee using current rotation by axis x
Bug #5539: Window resize breaks when going from a lower resolution to full screen resolution
Bug #5548: Certain exhausted topics can be highlighted again even though there's no new dialogue
Bug #5557: Diagonal movement is noticeably slower with analogue stick
Bug #5588: Randomly clicking on the journal's right-side page when it's empty shows random topics
Bug #5603: Setting constant effect cast style doesn't correct effects view
Bug #5604: Only one valid NIF root node is loaded from a single file
Bug #5611: Usable items with "0 Uses" should be used only once
Bug #5619: Input events are queued during save loading
Bug #5622: Can't properly interact with the console when in pause menu
Bug #5627: Bookart not shown if it isn't followed by <BR> statement
Bug #5633: Damage Spells in effect before god mode is enabled continue to hurt the player character and can kill them
Bug #5639: Tooltips cover Messageboxes
Bug #5644: Summon effects running on the player during game initialization cause crashes
Bug #5656: Sneaking characters block hits while standing
Bug #5661: Region sounds don't play at the right interval
Bug #5675: OpenMW-CS: FRMR subrecords are saved with the wrong MastIdx
Bug #5680: Bull Netches incorrectly aim over the player character's head and always miss
Bug #5681: Player character can clip or pass through bridges instead of colliding against them
Bug #5687: Bound items covering the same inventory slot expiring at the same time freezes the game
Bug #5688: Water shader broken indoors with enable indoor shadows = false
Bug #5695: ExplodeSpell for actors doesn't target the ground
Bug #5703: OpenMW-CS menu system crashing on XFCE
Bug #5706: AI sequences stop looping after the saved game is reloaded
Bug #5713: OpenMW-CS: Collada models are corrupted in Qt-based scene view
Bug #5731: OpenMW-CS: skirts are invisible on characters
Bug #5739: Saving and loading the save a second or two before hitting the ground doesn't count fall damage
Bug #5758: Paralyzed actors behavior is inconsistent with vanilla
Bug #5762: Movement solver is insufficiently robust
Bug #5800: Equipping a CE enchanted ring deselects an already equipped and selected enchanted ring from the spell menu
Bug #5807: Video decoding crash on ARM
Bug #5821: NPCs from mods getting removed if mod order was changed
Bug #5835: OpenMW doesn't accept negative values for NPC's hello, alarm, fight, and flee
Bug #5836: OpenMW dialogue/greeting/voice filter doesn't accept negative Ai values for NPC's hello, alarm, fight, and flee
Bug #5838: Local map and other menus become blank in some locations while playing Wizards' Islands mod.
Bug #5840: GetSoundPlaying "Health Damage" doesn't play when NPC hits target with shield effect ( vanilla engine behavior )
Bug #5841: Can't Cast Zero Cost Spells When Magicka is < 0
Bug #5869: Guards can initiate arrest dialogue behind locked doors
Bug #5871: The console appears if you type the Russian letter "Ё" in the name of the enchantment
Bug #5877: Effects appearing with empty icon
Bug #5899: Visible modal windows and dropdowns crashing game on exit
Bug #5902: NiZBufferProperty is unable to disable the depth test
Bug #5906: Sunglare doesn't work with Mesa drivers and AMD GPUs
Bug #5912: ImprovedBound mod doesn't work
Bug #5914: BM: The Swimmer can't reach destination
Bug #5923: Clicking on empty spaces between journal entries might show random topics
Bug #5934: AddItem command doesn't accept negative values
Bug #5975: NIF controllers from sheath meshes are used
Bug #5991: Activate should always be allowed for inventory items
Bug #5995: NiUVController doesn't calculate the UV offset properly
Bug #6007: Crash when ending cutscene is playing
Bug #6016: Greeting interrupts Fargoth's sneak-walk
Bug #6022: OpenMW-CS: Terrain selection is not updated when undoing/redoing terrain changes
Bug #6023: OpenMW-CS: Clicking on a reference in "Terrain land editing" mode discards corresponding select/edit action
Bug #6028: Particle system controller values are incorrectly used
Bug #6035: OpenMW-CS: Circle brush in "Terrain land editing" mode sometimes includes vertices outside its radius
Bug #6036: OpenMW-CS: Terrain selection at the border of cells omits certain corner vertices
Bug #6043: Actor can have torch missing when torch animation is played
Bug #6047: Mouse bindings can be triggered during save loading
Bug #6136: Game freezes when NPCs try to open doors that are about to be closed
Bug #6294: Game crashes with empty pathgrid
Feature #390: 3rd person look "over the shoulder"
Feature #832: OpenMW-CS: Handle deleted references
Feature #1536: Show more information about level on menu
Feature #2159: "Graying out" exhausted dialogue topics
Feature #2386: Distant Statics in the form of Object Paging
Feature #2404: Levelled List can not be placed into a container
Feature #2686: Timestamps in openmw.log
Feature #2798: Mutable ESM records
Feature #3171: OpenMW-CS: Instance drag selection
Feature #3983: Wizard: Add link to buy Morrowind
Feature #4201: Projectile-projectile collision
Feature #4486: Handle crashes on Windows
Feature #4894: Consider actors as obstacles for pathfinding
Feature #4899: Alpha-To-Coverage Anti-Aliasing for alpha testing
Feature #4917: Do not trigger NavMesh update when RecastMesh update should not change NavMesh
Feature #4977: Use the "default icon.tga" when an item's icon is not found
Feature #5043: Head Bobbing
Feature #5199: OpenMW-CS: Improve scene view colors
Feature #5297: Add a search function to the "Datafiles" tab of the OpenMW launcher
Feature #5362: Show the soul gems' trapped soul in count dialog
Feature #5445: Handle NiLines
Feature #5456: Basic collada animation support
Feature #5457: Realistic diagonal movement
Feature #5486: Fixes trainers to choose their training skills based on their base skill points
Feature #5500: Prepare enough navmesh tiles before scene loading ends
Feature #5511: Add in game option to toggle HRTF support in OpenMW
Feature #5519: Code Patch tab in launcher
Feature #5524: Resume failed script execution after reload
Feature #5525: Search fields tweaks (utf-8)
Feature #5545: Option to allow stealing from an unconscious NPC during combat
Feature #5551: Do not reboot PC after OpenMW installation on Windows
Feature #5563: Run physics update in background thread
Feature #5579: MCP SetAngle enhancement
Feature #5580: Service refusal filtering
@ -86,6 +363,17 @@
Feature #5642: Ability to attach arrows to actor skeleton instead of bow mesh
Feature #5649: Skyrim SE compressed BSA format support
Feature #5672: Make stretch menu background configuration more accessible
Feature #5692: Improve spell/magic item search to factor in magic effect names
Feature #5730: Add graphic herbalism option to the launcher and documents
Feature #5771: ori command should report where a mesh is loaded from and whether the x version is used.
Feature #5813: Instanced groundcover support
Feature #5814: Bsatool should be able to create BSA archives, not only to extract it
Feature #5828: Support more than 8 lights
Feature #5910: Fall back to delta time when physics can't keep up
Feature #5980: Support Bullet with double precision instead of one with single precision
Feature #6024: OpenMW-CS: Selecting terrain in "Terrain land editing" should support "Add to selection" and "Remove from selection" modes
Feature #6033: Include pathgrid to navigation mesh
Feature #6034: Find path based on area cost depending on NPC stats
Task #5480: Drop Qt4 support
Task #5520: Improve cell name autocompleter implementation
@ -97,7 +385,7 @@
Bug #2395: Duplicated plugins in the launcher when multiple data directories provide the same plugin
Bug #2679: Unable to map mouse wheel under control settings
Bug #2969: Scripted items can stack
Bug #2976: Data lines in global openmw.cfg take priority over user openmw.cfg
Bug #2976: [reopened in 0.47] Data lines in global openmw.cfg take priority over user openmw.cfg
Bug #2987: Editor: some chance and AI data fields can overflow
Bug #3006: 'else if' operator breaks script compilation
Bug #3109: SetPos/Position handles actors differently
@ -115,7 +403,6 @@
Bug #4009: Launcher does not show data files on the first run after installing
Bug #4077: Enchanted items are not recharged if they are not in the player's inventory
Bug #4141: PCSkipEquip isn't set to 1 when reading books/scrolls
Bug #4202: Open .omwaddon files without needing toopen openmw-cs first
Bug #4240: Ash storm origin coordinates and hand shielding animation behavior are incorrect
Bug #4262: Rain settings are hardcoded
Bug #4270: Closing doors while they are obstructed desyncs closing sfx
@ -205,7 +492,6 @@
Bug #4964: Multiple effect spell projectile sounds play louder than vanilla
Bug #4965: Global light attenuation settings setup is lacking
Bug #4969: "Miss" sound plays for any actor
Bug #4971: OpenMW-CS: Make rotations display as degrees instead of radians
Bug #4972: Player is able to use quickkeys while disableplayerfighting is active
Bug #4979: AiTravel maximum range depends on "actors processing range" setting
Bug #4980: Drowning mechanics is applied for actors indifferently from distance to player
@ -303,7 +589,6 @@
Bug #5350: An attempt to launch magic bolt causes "AL error invalid value" error
Bug #5352: Light source items' duration is decremented while they aren't visible
Feature #1724: Handle AvoidNode
Feature #2159: "Graying out" exhausted dialogue topics
Feature #2229: Improve pathfinding AI
Feature #3025: Analogue gamepad movement controls
Feature #3442: Default values for fallbacks from ini file
@ -316,6 +601,7 @@
Feature #4001: Toggle sneak controller shortcut
Feature #4068: OpenMW-CS: Add a button to reset key bindings to defaults
Feature #4129: Beta Comment to File
Feature #4202: Open .omwaddon files without needing to open openmw-cs first
Feature #4209: Editor: Faction rank sub-table
Feature #4255: Handle broken RepairedOnMe script function
Feature #4316: Implement RaiseRank/LowerRank functions properly
@ -338,6 +624,7 @@
Feature #4958: Support eight blood types
Feature #4962: Add casting animations for magic items
Feature #4968: Scalable UI widget skins
Feature #4971: OpenMW-CS: Make rotations display as degrees instead of radians
Feature #4994: Persistent pinnable windows hiding
Feature #5000: Compressed BSA format support
Feature #5005: Editor: Instance window via Scene window
@ -1814,6 +2101,7 @@
Bug #2025: Missing mouse-over text for non affordable items
Bug #2028: [MOD: Tamriel Rebuilt] Crashing when trying to enter interior cell "Ruinous Keep, Great Hall"
Bug #2029: Ienith Brothers Thiev's Guild quest journal entry not adding
Bug #3066: Editor doesn't check if IDs and other strings are longer than their hardcoded field length
Feature #471: Editor: Special case implementation for top-level window with single sub-window
Feature #472: Editor: Sub-Window re-use settings
Feature #704: Font colors import from fallback settings

@ -18,9 +18,10 @@ Known Issues:
New Features:
- Dialogue to split item stacks now displays the name of the trapped soul for stacks of soul gems (#5362)
- Basics of Collada animations are now supported via osgAnimation plugin (#5456)
New Editor Features:
- ?
- Instance selection modes are now implemented (centred cube, corner-dragged cube, sphere) with four user-configurable actions (select only, add to selection, remove from selection, invert selection) (#3171)
Bug Fixes:
- NiParticleColorModifier in NIF files is now properly handled which solves issues regarding particle effects, e.g., smoke and fire (#1952, #3676)
@ -33,8 +34,20 @@ Bug Fixes:
- Morrowind legacy madness: Using a key on a trapped door/container now only disarms the trap if the door/container is locked (#5370)
Editor Bug Fixes:
- Deleted and moved objects within a cell are now saved properly (#832)
- Disabled record sorting in Topic and Journal Info tables, implemented drag-move for records (#4357)
- Topic and Journal Info records can now be cloned with a different parent Topic/Journal Id (#4363)
- Verifier no longer checks for alleged 'race' entries in clothing body parts (#5400)
- Cell borders are now properly redrawn when undoing/redoing terrain changes (#5473)
- Loading mods now keeps the master index (#5675)
- Flicker and crashing on XFCE4 fixed (#5703)
- Collada models render properly in the Editor (#5713)
- Terrain-selection grid is now properly updated when undoing/redoing terrain changes (#6022)
- Tool outline and select/edit actions in "Terrain land editing" mode now ignore references (#6023)
- Primary-select and secondary-select actions in "Terrain land editing" mode now behave like in "Instance editing" mode (#6024)
- Using the circle brush to select terrain in the "Terrain land editing" mode no longer selects vertices outside the circle (#6035)
- Vertices at the NW and SE corners of a cell can now also be selected in "Terrain land editing" mode if the adjacent cells aren't loaded yet (#6036)
Miscellaneous:
- Prevent save-game bloating by using an appropriate fog texture format (#5108)
- Ensure that 'Enchantment autocalc" flag is treated as flag in OpenMW-CS and in our esm tools (#5363)
- Ensure that 'Enchantment autocalc" flag is treated as flag in OpenMW-CS and in our esm tools (#5363)

@ -30,55 +30,11 @@ command -v unixPathAsWindows >/dev/null 2>&1 || function unixPathAsWindows {
fi
}
function windowsSystemPathAsUnix {
if command -v cygpath >/dev/null 2>&1; then
cygpath -u -p $1
else
IFS=';' read -r -a paths <<< "$1"
declare -a convertedPaths
for entry in paths; do
convertedPaths+=(windowsPathAsUnix $entry)
done
convertedPath=printf ":%s" ${convertedPaths[@]}
echo ${convertedPath:1}
fi
}
# capture CMD environment so we know what's been changed
declare -A originalCmdEnv
originalIFS="$IFS"
IFS=$'\n\r'
for pair in $(cmd //c "set"); do
IFS='=' read -r -a separatedPair <<< "${pair}"
if [ ${#separatedPair[@]} -ne 2 ]; then
echo "Parsed '$pair' as ${#separatedPair[@]} parts, expected 2."
continue
fi
originalCmdEnv["${separatedPair[0]}"]="${separatedPair[1]}"
done
# capture CMD environment in a shell with MSVC activated
cmdEnv="$(cmd //c "$(unixPathAsWindows "$(dirname "${BASH_SOURCE[0]}")")\ActivateMSVC.bat" "&&" set)"
declare -A cmdEnvChanges
for pair in $cmdEnv; do
if [ -n "$pair" ]; then
IFS='=' read -r -a separatedPair <<< "${pair}"
if [ ${#separatedPair[@]} -ne 2 ]; then
echo "Parsed '$pair' as ${#separatedPair[@]} parts, expected 2."
continue
fi
key="${separatedPair[0]}"
value="${separatedPair[1]}"
if ! [ ${originalCmdEnv[$key]+_} ] || [ "${originalCmdEnv[$key]}" != "$value" ]; then
if [ $key != 'PATH' ] && [ $key != 'path' ] && [ $key != 'Path' ]; then
export "$key=$value"
else
export PATH=$(windowsSystemPathAsUnix $value)
fi
fi
fi
done
cmd //c "$(unixPathAsWindows "$(dirname "${BASH_SOURCE[0]}")")\ActivateMSVC.bat" "&&" "bash" "-c" "declare -px > declared_env.sh"
source ./declared_env.sh
rm declared_env.sh
MISSINGTOOLS=0
@ -93,6 +49,4 @@ if [ $MISSINGTOOLS -ne 0 ]; then
return 1
fi
IFS="$originalIFS"
restoreOldSettings
restoreOldSettings

@ -1,4 +1,4 @@
#!/bin/sh -ex
curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/android/openmw-android-deps-20201018.zip -o ~/openmw-android-deps.zip
unzip -o ~/openmw-android-deps -d /usr/lib/android-sdk/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr > /dev/null
curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/android/openmw-android-deps-20211114.zip -o ~/openmw-android-deps.zip
unzip -o ~/openmw-android-deps -d /android-ndk-r22/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr > /dev/null

@ -1,9 +1,27 @@
#!/bin/sh -e
#!/bin/sh -ex
export HOMEBREW_NO_EMOJI=1
brew update --quiet
# workaround python issue on travis
[ -z "${TRAVIS}" ] && brew uninstall --ignore-dependencies python@3.8 || true
[ -z "${TRAVIS}" ] && brew uninstall --ignore-dependencies python@3.9 || true
[ -z "${TRAVIS}" ] && brew uninstall --ignore-dependencies qt@6 || true
# Some of these tools can come from places other than brew, so check before installing
[ -z "${TRAVIS}" ] && brew reinstall xquartz
[ -z "${TRAVIS}" ] && brew reinstall fontconfig
command -v ccache >/dev/null 2>&1 || brew install ccache
command -v cmake >/dev/null 2>&1 || brew install cmake
command -v qmake >/dev/null 2>&1 || brew install qt
command -v qmake >/dev/null 2>&1 || brew install qt@5
brew install icu4c
brew install yaml-cpp
export PATH="/usr/local/opt/qt@5/bin:$PATH" # needed to use qmake in none default path as qt now points to qt6
curl -fSL -R -J https://downloads.openmw.org/osx/dependencies/openmw-deps-f8918dd.zip -o ~/openmw-deps.zip
ccache --version
cmake --version
qmake --version
curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20220225.zip -o ~/openmw-deps.zip
unzip -o ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null

@ -3,13 +3,31 @@
# hack to work around: FFmpeg version is too old, 3.2 is required
sed -i s/"NOT FFVER_OK"/"FALSE"/ CMakeLists.txt
mkdir build
# Silence a git warning
git config --global advice.detachedHead false
mkdir -p build
cd build
# Build a version of ICU for the host so that it can use the tools during the cross-compilation
mkdir -p icu-host-build
cd icu-host-build
if [ -r ../extern/fetched/icu/icu4c/source/configure ]; then
ICU_SOURCE_DIR=../extern/fetched/icu/icu4c/source
else
wget https://github.com/unicode-org/icu/archive/refs/tags/release-70-1.zip
unzip release-70-1.zip
ICU_SOURCE_DIR=./icu-release-70-1/icu4c/source
fi
${ICU_SOURCE_DIR}/configure --disable-tests --disable-samples --disable-icuio --disable-extras CC="ccache gcc" CXX="ccache g++"
make -j $(nproc)
cd ..
cmake \
-DCMAKE_TOOLCHAIN_FILE=/usr/lib/android-sdk/ndk-bundle/build/cmake/android.toolchain.cmake \
-DCMAKE_TOOLCHAIN_FILE=/android-ndk-r22/build/cmake/android.toolchain.cmake \
-DANDROID_ABI=arm64-v8a \
-DANDROID_PLATFORM=android-21 \
-DANDROID_LD=deprecated \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DCMAKE_INSTALL_PREFIX=install \
@ -21,5 +39,11 @@ cmake \
-DBUILD_ESSIMPORTER=0 \
-DBUILD_OPENCS=0 \
-DBUILD_WIZARD=0 \
-DMyGUI_LIBRARY="/usr/lib/android-sdk/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/libMyGUIEngineStatic.a" \
-DBUILD_NAVMESHTOOL=OFF \
-DBUILD_BULLETOBJECTTOOL=OFF \
-DOPENMW_USE_SYSTEM_MYGUI=OFF \
-DOPENMW_USE_SYSTEM_SQLITE3=OFF \
-DOPENMW_USE_SYSTEM_YAML_CPP=OFF \
-DOPENMW_USE_SYSTEM_ICU=OFF \
-DOPENMW_ICU_HOST_BUILD_DIR="$(pwd)/icu-host-build" \
..

@ -1,44 +1,109 @@
#!/bin/bash -ex
#!/bin/bash
set -xeo pipefail
free -m
# Silence a git warning
git config --global advice.detachedHead false
BUILD_UNITTESTS=OFF
BUILD_BENCHMARKS=OFF
if [[ "${BUILD_TESTS_ONLY}" ]]; then
export GOOGLETEST_DIR="$(pwd)/googletest/build/install"
export GOOGLETEST_DIR="${PWD}/googletest/build/install"
env GENERATOR='Unix Makefiles' CONFIGURATION=Release CI/build_googletest.sh
BUILD_UNITTESTS=ON
BUILD_BENCHMARKS=ON
fi
# setup our basic cmake build options
declare -a CMAKE_CONF_OPTS=(
-DCMAKE_C_COMPILER="${CC:-/usr/bin/cc}"
-DCMAKE_CXX_COMPILER="${CXX:-/usr/bin/c++}"
-DCMAKE_C_COMPILER_LAUNCHER=ccache
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache
-DCMAKE_INSTALL_PREFIX=install
-DCMAKE_BUILD_TYPE=RelWithDebInfo
-DBUILD_SHARED_LIBS=OFF
-DUSE_SYSTEM_TINYXML=ON
-DOPENMW_USE_SYSTEM_RECASTNAVIGATION=ON
-DOPENMW_CXX_FLAGS="-Werror -Werror=implicit-fallthrough" # flags specific to OpenMW project
)
if [[ $CI_OPENMW_USE_STATIC_DEPS ]]; then
CMAKE_CONF_OPTS+=(
-DOPENMW_USE_SYSTEM_MYGUI=OFF
-DOPENMW_USE_SYSTEM_OSG=OFF
-DOPENMW_USE_SYSTEM_BULLET=OFF
-DOPENMW_USE_SYSTEM_SQLITE3=OFF
-DOPENMW_USE_SYSTEM_RECASTNAVIGATION=OFF
)
fi
if [[ $CI_CLANG_TIDY ]]; then
CMAKE_CONF_OPTS+=(
-DCMAKE_CXX_CLANG_TIDY="clang-tidy;--warnings-as-errors=*"
)
fi
mkdir build
if [[ "${CMAKE_BUILD_TYPE}" ]]; then
CMAKE_CONF_OPTS+=(
-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
)
fi
if [[ "${CMAKE_CXX_FLAGS_DEBUG}" ]]; then
CMAKE_CONF_OPTS+=(
-DCMAKE_CXX_FLAGS_DEBUG="${CMAKE_CXX_FLAGS_DEBUG}"
)
fi
if [[ "${CMAKE_EXE_LINKER_FLAGS}" ]]; then
CMAKE_CONF_OPTS+=(
-DCMAKE_EXE_LINKER_FLAGS="${CMAKE_EXE_LINKER_FLAGS}"
)
fi
if [[ "${BUILD_WITH_CODE_COVERAGE}" ]]; then
CMAKE_CONF_OPTS+=(
-DBUILD_WITH_CODE_COVERAGE="${BUILD_WITH_CODE_COVERAGE}"
)
fi
mkdir -p build
cd build
if [[ "${BUILD_TESTS_ONLY}" ]]; then
# flags specific to our test suite
CXX_FLAGS="-Wno-error=deprecated-declarations -Wno-error=nonnull -Wno-error=deprecated-copy"
if [[ "${CXX}" == 'clang++' ]]; then
CXX_FLAGS="${CXX_FLAGS} -Wno-error=unused-lambda-capture -Wno-error=gnu-zero-variadic-macro-arguments"
fi
CMAKE_CONF_OPTS+=(
-DCMAKE_CXX_FLAGS="${CXX_FLAGS}"
)
${ANALYZE} cmake \
-D CMAKE_C_COMPILER="${CC}" \
-D CMAKE_CXX_COMPILER="${CXX}" \
-D CMAKE_C_COMPILER_LAUNCHER=ccache \
-D CMAKE_CXX_COMPILER_LAUNCHER=ccache \
-D CMAKE_INSTALL_PREFIX=install \
-D CMAKE_BUILD_TYPE=RelWithDebInfo \
-D USE_SYSTEM_TINYXML=TRUE \
-D BUILD_OPENMW=OFF \
-D BUILD_BSATOOL=OFF \
-D BUILD_ESMTOOL=OFF \
-D BUILD_LAUNCHER=OFF \
-D BUILD_MWINIIMPORTER=OFF \
-D BUILD_ESSIMPORTER=OFF \
-D BUILD_OPENCS=OFF \
-D BUILD_WIZARD=OFF \
-D BUILD_UNITTESTS=ON \
-D GTEST_ROOT="${GOOGLETEST_DIR}" \
-D GMOCK_ROOT="${GOOGLETEST_DIR}" \
"${CMAKE_CONF_OPTS[@]}" \
-DBUILD_OPENMW=OFF \
-DBUILD_BSATOOL=OFF \
-DBUILD_ESMTOOL=OFF \
-DBUILD_LAUNCHER=OFF \
-DBUILD_MWINIIMPORTER=OFF \
-DBUILD_ESSIMPORTER=OFF \
-DBUILD_OPENCS=OFF \
-DBUILD_WIZARD=OFF \
-DBUILD_NAVMESHTOOL=OFF \
-DBUILD_UNITTESTS=${BUILD_UNITTESTS} \
-DBUILD_BENCHMARKS=${BUILD_BENCHMARKS} \
-DGTEST_ROOT="${GOOGLETEST_DIR}" \
-DGMOCK_ROOT="${GOOGLETEST_DIR}" \
..
else
${ANALYZE} cmake \
-D CMAKE_C_COMPILER="${CC}" \
-D CMAKE_CXX_COMPILER="${CXX}" \
-D CMAKE_C_COMPILER_LAUNCHER=ccache \
-D CMAKE_CXX_COMPILER_LAUNCHER=ccache \
-D USE_SYSTEM_TINYXML=TRUE \
-D CMAKE_INSTALL_PREFIX=install \
-D CMAKE_BUILD_TYPE=Debug \
"${CMAKE_CONF_OPTS[@]}" \
..
fi

@ -62,6 +62,7 @@ VERBOSE=""
STRIP=""
SKIP_DOWNLOAD=""
SKIP_EXTRACT=""
USE_CCACHE=""
KEEP=""
UNITY_BUILD=""
VS_VERSION=""
@ -73,9 +74,10 @@ CONFIGURATIONS=()
TEST_FRAMEWORK=""
GOOGLE_INSTALL_ROOT=""
INSTALL_PREFIX="."
BULLET_DOUBLE=""
BULLET_DBL=""
BULLET_DBL_DISPLAY="Single precision"
BUILD_BENCHMARKS=""
OSG_MULTIVIEW_BUILD=""
USE_WERROR=""
USE_CLANG_TIDY=""
ACTIVATE_MSVC=""
SINGLE_CONFIG=""
@ -99,12 +101,11 @@ while [ $# -gt 0 ]; do
d )
SKIP_DOWNLOAD=true ;;
D )
BULLET_DOUBLE=true ;;
e )
SKIP_EXTRACT=true ;;
C )
USE_CCACHE=true ;;
k )
KEEP=true ;;
@ -117,7 +118,7 @@ while [ $# -gt 0 ]; do
n )
NMAKE=true ;;
N )
NINJA=true ;;
@ -139,6 +140,18 @@ while [ $# -gt 0 ]; do
INSTALL_PREFIX=$(echo "$1" | sed 's;\\;/;g' | sed -E 's;/+;/;g')
shift ;;
b )
BUILD_BENCHMARKS=true ;;
M )
OSG_MULTIVIEW_BUILD=true ;;
E )
USE_WERROR=true ;;
T )
USE_CLANG_TIDY=true ;;
h )
cat <<EOF
Usage: $0 [-cdehkpuvVi]
@ -147,10 +160,10 @@ Options:
Set the configuration, can also be set with environment variable CONFIGURATION.
For mutli-config generators, this is ignored, and all configurations are set up.
For single-config generators, several configurations can be set up at once by specifying -c multiple times.
-C
Use ccache.
-d
Skip checking the downloads.
-D
Use double-precision Bullet
-e
Skip extracting dependencies.
-h
@ -175,6 +188,14 @@ Options:
Run verbosely
-i
CMake install prefix
-b
Build benchmarks
-M
Use a multiview build of OSG
-E
Use warnings as errors (/WX)
-T
Run clang-tidy
EOF
wrappedExit 0
;;
@ -260,10 +281,10 @@ download() {
if [ -z $VERBOSE ]; then
RET=0
curl --silent --retry 10 -Ly 5 -o $FILE $URL || RET=$?
curl --silent --fail --retry 10 -Ly 5 -o $FILE $URL || RET=$?
else
RET=0
curl --retry 10 -Ly 5 -o $FILE $URL || RET=$?
curl --fail --retry 10 -Ly 5 -o $FILE $URL || RET=$?
fi
if [ $RET -ne 0 ]; then
@ -325,6 +346,16 @@ add_qt_platform_dlls() {
QT_PLATFORMS[$CONFIG]="${QT_PLATFORMS[$CONFIG]} $@"
}
declare -A QT_STYLES
QT_STYLES["Release"]=""
QT_STYLES["Debug"]=""
QT_STYLES["RelWithDebInfo"]=""
add_qt_style_dlls() {
local CONFIG=$1
shift
QT_STYLES[$CONFIG]="${QT_STYLES[$CONFIG]} $@"
}
if [ -z $PLATFORM ]; then
PLATFORM="$(uname -m)"
fi
@ -342,9 +373,9 @@ case $VS_VERSION in
MSVC_YEAR="2015"
MSVC_REAL_YEAR="2019"
MSVC_DISPLAY_YEAR="2019"
BOOST_VER="1.71.0"
BOOST_VER_URL="1_71_0"
BOOST_VER_SDK="107100"
BOOST_VER="1.79.0"
BOOST_VER_URL="1_79_0"
BOOST_VER_SDK="107900"
;;
15|15.0|2017 )
@ -423,9 +454,6 @@ if [ -n "$SINGLE_CONFIG" ]; then
if [ -n "$SKIP_DOWNLOAD" ]; then
RECURSIVE_OPTIONS+=("-d")
fi
if [ -n "$BULLET_DOUBLE" ]; then
RECURSIVE_OPTIONS+=("-D")
fi
if [ -n "$SKIP_EXTRACT" ]; then
RECURSIVE_OPTIONS+=("-e")
fi
@ -498,14 +526,33 @@ if ! [ -z $UNITY_BUILD ]; then
add_cmake_opts "-DOPENMW_UNITY_BUILD=True"
fi
if [ -n "$BULLET_DOUBLE" ]; then
BULLET_DBL="-double"
BULLET_DBL_DISPLAY="Double precision"
add_cmake_opts "-DBULLET_USE_DOUBLES=True"
if ! [ -z $USE_CCACHE ]; then
add_cmake_opts "-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache"
fi
# turn on LTO by default
add_cmake_opts "-DOPENMW_LTO_BUILD=True"
if ! [ -z "$USE_WERROR" ]; then
add_cmake_opts "-DOPENMW_MSVC_WERROR=ON"
fi
if ! [ -z "$USE_CLANG_TIDY" ]; then
add_cmake_opts "-DCMAKE_CXX_CLANG_TIDY=\"clang-tidy --warnings-as-errors=*\""
fi
ICU_VER="70_1"
OSG_ARCHIVE_NAME="OSGoS 3.6.5"
OSG_ARCHIVE="OSGoS-3.6.5-b02abe2-msvc${MSVC_REAL_YEAR}-win${BITS}"
OSG_ARCHIVE_REPO_URL="https://gitlab.com/OpenMW/openmw-deps/-/raw/main"
if ! [ -z $OSG_MULTIVIEW_BUILD ]; then
OSG_ARCHIVE_NAME="OSG-3.6-multiview"
OSG_ARCHIVE="OSG-3.6-multiview-ee297dce0-msvc${MSVC_REAL_YEAR}-win${BITS}"
OSG_ARCHIVE_REPO_URL="https://gitlab.com/madsbuvi/openmw-deps/-/raw/openmw-vr-ovr_multiview"
fi
echo
echo "==================================="
echo "Starting prebuild on MSVC${MSVC_DISPLAY_YEAR} WIN${BITS}"
@ -530,9 +577,9 @@ if [ -z $SKIP_DOWNLOAD ]; then
fi
# Bullet
download "Bullet 2.89 (${BULLET_DBL_DISPLAY})" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/Bullet-2.89-msvc${MSVC_YEAR}-win${BITS}${BULLET_DBL}.7z" \
"Bullet-2.89-msvc${MSVC_YEAR}-win${BITS}${BULLET_DBL}.7z"
download "Bullet 2.89" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/Bullet-2.89-msvc${MSVC_YEAR}-win${BITS}-double.7z" \
"Bullet-2.89-msvc${MSVC_YEAR}-win${BITS}-double.7z"
# FFmpeg
download "FFmpeg 4.2.2" \
@ -542,14 +589,14 @@ if [ -z $SKIP_DOWNLOAD ]; then
"ffmpeg-4.2.2-dev-win${BITS}.zip"
# MyGUI
download "MyGUI 3.4.0" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/MyGUI-3.4.0-msvc${MSVC_REAL_YEAR}-win${BITS}.7z" \
"MyGUI-3.4.0-msvc${MSVC_REAL_YEAR}-win${BITS}.7z"
download "MyGUI 3.4.1" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/MyGUI-3.4.1-msvc${MSVC_REAL_YEAR}-win${BITS}.7z" \
"MyGUI-3.4.1-msvc${MSVC_REAL_YEAR}-win${BITS}.7z"
if [ -n "$PDBS" ]; then
download "MyGUI symbols" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/MyGUI-3.4.0-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z" \
"MyGUI-3.4.0-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z"
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/MyGUI-3.4.1-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z" \
"MyGUI-3.4.1-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z"
fi
# OpenAL
@ -557,36 +604,46 @@ if [ -z $SKIP_DOWNLOAD ]; then
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/OpenAL-Soft-1.20.1.zip" \
"OpenAL-Soft-1.20.1.zip"
# OSG
download "OpenSceneGraph 3.6.5" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}.7z" \
"OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}.7z"
# OSGoS https://gitlab.com/madsbuvi/openmw-deps/-/raw/openmw-vr-ovr_multiview/windows/OSG-3.6-multiview-ee297dce0-msvc${MSVC_REAL_YEAR}-win${BITS}.7z
download "${OSG_ARCHIVE_NAME}" \
"${OSG_ARCHIVE_REPO_URL}/windows/${OSG_ARCHIVE}.7z" \
"${OSG_ARCHIVE}.7z"
if [ -n "$PDBS" ]; then
download "OpenSceneGraph symbols" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z" \
"OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z"
download "${OSG_ARCHIVE_NAME} symbols" \
"${OSG_ARCHIVE_REPO_URL}/windows/${OSG_ARCHIVE}-sym.7z" \
"${OSG_ARCHIVE}-sym.7z"
fi
# SDL2
download "SDL 2.0.12" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/SDL2-2.0.12.zip" \
"SDL2-2.0.12.zip"
download "SDL 2.0.22" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/SDL2-2.0.22.zip" \
"SDL2-2.0.22.zip"
# LZ4
download "LZ4 1.9.2" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/lz4_win${BITS}_v1_9_2.7z" \
"lz4_win${BITS}_v1_9_2.7z"
# LuaJIT
download "LuaJIT 2.1.0-beta3" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/LuaJIT-2.1.0-beta3-msvc${MSVC_REAL_YEAR}-win${BITS}.7z" \
"LuaJIT-2.1.0-beta3-msvc${MSVC_REAL_YEAR}-win${BITS}.7z"
# Google test and mock
if [ ! -z $TEST_FRAMEWORK ]; then
echo "Google test 1.10.0..."
if [ -n "$TEST_FRAMEWORK" ]; then
echo "Google test 1.11.0..."
if [ -d googletest ]; then
printf " Google test exists, skipping."
else
git clone -b release-1.10.0 https://github.com/google/googletest.git
git clone -b release-1.11.0 https://github.com/google/googletest.git
fi
fi
# ICU
download "ICU ${ICU_VER/_/.}"\
"https://github.com/unicode-org/icu/releases/download/release-${ICU_VER/_/-}/icu4c-${ICU_VER}-Win${BITS}-MSVC2019.zip" \
"icu4c-${ICU_VER}-Win${BITS}-MSVC2019.zip"
fi
cd .. #/..
@ -672,15 +729,15 @@ fi
cd $DEPS
echo
# Bullet
printf "Bullet 2.89 (${BULLET_DBL_DISPLAY})... "
printf "Bullet 2.89... "
{
cd $DEPS_INSTALL
if [ -d Bullet ]; then
printf -- "Exists. (No version checking) "
elif [ -z $SKIP_EXTRACT ]; then
rm -rf Bullet
eval 7z x -y "${DEPS}/Bullet-2.89-msvc${MSVC_YEAR}-win${BITS}${BULLET_DBL}.7z" $STRIP
mv "Bullet-2.89-msvc${MSVC_YEAR}-win${BITS}${BULLET_DBL}" Bullet
eval 7z x -y "${DEPS}/Bullet-2.89-msvc${MSVC_YEAR}-win${BITS}-double.7z" $STRIP
mv "Bullet-2.89-msvc${MSVC_YEAR}-win${BITS}-double" Bullet
fi
add_cmake_opts -DBULLET_ROOT="$(real_pwd)/Bullet"
echo Done.
@ -713,20 +770,20 @@ printf "FFmpeg 4.2.2... "
cd $DEPS
echo
# MyGUI
printf "MyGUI 3.4.0... "
printf "MyGUI 3.4.1... "
{
cd $DEPS_INSTALL
if [ -d MyGUI ] && \
grep "MYGUI_VERSION_MAJOR 3" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null && \
grep "MYGUI_VERSION_MINOR 4" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null && \
grep "MYGUI_VERSION_PATCH 0" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null
grep "MYGUI_VERSION_PATCH 1" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null
then
printf "Exists. "
elif [ -z $SKIP_EXTRACT ]; then
rm -rf MyGUI
eval 7z x -y "${DEPS}/MyGUI-3.4.0-msvc${MSVC_REAL_YEAR}-win${BITS}.7z" $STRIP
[ -n "$PDBS" ] && eval 7z x -y "${DEPS}/MyGUI-3.4.0-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z" $STRIP
mv "MyGUI-3.4.0-msvc${MSVC_REAL_YEAR}-win${BITS}" MyGUI
eval 7z x -y "${DEPS}/MyGUI-3.4.1-msvc${MSVC_REAL_YEAR}-win${BITS}.7z" $STRIP
[ -n "$PDBS" ] && eval 7z x -y "${DEPS}/MyGUI-3.4.1-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z" $STRIP
mv "MyGUI-3.4.1-msvc${MSVC_REAL_YEAR}-win${BITS}" MyGUI
fi
export MYGUI_HOME="$(real_pwd)/MyGUI"
for CONFIGURATION in ${CONFIGURATIONS[@]}; do
@ -762,8 +819,8 @@ printf "OpenAL-Soft 1.20.1... "
}
cd $DEPS
echo
# OSG
printf "OSG 3.6.5... "
# OSGoS
printf "${OSG_ARCHIVE_NAME}... "
{
cd $DEPS_INSTALL
if [ -d OSG ] && \
@ -774,9 +831,9 @@ printf "OSG 3.6.5... "
printf "Exists. "
elif [ -z $SKIP_EXTRACT ]; then
rm -rf OSG
eval 7z x -y "${DEPS}/OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}.7z" $STRIP
[ -n "$PDBS" ] && eval 7z x -y "${DEPS}/OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z" $STRIP
mv "OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}" OSG
eval 7z x -y "${DEPS}/${OSG_ARCHIVE}.7z" $STRIP
[ -n "$PDBS" ] && eval 7z x -y "${DEPS}/${OSG_ARCHIVE}-sym.7z" $STRIP
mv "${OSG_ARCHIVE}" OSG
fi
OSG_SDK="$(real_pwd)/OSG"
add_cmake_opts -DOSG_DIR="$OSG_SDK"
@ -786,8 +843,13 @@ printf "OSG 3.6.5... "
else
SUFFIX=""
fi
add_runtime_dlls $CONFIGURATION "$(pwd)/OSG/bin/"{OpenThreads,zlib,libpng}${SUFFIX}.dll \
"$(pwd)/OSG/bin/osg"{,Animation,DB,FX,GA,Particle,Text,Util,Viewer,Shadow}${SUFFIX}.dll
if ! [ -z $OSG_MULTIVIEW_BUILD ]; then
add_runtime_dlls $CONFIGURATION "$(pwd)/OSG/bin/"{ot21-OpenThreads,zlib,libpng16}${SUFFIX}.dll \
"$(pwd)/OSG/bin/osg162-osg"{,Animation,DB,FX,GA,Particle,Text,Util,Viewer,Shadow}${SUFFIX}.dll
else
add_runtime_dlls $CONFIGURATION "$(pwd)/OSG/bin/"{OpenThreads,zlib,libpng}${SUFFIX}.dll \
"$(pwd)/OSG/bin/osg"{,Animation,DB,FX,GA,Particle,Text,Util,Viewer,Shadow}${SUFFIX}.dll
fi
add_osg_dlls $CONFIGURATION "$(pwd)/OSG/bin/osgPlugins-3.6.5/osgdb_"{bmp,dds,freetype,jpeg,osg,png,tga}${SUFFIX}.dll
add_osg_dlls $CONFIGURATION "$(pwd)/OSG/bin/osgPlugins-3.6.5/osgdb_serializers_osg"{,animation,fx,ga,particle,text,util,viewer,shadow}${SUFFIX}.dll
done
@ -796,113 +858,92 @@ printf "OSG 3.6.5... "
cd $DEPS
echo
# Qt
if [ -z $APPVEYOR ]; then
printf "Qt 5.15.0... "
else
printf "Qt 5.13 AppVeyor... "
fi
printf "Qt 5.15.2... "
{
if [ $BITS -eq 64 ]; then
SUFFIX="_64"
else
SUFFIX=""
fi
if [ -z $APPVEYOR ]; then
cd $DEPS_INSTALL
qt_version="5.15.0"
if [ "win${BITS}_msvc${MSVC_REAL_YEAR}${SUFFIX}" == "win64_msvc2017_64" ]; then
echo "This combination of options is known not to work. Falling back to Qt 5.14.2."
qt_version="5.14.2"
fi
cd $DEPS_INSTALL
QT_SDK="$(real_pwd)/Qt/${qt_version}/msvc${MSVC_REAL_YEAR}${SUFFIX}"
qt_version="5.15.2"
if [ -d "Qt/${qt_version}" ]; then
printf "Exists. "
elif [ -z $SKIP_EXTRACT ]; then
if [ $MISSINGPYTHON -ne 0 ]; then
echo "Can't be automatically installed without Python."
wrappedExit 1
fi
QT_SDK="$(real_pwd)/Qt/${qt_version}/msvc${MSVC_REAL_YEAR}${SUFFIX}"
pushd "$DEPS" > /dev/null
if ! [ -d 'aqt-venv' ]; then
echo " Creating Virtualenv for aqt..."
run_cmd python -m venv aqt-venv
fi
if [ -d 'aqt-venv/bin' ]; then
VENV_BIN_DIR='bin'
elif [ -d 'aqt-venv/Scripts' ]; then
VENV_BIN_DIR='Scripts'
else
echo "Error: Failed to create virtualenv in expected location."
wrappedExit 1
fi
if [ -d "Qt/${qt_version}" ]; then
printf "Exists. "
elif [ -z $SKIP_EXTRACT ]; then
if [ $MISSINGPYTHON -ne 0 ]; then
echo "Can't be automatically installed without Python."
wrappedExit 1
fi
if ! [ -e "aqt-venv/${VENV_BIN_DIR}/aqt" ]; then
echo " Installing aqt wheel into virtualenv..."
run_cmd "aqt-venv/${VENV_BIN_DIR}/pip" install aqtinstall==0.9.2
fi
popd > /dev/null
pushd "$DEPS" > /dev/null
if ! [ -d 'aqt-venv' ]; then
echo " Creating Virtualenv for aqt..."
run_cmd python -m venv aqt-venv
fi
if [ -d 'aqt-venv/bin' ]; then
VENV_BIN_DIR='bin'
elif [ -d 'aqt-venv/Scripts' ]; then
VENV_BIN_DIR='Scripts'
else
echo "Error: Failed to create virtualenv in expected location."
wrappedExit 1
fi
rm -rf Qt
# check version
aqt-venv/${VENV_BIN_DIR}/pip list | grep 'aqtinstall\s*1.1.3' || [ $? -ne 0 ]
if [ $? -eq 0 ]; then
echo " Installing aqt wheel into virtualenv..."
run_cmd "aqt-venv/${VENV_BIN_DIR}/pip" install aqtinstall==1.1.3
fi
popd > /dev/null
mkdir Qt
cd Qt
rm -rf Qt
run_cmd "${DEPS}/aqt-venv/${VENV_BIN_DIR}/aqt" install $qt_version windows desktop "win${BITS}_msvc${MSVC_REAL_YEAR}${SUFFIX}"
mkdir Qt
cd Qt
printf " Cleaning up extraneous data... "
rm -rf Qt/{aqtinstall.log,Tools}
run_cmd "${DEPS}/aqt-venv/${VENV_BIN_DIR}/aqt" install $qt_version windows desktop "win${BITS}_msvc${MSVC_REAL_YEAR}${SUFFIX}"
echo Done.
fi
printf " Cleaning up extraneous data... "
rm -rf Qt/{aqtinstall.log,Tools}
cd $QT_SDK
add_cmake_opts -DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" \
-DCMAKE_PREFIX_PATH="$QT_SDK"
for CONFIGURATION in ${CONFIGURATIONS[@]}; do
if [ $CONFIGURATION == "Debug" ]; then
DLLSUFFIX="d"
else
DLLSUFFIX=""
fi
add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${DLLSUFFIX}.dll
add_qt_platform_dlls $CONFIGURATION "$(pwd)/plugins/platforms/qwindows${DLLSUFFIX}.dll"
done
echo Done.
else
QT_SDK="C:/Qt/5.13/msvc2017${SUFFIX}"
add_cmake_opts -DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" \
-DCMAKE_PREFIX_PATH="$QT_SDK"
for CONFIGURATION in ${CONFIGURATIONS[@]}; do
if [ $CONFIGURATION == "Debug" ]; then
DLLSUFFIX="d"
else
DLLSUFFIX=""
fi
DIR=$(windowsPathAsUnix "${QT_SDK}")
add_runtime_dlls $CONFIGURATION "${DIR}/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${DLLSUFFIX}.dll
add_qt_platform_dlls $CONFIGURATION "${DIR}/plugins/platforms/qwindows${DLLSUFFIX}.dll"
done
echo Done.
fi
cd $QT_SDK
add_cmake_opts -DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" \
-DCMAKE_PREFIX_PATH="$QT_SDK"
for CONFIGURATION in ${CONFIGURATIONS[@]}; do
if [ $CONFIGURATION == "Debug" ]; then
DLLSUFFIX="d"
else
DLLSUFFIX=""
fi
add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${DLLSUFFIX}.dll
add_qt_platform_dlls $CONFIGURATION "$(pwd)/plugins/platforms/qwindows${DLLSUFFIX}.dll"
add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll"
done
echo Done.
}
cd $DEPS
echo
# SDL2
printf "SDL 2.0.12... "
printf "SDL 2.0.22... "
{
if [ -d SDL2-2.0.12 ]; then
if [ -d SDL2-2.0.22 ]; then
printf "Exists. "
elif [ -z $SKIP_EXTRACT ]; then
rm -rf SDL2-2.0.12
eval 7z x -y SDL2-2.0.12.zip $STRIP
rm -rf SDL2-2.0.22
eval 7z x -y SDL2-2.0.22.zip $STRIP
fi
export SDL2DIR="$(real_pwd)/SDL2-2.0.12"
export SDL2DIR="$(real_pwd)/SDL2-2.0.22"
for config in ${CONFIGURATIONS[@]}; do
add_runtime_dlls $config "$(pwd)/SDL2-2.0.12/lib/x${ARCHSUFFIX}/SDL2.dll"
add_runtime_dlls $config "$(pwd)/SDL2-2.0.22/lib/x${ARCHSUFFIX}/SDL2.dll"
done
echo Done.
}
@ -915,7 +956,7 @@ printf "LZ4 1.9.2... "
printf "Exists. "
elif [ -z $SKIP_EXTRACT ]; then
rm -rf LZ4_1.9.2
eval 7z x -y lz4_win${BITS}_v1_9_2.7z -o./LZ4_1.9.2 $STRIP
eval 7z x -y lz4_win${BITS}_v1_9_2.7z -o$(real_pwd)/LZ4_1.9.2 $STRIP
fi
export LZ4DIR="$(real_pwd)/LZ4_1.9.2"
add_cmake_opts -DLZ4_INCLUDE_DIR="${LZ4DIR}/include" \
@ -933,9 +974,28 @@ printf "LZ4 1.9.2... "
}
cd $DEPS
echo
# LuaJIT 2.1.0-beta3
printf "LuaJIT 2.1.0-beta3... "
{
if [ -d LuaJIT ]; then
printf "Exists. "
elif [ -z $SKIP_EXTRACT ]; then
rm -rf LuaJIT
eval 7z x -y LuaJIT-2.1.0-beta3-msvc${MSVC_REAL_YEAR}-win${BITS}.7z -o$(real_pwd)/LuaJIT $STRIP
fi
export LUAJIT_DIR="$(real_pwd)/LuaJIT"
add_cmake_opts -DLuaJit_INCLUDE_DIR="${LUAJIT_DIR}/include" \
-DLuaJit_LIBRARY="${LUAJIT_DIR}/lib/lua51.lib"
for CONFIGURATION in ${CONFIGURATIONS[@]}; do
add_runtime_dlls $CONFIGURATION "$(pwd)/LuaJIT/bin/lua51.dll"
done
echo Done.
}
cd $DEPS
echo
# Google Test and Google Mock
if [ ! -z $TEST_FRAMEWORK ]; then
printf "Google test 1.10.0 ..."
if [ -n "$TEST_FRAMEWORK" ]; then
printf "Google test 1.11.0 ..."
cd googletest
mkdir -p build${MSVC_REAL_YEAR}
@ -987,12 +1047,40 @@ if [ ! -z $TEST_FRAMEWORK ]; then
fi
cd $DEPS
echo
# ICU
printf "ICU ${ICU_VER/_/.}... "
{
if [ -d ICU-${ICU_VER} ]; then
printf "Exists. "
elif [ -z $SKIP_EXTRACT ]; then
rm -rf ICU-${ICU_VER}
eval 7z x -y icu4c-${ICU_VER}-Win${BITS}-MSVC2019.zip -o$(real_pwd)/ICU-${ICU_VER} $STRIP
fi
ICU_ROOT="$(real_pwd)/ICU-${ICU_VER}"
add_cmake_opts -DICU_ROOT="${ICU_ROOT}" \
-DICU_INCLUDE_DIR="${ICU_ROOT}/include" \
-DICU_I18N_LIBRARY="${ICU_ROOT}/lib${BITS}/icuin.lib " \
-DICU_UC_LIBRARY="${ICU_ROOT}/lib${BITS}/icuuc.lib " \
-DICU_DEBUG=ON
for config in ${CONFIGURATIONS[@]}; do
add_runtime_dlls $config "$(pwd)/ICU-${ICU_VER}/bin${BITS}/icudt${ICU_VER/_*/}.dll"
add_runtime_dlls $config "$(pwd)/ICU-${ICU_VER}/bin${BITS}/icuin${ICU_VER/_*/}.dll"
add_runtime_dlls $config "$(pwd)/ICU-${ICU_VER}/bin${BITS}/icuuc${ICU_VER/_*/}.dll"
done
echo Done.
}
echo
cd $DEPS_INSTALL/..
echo
echo "Setting up OpenMW build..."
add_cmake_opts -DOPENMW_MP_BUILD=on
add_cmake_opts -DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}"
add_cmake_opts -DOPENMW_USE_SYSTEM_SQLITE3=OFF
add_cmake_opts -DOPENMW_USE_SYSTEM_YAML_CPP=OFF
if [ ! -z $CI ]; then
case $STEP in
components )
@ -1062,14 +1150,31 @@ fi
cp "$DLL" "${DLL_PREFIX}platforms"
done
echo
echo "- Qt Style DLLs..."
mkdir -p ${DLL_PREFIX}styles
for DLL in ${QT_STYLES[$CONFIGURATION]}; do
echo " $(basename $DLL)"
cp "$DLL" "${DLL_PREFIX}styles"
done
echo
done
#fi
if [ "${BUILD_BENCHMARKS}" ]; then
add_cmake_opts -DBUILD_BENCHMARKS=ON
fi
if [ -n "$ACTIVATE_MSVC" ]; then
echo -n "- Activating MSVC in the current shell... "
command -v vswhere >/dev/null 2>&1 || { echo "Error: vswhere is not on the path."; wrappedExit 1; }
MSVC_INSTALLATION_PATH=$(vswhere -products '*' -version "[$MSVC_REAL_VER,$(awk "BEGIN { print $MSVC_REAL_VER + 1; exit }"))" -property installationPath)
# There are so many arguments now that I'm going to document them:
# * products: allow Visual Studio or standalone build tools
# * version: obvious. Awk helps make a version range by adding one.
# * property installationPath: only give the installation path.
# * latest: return only one result if several candidates exist. Prefer the last installed/updated
# * requires: make sure it's got the MSVC compiler instead of, for example, just the .NET compiler. The .x86.x64 suffix means it's for either, not that it's the x64 on x86 cross compiler as you always get both
MSVC_INSTALLATION_PATH=$(vswhere -products '*' -version "[$MSVC_REAL_VER,$(awk "BEGIN { print $MSVC_REAL_VER + 1; exit }"))" -property installationPath -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64)
if [ -z "$MSVC_INSTALLATION_PATH" ]; then
echo "vswhere was unable to find MSVC $MSVC_DISPLAY_YEAR"
wrappedExit 1

@ -3,8 +3,11 @@
export CXX=clang++
export CC=clang
# Silence a git warning
git config --global advice.detachedHead false
DEPENDENCIES_ROOT="/private/tmp/openmw-deps/openmw-deps"
QT_PATH=$(brew --prefix qt)
QT_PATH=$(brew --prefix qt@5)
CCACHE_EXECUTABLE=$(brew --prefix ccache)/bin/ccache
mkdir build
cd build
@ -16,15 +19,16 @@ cmake \
-D CMAKE_CXX_FLAGS="-stdlib=libc++" \
-D CMAKE_C_FLAGS_RELEASE="-g -O0" \
-D CMAKE_CXX_FLAGS_RELEASE="-g -O0" \
-D CMAKE_OSX_DEPLOYMENT_TARGET="10.12" \
-D CMAKE_OSX_DEPLOYMENT_TARGET="10.15" \
-D CMAKE_BUILD_TYPE=RELEASE \
-D OPENMW_OSX_DEPLOYMENT=TRUE \
-D OPENMW_USE_SYSTEM_SQLITE3=OFF \
-D BUILD_OPENMW=TRUE \
-D BUILD_OPENCS=TRUE \
-D BUILD_ESMTOOL=TRUE \
-D BUILD_BSATOOL=TRUE \
-D BUILD_ESSIMPORTER=TRUE \
-D BUILD_NIFTEST=TRUE \
-D BULLET_USE_DOUBLES=TRUE \
-D ICU_ROOT="/usr/local/opt/icu4c" \
-G"Unix Makefiles" \
..

@ -1,6 +1,6 @@
#!/bin/sh -ex
git clone -b release-1.10.0 https://github.com/google/googletest.git
git clone -b release-1.11.0 https://github.com/google/googletest.git
cd googletest
mkdir build
cd build

@ -0,0 +1,100 @@
#!/bin/bash
set -euo pipefail
print_help() {
echo "usage: $0 [group]..."
echo
echo " available groups: "${!GROUPED_DEPS[@]}""
}
declare -rA GROUPED_DEPS=(
[gcc]="binutils gcc build-essential cmake ccache curl unzip git pkg-config"
[clang]="binutils clang make cmake ccache curl unzip git pkg-config"
# Common dependencies for building OpenMW.
[openmw-deps]="
libboost-filesystem-dev libboost-program-options-dev
libboost-system-dev libboost-iostreams-dev
libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev
libsdl2-dev libqt5opengl5-dev libopenal-dev libunshield-dev libtinyxml-dev
libbullet-dev liblz4-dev libpng-dev libjpeg-dev libluajit-5.1-dev
librecast-dev libsqlite3-dev ca-certificates libicu-dev libyaml-cpp-dev
"
# These dependencies can alternatively be built and linked statically.
[openmw-deps-dynamic]="libmygui-dev libopenscenegraph-dev libsqlite3-dev"
[clang-tidy]="clang-tidy"
# Pre-requisites for building MyGUI and OSG for static linking.
#
# * MyGUI and OSG: libsdl2-dev liblz4-dev libfreetype6-dev
# * OSG: libgl-dev
#
# Plugins:
# * DAE: libcollada-dom-dev libboost-system-dev libboost-filesystem-dev
# * JPEG: libjpeg-dev
# * PNG: libpng-dev
[openmw-deps-static]="
libcollada-dom-dev libfreetype6-dev libjpeg-dev libpng-dev
libsdl2-dev libboost-system-dev libboost-filesystem-dev libgl-dev
"
[openmw-coverage]="gcovr"
[openmw-integration-tests]="
ca-certificates
git
git-lfs
libavcodec58
libavformat58
libavutil56
libboost-filesystem1.71.0
libboost-iostreams1.71.0
libboost-program-options1.71.0
libboost-system1.71.0
libbullet2.88
libcollada-dom2.4-dp0
libicu66
libjpeg8
libluajit-5.1-2
liblz4-1
libmyguiengine3debian1v5
libopenal1
libopenscenegraph161
libpng16-16
libqt5opengl5
librecast1
libsdl2-2.0-0
libsqlite3-0
libswresample3
libswscale5
libtinyxml2.6.2v5
libyaml-cpp0.6
python3-pip
xvfb
"
)
if [[ $# -eq 0 ]]; then
>&2 print_help
exit 1
fi
deps=()
for group in "$@"; do
if [[ ! -v GROUPED_DEPS[$group] ]]; then
>&2 echo "error: unknown group ${group}"
exit 1
fi
deps+=(${GROUPED_DEPS[$group]})
done
export APT_CACHE_DIR="${PWD}/apt-cache"
set -x
mkdir -pv "$APT_CACHE_DIR"
apt-get update -yqq
apt-get -qq -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends software-properties-common >/dev/null
add-apt-repository -y ppa:openmw/openmw
apt-get -qq -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends "${deps[@]}" >/dev/null

@ -0,0 +1,10 @@
#!/bin/bash -ex
git clone --depth=1 https://gitlab.com/OpenMW/example-suite.git
xvfb-run --auto-servernum --server-args='-screen 0 640x480x24x60' \
scripts/integration_tests.py --omw build/install/bin/openmw --workdir integration_tests_output example-suite/
ls integration_tests_output/*.osg_stats.log | while read v; do
scripts/osg_stats.py --stats '.*' --regexp_match < "${v}"
done

@ -1,13 +1,37 @@
project(OpenMW)
cmake_minimum_required(VERSION 3.1.0)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# for link time optimization, remove if cmake version is >= 3.9
if(POLICY CMP0069)
if(POLICY CMP0069) # LTO
cmake_policy(SET CMP0069 NEW)
endif()
# for position-independent executable, remove if cmake version is >= 3.14
if(POLICY CMP0083)
cmake_policy(SET CMP0083 NEW)
endif()
# to link with freetype library
if(POLICY CMP0079)
cmake_policy(SET CMP0079 NEW)
endif()
# don't add /W3 flag by default for MSVC
if(POLICY CMP0092)
cmake_policy(SET CMP0092 NEW)
endif()
project(OpenMW)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
include(GNUInstallDirs)
option(OPENMW_GL4ES_MANUAL_INIT "Manually initialize gl4es. This is more reliable on platforms without a windowing system. Requires gl4es to be configured with -DNOEGL=ON -DNO_LOADER=ON -DNO_INIT_CONSTRUCTOR=ON." OFF)
if(OPENMW_GL4ES_MANUAL_INIT)
add_definitions(-DOPENMW_GL4ES_MANUAL_INIT)
endif()
# Apps and tools
option(BUILD_OPENMW "Build OpenMW" ON)
option(BUILD_LAUNCHER "Build Launcher" ON)
@ -21,7 +45,9 @@ option(BUILD_NIFTEST "Build nif file tester" ON)
option(BUILD_DOCS "Build documentation." OFF )
option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF)
option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF)
option(BULLET_USE_DOUBLES "Use double precision for Bullet" OFF)
option(BUILD_BENCHMARKS "Build benchmarks with Google Benchmark" OFF)
option(BUILD_NAVMESHTOOL "Build navmesh tool" ON)
option(BUILD_BULLETOBJECTTOOL "Build Bullet object tool" ON)
set(OpenGL_GL_PREFERENCE LEGACY) # Use LEGACY as we use GL2; GLNVD is for GL3 and up.
@ -49,14 +75,13 @@ set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/)
if (ANDROID)
set(CMAKE_FIND_ROOT_PATH ${OPENMW_DEPENDENCIES_DIR} "${CMAKE_FIND_ROOT_PATH}")
set (OSG_PLUGINS_DIR CACHE STRING "")
endif()
# Version
message(STATUS "Configuring OpenMW...")
set(OPENMW_VERSION_MAJOR 0)
set(OPENMW_VERSION_MINOR 47)
set(OPENMW_VERSION_MINOR 48)
set(OPENMW_VERSION_RELEASE 0)
set(OPENMW_VERSION_COMMITHASH "")
@ -92,17 +117,53 @@ endif(EXISTS ${PROJECT_SOURCE_DIR}/.git)
# Macros
include(OpenMWMacros)
include(WholeArchive)
# doxygen main page
configure_file ("${OpenMW_SOURCE_DIR}/docs/mainpage.hpp.cmake" "${OpenMW_BINARY_DIR}/docs/mainpage.hpp")
option(MYGUI_STATIC "Link static build of Mygui into the binaries" FALSE)
option(BOOST_STATIC "Link static build of Boost into the binaries" FALSE)
option(SDL2_STATIC "Link static build of SDL into the binaries" FALSE)
option(OSG_STATIC "Link static build of OpenSceneGraph into the binaries" FALSE)
option(QT_STATIC "Link static build of QT into the binaries" FALSE)
option(OPENMW_USE_SYSTEM_BULLET "Use system provided bullet physics library" ON)
if(OPENMW_USE_SYSTEM_BULLET)
set(_bullet_static_default OFF)
else()
set(_bullet_static_default ON)
endif()
option(BULLET_STATIC "Link static build of Bullet into the binaries" ${_bullet_static_default})
option(OPENMW_USE_SYSTEM_OSG "Use system provided OpenSceneGraph libraries" ON)
if(OPENMW_USE_SYSTEM_OSG)
set(_osg_static_default OFF)
else()
set(_osg_static_default ON)
endif()
option(OSG_STATIC "Link static build of OpenSceneGraph into the binaries" ${_osg_static_default})
option(OPENMW_USE_SYSTEM_MYGUI "Use system provided mygui library" ON)
if(OPENMW_USE_SYSTEM_MYGUI)
set(_mygui_static_default OFF)
else()
set(_mygui_static_default ON)
endif()
option(MYGUI_STATIC "Link static build of Mygui into the binaries" ${_mygui_static_default})
option(OPENMW_USE_SYSTEM_RECASTNAVIGATION "Use system provided recastnavigation library" OFF)
if(OPENMW_USE_SYSTEM_RECASTNAVIGATION)
set(_recastnavigation_static_default OFF)
find_package(RecastNavigation REQUIRED)
else()
set(_recastnavigation_static_default ON)
endif()
option(RECASTNAVIGATION_STATIC "Build recastnavigation static libraries" ${_recastnavigation_static_default})
option(OPENMW_USE_SYSTEM_SQLITE3 "Use system provided SQLite3 library" ON)
option(OPENMW_USE_SYSTEM_BENCHMARK "Use system Google Benchmark library." OFF)
option(OPENMW_UNITY_BUILD "Use fewer compilation units to speed up compile time" FALSE)
option(OPENMW_LTO_BUILD "Build OpenMW with Link-Time Optimization (Needs ~2GB of RAM)" OFF)
@ -118,18 +179,27 @@ option(OPENMW_OSX_DEPLOYMENT OFF)
if (MSVC)
option(OPENMW_MP_BUILD "Build OpenMW with /MP flag" OFF)
if (OPENMW_MP_BUILD)
add_compile_options(/MP)
endif()
# \bigobj is required:
# 1) for OPENMW_UNITY_BUILD;
# 2) to compile lua bindings in components, openmw and tests, because sol3 is heavily templated.
# there should be no relevant downsides to having it on:
# https://docs.microsoft.com/en-us/cpp/build/reference/bigobj-increase-number-of-sections-in-dot-obj-file
add_compile_options(/bigobj)
endif()
# Set up common paths
if (APPLE)
set(MORROWIND_DATA_FILES "./data" CACHE PATH "location of Morrowind data files")
set(OPENMW_RESOURCE_FILES "../Resources/resources" CACHE PATH "location of OpenMW resources files")
elseif(UNIX)
# Paths
SET(BINDIR "${CMAKE_INSTALL_PREFIX}/bin" CACHE PATH "Where to install binaries")
SET(LIBDIR "${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}" CACHE PATH "Where to install libraries")
SET(DATAROOTDIR "${CMAKE_INSTALL_PREFIX}/share" CACHE PATH "Sets the root of data directories to a non-default location")
SET(GLOBAL_DATA_PATH "${DATAROOTDIR}/games/" CACHE PATH "Set data path prefix")
SET(DATAROOTDIR "${CMAKE_INSTALL_DATAROOTDIR}" CACHE PATH "Sets the root of data directories to a non-default location")
SET(GLOBAL_DATA_PATH "${CMAKE_INSTALL_PREFIX}/share/games/" CACHE PATH "Set data path prefix")
SET(DATADIR "${GLOBAL_DATA_PATH}/openmw" CACHE PATH "Sets the openmw data directories to a non-default location")
SET(ICONDIR "${DATAROOTDIR}/pixmaps" CACHE PATH "Set icon dir")
SET(LICDIR "${DATAROOTDIR}/licenses/openmw" CACHE PATH "Sets the openmw license directory to a non-default location.")
@ -140,10 +210,8 @@ elseif(UNIX)
ENDIF()
SET(SYSCONFDIR "${GLOBAL_CONFIG_PATH}/openmw" CACHE PATH "Set config dir")
set(MORROWIND_DATA_FILES "${DATADIR}/data" CACHE PATH "location of Morrowind data files")
set(OPENMW_RESOURCE_FILES "${DATADIR}/resources" CACHE PATH "location of OpenMW resources files")
else()
set(MORROWIND_DATA_FILES "data" CACHE PATH "location of Morrowind data files")
set(OPENMW_RESOURCE_FILES "resources" CACHE PATH "location of OpenMW resources files")
endif(APPLE)
@ -151,6 +219,10 @@ 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)
endif()
if(MSVC)
add_compile_options("/utf-8")
endif()
# Dependencies
find_package(OpenGL REQUIRED)
@ -161,10 +233,47 @@ if (USE_QT)
find_package(Qt5Widgets REQUIRED)
find_package(Qt5Network REQUIRED)
find_package(Qt5OpenGL REQUIRED)
# Instruct CMake to run moc automatically when needed.
#set(CMAKE_AUTOMOC ON)
endif()
set(USED_OSG_COMPONENTS
osgDB
osgViewer
osgText
osgGA
osgParticle
osgUtil
osgFX
osgShadow
osgAnimation)
set(USED_OSG_PLUGINS
osgdb_bmp
osgdb_dds
osgdb_freetype
osgdb_jpeg
osgdb_osg
osgdb_png
osgdb_serializers_osg
osgdb_tga)
option(OPENMW_USE_SYSTEM_ICU "Use system ICU library instead of internal. If disabled, requires autotools" ON)
if(OPENMW_USE_SYSTEM_ICU)
find_package(ICU REQUIRED COMPONENTS uc i18n data)
endif()
option(OPENMW_USE_SYSTEM_YAML_CPP "Use system yaml-cpp library instead of internal." ON)
if(OPENMW_USE_SYSTEM_YAML_CPP)
set(_yaml_cpp_static_default OFF)
else()
set(_yaml_cpp_static_default ON)
endif()
option(YAML_CPP_STATIC "Link static build of yaml-cpp into the binaries" ${_yaml_cpp_static_default})
if (OPENMW_USE_SYSTEM_YAML_CPP)
find_package(yaml-cpp REQUIRED)
endif()
add_subdirectory(extern)
# Sound setup
# Require at least ffmpeg 3.2 for now
@ -198,16 +307,16 @@ if(FFmpeg_FOUND)
set(FFVER_OK FALSE)
endif()
endif()
if(NOT FFVER_OK AND NOT APPLE) # unable to detect on version on MacOS < 11.0
message(FATAL_ERROR "FFmpeg version is too old, 3.2 is required" )
endif()
endif()
if(NOT FFmpeg_FOUND)
message(FATAL_ERROR "FFmpeg was not found" )
endif()
if(NOT FFVER_OK)
message(FATAL_ERROR "FFmpeg version is too old, 3.2 is required" )
endif()
if(WIN32)
message("Can not detect FFmpeg version, at least the 3.2 is required" )
endif()
@ -237,7 +346,45 @@ if (WIN32)
add_definitions(-DSDL_MAIN_HANDLED)
# Get rid of useless crud from windows.h
add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN)
add_definitions(
-DNOMINMAX # name conflict with std::min, std::max
-DWIN32_LEAN_AND_MEAN
-DNOMB # name conflict with MWGui::MessageBox
-DNOGDI # name conflict with osgAnimation::MorphGeometry::RELATIVE
)
endif()
if(OPENMW_USE_SYSTEM_BULLET)
set(REQUIRED_BULLET_VERSION 286) # Bullet 286 required due to runtime bugfixes for btCapsuleShape
if (DEFINED ENV{TRAVIS_BRANCH} OR DEFINED ENV{APPVEYOR})
set(REQUIRED_BULLET_VERSION 283) # but for build testing, 283 is fine
endif()
# First, try BulletConfig-float64.cmake which comes with Debian derivatives.
# This file does not define the Bullet version in a CMake-friendly way.
find_package(Bullet CONFIGS BulletConfig-float64.cmake QUIET COMPONENTS BulletCollision LinearMath)
if (BULLET_FOUND)
string(REPLACE "." "" _bullet_version_num ${BULLET_VERSION_STRING})
if (_bullet_version_num VERSION_LESS REQUIRED_BULLET_VERSION)
message(FATAL_ERROR "System bullet version too old, OpenMW requires at least ${REQUIRED_BULLET_VERSION}, got ${_bullet_version_num}")
endif()
# Fix the relative include:
set(BULLET_INCLUDE_DIRS "${BULLET_ROOT_DIR}/${BULLET_INCLUDE_DIRS}")
include(FindPackageMessage)
find_package_message(Bullet "Found Bullet: ${BULLET_LIBRARIES} ${BULLET_VERSION_STRING}" "${BULLET_VERSION_STRING}-float64")
else()
find_package(Bullet ${REQUIRED_BULLET_VERSION} REQUIRED COMPONENTS BulletCollision LinearMath)
endif()
# Only link the Bullet libraries that we need:
string(REGEX MATCHALL "((optimized|debug);)?[^;]*(BulletCollision|LinearMath)[^;]*" BULLET_LIBRARIES "${BULLET_LIBRARIES}")
include(cmake/CheckBulletPrecision.cmake)
if (HAS_DOUBLE_PRECISION_BULLET)
message(STATUS "Bullet uses double precision")
else()
message(FATAL_ERROR "Bullet does not uses double precision")
endif()
endif()
if (NOT WIN32 AND BUILD_WIZARD) # windows users can just run the morrowind installer
@ -258,42 +405,35 @@ if(NOT HAVE_STDINT_H)
message(FATAL_ERROR "stdint.h was not found" )
endif()
if(OPENMW_USE_SYSTEM_OSG)
find_package(OpenSceneGraph 3.4.0 REQUIRED ${USED_OSG_COMPONENTS})
if (${OPENSCENEGRAPH_VERSION} VERSION_GREATER 3.6.2 AND ${OPENSCENEGRAPH_VERSION} VERSION_LESS 3.6.5)
message(FATAL_ERROR "OpenSceneGraph version ${OPENSCENEGRAPH_VERSION} has critical regressions which cause crashes. Please upgrade to 3.6.5 or later. We strongly recommend using the tip of the official 'OpenSceneGraph-3.6' branch or the tip of '3.6' OpenMW/osg (OSGoS).")
endif()
find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgParticle osgUtil osgFX osgShadow)
include_directories(SYSTEM ${OPENSCENEGRAPH_INCLUDE_DIRS})
if(OSG_STATIC)
find_package(OSGPlugins REQUIRED COMPONENTS ${USED_OSG_PLUGINS})
endif()
endif()
set(USED_OSG_PLUGINS
osgdb_bmp
osgdb_dds
osgdb_freetype
osgdb_jpeg
osgdb_osg
osgdb_png
osgdb_serializers_osg
osgdb_tga
)
set(OSGPlugins_LIB_DIR "")
foreach(OSGDB_LIB ${OSGDB_LIBRARY})
# Skip library type names
if(EXISTS ${OSGDB_LIB} AND NOT IS_DIRECTORY ${OSGDB_LIB})
get_filename_component(OSG_LIB_DIR ${OSGDB_LIB} DIRECTORY)
list(APPEND OSGPlugins_LIB_DIR "${OSG_LIB_DIR}/osgPlugins-${OPENSCENEGRAPH_VERSION}")
endif()
endforeach(OSGDB_LIB)
include_directories(BEFORE SYSTEM ${OPENSCENEGRAPH_INCLUDE_DIRS})
if(OSG_STATIC)
add_definitions(-DOSG_LIBRARY_STATIC)
find_package(OSGPlugins REQUIRED COMPONENTS ${USED_OSG_PLUGINS})
list(APPEND OPENSCENEGRAPH_LIBRARIES ${OSGPlugins_LIBRARIES})
endif()
include(cmake/CheckOsgMultiview.cmake)
if(HAVE_MULTIVIEW)
add_definitions(-DOSG_HAS_MULTIVIEW)
endif(HAVE_MULTIVIEW)
set(BOOST_COMPONENTS system filesystem program_options iostreams)
if(WIN32)
set(BOOST_COMPONENTS ${BOOST_COMPONENTS} locale)
if(MSVC)
set(BOOST_COMPONENTS ${BOOST_COMPONENTS} zlib)
# boost-zlib is not present (nor needed) in vcpkg version of boost.
# there, it is part of boost-iostreams instead.
set(BOOST_OPTIONAL_COMPONENTS zlib)
endif(MSVC)
endif(WIN32)
@ -301,26 +441,47 @@ IF(BOOST_STATIC)
set(Boost_USE_STATIC_LIBS ON)
endif()
set(REQUIRED_BULLET_VERSION 286) # Bullet 286 required due to runtime bugfixes for btCapsuleShape
if (DEFINED ENV{TRAVIS_BRANCH} OR DEFINED ENV{APPVEYOR})
set(REQUIRED_BULLET_VERSION 283) # but for build testing, 283 is fine
endif()
set(Boost_NO_BOOST_CMAKE ON)
find_package(Boost 1.6.2 REQUIRED COMPONENTS ${BOOST_COMPONENTS})
find_package(MyGUI 3.2.2 REQUIRED)
find_package(Boost 1.6.2 REQUIRED COMPONENTS ${BOOST_COMPONENTS} OPTIONAL_COMPONENTS ${BOOST_OPTIONAL_COMPONENTS})
if(Boost_VERSION_STRING VERSION_GREATER_EQUAL 1.77.0)
find_package(Boost 1.77.0 REQUIRED COMPONENTS atomic)
endif()
if(OPENMW_USE_SYSTEM_MYGUI)
find_package(MyGUI 3.4.1 REQUIRED)
endif()
find_package(SDL2 2.0.9 REQUIRED)
find_package(OpenAL REQUIRED)
find_package(Bullet ${REQUIRED_BULLET_VERSION} REQUIRED COMPONENTS BulletCollision LinearMath)
include_directories("."
SYSTEM
option(USE_LUAJIT "Switch Lua/LuaJit (TRUE is highly recommended)" TRUE)
if(USE_LUAJIT)
find_package(LuaJit REQUIRED)
set(LUA_INCLUDE_DIR ${LuaJit_INCLUDE_DIR})
set(LUA_LIBRARIES ${LuaJit_LIBRARIES})
else(USE_LUAJIT)
find_package(Lua REQUIRED)
add_compile_definitions(NO_LUAJIT)
endif(USE_LUAJIT)
# C++ library binding to Lua
set(SOL_INCLUDE_DIR ${OpenMW_SOURCE_DIR}/extern/sol3)
set(SOL_CONFIG_DIR ${OpenMW_SOURCE_DIR}/extern/sol_config)
include_directories(
BEFORE SYSTEM
"."
${SDL2_INCLUDE_DIR}
${Boost_INCLUDE_DIR}
${MyGUI_INCLUDE_DIRS}
${OPENAL_INCLUDE_DIR}
${OPENGL_INCLUDE_DIR}
${BULLET_INCLUDE_DIRS}
${LUA_INCLUDE_DIR}
${SOL_INCLUDE_DIR}
${SOL_CONFIG_DIR}
${ICU_INCLUDE_DIRS}
)
link_directories(${SDL2_LIBRARY_DIRS} ${Boost_LIBRARY_DIRS})
@ -337,9 +498,8 @@ if (APPLE)
"${APP_BUNDLE_DIR}/Contents/Resources/OpenMW.icns" COPYONLY)
endif (APPLE)
if (NOT APPLE)
set(OPENMW_MYGUI_FILES_ROOT ${OpenMW_BINARY_DIR})
set(OPENMW_SHADERS_ROOT ${OpenMW_BINARY_DIR})
if (NOT APPLE) # this is modified for macOS use later in "apps/open[mw|cs]/CMakeLists.txt"
set(OPENMW_RESOURCES_ROOT ${OpenMW_BINARY_DIR})
endif ()
add_subdirectory(files/)
@ -360,8 +520,8 @@ endif (APPLE)
# Other files
configure_resource_file(${OpenMW_SOURCE_DIR}/files/settings-default.cfg
"${OpenMW_BINARY_DIR}" "settings-default.cfg")
pack_resource_file(${OpenMW_SOURCE_DIR}/files/settings-default.cfg
"${OpenMW_BINARY_DIR}" "defaults.bin")
configure_resource_file(${OpenMW_SOURCE_DIR}/files/openmw.appdata.xml
"${OpenMW_BINARY_DIR}" "openmw.appdata.xml")
@ -376,8 +536,8 @@ else ()
"${OpenMW_BINARY_DIR}/openmw.cfg")
endif ()
configure_resource_file(${OpenMW_SOURCE_DIR}/files/openmw-cs.cfg
"${OpenMW_BINARY_DIR}" "openmw-cs.cfg")
pack_resource_file(${OpenMW_SOURCE_DIR}/files/openmw-cs.cfg
"${OpenMW_BINARY_DIR}" "defaults-cs.bin")
# Needs the copy version because the configure version assumes the end of the file has been reached when a null character is reached and there are no CMake expressions to evaluate.
copy_resource_file(${OpenMW_SOURCE_DIR}/files/opencs/defaultfilters
@ -415,7 +575,7 @@ endif()
if (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wundef -Wno-unused-parameter -pedantic -Wno-long-long")
set(CMAKE_CXX_FLAGS "-Wall -Wextra -Wundef -Wno-unused-parameter -pedantic -Wno-long-long ${CMAKE_CXX_FLAGS}")
add_definitions( -DBOOST_NO_CXX11_SCOPED_ENUMS=ON )
if (APPLE)
@ -432,27 +592,23 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang)
if (CMAKE_CXX_COMPILER_ID STREQUAL GNU AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.6 OR CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 4.6)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-but-set-parameter")
endif()
if (CMAKE_CXX_COMPILER_ID STREQUAL GNU AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 5.0 OR CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 5.0)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsuggest-override")
endif()
elseif (MSVC)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /FORCE:MULTIPLE")
endif (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang)
# Extern
set(RECASTNAVIGATION_STATIC ON CACHE BOOL "Build recastnavigation static libraries")
add_subdirectory (extern/recastnavigation EXCLUDE_FROM_ALL)
add_subdirectory (extern/osg-ffmpeg-videoplayer)
add_subdirectory (extern/oics)
add_subdirectory (extern/Base64)
if (BUILD_OPENCS)
add_subdirectory (extern/osgQt)
endif()
if (OPENMW_CXX_FLAGS)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OPENMW_CXX_FLAGS}")
endif()
# Components
add_subdirectory (components)
target_compile_definitions(components PRIVATE OPENMW_DOC_BASEURL="${OPENMW_DOC_BASEURL}")
# Apps and tools
if (BUILD_OPENMW)
@ -496,12 +652,20 @@ if (BUILD_UNITTESTS)
add_subdirectory( apps/openmw_test_suite )
endif()
if (BUILD_BENCHMARKS)
add_subdirectory(apps/benchmarks)
endif()
if (BUILD_NAVMESHTOOL)
add_subdirectory(apps/navmeshtool)
endif()
if (BUILD_BULLETOBJECTTOOL)
add_subdirectory( apps/bulletobjecttool )
endif()
if (WIN32)
if (MSVC)
if (OPENMW_MP_BUILD)
set( MT_BUILD "/MP")
endif()
foreach( OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES} )
string( TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG )
set( CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} "$(SolutionDir)$(Configuration)" )
@ -526,62 +690,24 @@ if (WIN32)
# Play a bit with the warning levels
set(WARNINGS "/Wall") # Since windows can only disable specific warnings, not enable them
set(WARNINGS "/W4")
set(WARNINGS_DISABLE
# Warnings that aren't enabled normally and don't need to be enabled
# They're unneeded and sometimes completely retarded warnings that /Wall enables
# Not going to bother commenting them as they tend to warn on every standard library file
4061 4263 4264 4266 4350 4371 4435 4514 4548 4571 4610 4619 4623 4625
4626 4628 4640 4668 4710 4711 4768 4820 4826 4917 4946 5032 5039 5045
# Warnings that are thrown on standard libraries and not OpenMW
4347 # Non-template function with same name and parameter count as template function
4365 # Variable signed/unsigned mismatch
4510 4512 # Unable to generate copy constructor/assignment operator as it's not public in the base
4706 # Assignment in conditional expression
4738 # Storing 32-bit float result in memory, possible loss of performance
4774 # Format string expected in argument is not a string literal
4986 # Undocumented warning that occurs in the crtdbg.h file
4987 # nonstandard extension used (triggered by setjmp.h)
4996 # Function was declared deprecated
# caused by OSG
4589 # Constructor of abstract class 'osg::Operation' ignores initializer for virtual base class 'osg::Referenced' (False warning)
# caused by boost
4191 # 'type cast' : unsafe conversion (1.56, thread_primitives.hpp, normally off)
4643 # Forward declaring 'X' in namespace std is not permitted by the C++ Standard. (in *_std_fwd.h files)
5204 # Class has virtual functions, but its trivial destructor is not virtual
# caused by MyGUI
4275 # non dll-interface class 'std::exception' used as base for dll-interface class 'MyGUI::Exception'
4297 # function assumed not to throw an exception but does
# OpenMW specific warnings
4099 # Type mismatch, declared class or struct is defined with other type
4100 # Unreferenced formal parameter (-Wunused-parameter)
4101 # Unreferenced local variable (-Wunused-variable)
4127 # Conditional expression is constant
4242 # Storing value in a variable of a smaller type, possible loss of data
4244 # Storing value of one type in variable of another (size_t in int, for example)
4245 # Signed/unsigned mismatch
4267 # Conversion from 'size_t' to 'int', possible loss of data
4305 # Truncating value (double to float, for example)
4309 # Variable overflow, trying to store 128 in a signed char for example
4351 # New behavior: elements of array 'array' will be default initialized (desired behavior)
4355 # Using 'this' in member initialization list
4464 # relative include path contains '..'
4505 # Unreferenced local function has been removed
4701 # Potentially uninitialized local variable used
4702 # Unreachable code
4714 # function 'QString QString::trimmed(void) &&' marked as __forceinline not inlined
4800 # Boolean optimization warning, e.g. myBool = (myInt != 0) instead of myBool = myInt
4996 # Function was declared deprecated
5054 # Deprecated operations between enumerations of different types caused by Qt headers
)
if( "${MyGUI_VERSION}" VERSION_LESS_EQUAL "3.4.0" )
set(WARNINGS_DISABLE ${WARNINGS_DISABLE}
4866 # compiler may not enforce left-to-right evaluation order for call
)
endif()
if (MSVC_VERSION GREATER 1800)
set(WARNINGS_DISABLE ${WARNINGS_DISABLE} 5026 5027
5031 # #pragma warning(pop): likely mismatch, popping warning state pushed in different file (config_begin.hpp, config_end.hpp)
if( "${MyGUI_VERSION}" VERSION_LESS_EQUAL "3.4.1" )
set(WARNINGS_DISABLE ${WARNINGS_DISABLE}
4275 # non dll-interface class 'MyGUI::delegates::IDelegateUnlink' used as base for dll-interface class 'MyGUI::Widget'
)
endif()
@ -589,47 +715,64 @@ if (WIN32)
set(WARNINGS "${WARNINGS} /wd${d}")
endforeach(d)
set_target_properties(components PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
set_target_properties(osg-ffmpeg-videoplayer PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
if(OPENMW_MSVC_WERROR)
set(WARNINGS "${WARNINGS} /WX")
endif()
set_target_properties(components PROPERTIES COMPILE_FLAGS "${WARNINGS}")
set_target_properties(osg-ffmpeg-videoplayer PROPERTIES COMPILE_FLAGS "${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} ${MT_BUILD}")
set_target_properties(bsatool PROPERTIES COMPILE_FLAGS "${WARNINGS}")
endif()
if (BUILD_ESMTOOL)
set_target_properties(esmtool PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
set_target_properties(esmtool PROPERTIES COMPILE_FLAGS "${WARNINGS}")
endif()
if (BUILD_ESSIMPORTER)
set_target_properties(openmw-essimporter PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
set_target_properties(openmw-essimporter PROPERTIES COMPILE_FLAGS "${WARNINGS}")
endif()
if (BUILD_LAUNCHER)
set_target_properties(openmw-launcher PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
set_target_properties(openmw-launcher PROPERTIES COMPILE_FLAGS "${WARNINGS}")
endif()
if (BUILD_MWINIIMPORTER)
set_target_properties(openmw-iniimporter PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
set_target_properties(openmw-iniimporter PROPERTIES COMPILE_FLAGS "${WARNINGS}")
endif()
if (BUILD_OPENCS)
set_target_properties(openmw-cs PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
set_target_properties(openmw-cs PROPERTIES COMPILE_FLAGS "${WARNINGS}")
endif()
if (BUILD_OPENMW)
if (OPENMW_UNITY_BUILD)
set_target_properties(openmw PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD} /bigobj")
else()
set_target_properties(openmw PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
endif()
set_target_properties(openmw PROPERTIES COMPILE_FLAGS "${WARNINGS}")
endif()
if (BUILD_WIZARD)
set_target_properties(openmw-wizard PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
set_target_properties(openmw-wizard PROPERTIES COMPILE_FLAGS "${WARNINGS}")
endif()
if (BUILD_UNITTESTS)
set_target_properties(openmw_test_suite PROPERTIES COMPILE_FLAGS "${WARNINGS}")
endif()
if (BUILD_BENCHMARKS)
set_target_properties(openmw_detournavigator_navmeshtilescache_benchmark PROPERTIES COMPILE_FLAGS "${WARNINGS}")
endif()
if (BUILD_NAVMESHTOOL)
set_target_properties(openmw-navmeshtool PROPERTIES COMPILE_FLAGS "${WARNINGS}")
endif()
if (BUILD_BULLETOBJECTTOOL)
set(WARNINGS "${WARNINGS} ${MT_BUILD}")
set_target_properties(openmw-bulletobjecttool PROPERTIES COMPILE_FLAGS "${WARNINGS}")
endif()
endif(MSVC)
@ -638,6 +781,15 @@ if (WIN32)
#set_target_properties(openmw PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:WINDOWS")
endif()
if (BUILD_OPENMW AND APPLE)
if (USE_LUAJIT)
# Without these flags LuaJit crashes on startup on OSX
set_target_properties(openmw PROPERTIES LINK_FLAGS "-pagezero_size 10000 -image_base 100000000")
endif(USE_LUAJIT)
target_compile_definitions(components PRIVATE GL_SILENCE_DEPRECATION=1)
target_compile_definitions(openmw PRIVATE GL_SILENCE_DEPRECATION=1)
endif()
# Apple bundling
if (OPENMW_OSX_DEPLOYMENT AND APPLE)
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.13 AND CMAKE_VERSION VERSION_LESS 3.13.4)
@ -769,8 +921,7 @@ elseif(NOT APPLE)
INSTALL(FILES "${OpenMW_SOURCE_DIR}/CHANGELOG.md" DESTINATION "." RENAME "CHANGELOG.txt")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/README.md" DESTINATION "." RENAME "README.txt")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/LICENSE" DESTINATION "." RENAME "LICENSE.txt")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/mygui/DejaVuFontLicense.txt" DESTINATION ".")
INSTALL(FILES "${INSTALL_SOURCE}/settings-default.cfg" DESTINATION ".")
INSTALL(FILES "${INSTALL_SOURCE}/defaults.bin" DESTINATION ".")
INSTALL(FILES "${INSTALL_SOURCE}/gamecontrollerdb.txt" DESTINATION ".")
INSTALL(DIRECTORY "${INSTALL_SOURCE}/resources" DESTINATION ".")
@ -812,13 +963,13 @@ elseif(NOT APPLE)
SET(VCREDIST32 "${OpenMW_BINARY_DIR}/vcredist_x86.exe")
if(EXISTS ${VCREDIST32})
INSTALL(FILES ${VCREDIST32} DESTINATION "redist")
SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ExecWait '\\\"$INSTDIR\\\\redist\\\\vcredist_x86.exe\\\" /q'" )
SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ExecWait '\\\"$INSTDIR\\\\redist\\\\vcredist_x86.exe\\\" /q /norestart'" )
endif(EXISTS ${VCREDIST32})
SET(VCREDIST64 "${OpenMW_BINARY_DIR}/vcredist_x64.exe")
if(EXISTS ${VCREDIST64})
INSTALL(FILES ${VCREDIST64} DESTINATION "redist")
SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ExecWait '\\\"$INSTDIR\\\\redist\\\\vcredist_x64.exe\\\" /q'" )
SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ExecWait '\\\"$INSTDIR\\\\redist\\\\vcredist_x64.exe\\\" /q /norestart'" )
endif(EXISTS ${VCREDIST64})
SET(OALREDIST "${OpenMW_BINARY_DIR}/oalinst.exe")
@ -838,11 +989,7 @@ elseif(NOT APPLE)
# Install binaries
IF(BUILD_OPENMW)
IF(ANDROID)
INSTALL(PROGRAMS "${INSTALL_SOURCE}/libopenmw.so" DESTINATION "${BINDIR}" )
ELSE(ANDROID)
INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw" DESTINATION "${BINDIR}" )
ENDIF(ANDROID)
INSTALL(PROGRAMS "$<TARGET_FILE:openmw>" DESTINATION "${BINDIR}" )
ENDIF(BUILD_OPENMW)
IF(BUILD_LAUNCHER)
INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-launcher" DESTINATION "${BINDIR}" )
@ -868,9 +1015,12 @@ elseif(NOT APPLE)
IF(BUILD_WIZARD)
INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-wizard" DESTINATION "${BINDIR}" )
ENDIF(BUILD_WIZARD)
# Install licenses
INSTALL(FILES "files/mygui/DejaVuFontLicense.txt" DESTINATION "${LICDIR}" )
if(BUILD_NAVMESHTOOL)
install(PROGRAMS "${INSTALL_SOURCE}/openmw-navmeshtool" DESTINATION "${BINDIR}" )
endif()
IF(BUILD_BULLETOBJECTTOOL)
INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-bulletobjecttool" DESTINATION "${BINDIR}" )
ENDIF(BUILD_BULLETOBJECTTOOL)
# Install icon and desktop file
INSTALL(FILES "${OpenMW_BINARY_DIR}/org.openmw.launcher.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "openmw")
@ -882,13 +1032,13 @@ elseif(NOT APPLE)
ENDIF(BUILD_OPENCS)
# Install global configuration files
INSTALL(FILES "${INSTALL_SOURCE}/settings-default.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
INSTALL(FILES "${INSTALL_SOURCE}/defaults.bin" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
INSTALL(FILES "${INSTALL_SOURCE}/openmw.cfg.install" DESTINATION "${SYSCONFDIR}" RENAME "openmw.cfg" COMPONENT "openmw")
INSTALL(FILES "${INSTALL_SOURCE}/resources/version" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
INSTALL(FILES "${INSTALL_SOURCE}/gamecontrollerdb.txt" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
IF(BUILD_OPENCS)
INSTALL(FILES "${INSTALL_SOURCE}/openmw-cs.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "opencs")
INSTALL(FILES "${INSTALL_SOURCE}/defaults-cs.bin" DESTINATION "${SYSCONFDIR}" COMPONENT "opencs")
ENDIF(BUILD_OPENCS)
# Install resources

@ -22,9 +22,9 @@ Pull Request Guidelines
To facilitate the review process, your pull request description should include the following, if applicable:
* A link back to the bug report or forum discussion that prompted the change. Note: when linking bugs, use the syntax ```[Bug #xyz](https://gitlab.com/OpenMW/openmw/issues/#xyz)``` to create a clickable link. Writing only 'Bug #xyz' will unfortunately create a link to the Github pull request with that number instead.
* Summary of the changes made
* Reasoning / motivation behind the change
* What testing you have carried out to verify the change
* Summary of the changes made.
* Reasoning / motivation behind the change.
* What testing you have carried out to verify the change.
Furthermore, we advise to:
@ -51,9 +51,9 @@ OpenMW, in its default configuration, is meant to be a faithful reimplementation
That said, we may sometimes evaluate such issues on an individual basis. Common exceptions to the above would be:
* Issues so glaring that they would severely limit the capabilities of the engine in the future (for example, the scripting engine not being allowed to access objects in remote cells)
* Bugs where the intent is very obvious, and that have little to no balancing impact (e.g. the bug were being tired made it easier to repair items, instead of harder)
* Bugs that were fixed in an official patch for Morrowind
* Issues so glaring that they would severely limit the capabilities of the engine in the future (for example, the scripting engine not being allowed to access objects in remote cells).
* Bugs where the intent is very obvious, and that have little to no balancing impact (e.g. the bug were being tired made it easier to repair items, instead of harder).
* Bugs that were fixed in an official patch for Morrowind.
Feature additions policy
=====================
@ -99,7 +99,7 @@ Code Review
Merging
=======
To be able to merge PRs, commit priviledges are required. If you do not have the priviledges, just ping someone that does have them with a short comment like "Looks good to me @user".
To be able to merge PRs, commit privileges are required. If you do not have the privileges, just ping someone that does have them with a short comment like "Looks good to me @user".
The person to merge the PR may either use github's Merge button or if using the command line, use the ```--no-ff``` flag (so a merge commit is created, just like with Github's merge button) and include the pull request number in the commit description.

@ -1,24 +1,26 @@
OpenMW
======
[![Build Status](https://api.travis-ci.org/OpenMW/openmw.svg)](https://travis-ci.org/OpenMW/openmw) [![Build status](https://ci.appveyor.com/api/projects/status/github/openmw/openmw?svg=true)](https://ci.appveyor.com/project/psi29a/openmw) [![Coverity Scan Build Status](https://scan.coverity.com/projects/3740/badge.svg)](https://scan.coverity.com/projects/3740) [![pipeline status](https://gitlab.com/OpenMW/openmw/badges/master/pipeline.svg)](https://gitlab.com/OpenMW/openmw/commits/master)
OpenMW is an open-source game engine that supports playing Morrowind by Bethesda Softworks. You need to own the game for OpenMW to play Morrowind.
OpenMW is an open-source open-world RPG game engine that supports playing Morrowind by Bethesda Softworks. You need to own the game for OpenMW to play Morrowind.
OpenMW also comes with OpenMW-CS, a replacement for Bethesda's Construction Set.
* Version: 0.47.0
* License: GPLv3 (see [LICENSE](https://github.com/OpenMW/openmw/blob/master/LICENSE) for more information)
* Version: 0.48.0
* License: GPLv3 (see [LICENSE](https://gitlab.com/OpenMW/openmw/-/raw/master/LICENSE) for more information)
* Website: https://www.openmw.org
* IRC: #openmw on irc.freenode.net
* IRC: #openmw on irc.libera.chat
* Discord: https://discord.gg/bWuqq2e
Font Licenses:
* DejaVuLGCSansMono.ttf: custom (see [files/mygui/DejaVuFontLicense.txt](https://github.com/OpenMW/openmw/blob/master/files/mygui/DejaVuFontLicense.txt) for more information)
* DejaVuLGCSansMono.ttf: custom (see [files/data/fonts/DejaVuFontLicense.txt](https://gitlab.com/OpenMW/openmw/-/raw/master/files/data/fonts/DejaVuFontLicense.txt) for more information)
* OMWAyembedt.ttf: SIL Open Font License (see [files/data/fonts/OMWAyembedtFontLicense.txt](https://gitlab.com/OpenMW/openmw/-/raw/master/files/data/fonts/OMWAyembedtFontLicense.txt) for more information)
* Pelagiad.ttf: SIL Open Font License (see [files/data/fonts/PelagiadFontLicense.txt](https://gitlab.com/OpenMW/openmw/-/raw/master/files/data/fonts/PelagiadFontLicense.txt) for more information)
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?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.
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.
@ -26,7 +28,7 @@ Getting Started
---------------
* [Official forums](https://forum.openmw.org/)
* [Installation instructions](https://wiki.openmw.org/index.php?title=Installation_Instructions)
* [Installation instructions](https://openmw.readthedocs.io/en/latest/manuals/installation/index.html)
* [Build from source](https://wiki.openmw.org/index.php?title=Development_Environment_Setup)
* [Testing the game](https://wiki.openmw.org/index.php?title=Testing)
* [How to contribute](https://wiki.openmw.org/index.php?title=Contribution_Wanted)

@ -0,0 +1,15 @@
if(OPENMW_USE_SYSTEM_BENCHMARK)
find_package(benchmark REQUIRED)
endif()
openmw_add_executable(openmw_detournavigator_navmeshtilescache_benchmark detournavigator/navmeshtilescache.cpp)
target_compile_features(openmw_detournavigator_navmeshtilescache_benchmark PRIVATE cxx_std_17)
target_link_libraries(openmw_detournavigator_navmeshtilescache_benchmark benchmark::benchmark components)
if (UNIX AND NOT APPLE)
target_link_libraries(openmw_detournavigator_navmeshtilescache_benchmark ${CMAKE_THREAD_LIBS_INIT})
endif()
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC)
target_precompile_headers(openmw_detournavigator_navmeshtilescache_benchmark PRIVATE <algorithm>)
endif()

@ -0,0 +1,290 @@
#include <benchmark/benchmark.h>
#include <components/detournavigator/navmeshtilescache.hpp>
#include <components/esm3/loadland.hpp>
#include <algorithm>
#include <random>
namespace
{
using namespace DetourNavigator;
struct Key
{
AgentBounds mAgentBounds;
TilePosition mTilePosition;
RecastMesh mRecastMesh;
};
struct Item
{
Key mKey;
PreparedNavMeshData mValue;
};
template <typename Random>
osg::Vec2i generateVec2i(int max, Random& random)
{
std::uniform_int_distribution<int> distribution(0, max);
return osg::Vec2i(distribution(random), distribution(random));
}
template <typename Random>
osg::Vec3f generateAgentHalfExtents(float min, float max, Random& random)
{
std::uniform_int_distribution<int> distribution(min, max);
return osg::Vec3f(distribution(random), distribution(random), distribution(random));
}
template <typename OutputIterator, typename Random>
void generateVertices(OutputIterator out, std::size_t number, Random& random)
{
std::uniform_real_distribution<float> distribution(0.0, 1.0);
std::generate_n(out, 3 * (number - number % 3), [&] { return distribution(random); });
}
template <typename OutputIterator, typename Random>
void generateIndices(OutputIterator out, int max, std::size_t number, Random& random)
{
std::uniform_int_distribution<int> distribution(0, max);
std::generate_n(out, number - number % 3, [&] { return distribution(random); });
}
AreaType toAreaType(int index)
{
switch (index)
{
case 0: return AreaType_null;
case 1: return AreaType_water;
case 2: return AreaType_door;
case 3: return AreaType_pathgrid;
case 4: return AreaType_ground;
}
return AreaType_null;
}
template <typename Random>
AreaType generateAreaType(Random& random)
{
std::uniform_int_distribution<int> distribution(0, 4);
return toAreaType(distribution(random));
}
template <typename OutputIterator, typename Random>
void generateAreaTypes(OutputIterator out, std::size_t triangles, Random& random)
{
std::generate_n(out, triangles, [&] { return generateAreaType(random); });
}
template <typename OutputIterator, typename Random>
void generateWater(OutputIterator out, std::size_t count, Random& random)
{
std::uniform_real_distribution<float> distribution(0.0, 1.0);
std::generate_n(out, count, [&] {
return CellWater {generateVec2i(1000, random), Water {ESM::Land::REAL_SIZE, distribution(random)}};
});
}
template <class Random>
Mesh generateMesh(std::size_t triangles, Random& random)
{
std::uniform_real_distribution<float> distribution(0.0, 1.0);
std::vector<float> vertices;
std::vector<int> indices;
std::vector<AreaType> areaTypes;
if (distribution(random) < 0.939)
{
generateVertices(std::back_inserter(vertices), triangles * 2.467, random);
generateIndices(std::back_inserter(indices), static_cast<int>(vertices.size() / 3) - 1, vertices.size() * 1.279, random);
generateAreaTypes(std::back_inserter(areaTypes), indices.size() / 3, random);
}
return Mesh(std::move(indices), std::move(vertices), std::move(areaTypes));
}
template <class Random>
Heightfield generateHeightfield(Random& random)
{
std::uniform_real_distribution<float> distribution(0.0, 1.0);
Heightfield result;
result.mCellPosition = generateVec2i(1000, random);
result.mCellSize = ESM::Land::REAL_SIZE;
result.mMinHeight = distribution(random);
result.mMaxHeight = result.mMinHeight + 1.0;
result.mLength = static_cast<std::uint8_t>(ESM::Land::LAND_SIZE);
std::generate_n(std::back_inserter(result.mHeights), ESM::Land::LAND_NUM_VERTS, [&]
{
return distribution(random);
});
result.mOriginalSize = ESM::Land::LAND_SIZE;
result.mMinX = 0;
result.mMinY = 0;
return result;
}
template <class Random>
FlatHeightfield generateFlatHeightfield(Random& random)
{
std::uniform_real_distribution<float> distribution(0.0, 1.0);
FlatHeightfield result;
result.mCellPosition = generateVec2i(1000, random);
result.mCellSize = ESM::Land::REAL_SIZE;
result.mHeight = distribution(random);
return result;
}
template <class Random>
Key generateKey(std::size_t triangles, Random& random)
{
const CollisionShapeType agentShapeType = CollisionShapeType::Aabb;
const osg::Vec3f agentHalfExtents = generateAgentHalfExtents(0.5, 1.5, random);
const TilePosition tilePosition = generateVec2i(10000, random);
const std::size_t generation = std::uniform_int_distribution<std::size_t>(0, 100)(random);
const std::size_t revision = std::uniform_int_distribution<std::size_t>(0, 10000)(random);
Mesh mesh = generateMesh(triangles, random);
std::vector<CellWater> water;
generateWater(std::back_inserter(water), 1, random);
RecastMesh recastMesh(generation, revision, std::move(mesh), std::move(water),
{generateHeightfield(random)}, {generateFlatHeightfield(random)}, {});
return Key {AgentBounds {agentShapeType, agentHalfExtents}, tilePosition, std::move(recastMesh)};
}
constexpr std::size_t trianglesPerTile = 239;
template <typename OutputIterator, typename Random>
void generateKeys(OutputIterator out, std::size_t count, Random& random)
{
std::generate_n(out, count, [&] { return generateKey(trianglesPerTile, random); });
}
template <typename OutputIterator, typename Random>
void fillCache(OutputIterator out, Random& random, NavMeshTilesCache& cache)
{
std::size_t size = cache.getStats().mNavMeshCacheSize;
while (true)
{
Key key = generateKey(trianglesPerTile, random);
cache.set(key.mAgentBounds, key.mTilePosition, key.mRecastMesh,
std::make_unique<PreparedNavMeshData>());
*out++ = std::move(key);
const std::size_t newSize = cache.getStats().mNavMeshCacheSize;
if (size >= newSize)
break;
size = newSize;
}
}
template <std::size_t maxCacheSize, int hitPercentage>
void getFromFilledCache(benchmark::State& state)
{
NavMeshTilesCache cache(maxCacheSize);
std::minstd_rand random;
std::vector<Key> keys;
fillCache(std::back_inserter(keys), random, cache);
generateKeys(std::back_inserter(keys), keys.size() * (100 - hitPercentage) / 100, random);
std::size_t n = 0;
while (state.KeepRunning())
{
const auto& key = keys[n++ % keys.size()];
const auto result = cache.get(key.mAgentBounds, key.mTilePosition, key.mRecastMesh);
benchmark::DoNotOptimize(result);
}
}
void getFromFilledCache_1m_100hit(benchmark::State& state)
{
getFromFilledCache<1 * 1024 * 1024, 100>(state);
}
void getFromFilledCache_4m_100hit(benchmark::State& state)
{
getFromFilledCache<4 * 1024 * 1024, 100>(state);
}
void getFromFilledCache_16m_100hit(benchmark::State& state)
{
getFromFilledCache<16 * 1024 * 1024, 100>(state);
}
void getFromFilledCache_64m_100hit(benchmark::State& state)
{
getFromFilledCache<64 * 1024 * 1024, 100>(state);
}
void getFromFilledCache_1m_70hit(benchmark::State& state)
{
getFromFilledCache<1 * 1024 * 1024, 70>(state);
}
void getFromFilledCache_4m_70hit(benchmark::State& state)
{
getFromFilledCache<4 * 1024 * 1024, 70>(state);
}
void getFromFilledCache_16m_70hit(benchmark::State& state)
{
getFromFilledCache<16 * 1024 * 1024, 70>(state);
}
void getFromFilledCache_64m_70hit(benchmark::State& state)
{
getFromFilledCache<64 * 1024 * 1024, 70>(state);
}
template <std::size_t maxCacheSize>
void setToBoundedNonEmptyCache(benchmark::State& state)
{
NavMeshTilesCache cache(maxCacheSize);
std::minstd_rand random;
std::vector<Key> keys;
fillCache(std::back_inserter(keys), random, cache);
generateKeys(std::back_inserter(keys), keys.size() * 2, random);
std::reverse(keys.begin(), keys.end());
std::size_t n = 0;
while (state.KeepRunning())
{
const auto& key = keys[n++ % keys.size()];
const auto result = cache.set(key.mAgentBounds, key.mTilePosition, key.mRecastMesh,
std::make_unique<PreparedNavMeshData>());
benchmark::DoNotOptimize(result);
}
}
void setToBoundedNonEmptyCache_1m(benchmark::State& state)
{
setToBoundedNonEmptyCache<1 * 1024 * 1024>(state);
}
void setToBoundedNonEmptyCache_4m(benchmark::State& state)
{
setToBoundedNonEmptyCache<4 * 1024 * 1024>(state);
}
void setToBoundedNonEmptyCache_16m(benchmark::State& state)
{
setToBoundedNonEmptyCache<16 * 1024 * 1024>(state);
}
void setToBoundedNonEmptyCache_64m(benchmark::State& state)
{
setToBoundedNonEmptyCache<64 * 1024 * 1024>(state);
}
} // namespace
BENCHMARK(getFromFilledCache_1m_100hit);
BENCHMARK(getFromFilledCache_4m_100hit);
BENCHMARK(getFromFilledCache_16m_100hit);
BENCHMARK(getFromFilledCache_64m_100hit);
BENCHMARK(getFromFilledCache_1m_70hit);
BENCHMARK(getFromFilledCache_4m_70hit);
BENCHMARK(getFromFilledCache_16m_70hit);
BENCHMARK(getFromFilledCache_64m_70hit);
BENCHMARK(setToBoundedNonEmptyCache_1m);
BENCHMARK(setToBoundedNonEmptyCache_4m);
BENCHMARK(setToBoundedNonEmptyCache_16m);
BENCHMARK(setToBoundedNonEmptyCache_64m);
BENCHMARK_MAIN();

@ -10,7 +10,6 @@ openmw_add_executable(bsatool
target_link_libraries(bsatool
${Boost_PROGRAM_OPTIONS_LIBRARY}
${Boost_FILESYSTEM_LIBRARY}
components
)
@ -18,3 +17,11 @@ if (BUILD_WITH_CODE_COVERAGE)
add_definitions (--coverage)
target_link_libraries(bsatool gcov)
endif()
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC)
target_precompile_headers(bsatool PRIVATE
<filesystem>
<fstream>
<vector>
)
endif()

@ -1,10 +1,10 @@
#include <filesystem>
#include <fstream>
#include <iostream>
#include <iomanip>
#include <vector>
#include <boost/program_options.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <components/bsa/compressedbsafile.hpp>
#include <components/misc/stringops.hpp>
@ -13,13 +13,13 @@
// Create local aliases for brevity
namespace bpo = boost::program_options;
namespace bfs = boost::filesystem;
struct Arguments
{
std::string mode;
std::string filename;
std::string extractfile;
std::string addfile;
std::string outdir;
bool longformat;
@ -36,6 +36,10 @@ bool parseOptions (int argc, char** argv, Arguments &info)
" Extract a file from the input archive.\n\n"
" bsatool extractall archivefile [output_directory]\n"
" Extract all files from the input archive.\n\n"
" bsatool add [-a] archivefile file_to_add\n"
" Add a file to the input archive.\n\n"
" bsatool create [-c] archivefile\n"
" Create an archive.\n\n"
"Allowed options");
desc.add_options()
@ -95,7 +99,7 @@ bool parseOptions (int argc, char** argv, Arguments &info)
}
info.mode = variables["mode"].as<std::string>();
if (!(info.mode == "list" || info.mode == "extract" || info.mode == "extractall"))
if (!(info.mode == "list" || info.mode == "extract" || info.mode == "extractall" || info.mode == "add" || info.mode == "create"))
{
std::cout << std::endl << "ERROR: invalid mode \"" << info.mode << "\"\n\n"
<< desc << std::endl;
@ -126,6 +130,17 @@ bool parseOptions (int argc, char** argv, Arguments &info)
if (variables["input-file"].as< std::vector<std::string> >().size() > 2)
info.outdir = variables["input-file"].as< std::vector<std::string> >()[2];
}
else if (info.mode == "add")
{
if (variables["input-file"].as< std::vector<std::string> >().size() < 1)
{
std::cout << "\nERROR: file to add unspecified\n\n"
<< desc << std::endl;
return false;
}
if (variables["input-file"].as< std::vector<std::string> >().size() > 1)
info.addfile = variables["input-file"].as< std::vector<std::string> >()[1];
}
else if (variables["input-file"].as< std::vector<std::string> >().size() > 1)
info.outdir = variables["input-file"].as< std::vector<std::string> >()[1];
@ -135,72 +150,31 @@ bool parseOptions (int argc, char** argv, Arguments &info)
return true;
}
int list(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info);
int extract(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info);
int extractAll(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info);
int main(int argc, char** argv)
{
try
{
Arguments info;
if(!parseOptions (argc, argv, info))
return 1;
// Open file
std::unique_ptr<Bsa::BSAFile> bsa;
Bsa::BsaVersion bsaVersion = Bsa::CompressedBSAFile::detectVersion(info.filename);
if (bsaVersion == Bsa::BSAVER_COMPRESSED)
bsa = std::make_unique<Bsa::CompressedBSAFile>(Bsa::CompressedBSAFile());
else
bsa = std::make_unique<Bsa::BSAFile>(Bsa::BSAFile());
bsa->open(info.filename);
if (info.mode == "list")
return list(bsa, info);
else if (info.mode == "extract")
return extract(bsa, info);
else if (info.mode == "extractall")
return extractAll(bsa, info);
else
{
std::cout << "Unsupported mode. That is not supposed to happen." << std::endl;
return 1;
}
}
catch (std::exception& e)
{
std::cerr << "ERROR reading BSA archive\nDetails:\n" << e.what() << std::endl;
return 2;
}
}
int list(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info)
template<typename File>
int list(std::unique_ptr<File>& bsa, Arguments& info)
{
// List all files
const Bsa::BSAFile::FileList &files = bsa->getList();
const auto &files = bsa->getList();
for (const auto& file : files)
{
if(info.longformat)
{
// Long format
std::ios::fmtflags f(std::cout.flags());
std::cout << std::setw(50) << std::left << file.name;
std::cout << std::setw(50) << std::left << file.name();
std::cout << std::setw(8) << std::left << std::dec << file.fileSize;
std::cout << "@ 0x" << std::hex << file.offset << std::endl;
std::cout.flags(f);
}
else
std::cout << file.name << std::endl;
std::cout << file.name() << std::endl;
}
return 0;
}
int extract(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info)
template<typename File>
int extract(std::unique_ptr<File>& bsa, Arguments& info)
{
std::string archivePath = info.extractfile;
Misc::StringUtils::replaceAll(archivePath, "/", "\\");
@ -208,7 +182,17 @@ int extract(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info)
std::string extractPath = info.extractfile;
Misc::StringUtils::replaceAll(extractPath, "\\", "/");
if (!bsa->exists(archivePath.c_str()))
Files::IStreamPtr stream;
// Get a stream for the file to extract
for (auto it = bsa->getList().rbegin(); it != bsa->getList().rend(); ++it)
{
if (Misc::StringUtils::ciEqual(std::string(it->name()), archivePath))
{
stream = bsa->getFile(&*it);
break;
}
}
if (!stream)
{
std::cout << "ERROR: file '" << archivePath << "' not found\n";
std::cout << "In archive: " << info.filename << std::endl;
@ -216,29 +200,26 @@ int extract(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info)
}
// Get the target path (the path the file will be extracted to)
bfs::path relPath (extractPath);
bfs::path outdir (info.outdir);
std::filesystem::path relPath (extractPath);
std::filesystem::path outdir (info.outdir);
bfs::path target;
std::filesystem::path target;
if (info.fullpath)
target = outdir / relPath;
else
target = outdir / relPath.filename();
// Create the directory hierarchy
bfs::create_directories(target.parent_path());
std::filesystem::create_directories(target.parent_path());
bfs::file_status s = bfs::status(target.parent_path());
if (!bfs::is_directory(s))
std::filesystem::file_status s = std::filesystem::status(target.parent_path());
if (!std::filesystem::is_directory(s))
{
std::cout << "ERROR: " << target.parent_path() << " is not a directory." << std::endl;
return 3;
}
// Get a stream for the file to extract
Files::IStreamPtr stream = bsa->getFile(archivePath.c_str());
bfs::ofstream out(target, std::ios::binary);
std::ofstream out(target, std::ios::binary);
// Write the file to disk
std::cout << "Extracting " << info.extractfile << " to " << target << std::endl;
@ -249,31 +230,31 @@ int extract(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info)
return 0;
}
int extractAll(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info)
template<typename File>
int extractAll(std::unique_ptr<File>& bsa, Arguments& info)
{
for (const auto &file : bsa->getList())
{
std::string extractPath(file.name);
std::string extractPath(file.name());
Misc::StringUtils::replaceAll(extractPath, "\\", "/");
// Get the target path (the path the file will be extracted to)
bfs::path target (info.outdir);
std::filesystem::path target (info.outdir);
target /= extractPath;
// Create the directory hierarchy
bfs::create_directories(target.parent_path());
std::filesystem::create_directories(target.parent_path());
bfs::file_status s = bfs::status(target.parent_path());
if (!bfs::is_directory(s))
std::filesystem::file_status s = std::filesystem::status(target.parent_path());
if (!std::filesystem::is_directory(s))
{
std::cout << "ERROR: " << target.parent_path() << " is not a directory." << std::endl;
return 3;
}
// Get a stream for the file to extract
// (inefficient because getFile iter on the list again)
Files::IStreamPtr data = bsa->getFile(file.name);
bfs::ofstream out(target, std::ios::binary);
Files::IStreamPtr data = bsa->getFile(&file);
std::ofstream out(target, std::ios::binary);
// Write the file to disk
std::cout << "Extracting " << target << std::endl;
@ -283,3 +264,63 @@ int extractAll(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info)
return 0;
}
template<typename File>
int add(std::unique_ptr<File>& bsa, Arguments& info)
{
std::fstream stream(info.addfile, std::ios_base::binary | std::ios_base::out | std::ios_base::in);
bsa->addFile(info.addfile, stream);
return 0;
}
template<typename File>
int call(Arguments& info)
{
std::unique_ptr<File> bsa = std::make_unique<File>();
if (info.mode == "create")
{
bsa->open(info.filename);
return 0;
}
bsa->open(info.filename);
if (info.mode == "list")
return list(bsa, info);
else if (info.mode == "extract")
return extract(bsa, info);
else if (info.mode == "extractall")
return extractAll(bsa, info);
else if (info.mode == "add")
return add(bsa, info);
else
{
std::cout << "Unsupported mode. That is not supposed to happen." << std::endl;
return 1;
}
}
int main(int argc, char** argv)
{
try
{
Arguments info;
if (!parseOptions(argc, argv, info))
return 1;
// Open file
Bsa::BsaVersion bsaVersion = Bsa::CompressedBSAFile::detectVersion(info.filename);
if (bsaVersion == Bsa::BSAVER_COMPRESSED)
return call<Bsa::CompressedBSAFile>(info);
else
return call<Bsa::BSAFile>(info);
}
catch (std::exception& e)
{
std::cerr << "ERROR reading BSA archive\nDetails:\n" << e.what() << std::endl;
return 2;
}
}

@ -0,0 +1,27 @@
set(BULLETMESHTOOL
main.cpp
)
source_group(apps\\bulletobjecttool FILES ${BULLETMESHTOOL})
openmw_add_executable(openmw-bulletobjecttool ${BULLETMESHTOOL})
target_link_libraries(openmw-bulletobjecttool
${Boost_PROGRAM_OPTIONS_LIBRARY}
components
)
if (BUILD_WITH_CODE_COVERAGE)
add_definitions(--coverage)
target_link_libraries(openmw-bulletobjecttool gcov)
endif()
if (WIN32)
install(TARGETS openmw-bulletobjecttool RUNTIME DESTINATION ".")
endif()
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC)
target_precompile_headers(openmw-bulletobjecttool PRIVATE
<string>
<vector>
)
endif()

@ -0,0 +1,203 @@
#include <components/debug/debugging.hpp>
#include <components/esm3/esmreader.hpp>
#include <components/esm3/loadcell.hpp>
#include <components/esmloader/esmdata.hpp>
#include <components/esmloader/load.hpp>
#include <components/fallback/fallback.hpp>
#include <components/fallback/validate.hpp>
#include <components/files/configurationmanager.hpp>
#include <components/resource/bulletshapemanager.hpp>
#include <components/resource/foreachbulletobject.hpp>
#include <components/resource/imagemanager.hpp>
#include <components/resource/niffilemanager.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/settings/settings.hpp>
#include <components/version/version.hpp>
#include <components/vfs/manager.hpp>
#include <components/vfs/registerarchives.hpp>
#include <components/esm3/readerscache.hpp>
#include <components/platform/platform.hpp>
#include <boost/program_options.hpp>
#include <charconv>
#include <cstddef>
#include <iomanip>
#include <stdexcept>
#include <string>
#include <vector>
namespace
{
namespace bpo = boost::program_options;
using StringsVector = std::vector<std::string>;
bpo::options_description makeOptionsDescription()
{
using Fallback::FallbackMap;
bpo::options_description result;
result.add_options()
("help", "print help message")
("version", "print version information and quit")
("data", bpo::value<Files::MaybeQuotedPathContainer>()->default_value(Files::MaybeQuotedPathContainer(), "data")
->multitoken()->composing(), "set data directories (later directories have higher priority)")
("data-local", bpo::value<Files::MaybeQuotedPathContainer::value_type>()->default_value(Files::MaybeQuotedPathContainer::value_type(), ""),
"set local data directory (highest priority)")
("fallback-archive", bpo::value<StringsVector>()->default_value(StringsVector(), "fallback-archive")
->multitoken()->composing(), "set fallback BSA archives (later archives have higher priority)")
("resources", bpo::value<Files::MaybeQuotedPath>()->default_value(Files::MaybeQuotedPath(), "resources"),
"set resources directory")
("content", bpo::value<StringsVector>()->default_value(StringsVector(), "")
->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon/omwscripts")
("fs-strict", bpo::value<bool>()->implicit_value(true)
->default_value(false), "strict file system handling (no case folding)")
("encoding", bpo::value<std::string>()->
default_value("win1252"),
"Character encoding used in OpenMW game messages:\n"
"\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n"
"\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n"
"\n\twin1252 - Western European (Latin) alphabet, used by default")
("fallback", bpo::value<FallbackMap>()->default_value(FallbackMap(), "")
->multitoken()->composing(), "fallback values")
;
Files::ConfigurationManager::addCommonOptions(result);
return result;
}
struct WriteArray
{
const float (&mValue)[3];
friend std::ostream& operator <<(std::ostream& stream, const WriteArray& value)
{
for (std::size_t i = 0; i < 2; ++i)
stream << std::setprecision(std::numeric_limits<float>::max_exponent10) << value.mValue[i] << ", ";
return stream << std::setprecision(std::numeric_limits<float>::max_exponent10) << value.mValue[2];
}
};
std::string toHex(std::string_view value)
{
std::string buffer(value.size() * 2, '0');
char* out = buffer.data();
for (const char v : value)
{
const std::ptrdiff_t space = static_cast<std::ptrdiff_t>(static_cast<std::uint8_t>(v) <= 0xf);
const auto [ptr, ec] = std::to_chars(out + space, out + space + 2, static_cast<std::uint8_t>(v), 16);
if (ec != std::errc())
throw std::system_error(std::make_error_code(ec));
out += 2;
}
return buffer;
}
int runBulletObjectTool(int argc, char *argv[])
{
Platform::init();
bpo::options_description desc = makeOptionsDescription();
bpo::parsed_options options = bpo::command_line_parser(argc, argv)
.options(desc).allow_unregistered().run();
bpo::variables_map variables;
bpo::store(options, variables);
bpo::notify(variables);
if (variables.find("help") != variables.end())
{
getRawStdout() << desc << std::endl;
return 0;
}
Files::ConfigurationManager config;
bpo::variables_map composingVariables = Files::separateComposingVariables(variables, desc);
config.readConfiguration(variables, desc);
Files::mergeComposingVariables(variables, composingVariables, desc);
const std::string encoding(variables["encoding"].as<std::string>());
Log(Debug::Info) << ToUTF8::encodingUsingMessage(encoding);
ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(encoding));
Files::PathContainer dataDirs(asPathContainer(variables["data"].as<Files::MaybeQuotedPathContainer>()));
auto local = variables["data-local"].as<Files::MaybeQuotedPathContainer::value_type>();
if (!local.empty())
dataDirs.push_back(std::move(local));
config.filterOutNonExistingPaths(dataDirs);
const auto fsStrict = variables["fs-strict"].as<bool>();
const auto resDir = variables["resources"].as<Files::MaybeQuotedPath>();
Version::Version v = Version::getOpenmwVersion(resDir.string());
Log(Debug::Info) << v.describe();
dataDirs.insert(dataDirs.begin(), resDir / "vfs");
const auto fileCollections = Files::Collections(dataDirs, !fsStrict);
const auto archives = variables["fallback-archive"].as<StringsVector>();
const auto contentFiles = variables["content"].as<StringsVector>();
Fallback::Map::init(variables["fallback"].as<Fallback::FallbackMap>().mMap);
VFS::Manager vfs(fsStrict);
VFS::registerArchives(&vfs, fileCollections, archives, true);
Settings::Manager settings;
settings.load(config);
ESM::ReadersCache readers;
EsmLoader::Query query;
query.mLoadActivators = true;
query.mLoadCells = true;
query.mLoadContainers = true;
query.mLoadDoors = true;
query.mLoadGameSettings = true;
query.mLoadLands = true;
query.mLoadStatics = true;
const EsmLoader::EsmData esmData = EsmLoader::loadEsmData(query, contentFiles, fileCollections, readers, &encoder);
Resource::ImageManager imageManager(&vfs);
Resource::NifFileManager nifFileManager(&vfs);
Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager);
Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager);
Resource::forEachBulletObject(readers, vfs, bulletShapeManager, esmData,
[] (const ESM::Cell& cell, const Resource::BulletObject& object)
{
Log(Debug::Verbose) << "Found bullet object in " << (cell.isExterior() ? "exterior" : "interior")
<< " cell \"" << cell.getDescription() << "\":"
<< " fileName=\"" << object.mShape->mFileName << '"'
<< " fileHash=" << toHex(object.mShape->mFileHash)
<< " collisionShape=" << std::boolalpha << (object.mShape->mCollisionShape == nullptr)
<< " avoidCollisionShape=" << std::boolalpha << (object.mShape->mAvoidCollisionShape == nullptr)
<< " position=(" << WriteArray {object.mPosition.pos} << ')'
<< " rotation=(" << WriteArray {object.mPosition.rot} << ')'
<< " scale=" << std::setprecision(std::numeric_limits<float>::max_exponent10) << object.mScale;
});
Log(Debug::Info) << "Done";
return 0;
}
}
int main(int argc, char *argv[])
{
return wrapApplication(runBulletObjectTool, argc, argv, "BulletObjectTool");
}

@ -4,6 +4,9 @@ set(ESMTOOL
labels.cpp
record.hpp
record.cpp
arguments.hpp
tes4.hpp
tes4.cpp
)
source_group(apps\\esmtool FILES ${ESMTOOL})
@ -21,3 +24,11 @@ if (BUILD_WITH_CODE_COVERAGE)
add_definitions (--coverage)
target_link_libraries(esmtool gcov)
endif()
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC)
target_precompile_headers(esmtool PRIVATE
<fstream>
<string>
<vector>
)
endif()

@ -0,0 +1,28 @@
#ifndef OPENMW_ESMTOOL_ARGUMENTS_H
#define OPENMW_ESMTOOL_ARGUMENTS_H
#include <vector>
#include <optional>
#include <components/esm/format.hpp>
namespace EsmTool
{
struct Arguments
{
std::optional<ESM::Format> mRawFormat;
bool quiet_given = false;
bool loadcells_given = false;
bool plain_given = false;
std::string mode;
std::string encoding;
std::string filename;
std::string outname;
std::vector<std::string> types;
std::string name;
};
}
#endif

@ -2,71 +2,46 @@
#include <vector>
#include <deque>
#include <list>
#include <unordered_set>
#include <map>
#include <set>
#include <fstream>
#include <cmath>
#include <memory>
#include <optional>
#include <iomanip>
#include <boost/program_options.hpp>
#include <components/esm/esmreader.hpp>
#include <components/esm/esmwriter.hpp>
#include <components/esm3/esmreader.hpp>
#include <components/esm3/esmwriter.hpp>
#include <components/esm/records.hpp>
#include <components/esm/format.hpp>
#include <components/files/openfile.hpp>
#include "record.hpp"
#include "labels.hpp"
#include "arguments.hpp"
#include "tes4.hpp"
namespace
{
#define ESMTOOL_VERSION 1.2
using namespace EsmTool;
constexpr unsigned majorVersion = 1;
constexpr unsigned minorVersion = 3;
// Create a local alias for brevity
namespace bpo = boost::program_options;
struct ESMData
{
std::string author;
std::string description;
unsigned int version;
std::vector<ESM::Header::MasterData> masters;
std::deque<EsmTool::RecordBase *> mRecords;
ESM::Header mHeader;
std::deque<std::unique_ptr<EsmTool::RecordBase>> mRecords;
// Value: (Reference, Deleted flag)
std::map<ESM::Cell *, std::deque<std::pair<ESM::CellRef, bool> > > mCellRefs;
std::map<int, int> mRecordStats;
static const std::set<int> sLabeledRec;
};
static const int sLabeledRecIds[] = {
ESM::REC_GLOB, ESM::REC_CLAS, ESM::REC_FACT, ESM::REC_RACE, ESM::REC_SOUN,
ESM::REC_REGN, ESM::REC_BSGN, ESM::REC_LTEX, ESM::REC_STAT, ESM::REC_DOOR,
ESM::REC_MISC, ESM::REC_WEAP, ESM::REC_CONT, ESM::REC_SPEL, ESM::REC_CREA,
ESM::REC_BODY, ESM::REC_LIGH, ESM::REC_ENCH, ESM::REC_NPC_, ESM::REC_ARMO,
ESM::REC_CLOT, ESM::REC_REPA, ESM::REC_ACTI, ESM::REC_APPA, ESM::REC_LOCK,
ESM::REC_PROB, ESM::REC_INGR, ESM::REC_BOOK, ESM::REC_ALCH, ESM::REC_LEVI,
ESM::REC_LEVC, ESM::REC_SNDG, ESM::REC_CELL, ESM::REC_DIAL
};
const std::set<int> ESMData::sLabeledRec =
std::set<int>(sLabeledRecIds, sLabeledRecIds + 34);
// Based on the legacy struct
struct Arguments
{
bool raw_given;
bool quiet_given;
bool loadcells_given;
bool plain_given;
std::string mode;
std::string encoding;
std::string filename;
std::string outname;
std::vector<std::string> types;
std::string name;
ESMData data;
ESM::ESMReader reader;
ESM::ESMWriter writer;
};
bool parseOptions (int argc, char** argv, Arguments &info)
@ -76,7 +51,10 @@ bool parseOptions (int argc, char** argv, Arguments &info)
desc.add_options()
("help,h", "print help message.")
("version,v", "print version information and quit.")
("raw,r", "Show an unformatted list of all records and subrecords.")
("raw,r", bpo::value<std::string>(),
"Show an unformatted list of all records and subrecords of given format:\n"
"\n\tTES3"
"\n\tTES4")
// The intention is that this option would interact better
// with other modes including clone, dump, and raw.
("type,t", bpo::value< std::vector<std::string> >(),
@ -138,12 +116,12 @@ bool parseOptions (int argc, char** argv, Arguments &info)
}
if (variables.count ("version"))
{
std::cout << "ESMTool version " << ESMTOOL_VERSION << std::endl;
std::cout << "ESMTool version " << majorVersion << '.' << minorVersion << std::endl;
return false;
}
if (!variables.count("mode"))
{
std::cout << "No mode specified!" << std::endl << std::endl
std::cout << "No mode specified!\n\n"
<< desc << finalText << std::endl;
return false;
}
@ -156,7 +134,7 @@ bool parseOptions (int argc, char** argv, Arguments &info)
info.mode = variables["mode"].as<std::string>();
if (!(info.mode == "dump" || info.mode == "clone" || info.mode == "comp"))
{
std::cout << std::endl << "ERROR: invalid mode \"" << info.mode << "\"" << std::endl << std::endl
std::cout << "\nERROR: invalid mode \"" << info.mode << "\"\n\n"
<< desc << finalText << std::endl;
return false;
}
@ -180,7 +158,9 @@ bool parseOptions (int argc, char** argv, Arguments &info)
if (variables["input-file"].as< std::vector<std::string> >().size() > 1)
info.outname = variables["input-file"].as< std::vector<std::string> >()[1];
info.raw_given = variables.count ("raw") != 0;
if (const auto it = variables.find("raw"); it != variables.end())
info.mRawFormat = ESM::parseFormat(it->second.as<std::string>());
info.quiet_given = variables.count ("quiet") != 0;
info.loadcells_given = variables.count ("loadcells") != 0;
info.plain_given = variables.count("plain") != 0;
@ -189,7 +169,7 @@ bool parseOptions (int argc, char** argv, Arguments &info)
info.encoding = variables["encoding"].as<std::string>();
if(info.encoding != "win1250" && info.encoding != "win1251" && info.encoding != "win1252")
{
std::cout << info.encoding << " is not a valid encoding option." << std::endl;
std::cout << info.encoding << " is not a valid encoding option.\n";
info.encoding = "win1252";
}
std::cout << ToUTF8::encodingUsingMessage(info.encoding) << std::endl;
@ -197,12 +177,13 @@ bool parseOptions (int argc, char** argv, Arguments &info)
return true;
}
void printRaw(ESM::ESMReader &esm);
void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info);
void loadCell(const Arguments& info, ESM::Cell &cell, ESM::ESMReader &esm, ESMData* data);
int load(const Arguments& info, ESMData* data);
int clone(const Arguments& info);
int comp(const Arguments& info);
int load(Arguments& info);
int clone(Arguments& info);
int comp(Arguments& info);
}
int main(int argc, char**argv)
{
@ -213,7 +194,7 @@ int main(int argc, char**argv)
return 1;
if (info.mode == "dump")
return load(info);
return load(info, nullptr);
else if (info.mode == "clone")
return clone(info);
else if (info.mode == "comp")
@ -233,7 +214,10 @@ int main(int argc, char**argv)
return 0;
}
void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info)
namespace
{
void loadCell(const Arguments& info, ESM::Cell &cell, ESM::ESMReader &esm, ESMData* data)
{
bool quiet = (info.quiet_given || info.mode == "clone");
bool save = (info.mode == "clone");
@ -249,55 +233,66 @@ void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info)
if(!quiet) std::cout << " References:\n";
bool deleted = false;
while(cell.getNextRef(esm, ref, deleted))
ESM::MovedCellRef movedCellRef;
bool moved = false;
while(cell.getNextRef(esm, ref, deleted, movedCellRef, moved))
{
if (save) {
info.data.mCellRefs[&cell].push_back(std::make_pair(ref, deleted));
}
if (data != nullptr && save)
data->mCellRefs[&cell].push_back(std::make_pair(ref, deleted));
if(quiet) continue;
std::cout << " Refnum: " << ref.mRefNum.mIndex << std::endl;
std::cout << " ID: " << ref.mRefID << std::endl;
std::cout << " Position: (" << ref.mPos.pos[0] << ", " << ref.mPos.pos[1] << ", " << ref.mPos.pos[2] << ")" << std::endl;
std::cout << " - Refnum: " << ref.mRefNum.mIndex << '\n';
std::cout << " ID: " << ref.mRefID << '\n';
std::cout << " Position: (" << ref.mPos.pos[0] << ", " << ref.mPos.pos[1] << ", " << ref.mPos.pos[2] << ")\n";
if (ref.mScale != 1.f)
std::cout << " Scale: " << ref.mScale << std::endl;
std::cout << " Scale: " << ref.mScale << '\n';
if (!ref.mOwner.empty())
std::cout << " Owner: " << ref.mOwner << std::endl;
std::cout << " Owner: " << ref.mOwner << '\n';
if (!ref.mGlobalVariable.empty())
std::cout << " Global: " << ref.mGlobalVariable << std::endl;
std::cout << " Global: " << ref.mGlobalVariable << '\n';
if (!ref.mFaction.empty())
std::cout << " Faction: " << ref.mFaction << std::endl;
std::cout << " Faction: " << ref.mFaction << '\n';
if (!ref.mFaction.empty() || ref.mFactionRank != -2)
std::cout << " Faction rank: " << ref.mFactionRank << std::endl;
std::cout << " Enchantment charge: " << ref.mEnchantmentCharge << std::endl;
std::cout << " Uses/health: " << ref.mChargeInt << std::endl;
std::cout << " Gold value: " << ref.mGoldValue << std::endl;
std::cout << " Blocked: " << static_cast<int>(ref.mReferenceBlocked) << std::endl;
std::cout << " Deleted: " << deleted << std::endl;
std::cout << " Faction rank: " << ref.mFactionRank << '\n';
std::cout << " Enchantment charge: " << ref.mEnchantmentCharge << '\n';
std::cout << " Uses/health: " << ref.mChargeInt << '\n';
std::cout << " Gold value: " << ref.mGoldValue << '\n';
std::cout << " Blocked: " << static_cast<int>(ref.mReferenceBlocked) << '\n';
std::cout << " Deleted: " << deleted << '\n';
if (!ref.mKey.empty())
std::cout << " Key: " << ref.mKey << std::endl;
std::cout << " Lock level: " << ref.mLockLevel << std::endl;
std::cout << " Key: " << ref.mKey << '\n';
std::cout << " Lock level: " << ref.mLockLevel << '\n';
if (!ref.mTrap.empty())
std::cout << " Trap: " << ref.mTrap << std::endl;
std::cout << " Trap: " << ref.mTrap << '\n';
if (!ref.mSoul.empty())
std::cout << " Soul: " << ref.mSoul << std::endl;
std::cout << " Soul: " << ref.mSoul << '\n';
if (ref.mTeleport)
{
std::cout << " Destination position: (" << ref.mDoorDest.pos[0] << ", "
<< ref.mDoorDest.pos[1] << ", " << ref.mDoorDest.pos[2] << ")" << std::endl;
<< ref.mDoorDest.pos[1] << ", " << ref.mDoorDest.pos[2] << ")\n";
if (!ref.mDestCell.empty())
std::cout << " Destination cell: " << ref.mDestCell << std::endl;
std::cout << " Destination cell: " << ref.mDestCell << '\n';
}
std::cout << " Moved: " << std::boolalpha << moved << std::noboolalpha << '\n';
if (moved)
{
std::cout << " Moved refnum: " << movedCellRef.mRefNum.mIndex << '\n';
std::cout << " Moved content file: " << movedCellRef.mRefNum.mContentFile << '\n';
std::cout << " Target: " << movedCellRef.mTarget[0] << ", " << movedCellRef.mTarget[1] << '\n';
}
}
}
void printRaw(ESM::ESMReader &esm)
void printRawTes3(std::string_view path)
{
std::cout << "TES3 RAW file listing: " << path << '\n';
ESM::ESMReader esm;
esm.openRaw(path);
while(esm.hasMoreRecs())
{
ESM::NAME n = esm.getRecName();
std::cout << "Record: " << n.toString() << std::endl;
std::cout << "Record: " << n.toStringView() << '\n';
esm.getRecHeader();
while(esm.hasMoreSubs())
{
@ -306,75 +301,62 @@ void printRaw(ESM::ESMReader &esm)
esm.skipHSub();
n = esm.retSubName();
std::ios::fmtflags f(std::cout.flags());
std::cout << " " << n.toString() << " - " << esm.getSubSize()
<< " bytes @ 0x" << std::hex << offs << "\n";
std::cout << " " << n.toStringView() << " - " << esm.getSubSize()
<< " bytes @ 0x" << std::hex << offs << '\n';
std::cout.flags(f);
}
}
}
int load(Arguments& info)
int loadTes3(const Arguments& info, std::unique_ptr<std::ifstream>&& stream, ESMData* data)
{
ESM::ESMReader& esm = info.reader;
std::cout << "Loading TES3 file: " << info.filename << '\n';
ESM::ESMReader esm;
ToUTF8::Utf8Encoder encoder (ToUTF8::calculateEncoding(info.encoding));
esm.setEncoder(&encoder);
std::string filename = info.filename;
std::cout << "Loading file: " << filename << std::endl;
std::list<int> skipped;
try {
if(info.raw_given && info.mode == "dump")
{
std::cout << "RAW file listing:\n";
esm.openRaw(filename);
printRaw(esm);
return 0;
}
std::unordered_set<uint32_t> skipped;
try
{
bool quiet = (info.quiet_given || info.mode == "clone");
bool loadCells = (info.loadcells_given || info.mode == "clone");
bool save = (info.mode == "clone");
esm.open(filename);
esm.open(std::move(stream), info.filename);
info.data.author = esm.getAuthor();
info.data.description = esm.getDesc();
info.data.masters = esm.getGameFiles();
if (data != nullptr)
data->mHeader = esm.getHeader();
if (!quiet)
{
std::cout << "Author: " << esm.getAuthor() << std::endl
<< "Description: " << esm.getDesc() << std::endl
<< "File format version: " << esm.getFVer() << std::endl;
std::cout << "Author: " << esm.getAuthor() << '\n'
<< "Description: " << esm.getDesc() << '\n'
<< "File format version: " << esm.getFVer() << '\n';
std::vector<ESM::Header::MasterData> masterData = esm.getGameFiles();
if (!masterData.empty())
{
std::cout << "Masters:" << std::endl;
std::cout << "Masters:" << '\n';
for(const auto& master : masterData)
std::cout << " " << master.name << ", " << master.size << " bytes" << std::endl;
std::cout << " " << master.name << ", " << master.size << " bytes\n";
}
}
// Loop through all records
while(esm.hasMoreRecs())
{
ESM::NAME n = esm.getRecName();
const ESM::NAME n = esm.getRecName();
uint32_t flags;
esm.getRecHeader(flags);
EsmTool::RecordBase *record = EsmTool::RecordBase::create(n);
auto record = EsmTool::RecordBase::create(n);
if (record == nullptr)
{
if (std::find(skipped.begin(), skipped.end(), n.intval) == skipped.end())
if (!quiet && skipped.count(n.toInt()) == 0)
{
std::cout << "Skipping " << n.toString() << " records." << std::endl;
skipped.push_back(n.intval);
std::cout << "Skipping " << n.toStringView() << " records.\n";
skipped.emplace(n.toInt());
}
esm.skipRecord();
@ -390,54 +372,88 @@ int load(Arguments& info)
// Is the user interested in this record type?
bool interested = true;
if (!info.types.empty())
{
std::vector<std::string>::iterator match;
match = std::find(info.types.begin(), info.types.end(), n.toString());
if (match == info.types.end()) interested = false;
}
if (!info.types.empty() && std::find(info.types.begin(), info.types.end(), n.toStringView()) == info.types.end())
interested = false;
if (!info.name.empty() && !Misc::StringUtils::ciEqual(info.name, record->getId()))
interested = false;
if(!quiet && interested)
{
std::cout << "\nRecord: " << n.toString() << " '" << record->getId() << "'\n";
std::cout << "\nRecord: " << n.toStringView() << " '" << record->getId() << "'\n"
<< "Record flags: " << recordFlags(record->getFlags()) << '\n';
record->print();
}
if (record->getType().intval == ESM::REC_CELL && loadCells && interested)
if (record->getType().toInt() == ESM::REC_CELL && loadCells && interested)
{
loadCell(record->cast<ESM::Cell>()->get(), esm, info);
loadCell(info, record->cast<ESM::Cell>()->get(), esm, data);
}
if (save)
{
info.data.mRecords.push_back(record);
}
else
if (data != nullptr)
{
delete record;
if (save)
data->mRecords.push_back(std::move(record));
++data->mRecordStats[n.toInt()];
}
++info.data.mRecordStats[n.intval];
}
} catch(std::exception &e) {
}
catch (const std::exception &e)
{
std::cout << "\nERROR:\n\n " << e.what() << std::endl;
for (const EsmTool::RecordBase* record : info.data.mRecords)
delete record;
info.data.mRecords.clear();
if (data != nullptr)
data->mRecords.clear();
return 1;
}
return 0;
}
#include <iomanip>
int load(const Arguments& info, ESMData* data)
{
if (info.mRawFormat.has_value() && info.mode == "dump")
{
switch (*info.mRawFormat)
{
case ESM::Format::Tes3:
printRawTes3(info.filename);
break;
case ESM::Format::Tes4:
std::cout << "Printing raw TES4 file is not supported: " << info.filename << "\n";
break;
}
return 0;
}
int clone(Arguments& info)
auto stream = Files::openBinaryInputFileStream(info.filename);
if (!stream->is_open())
{
std::cout << "Failed to open file: " << std::strerror(errno) << '\n';
return -1;
}
const ESM::Format format = ESM::readFormat(*stream);
stream->seekg(0);
switch (format)
{
case ESM::Format::Tes3:
return loadTes3(info, std::move(stream), data);
case ESM::Format::Tes4:
if (data != nullptr)
{
std::cout << "Collecting data from esm file is not supported for TES4\n";
return -1;
}
return loadTes4(info, std::move(stream));
}
std::cout << "Unsupported ESM format: " << ESM::NAME(format).toStringView() << '\n';
return -1;
}
int clone(const Arguments& info)
{
if (info.outname.empty())
{
@ -445,71 +461,68 @@ int clone(Arguments& info)
return 1;
}
if (load(info) != 0)
ESMData data;
if (load(info, &data) != 0)
{
std::cout << "Failed to load, aborting." << std::endl;
return 1;
}
size_t recordCount = info.data.mRecords.size();
size_t recordCount = data.mRecords.size();
int digitCount = 1; // For a nicer output
if (recordCount > 0)
digitCount = (int)std::log10(recordCount) + 1;
std::cout << "Loaded " << recordCount << " records:" << std::endl << std::endl;
std::cout << "Loaded " << recordCount << " records:\n\n";
int i = 0;
for (std::pair<int, int> stat : info.data.mRecordStats)
for (std::pair<int, int> stat : data.mRecordStats)
{
ESM::NAME name;
name.intval = stat.first;
name = stat.first;
int amount = stat.second;
std::cout << std::setw(digitCount) << amount << " " << name.toString() << " ";
std::cout << std::setw(digitCount) << amount << " " << name.toStringView() << " ";
if (++i % 3 == 0)
std::cout << std::endl;
std::cout << '\n';
}
if (i % 3 != 0)
std::cout << std::endl;
std::cout << '\n';
std::cout << std::endl << "Saving records to: " << info.outname << "..." << std::endl;
std::cout << "\nSaving records to: " << info.outname << "...\n";
ESM::ESMWriter& esm = info.writer;
ESM::ESMWriter esm;
ToUTF8::Utf8Encoder encoder (ToUTF8::calculateEncoding(info.encoding));
esm.setEncoder(&encoder);
esm.setAuthor(info.data.author);
esm.setDescription(info.data.description);
esm.setVersion(info.data.version);
esm.setHeader(data.mHeader);
esm.setVersion(ESM::VER_13);
esm.setRecordCount (recordCount);
for (const ESM::Header::MasterData &master : info.data.masters)
esm.addMaster(master.name, master.size);
std::fstream save(info.outname.c_str(), std::fstream::out | std::fstream::binary);
esm.save(save);
int saved = 0;
for (EsmTool::RecordBase* record : info.data.mRecords)
for (auto& record : data.mRecords)
{
if (i <= 0)
break;
const ESM::NAME& typeName = record->getType();
const ESM::NAME typeName = record->getType();
esm.startRecord(typeName.toString(), record->getFlags());
esm.startRecord(typeName, record->getFlags());
record->save(esm);
if (typeName.intval == ESM::REC_CELL) {
if (typeName.toInt() == ESM::REC_CELL) {
ESM::Cell *ptr = &record->cast<ESM::Cell>()->get();
if (!info.data.mCellRefs[ptr].empty())
if (!data.mCellRefs[ptr].empty())
{
for (std::pair<ESM::CellRef, bool> &ref : info.data.mCellRefs[ptr])
for (std::pair<ESM::CellRef, bool> &ref : data.mCellRefs[ptr])
ref.first.save(esm, ref.second);
}
}
esm.endRecord(typeName.toString());
esm.endRecord(typeName);
saved++;
int perc = recordCount == 0 ? 100 : (int)((saved / (float)recordCount)*100);
@ -527,7 +540,7 @@ int clone(Arguments& info)
return 0;
}
int comp(Arguments& info)
int comp(const Arguments& info)
{
if (info.filename.empty() || info.outname.empty())
{
@ -538,9 +551,6 @@ int comp(Arguments& info)
Arguments fileOne;
Arguments fileTwo;
fileOne.raw_given = false;
fileTwo.raw_given = false;
fileOne.mode = "clone";
fileTwo.mode = "clone";
@ -550,19 +560,21 @@ int comp(Arguments& info)
fileOne.filename = info.filename;
fileTwo.filename = info.outname;
if (load(fileOne) != 0)
ESMData dataOne;
if (load(fileOne, &dataOne) != 0)
{
std::cout << "Failed to load " << info.filename << ", aborting comparison." << std::endl;
return 1;
}
if (load(fileTwo) != 0)
ESMData dataTwo;
if (load(fileTwo, &dataTwo) != 0)
{
std::cout << "Failed to load " << info.outname << ", aborting comparison." << std::endl;
return 1;
}
if (fileOne.data.mRecords.size() != fileTwo.data.mRecords.size())
if (dataOne.mRecords.size() != dataTwo.mRecords.size())
{
std::cout << "Not equal, different amount of records." << std::endl;
return 1;
@ -570,3 +582,5 @@ int comp(Arguments& info)
return 0;
}
}

@ -1,17 +1,17 @@
#include "labels.hpp"
#include <components/esm/loadbody.hpp>
#include <components/esm/loadcell.hpp>
#include <components/esm/loadcont.hpp>
#include <components/esm/loadcrea.hpp>
#include <components/esm/loadench.hpp>
#include <components/esm/loadlevlist.hpp>
#include <components/esm/loadligh.hpp>
#include <components/esm/loadmgef.hpp>
#include <components/esm/loadnpc.hpp>
#include <components/esm/loadrace.hpp>
#include <components/esm/loadspel.hpp>
#include <components/esm/loadweap.hpp>
#include <components/esm3/loadbody.hpp>
#include <components/esm3/loadcell.hpp>
#include <components/esm3/loadcont.hpp>
#include <components/esm3/loadcrea.hpp>
#include <components/esm3/loadench.hpp>
#include <components/esm3/loadlevlist.hpp>
#include <components/esm3/loadligh.hpp>
#include <components/esm3/loadmgef.hpp>
#include <components/esm3/loadnpc.hpp>
#include <components/esm3/loadrace.hpp>
#include <components/esm3/loadspel.hpp>
#include <components/esm3/loadweap.hpp>
#include <components/misc/stringops.hpp>
@ -902,3 +902,17 @@ std::string weaponFlags(int flags)
properties += Misc::StringUtils::format("(0x%08X)", flags);
return properties;
}
std::string recordFlags(uint32_t flags)
{
std::string properties;
if (flags == 0) properties += "[None] ";
if (flags & ESM::FLAG_Deleted) properties += "Deleted ";
if (flags & ESM::FLAG_Persistent) properties += "Persistent ";
if (flags & ESM::FLAG_Ignored) properties += "Ignored ";
if (flags & ESM::FLAG_Blocked) properties += "Blocked ";
int unused = ~(ESM::FLAG_Deleted | ESM::FLAG_Persistent | ESM::FLAG_Ignored | ESM::FLAG_Blocked);
if (flags & unused) properties += "Invalid ";
properties += Misc::StringUtils::format("(0x%08X)", flags);
return properties;
}

@ -60,6 +60,8 @@ std::string raceFlags(int flags);
std::string spellFlags(int flags);
std::string weaponFlags(int flags);
std::string recordFlags(uint32_t flags);
// Missing flags functions:
// aiServicesFlags, possibly more

@ -30,7 +30,7 @@ void printAIPackage(const ESM::AIPackage& p)
{
std::cout << " Travel Coordinates: (" << p.mTravel.mX << ","
<< p.mTravel.mY << "," << p.mTravel.mZ << ")" << std::endl;
std::cout << " Travel Unknown: " << p.mTravel.mUnk << std::endl;
std::cout << " Should repeat: " << p.mTravel.mShouldRepeat << std::endl;
}
else if (p.mType == ESM::AI_Follow || p.mType == ESM::AI_Escort)
{
@ -38,12 +38,12 @@ void printAIPackage(const ESM::AIPackage& p)
<< p.mTarget.mY << "," << p.mTarget.mZ << ")" << std::endl;
std::cout << " Duration: " << p.mTarget.mDuration << std::endl;
std::cout << " Target ID: " << p.mTarget.mId.toString() << std::endl;
std::cout << " Unknown: " << p.mTarget.mUnk << std::endl;
std::cout << " Should repeat: " << p.mTarget.mShouldRepeat << std::endl;
}
else if (p.mType == ESM::AI_Activate)
{
std::cout << " Name: " << p.mActivate.mName.toString() << std::endl;
std::cout << " Activate Unknown: " << p.mActivate.mUnk << std::endl;
std::cout << " Should repeat: " << p.mActivate.mShouldRepeat << std::endl;
}
else {
std::cout << " BadPackage: " << Misc::StringUtils::format("0x%08X", p.mType) << std::endl;
@ -171,226 +171,227 @@ void printTransport(const std::vector<ESM::Transport::Dest>& transport)
namespace EsmTool {
RecordBase *
RecordBase::create(ESM::NAME type)
std::unique_ptr<RecordBase> RecordBase::create(const ESM::NAME type)
{
RecordBase *record = nullptr;
std::unique_ptr<RecordBase> record;
switch (type.intval) {
switch (type.toInt())
{
case ESM::REC_ACTI:
{
record = new EsmTool::Record<ESM::Activator>;
record = std::make_unique<EsmTool::Record<ESM::Activator>>();
break;
}
case ESM::REC_ALCH:
{
record = new EsmTool::Record<ESM::Potion>;
record = std::make_unique<EsmTool::Record<ESM::Potion>>();
break;
}
case ESM::REC_APPA:
{
record = new EsmTool::Record<ESM::Apparatus>;
record = std::make_unique<EsmTool::Record<ESM::Apparatus>>();
break;
}
case ESM::REC_ARMO:
{
record = new EsmTool::Record<ESM::Armor>;
record = std::make_unique<EsmTool::Record<ESM::Armor>>();
break;
}
case ESM::REC_BODY:
{
record = new EsmTool::Record<ESM::BodyPart>;
record = std::make_unique<EsmTool::Record<ESM::BodyPart>>();
break;
}
case ESM::REC_BOOK:
{
record = new EsmTool::Record<ESM::Book>;
record = std::make_unique<EsmTool::Record<ESM::Book>>();
break;
}
case ESM::REC_BSGN:
{
record = new EsmTool::Record<ESM::BirthSign>;
record = std::make_unique<EsmTool::Record<ESM::BirthSign>>();
break;
}
case ESM::REC_CELL:
{
record = new EsmTool::Record<ESM::Cell>;
record = std::make_unique<EsmTool::Record<ESM::Cell>>();
break;
}
case ESM::REC_CLAS:
{
record = new EsmTool::Record<ESM::Class>;
record = std::make_unique<EsmTool::Record<ESM::Class>>();
break;
}
case ESM::REC_CLOT:
{
record = new EsmTool::Record<ESM::Clothing>;
record = std::make_unique<EsmTool::Record<ESM::Clothing>>();
break;
}
case ESM::REC_CONT:
{
record = new EsmTool::Record<ESM::Container>;
record = std::make_unique<EsmTool::Record<ESM::Container>>();
break;
}
case ESM::REC_CREA:
{
record = new EsmTool::Record<ESM::Creature>;
record = std::make_unique<EsmTool::Record<ESM::Creature>>();
break;
}
case ESM::REC_DIAL:
{
record = new EsmTool::Record<ESM::Dialogue>;
record = std::make_unique<EsmTool::Record<ESM::Dialogue>>();
break;
}
case ESM::REC_DOOR:
{
record = new EsmTool::Record<ESM::Door>;
record = std::make_unique<EsmTool::Record<ESM::Door>>();
break;
}
case ESM::REC_ENCH:
{
record = new EsmTool::Record<ESM::Enchantment>;
record = std::make_unique<EsmTool::Record<ESM::Enchantment>>();
break;
}
case ESM::REC_FACT:
{
record = new EsmTool::Record<ESM::Faction>;
record = std::make_unique<EsmTool::Record<ESM::Faction>>();
break;
}
case ESM::REC_GLOB:
{
record = new EsmTool::Record<ESM::Global>;
record = std::make_unique<EsmTool::Record<ESM::Global>>();
break;
}
case ESM::REC_GMST:
{
record = new EsmTool::Record<ESM::GameSetting>;
record = std::make_unique<EsmTool::Record<ESM::GameSetting>>();
break;
}
case ESM::REC_INFO:
{
record = new EsmTool::Record<ESM::DialInfo>;
record = std::make_unique<EsmTool::Record<ESM::DialInfo>>();
break;
}
case ESM::REC_INGR:
{
record = new EsmTool::Record<ESM::Ingredient>;
record = std::make_unique<EsmTool::Record<ESM::Ingredient>>();
break;
}
case ESM::REC_LAND:
{
record = new EsmTool::Record<ESM::Land>;
record = std::make_unique<EsmTool::Record<ESM::Land>>();
break;
}
case ESM::REC_LEVI:
{
record = new EsmTool::Record<ESM::ItemLevList>;
record = std::make_unique<EsmTool::Record<ESM::ItemLevList>>();
break;
}
case ESM::REC_LEVC:
{
record = new EsmTool::Record<ESM::CreatureLevList>;
record = std::make_unique<EsmTool::Record<ESM::CreatureLevList>>();
break;
}
case ESM::REC_LIGH:
{
record = new EsmTool::Record<ESM::Light>;
record = std::make_unique<EsmTool::Record<ESM::Light>>();
break;
}
case ESM::REC_LOCK:
{
record = new EsmTool::Record<ESM::Lockpick>;
record = std::make_unique<EsmTool::Record<ESM::Lockpick>>();
break;
}
case ESM::REC_LTEX:
{
record = new EsmTool::Record<ESM::LandTexture>;
record = std::make_unique<EsmTool::Record<ESM::LandTexture>>();
break;
}
case ESM::REC_MISC:
{
record = new EsmTool::Record<ESM::Miscellaneous>;
record = std::make_unique<EsmTool::Record<ESM::Miscellaneous>>();
break;
}
case ESM::REC_MGEF:
{
record = new EsmTool::Record<ESM::MagicEffect>;
record = std::make_unique<EsmTool::Record<ESM::MagicEffect>>();
break;
}
case ESM::REC_NPC_:
{
record = new EsmTool::Record<ESM::NPC>;
record = std::make_unique<EsmTool::Record<ESM::NPC>>();
break;
}
case ESM::REC_PGRD:
{
record = new EsmTool::Record<ESM::Pathgrid>;
record = std::make_unique<EsmTool::Record<ESM::Pathgrid>>();
break;
}
case ESM::REC_PROB:
{
record = new EsmTool::Record<ESM::Probe>;
record = std::make_unique<EsmTool::Record<ESM::Probe>>();
break;
}
case ESM::REC_RACE:
{
record = new EsmTool::Record<ESM::Race>;
record = std::make_unique<EsmTool::Record<ESM::Race>>();
break;
}
case ESM::REC_REGN:
{
record = new EsmTool::Record<ESM::Region>;
record = std::make_unique<EsmTool::Record<ESM::Region>>();
break;
}
case ESM::REC_REPA:
{
record = new EsmTool::Record<ESM::Repair>;
record = std::make_unique<EsmTool::Record<ESM::Repair>>();
break;
}
case ESM::REC_SCPT:
{
record = new EsmTool::Record<ESM::Script>;
record = std::make_unique<EsmTool::Record<ESM::Script>>();
break;
}
case ESM::REC_SKIL:
{
record = new EsmTool::Record<ESM::Skill>;
record = std::make_unique<EsmTool::Record<ESM::Skill>>();
break;
}
case ESM::REC_SNDG:
{
record = new EsmTool::Record<ESM::SoundGenerator>;
record = std::make_unique<EsmTool::Record<ESM::SoundGenerator>>();
break;
}
case ESM::REC_SOUN:
{
record = new EsmTool::Record<ESM::Sound>;
record = std::make_unique<EsmTool::Record<ESM::Sound>>();
break;
}
case ESM::REC_SPEL:
{
record = new EsmTool::Record<ESM::Spell>;
record = std::make_unique<EsmTool::Record<ESM::Spell>>();
break;
}
case ESM::REC_STAT:
{
record = new EsmTool::Record<ESM::Static>;
record = std::make_unique<EsmTool::Record<ESM::Static>>();
break;
}
case ESM::REC_WEAP:
{
record = new EsmTool::Record<ESM::Weapon>;
record = std::make_unique<EsmTool::Record<ESM::Weapon>>();
break;
}
case ESM::REC_SSCR:
{
record = new EsmTool::Record<ESM::StartScript>;
record = std::make_unique<EsmTool::Record<ESM::StartScript>>();
break;
}
default:
record = nullptr;
break;
}
if (record) {
if (record)
{
record->mType = type;
}
return record;
@ -1183,8 +1184,8 @@ void Record<ESM::Region>::print()
std::cout << " Thunder: " << (int)mData.mData.mThunder << std::endl;
std::cout << " Ash: " << (int)mData.mData.mAsh << std::endl;
std::cout << " Blight: " << (int)mData.mData.mBlight << std::endl;
std::cout << " UnknownA: " << (int)mData.mData.mA << std::endl;
std::cout << " UnknownB: " << (int)mData.mData.mB << std::endl;
std::cout << " Snow: " << (int)mData.mData.mSnow << std::endl;
std::cout << " Blizzard: " << (int)mData.mData.mBlizzard << std::endl;
std::cout << " Map Color: " << mData.mMapColor << std::endl;
if (!mData.mSleepList.empty())
std::cout << " Sleep List: " << mData.mSleepList << std::endl;

@ -2,6 +2,7 @@
#define OPENMW_ESMTOOL_RECORD_H
#include <string>
#include <memory>
#include <components/esm/records.hpp>
@ -54,7 +55,7 @@ namespace EsmTool
virtual void save(ESM::ESMWriter &esm) = 0;
virtual void print() = 0;
static RecordBase *create(ESM::NAME type);
static std::unique_ptr<RecordBase> create(ESM::NAME type);
// just make it a bit shorter
template <class T>

@ -0,0 +1,329 @@
#include "tes4.hpp"
#include "arguments.hpp"
#include "labels.hpp"
#include <fstream>
#include <iostream>
#include <type_traits>
#include <components/esm/esmcommon.hpp>
#include <components/esm4/reader.hpp>
#include <components/esm4/records.hpp>
namespace EsmTool
{
namespace
{
struct Params
{
const bool mQuite;
explicit Params(const Arguments& info)
: mQuite(info.quiet_given || info.mode == "clone")
{}
};
std::string toString(ESM4::GroupType type)
{
switch (type)
{
case ESM4::Grp_RecordType: return "RecordType";
case ESM4::Grp_WorldChild: return "WorldChild";
case ESM4::Grp_InteriorCell: return "InteriorCell";
case ESM4::Grp_InteriorSubCell: return "InteriorSubCell";
case ESM4::Grp_ExteriorCell: return "ExteriorCell";
case ESM4::Grp_ExteriorSubCell: return "ExteriorSubCell";
case ESM4::Grp_CellChild: return "CellChild";
case ESM4::Grp_TopicChild: return "TopicChild";
case ESM4::Grp_CellPersistentChild: return "CellPersistentChild";
case ESM4::Grp_CellTemporaryChild: return "CellTemporaryChild";
case ESM4::Grp_CellVisibleDistChild: return "CellVisibleDistChild";
}
return "Unknown (" + std::to_string(type) + ")";
}
template <class T, class = std::void_t<>>
struct HasFormId : std::false_type {};
template <class T>
struct HasFormId<T, std::void_t<decltype(T::mFormId)>> : std::true_type {};
template <class T>
constexpr bool hasFormId = HasFormId<T>::value;
template <class T, class = std::void_t<>>
struct HasFlags : std::false_type {};
template <class T>
struct HasFlags<T, std::void_t<decltype(T::mFlags)>> : std::true_type {};
template <class T>
constexpr bool hasFlags = HasFlags<T>::value;
template <class T>
void readTypedRecord(const Params& params, ESM4::Reader& reader)
{
reader.getRecordData();
T value;
value.load(reader);
if (params.mQuite)
return;
std::cout << "\n Record: " << ESM::NAME(reader.hdr().record.typeId).toStringView();
if constexpr (hasFormId<T>)
std::cout << ' ' << value.mFormId;
if constexpr (hasFlags<T>)
std::cout << "\n Record flags: " << recordFlags(value.mFlags);
std::cout << '\n';
}
void readRecord(const Params& params, ESM4::Reader& reader)
{
switch (static_cast<ESM4::RecordTypes>(reader.hdr().record.typeId))
{
case ESM4::REC_AACT: break;
case ESM4::REC_ACHR: return readTypedRecord<ESM4::ActorCharacter>(params, reader);
case ESM4::REC_ACRE: return readTypedRecord<ESM4::ActorCreature>(params, reader);
case ESM4::REC_ACTI: return readTypedRecord<ESM4::Activator>(params, reader);
case ESM4::REC_ADDN: break;
case ESM4::REC_ALCH: return readTypedRecord<ESM4::Potion>(params, reader);
case ESM4::REC_ALOC: return readTypedRecord<ESM4::MediaLocationController>(params, reader);
case ESM4::REC_AMMO: return readTypedRecord<ESM4::Ammunition>(params, reader);
case ESM4::REC_ANIO: return readTypedRecord<ESM4::AnimObject>(params, reader);
case ESM4::REC_APPA: return readTypedRecord<ESM4::Apparatus>(params, reader);
case ESM4::REC_ARMA: return readTypedRecord<ESM4::ArmorAddon>(params, reader);
case ESM4::REC_ARMO: return readTypedRecord<ESM4::Armor>(params, reader);
case ESM4::REC_ARTO: break;
case ESM4::REC_ASPC: return readTypedRecord<ESM4::AcousticSpace>(params, reader);
case ESM4::REC_ASTP: break;
case ESM4::REC_AVIF: break;
case ESM4::REC_BOOK: return readTypedRecord<ESM4::Book>(params, reader);
case ESM4::REC_BPTD: return readTypedRecord<ESM4::BodyPartData>(params, reader);
case ESM4::REC_CAMS: break;
case ESM4::REC_CCRD: break;
case ESM4::REC_CELL: return readTypedRecord<ESM4::Cell>(params, reader);
case ESM4::REC_CLAS: return readTypedRecord<ESM4::Class>(params, reader);
case ESM4::REC_CLFM: return readTypedRecord<ESM4::Colour>(params, reader);
case ESM4::REC_CLMT: break;
case ESM4::REC_CLOT: return readTypedRecord<ESM4::Clothing>(params, reader);
case ESM4::REC_CMNY: break;
case ESM4::REC_COBJ: break;
case ESM4::REC_COLL: break;
case ESM4::REC_CONT: return readTypedRecord<ESM4::Container>(params, reader);
case ESM4::REC_CPTH: break;
case ESM4::REC_CREA: return readTypedRecord<ESM4::Creature>(params, reader);
case ESM4::REC_CSTY: break;
case ESM4::REC_DEBR: break;
case ESM4::REC_DIAL: return readTypedRecord<ESM4::Dialogue>(params, reader);
case ESM4::REC_DLBR: break;
case ESM4::REC_DLVW: break;
case ESM4::REC_DOBJ: return readTypedRecord<ESM4::DefaultObj>(params, reader);
case ESM4::REC_DOOR: return readTypedRecord<ESM4::Door>(params, reader);
case ESM4::REC_DUAL: break;
case ESM4::REC_ECZN: break;
case ESM4::REC_EFSH: break;
case ESM4::REC_ENCH: break;
case ESM4::REC_EQUP: break;
case ESM4::REC_EXPL: break;
case ESM4::REC_EYES: return readTypedRecord<ESM4::Eyes>(params, reader);
case ESM4::REC_FACT: break;
case ESM4::REC_FLOR: return readTypedRecord<ESM4::Flora>(params, reader);
case ESM4::REC_FLST: return readTypedRecord<ESM4::FormIdList>(params, reader);
case ESM4::REC_FSTP: break;
case ESM4::REC_FSTS: break;
case ESM4::REC_FURN: return readTypedRecord<ESM4::Furniture>(params, reader);
case ESM4::REC_GLOB: return readTypedRecord<ESM4::GlobalVariable>(params, reader);
case ESM4::REC_GMST: break;
case ESM4::REC_GRAS: return readTypedRecord<ESM4::Grass>(params, reader);
case ESM4::REC_GRUP: break;
case ESM4::REC_HAIR: return readTypedRecord<ESM4::Hair>(params, reader);
case ESM4::REC_HAZD: break;
case ESM4::REC_HDPT: return readTypedRecord<ESM4::HeadPart>(params, reader);
case ESM4::REC_IDLE:
// FIXME: ESM4::IdleAnimation::load does not work with Oblivion.esm
// return readTypedRecord<ESM4::IdleAnimation>(params, reader);
break;
case ESM4::REC_IDLM: return readTypedRecord<ESM4::IdleMarker>(params, reader);
case ESM4::REC_IMAD: break;
case ESM4::REC_IMGS: break;
case ESM4::REC_IMOD: return readTypedRecord<ESM4::ItemMod>(params, reader);
case ESM4::REC_INFO: return readTypedRecord<ESM4::DialogInfo>(params, reader);
case ESM4::REC_INGR: return readTypedRecord<ESM4::Ingredient>(params, reader);
case ESM4::REC_IPCT: break;
case ESM4::REC_IPDS: break;
case ESM4::REC_KEYM: return readTypedRecord<ESM4::Key>(params, reader);
case ESM4::REC_KYWD: break;
case ESM4::REC_LAND: return readTypedRecord<ESM4::Land>(params, reader);
case ESM4::REC_LCRT: break;
case ESM4::REC_LCTN: break;
case ESM4::REC_LGTM: return readTypedRecord<ESM4::LightingTemplate>(params, reader);
case ESM4::REC_LIGH: return readTypedRecord<ESM4::Light>(params, reader);
case ESM4::REC_LSCR: break;
case ESM4::REC_LTEX: return readTypedRecord<ESM4::LandTexture>(params, reader);
case ESM4::REC_LVLC: return readTypedRecord<ESM4::LevelledCreature>(params, reader);
case ESM4::REC_LVLI: return readTypedRecord<ESM4::LevelledItem>(params, reader);
case ESM4::REC_LVLN: return readTypedRecord<ESM4::LevelledNpc>(params, reader);
case ESM4::REC_LVSP: break;
case ESM4::REC_MATO: return readTypedRecord<ESM4::Material>(params, reader);
case ESM4::REC_MATT: break;
case ESM4::REC_MESG: break;
case ESM4::REC_MGEF: break;
case ESM4::REC_MISC: return readTypedRecord<ESM4::MiscItem>(params, reader);
case ESM4::REC_MOVT: break;
case ESM4::REC_MSET: return readTypedRecord<ESM4::MediaSet>(params, reader);
case ESM4::REC_MSTT: return readTypedRecord<ESM4::MovableStatic>(params, reader);
case ESM4::REC_MUSC: return readTypedRecord<ESM4::Music>(params, reader);
case ESM4::REC_MUST: break;
case ESM4::REC_NAVI: return readTypedRecord<ESM4::Navigation>(params, reader);
case ESM4::REC_NAVM: return readTypedRecord<ESM4::NavMesh>(params, reader);
case ESM4::REC_NOTE: return readTypedRecord<ESM4::Note>(params, reader);
case ESM4::REC_NPC_: return readTypedRecord<ESM4::Npc>(params, reader);
case ESM4::REC_OTFT: return readTypedRecord<ESM4::Outfit>(params, reader);
case ESM4::REC_PACK: return readTypedRecord<ESM4::AIPackage>(params, reader);
case ESM4::REC_PERK: break;
case ESM4::REC_PGRD: return readTypedRecord<ESM4::Pathgrid>(params, reader);
case ESM4::REC_PGRE: return readTypedRecord<ESM4::PlacedGrenade>(params, reader);
case ESM4::REC_PHZD: break;
case ESM4::REC_PROJ: break;
case ESM4::REC_PWAT: return readTypedRecord<ESM4::PlaceableWater>(params, reader);
case ESM4::REC_QUST: return readTypedRecord<ESM4::Quest>(params, reader);
case ESM4::REC_RACE: return readTypedRecord<ESM4::Race>(params, reader);
case ESM4::REC_REFR: return readTypedRecord<ESM4::Reference>(params, reader);
case ESM4::REC_REGN: return readTypedRecord<ESM4::Region>(params, reader);
case ESM4::REC_RELA: break;
case ESM4::REC_REVB: break;
case ESM4::REC_RFCT: break;
case ESM4::REC_ROAD: return readTypedRecord<ESM4::Road>(params, reader);
case ESM4::REC_SBSP: return readTypedRecord<ESM4::SubSpace>(params, reader);
case ESM4::REC_SCEN: break;
case ESM4::REC_SCOL: return readTypedRecord<ESM4::StaticCollection>(params, reader);
case ESM4::REC_SCPT: return readTypedRecord<ESM4::Script>(params, reader);
case ESM4::REC_SCRL: return readTypedRecord<ESM4::Scroll>(params, reader);
case ESM4::REC_SGST: return readTypedRecord<ESM4::SigilStone>(params, reader);
case ESM4::REC_SHOU: break;
case ESM4::REC_SLGM: return readTypedRecord<ESM4::SoulGem>(params, reader);
case ESM4::REC_SMBN: break;
case ESM4::REC_SMEN: break;
case ESM4::REC_SMQN: break;
case ESM4::REC_SNCT: break;
case ESM4::REC_SNDR: return readTypedRecord<ESM4::SoundReference>(params, reader);
case ESM4::REC_SOPM: break;
case ESM4::REC_SOUN: return readTypedRecord<ESM4::Sound>(params, reader);
case ESM4::REC_SPEL: break;
case ESM4::REC_SPGD: break;
case ESM4::REC_STAT: return readTypedRecord<ESM4::Static>(params, reader);
case ESM4::REC_TACT: return readTypedRecord<ESM4::TalkingActivator>(params, reader);
case ESM4::REC_TERM: return readTypedRecord<ESM4::Terminal>(params, reader);
case ESM4::REC_TES4: return readTypedRecord<ESM4::Header>(params, reader);
case ESM4::REC_TREE: return readTypedRecord<ESM4::Tree>(params, reader);
case ESM4::REC_TXST: return readTypedRecord<ESM4::TextureSet>(params, reader);
case ESM4::REC_VTYP: break;
case ESM4::REC_WATR: break;
case ESM4::REC_WEAP: return readTypedRecord<ESM4::Weapon>(params, reader);
case ESM4::REC_WOOP: break;
case ESM4::REC_WRLD: return readTypedRecord<ESM4::World>(params, reader);
case ESM4::REC_WTHR: break;
}
if (!params.mQuite)
std::cout << "\n Unsupported record: " << ESM::NAME(reader.hdr().record.typeId).toStringView() << '\n';
reader.skipRecordData();
}
bool readItem(const Params& params, ESM4::Reader& reader);
bool readGroup(const Params& params, ESM4::Reader& reader)
{
const ESM4::RecordHeader& header = reader.hdr();
if (!params.mQuite)
std::cout << "\nGroup: " << toString(static_cast<ESM4::GroupType>(header.group.type))
<< " " << ESM::NAME(header.group.typeId).toStringView() << '\n';
switch (static_cast<ESM4::GroupType>(header.group.type))
{
case ESM4::Grp_RecordType:
case ESM4::Grp_InteriorCell:
case ESM4::Grp_InteriorSubCell:
case ESM4::Grp_ExteriorCell:
case ESM4::Grp_ExteriorSubCell:
reader.enterGroup();
return readItem(params, reader);
case ESM4::Grp_WorldChild:
case ESM4::Grp_CellChild:
case ESM4::Grp_TopicChild:
case ESM4::Grp_CellPersistentChild:
case ESM4::Grp_CellTemporaryChild:
case ESM4::Grp_CellVisibleDistChild:
reader.adjustGRUPFormId();
reader.enterGroup();
if (!reader.hasMoreRecs())
return false;
return readItem(params, reader);
}
reader.skipGroup();
return true;
}
bool readItem(const Params& params, ESM4::Reader& reader)
{
if (!reader.getRecordHeader() || !reader.hasMoreRecs())
return false;
const ESM4::RecordHeader& header = reader.hdr();
if (header.record.typeId == ESM4::REC_GRUP)
return readGroup(params, reader);
readRecord(params, reader);
return true;
}
}
int loadTes4(const Arguments& info, std::unique_ptr<std::ifstream>&& stream)
{
std::cout << "Loading TES4 file: " << info.filename << '\n';
try
{
const ToUTF8::StatelessUtf8Encoder encoder(ToUTF8::calculateEncoding(info.encoding));
ESM4::Reader reader(std::move(stream), info.filename);
reader.setEncoder(&encoder);
const Params params(info);
if (!params.mQuite)
{
std::cout << "Author: " << reader.getAuthor() << '\n'
<< "Description: " << reader.getDesc() << '\n'
<< "File format version: " << reader.esmVersion() << '\n';
if (const std::vector<ESM::MasterData>& masterData = reader.getGameFiles(); !masterData.empty())
{
std::cout << "Masters:" << '\n';
for (const auto& master : masterData)
std::cout << " " << master.name << ", " << master.size << " bytes\n";
}
}
while (reader.hasMoreRecs())
{
reader.exitGroupCheck();
if (!readItem(params, reader))
break;
}
}
catch (const std::exception& e)
{
std::cout << "\nERROR:\n\n " << e.what() << std::endl;
return -1;
}
return 0;
}
}

@ -0,0 +1,15 @@
#ifndef OPENMW_ESMTOOL_TES4_H
#define OPENMW_ESMTOOL_TES4_H
#include <fstream>
#include <iosfwd>
#include <memory>
namespace EsmTool
{
struct Arguments;
int loadTes4(const Arguments& info, std::unique_ptr<std::ifstream>&& stream);
}
#endif

@ -36,7 +36,6 @@ openmw_add_executable(openmw-essimporter
target_link_libraries(openmw-essimporter
${Boost_PROGRAM_OPTIONS_LIBRARY}
${Boost_FILESYSTEM_LIBRARY}
components
)
@ -48,3 +47,13 @@ endif()
if (WIN32)
INSTALL(TARGETS openmw-essimporter RUNTIME DESTINATION ".")
endif(WIN32)
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC)
target_precompile_headers(openmw-essimporter PRIVATE
<algorithm>
<filesystem>
<fstream>
<string>
<vector>
)
endif()

@ -1,10 +1,10 @@
#ifndef OPENMW_ESSIMPORT_CONVERTACDT_H
#define OPENMW_ESSIMPORT_CONVERTACDT_H
#include <components/esm/creaturestats.hpp>
#include <components/esm/npcstats.hpp>
#include <components/esm/loadskil.hpp>
#include <components/esm/animationstate.hpp>
#include <components/esm3/creaturestats.hpp>
#include <components/esm3/npcstats.hpp>
#include <components/esm3/loadskil.hpp>
#include <components/esm3/animationstate.hpp>
#include "importacdt.hpp"

@ -3,7 +3,7 @@
#include "importcntc.hpp"
#include <components/esm/containerstate.hpp>
#include <components/esm3/containerstate.hpp>
namespace ESSImport
{

@ -3,7 +3,7 @@
#include "importcrec.hpp"
#include <components/esm/creaturestate.hpp>
#include <components/esm3/creaturestate.hpp>
namespace ESSImport
{

@ -5,8 +5,8 @@
#include <osgDB/WriteFile>
#include <components/esm/creaturestate.hpp>
#include <components/esm/containerstate.hpp>
#include <components/esm3/creaturestate.hpp>
#include <components/esm3/containerstate.hpp>
#include <components/misc/constants.hpp>
@ -68,7 +68,7 @@ namespace
{
if (isIndexedRefId(indexedRefId))
{
int refIndex;
int refIndex = 0;
std::string refId;
splitIndexedRefId(indexedRefId, refIndex, refId);
@ -278,7 +278,7 @@ namespace ESSImport
while (esm.isNextSub("MPCD"))
{
float notepos[3];
esm.getHT(notepos, 3*sizeof(float));
esm.getHTSized<3 * sizeof(float)>(notepos);
// Markers seem to be arranged in a 32*32 grid, notepos has grid-indices.
// This seems to be the reason markers can't be placed everywhere in interior cells,
@ -320,6 +320,8 @@ namespace ESSImport
esm.startRecord(ESM::REC_CSTA);
ESM::CellState csta;
csta.mHasFogOfWar = 0;
csta.mLastRespawn.mDay = 0;
csta.mLastRespawn.mHour = 0;
csta.mId = esmcell.getCellId();
csta.mId.save(esm);
// TODO csta.mLastRespawn;
@ -352,12 +354,12 @@ namespace ESSImport
}
else
{
int refIndex;
int refIndex = 0;
splitIndexedRefId(cellref.mIndexedRefId, refIndex, out.mRefID);
std::string idLower = Misc::StringUtils::lowerCase(out.mRefID);
std::map<std::pair<int, std::string>, NPCC>::const_iterator npccIt = mContext->mNpcChanges.find(
auto npccIt = mContext->mNpcChanges.find(
std::make_pair(refIndex, out.mRefID));
if (npccIt != mContext->mNpcChanges.end())
{
@ -369,6 +371,8 @@ namespace ESSImport
// from the ESM with default values
if (cellref.mHasACDT)
convertACDT(cellref.mACDT, objstate.mCreatureStats);
else
objstate.mCreatureStats.mMissingACDT = true;
if (cellref.mHasACSC)
convertACSC(cellref.mACSC, objstate.mCreatureStats);
convertNpcData(cellref, objstate.mNpcStats);
@ -383,7 +387,7 @@ namespace ESSImport
continue;
}
std::map<std::pair<int, std::string>, CNTC>::const_iterator cntcIt = mContext->mContainerChanges.find(
auto cntcIt = mContext->mContainerChanges.find(
std::make_pair(refIndex, out.mRefID));
if (cntcIt != mContext->mContainerChanges.end())
{
@ -398,7 +402,7 @@ namespace ESSImport
continue;
}
std::map<std::pair<int, std::string>, CREC>::const_iterator crecIt = mContext->mCreatureChanges.find(
auto crecIt = mContext->mCreatureChanges.find(
std::make_pair(refIndex, out.mRefID));
if (crecIt != mContext->mCreatureChanges.end())
{
@ -410,6 +414,8 @@ namespace ESSImport
// from the ESM with default values
if (cellref.mHasACDT)
convertACDT(cellref.mACDT, objstate.mCreatureStats);
else
objstate.mCreatureStats.mMissingACDT = true;
if (cellref.mHasACSC)
convertACSC(cellref.mACSC, objstate.mCreatureStats);
convertCREC(crecIt->second, objstate);
@ -486,6 +492,7 @@ namespace ESSImport
out.mSpellId = it->mSPDT.mId.toString();
out.mSpeed = pnam.mSpeed * 0.001f; // not sure where this factor comes from
out.mSlot = 0;
esm.startRecord(ESM::REC_MPRJ);
out.save(esm);

@ -6,23 +6,23 @@
#include <osg/Image>
#include <osg/ref_ptr>
#include <components/esm/esmreader.hpp>
#include <components/esm/esmwriter.hpp>
#include <components/esm/loadcell.hpp>
#include <components/esm/loadbook.hpp>
#include <components/esm/loadclas.hpp>
#include <components/esm/loadglob.hpp>
#include <components/esm/cellstate.hpp>
#include <components/esm/loadfact.hpp>
#include <components/esm/dialoguestate.hpp>
#include <components/esm/custommarkerstate.hpp>
#include <components/esm/loadcrea.hpp>
#include <components/esm/weatherstate.hpp>
#include <components/esm/globalscript.hpp>
#include <components/esm/queststate.hpp>
#include <components/esm/stolenitems.hpp>
#include <components/esm/projectilestate.hpp>
#include <components/esm3/esmreader.hpp>
#include <components/esm3/esmwriter.hpp>
#include <components/esm3/loadcell.hpp>
#include <components/esm3/loadbook.hpp>
#include <components/esm3/loadclas.hpp>
#include <components/esm3/loadglob.hpp>
#include <components/esm3/cellstate.hpp>
#include <components/esm3/loadfact.hpp>
#include <components/esm3/dialoguestate.hpp>
#include <components/esm3/custommarkerstate.hpp>
#include <components/esm3/loadcrea.hpp>
#include <components/esm3/weatherstate.hpp>
#include <components/esm3/globalscript.hpp>
#include <components/esm3/queststate.hpp>
#include <components/esm3/stolenitems.hpp>
#include <components/esm3/projectilestate.hpp>
#include "importcrec.hpp"
#include "importcntc.hpp"
@ -124,11 +124,9 @@ public:
{
mContext->mPlayer.mObject.mCreatureStats.mLevel = npc.mNpdt.mLevel;
mContext->mPlayerBase = npc;
ESM::SpellState::SpellParams empty;
// FIXME: player start spells and birthsign spells aren't listed here,
// need to fix openmw to account for this
for (const auto & spell : npc.mSpells.mList)
mContext->mPlayer.mObject.mCreatureStats.mSpells.mSpells[spell] = empty;
mContext->mPlayer.mObject.mCreatureStats.mSpells.mSpells = npc.mSpells.mList;
// Clear the list now that we've written it, this prevents issues cropping up with
// ensureCustomData() in OpenMW tripping over no longer existing spells, where an error would be fatal.
@ -374,7 +372,7 @@ public:
void write(ESM::ESMWriter &esm) override
{
esm.startRecord(ESM::REC_DCOU);
for (std::map<std::string, int>::const_iterator it = mKillCounter.begin(); it != mKillCounter.end(); ++it)
for (auto it = mKillCounter.begin(); it != mKillCounter.end(); ++it)
{
esm.writeHNString("ID__", it->first);
esm.writeHNT ("COUN", it->second);
@ -397,7 +395,7 @@ public:
faction.load(esm, isDeleted);
std::string id = Misc::StringUtils::lowerCase(faction.mId);
for (std::map<std::string, int>::const_iterator it = faction.mReactions.begin(); it != faction.mReactions.end(); ++it)
for (auto it = faction.mReactions.begin(); it != faction.mReactions.end(); ++it)
{
std::string faction2 = Misc::StringUtils::lowerCase(it->first);
mContext->mDialogueState.mChangedFactionReaction[id].insert(std::make_pair(faction2, it->second));
@ -431,7 +429,7 @@ public:
void write(ESM::ESMWriter &esm) override
{
ESM::StolenItems items;
for (std::map<std::string, std::set<Owner> >::const_iterator it = mStolenItems.begin(); it != mStolenItems.end(); ++it)
for (auto it = mStolenItems.begin(); it != mStolenItems.end(); ++it)
{
std::map<std::pair<std::string, bool>, int> owners;
for (const auto & ownerIt : it->second)
@ -487,7 +485,7 @@ public:
}
void write(ESM::ESMWriter &esm) override
{
for (std::map<std::string, DIAL>::const_iterator it = mDials.begin(); it != mDials.end(); ++it)
for (auto it = mDials.begin(); it != mDials.end(); ++it)
{
esm.startRecord(ESM::REC_QUES);
ESM::QuestState state;
@ -545,9 +543,7 @@ public:
}
else
{
std::stringstream error;
error << "Invalid weather ID:" << weatherID << std::endl;
throw std::runtime_error(error.str());
throw std::runtime_error("Invalid weather ID: " + std::to_string(weatherID));
}
}

@ -3,7 +3,7 @@
#include "importinventory.hpp"
#include <components/esm/inventorystate.hpp>
#include <components/esm3/inventorystate.hpp>
namespace ESSImport
{

@ -3,7 +3,7 @@
#include "importnpcc.hpp"
#include <components/esm/npcstate.hpp>
#include <components/esm3/npcstate.hpp>
namespace ESSImport
{

@ -3,8 +3,8 @@
#include "importplayer.hpp"
#include <components/esm/player.hpp>
#include <components/esm/controlsstate.hpp>
#include <components/esm3/player.hpp>
#include <components/esm3/controlsstate.hpp>
namespace ESSImport
{

@ -11,6 +11,7 @@ namespace ESSImport
{
out.mId = Misc::StringUtils::lowerCase(scpt.mSCHD.mName.toString());
out.mRunning = scpt.mRunning;
out.mTargetRef.unset(); // TODO: convert target reference of global script
convertSCRI(scpt.mSCRI, out.mLocals);
}

@ -1,7 +1,7 @@
#ifndef OPENMW_ESSIMPORT_CONVERTSCPT_H
#define OPENMW_ESSIMPORT_CONVERTSCPT_H
#include <components/esm/globalscript.hpp>
#include <components/esm3/globalscript.hpp>
#include "importscpt.hpp"

@ -3,7 +3,7 @@
#include "importscri.hpp"
#include <components/esm/locals.hpp>
#include <components/esm3/locals.hpp>
namespace ESSImport
{

@ -1,14 +1,16 @@
#include "importacdt.hpp"
#include <components/esm/esmreader.hpp>
#include <components/esm3/esmreader.hpp>
#include <components/esm/cellref.hpp>
#include <components/esm3/cellref.hpp>
namespace ESSImport
{
void ActorData::load(ESM::ESMReader &esm)
{
blank();
if (esm.isNextSub("ACTN"))
{
/*

@ -3,7 +3,7 @@
#include <string>
#include <components/esm/cellref.hpp>
#include <components/esm3/cellref.hpp>
#include "importscri.hpp"

@ -1,6 +1,6 @@
#include "importcellref.hpp"
#include <components/esm/esmreader.hpp>
#include <components/esm3/esmreader.hpp>
namespace ESSImport
{
@ -35,8 +35,8 @@ namespace ESSImport
// DATA should occur for all references, except levelled creature spawners
// I've seen DATA *twice* on a creature record, and with the exact same content too! weird
// alarmvoi0000.ess
esm.getHNOT(mPos, "DATA", 24);
esm.getHNOT(mPos, "DATA", 24);
esm.getHNOTSized<24>(mPos, "DATA");
esm.getHNOTSized<24>(mPos, "DATA");
mDeleted = 0;
if (esm.isNextSub("DELE"))

@ -3,7 +3,7 @@
#include <string>
#include <components/esm/cellref.hpp>
#include <components/esm3/cellref.hpp>
#include "importacdt.hpp"
@ -27,7 +27,7 @@ namespace ESSImport
void load(ESM::ESMReader& esm) override;
virtual ~CellRef() = default;
~CellRef() override = default;
};
}

@ -1,6 +1,6 @@
#include "importcntc.hpp"
#include <components/esm/esmreader.hpp>
#include <components/esm3/esmreader.hpp>
namespace ESSImport
{

@ -1,6 +1,6 @@
#include "importcrec.hpp"
#include <components/esm/esmreader.hpp>
#include <components/esm3/esmreader.hpp>
namespace ESSImport
{

@ -2,7 +2,7 @@
#define OPENMW_ESSIMPORT_CREC_H
#include "importinventory.hpp"
#include <components/esm/aipackage.hpp>
#include <components/esm3/aipackage.hpp>
namespace ESM
{

@ -1,6 +1,6 @@
#include "importdial.hpp"
#include <components/esm/esmreader.hpp>
#include <components/esm3/esmreader.hpp>
namespace ESSImport
{

@ -1,27 +1,26 @@
#include "importer.hpp"
#include <iomanip>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <filesystem>
#include <fstream>
#include <osgDB/ReadFile>
#include <osg/ImageUtils>
#include <components/esm/esmreader.hpp>
#include <components/esm/esmwriter.hpp>
#include <components/esm3/esmreader.hpp>
#include <components/esm3/esmwriter.hpp>
#include <components/esm/defs.hpp>
#include <components/esm/savedgame.hpp>
#include <components/esm/player.hpp>
#include <components/esm3/savedgame.hpp>
#include <components/esm3/player.hpp>
#include <components/esm/loadalch.hpp>
#include <components/esm/loadspel.hpp>
#include <components/esm/loadarmo.hpp>
#include <components/esm/loadweap.hpp>
#include <components/esm/loadclot.hpp>
#include <components/esm/loadench.hpp>
#include <components/esm/loadlevlist.hpp>
#include <components/esm3/loadalch.hpp>
#include <components/esm3/loadspel.hpp>
#include <components/esm3/loadarmo.hpp>
#include <components/esm3/loadweap.hpp>
#include <components/esm3/loadclot.hpp>
#include <components/esm3/loadench.hpp>
#include <components/esm3/loadlevlist.hpp>
#include <components/misc/constants.hpp>
@ -264,48 +263,48 @@ namespace ESSImport
const ESM::Header& header = esm.getHeader();
context.mPlayerCellName = header.mGameData.mCurrentCell.toString();
const unsigned int recREFR = ESM::FourCC<'R','E','F','R'>::value;
const unsigned int recPCDT = ESM::FourCC<'P','C','D','T'>::value;
const unsigned int recFMAP = ESM::FourCC<'F','M','A','P'>::value;
const unsigned int recKLST = ESM::FourCC<'K','L','S','T'>::value;
const unsigned int recSTLN = ESM::FourCC<'S','T','L','N'>::value;
const unsigned int recGAME = ESM::FourCC<'G','A','M','E'>::value;
const unsigned int recJOUR = ESM::FourCC<'J','O','U','R'>::value;
const unsigned int recSPLM = ESM::FourCC<'S','P','L','M'>::value;
std::map<unsigned int, std::shared_ptr<Converter> > converters;
converters[ESM::REC_GLOB] = std::shared_ptr<Converter>(new ConvertGlobal());
converters[ESM::REC_BOOK] = std::shared_ptr<Converter>(new ConvertBook());
converters[ESM::REC_NPC_] = std::shared_ptr<Converter>(new ConvertNPC());
converters[ESM::REC_CREA] = std::shared_ptr<Converter>(new ConvertCREA());
converters[ESM::REC_NPCC] = std::shared_ptr<Converter>(new ConvertNPCC());
converters[ESM::REC_CREC] = std::shared_ptr<Converter>(new ConvertCREC());
converters[recREFR ] = std::shared_ptr<Converter>(new ConvertREFR());
converters[recPCDT ] = std::shared_ptr<Converter>(new ConvertPCDT());
converters[recFMAP ] = std::shared_ptr<Converter>(new ConvertFMAP());
converters[recKLST ] = std::shared_ptr<Converter>(new ConvertKLST());
converters[recSTLN ] = std::shared_ptr<Converter>(new ConvertSTLN());
converters[recGAME ] = std::shared_ptr<Converter>(new ConvertGAME());
converters[ESM::REC_CELL] = std::shared_ptr<Converter>(new ConvertCell());
converters[ESM::REC_ALCH] = std::shared_ptr<Converter>(new DefaultConverter<ESM::Potion>());
converters[ESM::REC_CLAS] = std::shared_ptr<Converter>(new ConvertClass());
converters[ESM::REC_SPEL] = std::shared_ptr<Converter>(new DefaultConverter<ESM::Spell>());
converters[ESM::REC_ARMO] = std::shared_ptr<Converter>(new DefaultConverter<ESM::Armor>());
converters[ESM::REC_WEAP] = std::shared_ptr<Converter>(new DefaultConverter<ESM::Weapon>());
converters[ESM::REC_CLOT] = std::shared_ptr<Converter>(new DefaultConverter<ESM::Clothing>());
converters[ESM::REC_ENCH] = std::shared_ptr<Converter>(new DefaultConverter<ESM::Enchantment>());
converters[ESM::REC_WEAP] = std::shared_ptr<Converter>(new DefaultConverter<ESM::Weapon>());
converters[ESM::REC_LEVC] = std::shared_ptr<Converter>(new DefaultConverter<ESM::CreatureLevList>());
converters[ESM::REC_LEVI] = std::shared_ptr<Converter>(new DefaultConverter<ESM::ItemLevList>());
converters[ESM::REC_CNTC] = std::shared_ptr<Converter>(new ConvertCNTC());
converters[ESM::REC_FACT] = std::shared_ptr<Converter>(new ConvertFACT());
converters[ESM::REC_INFO] = std::shared_ptr<Converter>(new ConvertINFO());
converters[ESM::REC_DIAL] = std::shared_ptr<Converter>(new ConvertDIAL());
converters[ESM::REC_QUES] = std::shared_ptr<Converter>(new ConvertQUES());
converters[recJOUR ] = std::shared_ptr<Converter>(new ConvertJOUR());
converters[ESM::REC_SCPT] = std::shared_ptr<Converter>(new ConvertSCPT());
converters[ESM::REC_PROJ] = std::shared_ptr<Converter>(new ConvertPROJ());
converters[recSPLM] = std::shared_ptr<Converter>(new ConvertSPLM());
const unsigned int recREFR = ESM::fourCC("REFR");
const unsigned int recPCDT = ESM::fourCC("PCDT");
const unsigned int recFMAP = ESM::fourCC("FMAP");
const unsigned int recKLST = ESM::fourCC("KLST");
const unsigned int recSTLN = ESM::fourCC("STLN");
const unsigned int recGAME = ESM::fourCC("GAME");
const unsigned int recJOUR = ESM::fourCC("JOUR");
const unsigned int recSPLM = ESM::fourCC("SPLM");
std::map<unsigned int, std::unique_ptr<Converter>> converters;
converters[ESM::REC_GLOB] = std::make_unique<ConvertGlobal>();
converters[ESM::REC_BOOK] = std::make_unique<ConvertBook>();
converters[ESM::REC_NPC_] = std::make_unique<ConvertNPC>();
converters[ESM::REC_CREA] = std::make_unique<ConvertCREA>();
converters[ESM::REC_NPCC] = std::make_unique<ConvertNPCC>();
converters[ESM::REC_CREC] = std::make_unique<ConvertCREC>();
converters[recREFR ] = std::make_unique<ConvertREFR>();
converters[recPCDT ] = std::make_unique<ConvertPCDT>();
converters[recFMAP ] = std::make_unique<ConvertFMAP>();
converters[recKLST ] = std::make_unique<ConvertKLST>();
converters[recSTLN ] = std::make_unique<ConvertSTLN>();
converters[recGAME ] = std::make_unique<ConvertGAME>();
converters[ESM::REC_CELL] = std::make_unique<ConvertCell>();
converters[ESM::REC_ALCH] = std::make_unique<DefaultConverter<ESM::Potion>>();
converters[ESM::REC_CLAS] = std::make_unique<ConvertClass>();
converters[ESM::REC_SPEL] = std::make_unique<DefaultConverter<ESM::Spell>>();
converters[ESM::REC_ARMO] = std::make_unique<DefaultConverter<ESM::Armor>>();
converters[ESM::REC_WEAP] = std::make_unique<DefaultConverter<ESM::Weapon>>();
converters[ESM::REC_CLOT] = std::make_unique<DefaultConverter<ESM::Clothing>>();
converters[ESM::REC_ENCH] = std::make_unique<DefaultConverter<ESM::Enchantment>>();
converters[ESM::REC_WEAP] = std::make_unique<DefaultConverter<ESM::Weapon>>();
converters[ESM::REC_LEVC] = std::make_unique<DefaultConverter<ESM::CreatureLevList>>();
converters[ESM::REC_LEVI] = std::make_unique<DefaultConverter<ESM::ItemLevList>>();
converters[ESM::REC_CNTC] = std::make_unique<ConvertCNTC>();
converters[ESM::REC_FACT] = std::make_unique<ConvertFACT>();
converters[ESM::REC_INFO] = std::make_unique<ConvertINFO>();
converters[ESM::REC_DIAL] = std::make_unique<ConvertDIAL>();
converters[ESM::REC_QUES] = std::make_unique<ConvertQUES>();
converters[recJOUR ] = std::make_unique<ConvertJOUR>();
converters[ESM::REC_SCPT] = std::make_unique<ConvertSCPT>();
converters[ESM::REC_PROJ] = std::make_unique<ConvertPROJ>();
converters[recSPLM] = std::make_unique<ConvertSPLM>();
// TODO:
// - REGN (weather in certain regions?)
@ -324,14 +323,14 @@ namespace ESSImport
ESM::NAME n = esm.getRecName();
esm.getRecHeader();
auto it = converters.find(n.intval);
auto it = converters.find(n.toInt());
if (it != converters.end())
{
it->second->read(esm);
}
else
{
if (unknownRecords.insert(n.intval).second)
if (unknownRecords.insert(n.toInt()).second)
{
std::ios::fmtflags f(std::cerr.flags());
std::cerr << "Error: unknown record " << n.toString() << " (0x" << std::hex << esm.getFileOffset() << ")" << std::endl;
@ -346,7 +345,7 @@ namespace ESSImport
writer.setFormat (ESM::SavedGame::sCurrentFormat);
boost::filesystem::ofstream stream(boost::filesystem::path(mOutFile), std::ios::out | std::ios::binary);
std::ofstream stream(std::filesystem::path(mOutFile), std::ios::out | std::ios::binary);
// all unused
writer.setVersion(0);
writer.setType(0);
@ -369,6 +368,7 @@ namespace ESSImport
profile.mInGameTime.mGameHour = context.mHour;
profile.mInGameTime.mMonth = context.mMonth;
profile.mInGameTime.mYear = context.mYear;
profile.mTimePlayed = 0;
profile.mPlayerCell = header.mGameData.mCurrentCell.toString();
if (context.mPlayerBase.mClass == "NEWCLASSID_CHARGEN")
profile.mPlayerClassName = context.mCustomPlayerClassName;
@ -385,7 +385,7 @@ namespace ESSImport
// Writing order should be Dynamic Store -> Cells -> Player,
// so that references to dynamic records can be recognized when loading
for (std::map<unsigned int, std::shared_ptr<Converter> >::const_iterator it = converters.begin();
for (auto it = converters.begin();
it != converters.end(); ++it)
{
if (it->second->getStage() != 0)
@ -398,7 +398,7 @@ namespace ESSImport
context.mPlayerBase.save(writer);
writer.endRecord(ESM::REC_NPC_);
for (std::map<unsigned int, std::shared_ptr<Converter> >::const_iterator it = converters.begin();
for (auto it = converters.begin();
it != converters.end(); ++it)
{
if (it->second->getStage() != 1)
@ -423,7 +423,7 @@ namespace ESSImport
writer.endRecord(ESM::REC_ACTC);
// Stage 2 requires cell references to be written / actors IDs assigned
for (std::map<unsigned int, std::shared_ptr<Converter> >::const_iterator it = converters.begin();
for (auto it = converters.begin();
it != converters.end(); ++it)
{
if (it->second->getStage() != 2)

@ -3,13 +3,13 @@
#include <map>
#include <components/esm/loadnpc.hpp>
#include <components/esm/player.hpp>
#include <components/esm/dialoguestate.hpp>
#include <components/esm/globalmap.hpp>
#include <components/esm/loadcrea.hpp>
#include <components/esm/loadnpc.hpp>
#include <components/esm/controlsstate.hpp>
#include <components/esm3/loadnpc.hpp>
#include <components/esm3/player.hpp>
#include <components/esm3/dialoguestate.hpp>
#include <components/esm3/globalmap.hpp>
#include <components/esm3/loadcrea.hpp>
#include <components/esm3/loadnpc.hpp>
#include <components/esm3/controlsstate.hpp>
#include "importnpcc.hpp"
#include "importcrec.hpp"

@ -1,6 +1,6 @@
#include "importgame.hpp"
#include <components/esm/esmreader.hpp>
#include <components/esm3/esmreader.hpp>
namespace ESSImport
{

@ -1,6 +1,6 @@
#include "importinfo.hpp"
#include <components/esm/esmreader.hpp>
#include <components/esm3/esmreader.hpp>
namespace ESSImport
{

@ -2,7 +2,7 @@
#include <stdexcept>
#include <components/esm/esmreader.hpp>
#include <components/esm3/esmreader.hpp>
namespace ESSImport
{
@ -19,6 +19,7 @@ namespace ESSImport
item.mCount = contItem.mCount;
item.mRelativeEquipmentSlot = -1;
item.mLockLevel = 0;
item.mRefNum.unset();
unsigned int itemCount = std::abs(item.mCount);
bool separateStacks = false;

@ -4,7 +4,7 @@
#include <vector>
#include <string>
#include <components/esm/cellref.hpp>
#include <components/esm3/cellref.hpp>
#include <components/esm/esmcommon.hpp>
#include "importscri.hpp"

@ -1,6 +1,6 @@
#include "importjour.hpp"
#include <components/esm/esmreader.hpp>
#include <components/esm3/esmreader.hpp>
namespace ESSImport
{

@ -1,6 +1,6 @@
#include "importklst.hpp"
#include <components/esm/esmreader.hpp>
#include <components/esm3/esmreader.hpp>
namespace ESSImport
{

@ -1,6 +1,6 @@
#include "importnpcc.hpp"
#include <components/esm/esmreader.hpp>
#include <components/esm3/esmreader.hpp>
namespace ESSImport
{

@ -1,9 +1,9 @@
#ifndef OPENMW_ESSIMPORT_NPCC_H
#define OPENMW_ESSIMPORT_NPCC_H
#include <components/esm/loadcont.hpp>
#include <components/esm3/loadcont.hpp>
#include <components/esm/aipackage.hpp>
#include <components/esm3/aipackage.hpp>
#include "importinventory.hpp"

@ -1,6 +1,6 @@
#include "importplayer.hpp"
#include <components/esm/esmreader.hpp>
#include <components/esm3/esmreader.hpp>
namespace ESSImport
{
@ -13,7 +13,7 @@ namespace ESSImport
mActorData.load(esm);
esm.getHNOT(mPos, "DATA", 24);
esm.getHNOTSized<24>(mPos, "DATA");
}
void PCDT::load(ESM::ESMReader &esm)

@ -5,7 +5,7 @@
#include <string>
#include <components/esm/defs.hpp>
#include <components/esm/cellref.hpp>
#include <components/esm3/cellref.hpp>
#include <components/esm/esmcommon.hpp>
#include "importacdt.hpp"

@ -1,6 +1,6 @@
#include "importproj.h"
#include <components/esm/esmreader.hpp>
#include <components/esm3/esmreader.hpp>
namespace ESSImport
{

@ -1,6 +1,6 @@
#include "importques.hpp"
#include <components/esm/esmreader.hpp>
#include <components/esm3/esmreader.hpp>
namespace ESSImport
{

@ -1,7 +1,6 @@
#include "importscpt.hpp"
#include <components/esm/esmreader.hpp>
#include <components/esm3/esmreader.hpp>
namespace ESSImport

@ -3,7 +3,7 @@
#include "importscri.hpp"
#include <components/esm/loadscpt.hpp>
#include <components/esm3/loadscpt.hpp>
namespace ESM
{

@ -1,6 +1,7 @@
#include "importscri.hpp"
#include <components/esm/esmreader.hpp>
#include <components/esm3/esmreader.hpp>
namespace ESSImport
{

@ -1,7 +1,7 @@
#ifndef OPENMW_ESSIMPORT_IMPORTSCRI_H
#define OPENMW_ESSIMPORT_IMPORTSCRI_H
#include <components/esm/variant.hpp>
#include <components/esm3/variant.hpp>
#include <vector>

@ -1,6 +1,6 @@
#include "importsplm.h"
#include <components/esm/esmreader.hpp>
#include <components/esm3/esmreader.hpp>
namespace ESSImport
{

@ -41,7 +41,7 @@ struct SPLM
{
int mUnknown;
unsigned char mUnknown2;
ESM::FIXED_STRING<35> mItemId; // disintegrated item / bound item / item to re-equip after expiration
ESM::FixedString<35> mItemId; // disintegrated item / bound item / item to re-equip after expiration
};
struct CNAM // 36 bytes

@ -1,15 +1,13 @@
#include <iostream>
#include <filesystem>
#include <boost/program_options.hpp>
#include <boost/filesystem.hpp>
#include <components/files/configurationmanager.hpp>
#include "importer.hpp"
namespace bpo = boost::program_options;
namespace bfs = boost::filesystem;
int main(int argc, char** argv)
@ -26,6 +24,7 @@ int main(int argc, char** argv)
("encoding", boost::program_options::value<std::string>()->default_value("win1252"), "encoding of the save file")
;
p_desc.add("mwsave", 1).add("output", 1);
Files::ConfigurationManager::addCommonOptions(desc);
bpo::variables_map variables;
@ -57,7 +56,7 @@ int main(int argc, char** argv)
else
{
const std::string& ext = ".omwsave";
if (boost::filesystem::exists(boost::filesystem::path(outputFile))
if (std::filesystem::exists(std::filesystem::path(outputFile))
&& (outputFile.size() < ext.size() || outputFile.substr(outputFile.size()-ext.size()) != ext))
{
throw std::runtime_error("Output file already exists and does not end in .omwsave. Did you mean to use --compare?");

@ -13,6 +13,7 @@ set(LAUNCHER
utils/profilescombobox.cpp
utils/textinputdialog.cpp
utils/lineedit.cpp
utils/openalutil.cpp
${CMAKE_SOURCE_DIR}/files/windows/launcher.rc
)
@ -31,25 +32,10 @@ set(LAUNCHER_HEADER
utils/profilescombobox.hpp
utils/textinputdialog.hpp
utils/lineedit.hpp
utils/openalutil.hpp
)
# Headers that must be pre-processed
set(LAUNCHER_HEADER_MOC
datafilespage.hpp
graphicspage.hpp
maindialog.hpp
playpage.hpp
textslotmsgbox.hpp
settingspage.hpp
advancedpage.hpp
utils/cellnameloader.hpp
utils/textinputdialog.hpp
utils/profilescombobox.hpp
utils/lineedit.hpp
)
set(LAUNCHER_UI
${CMAKE_SOURCE_DIR}/files/ui/datafilespage.ui
${CMAKE_SOURCE_DIR}/files/ui/graphicspage.ui
@ -58,6 +44,7 @@ set(LAUNCHER_UI
${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui
${CMAKE_SOURCE_DIR}/files/ui/settingspage.ui
${CMAKE_SOURCE_DIR}/files/ui/advancedpage.ui
${CMAKE_SOURCE_DIR}/files/ui/directorypicker.ui
)
source_group(launcher FILES ${LAUNCHER} ${LAUNCHER_HEADER})
@ -71,7 +58,6 @@ if(WIN32)
endif(WIN32)
QT5_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc)
QT5_WRAP_CPP(MOC_SRCS ${LAUNCHER_HEADER_MOC})
QT5_WRAP_UI(UI_HDRS ${LAUNCHER_UI})
include_directories(${CMAKE_CURRENT_BINARY_DIR})
@ -95,7 +81,8 @@ endif (WIN32)
target_link_libraries(openmw-launcher
${SDL2_LIBRARY_ONLY}
components
${OPENAL_LIBRARY}
components_qt
)
target_link_libraries(openmw-launcher Qt5::Widgets Qt5::Core)
@ -105,4 +92,16 @@ if (BUILD_WITH_CODE_COVERAGE)
target_link_libraries(openmw-launcher gcov)
endif()
if(USE_QT)
set_property(TARGET openmw-launcher PROPERTY AUTOMOC ON)
endif(USE_QT)
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC)
target_precompile_headers(openmw-launcher PRIVATE
<boost/program_options/options_description.hpp>
<algorithm>
<string>
<vector>
)
endif()

@ -1,27 +1,37 @@
#include "advancedpage.hpp"
#include <components/config/gamesettings.hpp>
#include <components/config/launchersettings.hpp>
#include <array>
#include <string>
#include <cmath>
#include <QFileDialog>
#include <QCompleter>
#include <QProxyStyle>
#include <QString>
#include <components/config/gamesettings.hpp>
#include <components/contentselector/view/contentselector.hpp>
#include <components/contentselector/model/esmfile.hpp>
#include <cmath>
#include "utils/openalutil.hpp"
Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg,
Config::GameSettings &gameSettings,
Settings::Manager &engineSettings, QWidget *parent)
Launcher::AdvancedPage::AdvancedPage(Config::GameSettings &gameSettings, QWidget *parent)
: QWidget(parent)
, mCfgMgr(cfg)
, mGameSettings(gameSettings)
, mEngineSettings(engineSettings)
{
setObjectName ("AdvancedPage");
setupUi(this);
for(const std::string& name : Launcher::enumerateOpenALDevices())
{
audioDeviceSelectorComboBox->addItem(QString::fromStdString(name), QString::fromStdString(name));
}
for(const std::string& name : Launcher::enumerateOpenALDevicesHrtf())
{
hrtfProfileSelectorComboBox->addItem(QString::fromStdString(name), QString::fromStdString(name));
}
loadSettings();
mCellNameCompleter.setModel(&mCellNameCompleterModel);
startDefaultCharacterAtField->setCompleter(&mCellNameCompleter);
}
@ -64,12 +74,12 @@ namespace
double convertToCells(double unitRadius)
{
return std::round((unitRadius / 0.93 + 1024) / CellSizeInUnits);
return unitRadius / CellSizeInUnits;
}
double convertToUnits(double CellGridRadius)
int convertToUnits(double CellGridRadius)
{
return (CellSizeInUnits * CellGridRadius - 1024) * 0.93;
return static_cast<int>(CellSizeInUnits * CellGridRadius);
}
}
@ -89,14 +99,15 @@ bool Launcher::AdvancedPage::loadSettings()
loadSettingBool(normaliseRaceSpeedCheckBox, "normalise race speed", "Game");
loadSettingBool(swimUpwardCorrectionCheckBox, "swim upward correction", "Game");
loadSettingBool(avoidCollisionsCheckBox, "NPCs avoid collisions", "Game");
int unarmedFactorsStrengthIndex = mEngineSettings.getInt("strength influences hand to hand", "Game");
int unarmedFactorsStrengthIndex = Settings::Manager::getInt("strength influences hand to hand", "Game");
if (unarmedFactorsStrengthIndex >= 0 && unarmedFactorsStrengthIndex <= 2)
unarmedFactorsStrengthComboBox->setCurrentIndex(unarmedFactorsStrengthIndex);
loadSettingBool(stealingFromKnockedOutCheckBox, "always allow stealing from knocked out actors", "Game");
loadSettingBool(enableNavigatorCheckBox, "enable", "Navigator");
int numPhysicsThreads = mEngineSettings.getInt("async num threads", "Physics");
int numPhysicsThreads = Settings::Manager::getInt("async num threads", "Physics");
if (numPhysicsThreads >= 0)
physicsThreadsSpinBox->setValue(numPhysicsThreads);
loadSettingBool(allowNPCToFollowOverWaterSurfaceCheckBox, "allow actors to follow over water surface", "Game");
}
// Visuals
@ -106,11 +117,15 @@ bool Launcher::AdvancedPage::loadSettings()
loadSettingBool(autoUseTerrainNormalMapsCheckBox, "auto use terrain normal maps", "Shaders");
loadSettingBool(autoUseTerrainSpecularMapsCheckBox, "auto use terrain specular maps", "Shaders");
loadSettingBool(bumpMapLocalLightingCheckBox, "apply lighting to environment maps", "Shaders");
loadSettingBool(radialFogCheckBox, "radial fog", "Shaders");
loadSettingBool(softParticlesCheckBox, "soft particles", "Shaders");
loadSettingBool(antialiasAlphaTestCheckBox, "antialias alpha test", "Shaders");
if (Settings::Manager::getInt("antialiasing", "Video") == 0) {
antialiasAlphaTestCheckBox->setCheckState(Qt::Unchecked);
}
loadSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game");
connect(animSourcesCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotAnimSourcesToggled(bool)));
loadSettingBool(animSourcesCheckBox, "use additional anim sources", "Game");
if (animSourcesCheckBox->checkState())
if (animSourcesCheckBox->checkState() != Qt::Unchecked)
{
loadSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game");
loadSettingBool(shieldSheathingCheckBox, "shield sheathing", "Game");
@ -118,27 +133,56 @@ bool Launcher::AdvancedPage::loadSettings()
loadSettingBool(turnToMovementDirectionCheckBox, "turn to movement direction", "Game");
loadSettingBool(smoothMovementCheckBox, "smooth movement", "Game");
const bool distantTerrain = mEngineSettings.getBool("distant terrain", "Terrain");
const bool objectPaging = mEngineSettings.getBool("object paging", "Terrain");
const bool distantTerrain = Settings::Manager::getBool("distant terrain", "Terrain");
const bool objectPaging = Settings::Manager::getBool("object paging", "Terrain");
if (distantTerrain && objectPaging) {
distantLandCheckBox->setCheckState(Qt::Checked);
}
loadSettingBool(activeGridObjectPagingCheckBox, "object paging active grid", "Terrain");
viewingDistanceComboBox->setValue(convertToCells(mEngineSettings.getInt("viewing distance", "Camera")));
viewingDistanceComboBox->setValue(convertToCells(Settings::Manager::getInt("viewing distance", "Camera")));
objectPagingMinSizeComboBox->setValue(Settings::Manager::getDouble("object paging min size", "Terrain"));
loadSettingBool(nightDaySwitchesCheckBox, "day night switches", "Game");
connect(postprocessEnabledCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotPostProcessToggled(bool)));
loadSettingBool(postprocessEnabledCheckBox, "enabled", "Post Processing");
loadSettingBool(postprocessLiveReloadCheckBox, "live reload", "Post Processing");
loadSettingBool(postprocessTransparentPostpassCheckBox, "transparent postpass", "Post Processing");
postprocessHDRTimeComboBox->setValue(Settings::Manager::getDouble("hdr exposure time", "Post Processing"));
connect(skyBlendingCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotSkyBlendingToggled(bool)));
loadSettingBool(radialFogCheckBox, "radial fog", "Fog");
loadSettingBool(exponentialFogCheckBox, "exponential fog", "Fog");
loadSettingBool(skyBlendingCheckBox, "sky blending", "Fog");
skyBlendingStartComboBox->setValue(Settings::Manager::getDouble("sky blending start", "Fog"));
}
// Camera
// Audio
{
loadSettingBool(viewOverShoulderCheckBox, "view over shoulder", "Camera");
connect(viewOverShoulderCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotViewOverShoulderToggled(bool)));
viewOverShoulderVerticalLayout->setEnabled(viewOverShoulderCheckBox->checkState());
loadSettingBool(autoSwitchShoulderCheckBox, "auto switch shoulder", "Camera");
loadSettingBool(previewIfStandStillCheckBox, "preview if stand still", "Camera");
loadSettingBool(deferredPreviewRotationCheckBox, "deferred preview rotation", "Camera");
loadSettingBool(headBobbingCheckBox, "head bobbing", "Camera");
defaultShoulderComboBox->setCurrentIndex(
mEngineSettings.getVector2("view over shoulder offset", "Camera").x() >= 0 ? 0 : 1);
std::string selectedAudioDevice = Settings::Manager::getString("device", "Sound");
if (selectedAudioDevice.empty() == false)
{
int audioDeviceIndex = audioDeviceSelectorComboBox->findData(QString::fromStdString(selectedAudioDevice));
if (audioDeviceIndex != -1)
{
audioDeviceSelectorComboBox->setCurrentIndex(audioDeviceIndex);
}
}
int hrtfEnabledIndex = Settings::Manager::getInt("hrtf enable", "Sound");
if (hrtfEnabledIndex >= -1 && hrtfEnabledIndex <= 1)
{
enableHRTFComboBox->setCurrentIndex(hrtfEnabledIndex + 1);
}
std::string selectedHRTFProfile = Settings::Manager::getString("hrtf", "Sound");
if (selectedHRTFProfile.empty() == false)
{
int hrtfProfileIndex = hrtfProfileSelectorComboBox->findData(QString::fromStdString(selectedHRTFProfile));
if (hrtfProfileIndex != -1)
{
hrtfProfileSelectorComboBox->setCurrentIndex(hrtfProfileIndex);
}
}
}
// Interface Changes
@ -148,11 +192,14 @@ bool Launcher::AdvancedPage::loadSettings()
loadSettingBool(showMeleeInfoCheckBox, "show melee info", "Game");
loadSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game");
loadSettingBool(changeDialogTopicsCheckBox, "color topic enable", "GUI");
int showOwnedIndex = mEngineSettings.getInt("show owned", "Game");
int showOwnedIndex = Settings::Manager::getInt("show owned", "Game");
// Match the index with the option (only 0, 1, 2, or 3 are valid). Will default to 0 if invalid.
if (showOwnedIndex >= 0 && showOwnedIndex <= 3)
showOwnedComboBox->setCurrentIndex(showOwnedIndex);
loadSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI");
loadSettingBool(useZoomOnMapCheckBox, "allow zooming", "Map");
loadSettingBool(graphicHerbalismCheckBox, "graphic herbalism", "Game");
scalingSpinBox->setValue(Settings::Manager::getFloat("scaling factor", "GUI"));
}
// Bug fixes
@ -165,13 +212,15 @@ bool Launcher::AdvancedPage::loadSettings()
{
// Saves
loadSettingBool(timePlayedCheckbox, "timeplayed", "Saves");
maximumQuicksavesComboBox->setValue(mEngineSettings.getInt("max quicksaves", "Saves"));
loadSettingInt(maximumQuicksavesComboBox,"max quicksaves", "Saves");
// Other Settings
QString screenshotFormatString = QString::fromStdString(mEngineSettings.getString("screenshot format", "General")).toUpper();
QString screenshotFormatString = QString::fromStdString(Settings::Manager::getString("screenshot format", "General")).toUpper();
if (screenshotFormatComboBox->findText(screenshotFormatString) == -1)
screenshotFormatComboBox->addItem(screenshotFormatString);
screenshotFormatComboBox->setCurrentIndex(screenshotFormatComboBox->findText(screenshotFormatString));
loadSettingBool(notifyOnSavedScreenshotCheckBox, "notify on saved screenshot", "General");
}
// Testing
@ -208,14 +257,11 @@ void Launcher::AdvancedPage::saveSettings()
saveSettingBool(normaliseRaceSpeedCheckBox, "normalise race speed", "Game");
saveSettingBool(swimUpwardCorrectionCheckBox, "swim upward correction", "Game");
saveSettingBool(avoidCollisionsCheckBox, "NPCs avoid collisions", "Game");
int unarmedFactorsStrengthIndex = unarmedFactorsStrengthComboBox->currentIndex();
if (unarmedFactorsStrengthIndex != mEngineSettings.getInt("strength influences hand to hand", "Game"))
mEngineSettings.setInt("strength influences hand to hand", "Game", unarmedFactorsStrengthIndex);
saveSettingInt(unarmedFactorsStrengthComboBox, "strength influences hand to hand", "Game");
saveSettingBool(stealingFromKnockedOutCheckBox, "always allow stealing from knocked out actors", "Game");
saveSettingBool(enableNavigatorCheckBox, "enable", "Navigator");
int numPhysicsThreads = physicsThreadsSpinBox->value();
if (numPhysicsThreads != mEngineSettings.getInt("async num threads", "Physics"))
mEngineSettings.setInt("async num threads", "Physics", numPhysicsThreads);
saveSettingInt(physicsThreadsSpinBox, "async num threads", "Physics");
saveSettingBool(allowNPCToFollowOverWaterSurfaceCheckBox, "allow actors to follow over water surface", "Game");
}
// Visuals
@ -225,7 +271,9 @@ void Launcher::AdvancedPage::saveSettings()
saveSettingBool(autoUseTerrainNormalMapsCheckBox, "auto use terrain normal maps", "Shaders");
saveSettingBool(autoUseTerrainSpecularMapsCheckBox, "auto use terrain specular maps", "Shaders");
saveSettingBool(bumpMapLocalLightingCheckBox, "apply lighting to environment maps", "Shaders");
saveSettingBool(radialFogCheckBox, "radial fog", "Shaders");
saveSettingBool(radialFogCheckBox, "radial fog", "Fog");
saveSettingBool(softParticlesCheckBox, "soft particles", "Shaders");
saveSettingBool(antialiasAlphaTestCheckBox, "antialias alpha test", "Shaders");
saveSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game");
saveSettingBool(animSourcesCheckBox, "use additional anim sources", "Game");
saveSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game");
@ -233,38 +281,69 @@ void Launcher::AdvancedPage::saveSettings()
saveSettingBool(turnToMovementDirectionCheckBox, "turn to movement direction", "Game");
saveSettingBool(smoothMovementCheckBox, "smooth movement", "Game");
const bool distantTerrain = mEngineSettings.getBool("distant terrain", "Terrain");
const bool objectPaging = mEngineSettings.getBool("object paging", "Terrain");
const bool distantTerrain = Settings::Manager::getBool("distant terrain", "Terrain");
const bool objectPaging = Settings::Manager::getBool("object paging", "Terrain");
const bool wantDistantLand = distantLandCheckBox->checkState();
if (wantDistantLand != (distantTerrain && objectPaging)) {
mEngineSettings.setBool("distant terrain", "Terrain", wantDistantLand);
mEngineSettings.setBool("object paging", "Terrain", wantDistantLand);
Settings::Manager::setBool("distant terrain", "Terrain", wantDistantLand);
Settings::Manager::setBool("object paging", "Terrain", wantDistantLand);
}
saveSettingBool(activeGridObjectPagingCheckBox, "object paging active grid", "Terrain");
double viewingDistance = viewingDistanceComboBox->value();
if (viewingDistance != convertToCells(mEngineSettings.getInt("viewing distance", "Camera")))
int viewingDistance = convertToUnits(viewingDistanceComboBox->value());
if (viewingDistance != Settings::Manager::getInt("viewing distance", "Camera"))
{
mEngineSettings.setInt("viewing distance", "Camera", convertToUnits(viewingDistance));
Settings::Manager::setInt("viewing distance", "Camera", viewingDistance);
}
double objectPagingMinSize = objectPagingMinSizeComboBox->value();
if (objectPagingMinSize != Settings::Manager::getDouble("object paging min size", "Terrain"))
Settings::Manager::setDouble("object paging min size", "Terrain", objectPagingMinSize);
saveSettingBool(nightDaySwitchesCheckBox, "day night switches", "Game");
saveSettingBool(postprocessEnabledCheckBox, "enabled", "Post Processing");
saveSettingBool(postprocessLiveReloadCheckBox, "live reload", "Post Processing");
saveSettingBool(postprocessTransparentPostpassCheckBox, "transparent postpass", "Post Processing");
double hdrExposureTime = postprocessHDRTimeComboBox->value();
if (hdrExposureTime != Settings::Manager::getDouble("hdr exposure time", "Post Processing"))
Settings::Manager::setDouble("hdr exposure time", "Post Processing", hdrExposureTime);
saveSettingBool(radialFogCheckBox, "radial fog", "Fog");
saveSettingBool(exponentialFogCheckBox, "exponential fog", "Fog");
saveSettingBool(skyBlendingCheckBox, "sky blending", "Fog");
Settings::Manager::setDouble("sky blending start", "Fog", skyBlendingStartComboBox->value());
}
// Camera
// Audio
{
saveSettingBool(viewOverShoulderCheckBox, "view over shoulder", "Camera");
saveSettingBool(autoSwitchShoulderCheckBox, "auto switch shoulder", "Camera");
saveSettingBool(previewIfStandStillCheckBox, "preview if stand still", "Camera");
saveSettingBool(deferredPreviewRotationCheckBox, "deferred preview rotation", "Camera");
saveSettingBool(headBobbingCheckBox, "head bobbing", "Camera");
osg::Vec2f shoulderOffset = mEngineSettings.getVector2("view over shoulder offset", "Camera");
if (defaultShoulderComboBox->currentIndex() != (shoulderOffset.x() >= 0 ? 0 : 1))
int audioDeviceIndex = audioDeviceSelectorComboBox->currentIndex();
std::string prevAudioDevice = Settings::Manager::getString("device", "Sound");
if (audioDeviceIndex != 0)
{
if (defaultShoulderComboBox->currentIndex() == 0)
shoulderOffset.x() = std::abs(shoulderOffset.x());
else
shoulderOffset.x() = -std::abs(shoulderOffset.x());
mEngineSettings.setVector2("view over shoulder offset", "Camera", shoulderOffset);
const std::string& newAudioDevice = audioDeviceSelectorComboBox->currentText().toUtf8().constData();
if (newAudioDevice != prevAudioDevice)
Settings::Manager::setString("device", "Sound", newAudioDevice);
}
else if (!prevAudioDevice.empty())
{
Settings::Manager::setString("device", "Sound", {});
}
int hrtfEnabledIndex = enableHRTFComboBox->currentIndex() - 1;
if (hrtfEnabledIndex != Settings::Manager::getInt("hrtf enable", "Sound"))
{
Settings::Manager::setInt("hrtf enable", "Sound", hrtfEnabledIndex);
}
int selectedHRTFProfileIndex = hrtfProfileSelectorComboBox->currentIndex();
std::string prevHRTFProfile = Settings::Manager::getString("hrtf", "Sound");
if (selectedHRTFProfileIndex != 0)
{
const std::string& newHRTFProfile = hrtfProfileSelectorComboBox->currentText().toUtf8().constData();
if (newHRTFProfile != prevHRTFProfile)
Settings::Manager::setString("hrtf", "Sound", newHRTFProfile);
}
else if (!prevHRTFProfile.empty())
{
Settings::Manager::setString("hrtf", "Sound", {});
}
}
@ -275,10 +354,13 @@ void Launcher::AdvancedPage::saveSettings()
saveSettingBool(showMeleeInfoCheckBox, "show melee info", "Game");
saveSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game");
saveSettingBool(changeDialogTopicsCheckBox, "color topic enable", "GUI");
int showOwnedCurrentIndex = showOwnedComboBox->currentIndex();
if (showOwnedCurrentIndex != mEngineSettings.getInt("show owned", "Game"))
mEngineSettings.setInt("show owned", "Game", showOwnedCurrentIndex);
saveSettingInt(showOwnedComboBox,"show owned", "Game");
saveSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI");
saveSettingBool(useZoomOnMapCheckBox, "allow zooming", "Map");
saveSettingBool(graphicHerbalismCheckBox, "graphic herbalism", "Game");
float uiScalingFactor = scalingSpinBox->value();
if (uiScalingFactor != Settings::Manager::getFloat("scaling factor", "GUI"))
Settings::Manager::setFloat("scaling factor", "GUI", uiScalingFactor);
}
// Bug fixes
@ -291,16 +373,14 @@ void Launcher::AdvancedPage::saveSettings()
{
// Saves Settings
saveSettingBool(timePlayedCheckbox, "timeplayed", "Saves");
int maximumQuicksaves = maximumQuicksavesComboBox->value();
if (maximumQuicksaves != mEngineSettings.getInt("max quicksaves", "Saves"))
{
mEngineSettings.setInt("max quicksaves", "Saves", maximumQuicksaves);
}
saveSettingInt(maximumQuicksavesComboBox, "max quicksaves", "Saves");
// Other Settings
std::string screenshotFormatString = screenshotFormatComboBox->currentText().toLower().toStdString();
if (screenshotFormatString != mEngineSettings.getString("screenshot format", "General"))
mEngineSettings.setString("screenshot format", "General", screenshotFormatString);
if (screenshotFormatString != Settings::Manager::getString("screenshot format", "General"))
Settings::Manager::setString("screenshot format", "General", screenshotFormatString);
saveSettingBool(notifyOnSavedScreenshotCheckBox, "notify on saved screenshot", "General");
}
// Testing
@ -324,15 +404,41 @@ void Launcher::AdvancedPage::saveSettings()
void Launcher::AdvancedPage::loadSettingBool(QCheckBox *checkbox, const std::string &setting, const std::string &group)
{
if (mEngineSettings.getBool(setting, group))
if (Settings::Manager::getBool(setting, group))
checkbox->setCheckState(Qt::Checked);
}
void Launcher::AdvancedPage::saveSettingBool(QCheckBox *checkbox, const std::string &setting, const std::string &group)
{
bool cValue = checkbox->checkState();
if (cValue != mEngineSettings.getBool(setting, group))
mEngineSettings.setBool(setting, group, cValue);
if (cValue != Settings::Manager::getBool(setting, group))
Settings::Manager::setBool(setting, group, cValue);
}
void Launcher::AdvancedPage::loadSettingInt(QComboBox *comboBox, const std::string &setting, const std::string &group)
{
int currentIndex = Settings::Manager::getInt(setting, group);
comboBox->setCurrentIndex(currentIndex);
}
void Launcher::AdvancedPage::saveSettingInt(QComboBox *comboBox, const std::string &setting, const std::string &group)
{
int currentIndex = comboBox->currentIndex();
if (currentIndex != Settings::Manager::getInt(setting, group))
Settings::Manager::setInt(setting, group, currentIndex);
}
void Launcher::AdvancedPage::loadSettingInt(QSpinBox *spinBox, const std::string &setting, const std::string &group)
{
int value = Settings::Manager::getInt(setting, group);
spinBox->setValue(value);
}
void Launcher::AdvancedPage::saveSettingInt(QSpinBox *spinBox, const std::string &setting, const std::string &group)
{
int value = spinBox->value();
if (value != Settings::Manager::getInt(setting, group))
Settings::Manager::setInt(setting, group, value);
}
void Launcher::AdvancedPage::slotLoadedCellsChanged(QStringList cellNames)
@ -351,7 +457,16 @@ void Launcher::AdvancedPage::slotAnimSourcesToggled(bool checked)
}
}
void Launcher::AdvancedPage::slotViewOverShoulderToggled(bool checked)
void Launcher::AdvancedPage::slotPostProcessToggled(bool checked)
{
postprocessLiveReloadCheckBox->setEnabled(checked);
postprocessTransparentPostpassCheckBox->setEnabled(checked);
postprocessHDRTimeComboBox->setEnabled(checked);
postprocessHDRTimeLabel->setEnabled(checked);
}
void Launcher::AdvancedPage::slotSkyBlendingToggled(bool checked)
{
viewOverShoulderVerticalLayout->setEnabled(viewOverShoulderCheckBox->checkState());
skyBlendingStartComboBox->setEnabled(checked);
skyBlendingStartLabel->setEnabled(checked);
}

@ -1,7 +1,6 @@
#ifndef ADVANCEDPAGE_H
#define ADVANCEDPAGE_H
#include <QWidget>
#include <QCompleter>
#include <QStringListModel>
@ -9,7 +8,6 @@
#include <components/settings/settings.hpp>
namespace Files { struct ConfigurationManager; }
namespace Config { class GameSettings; }
namespace Launcher
@ -19,8 +17,7 @@ namespace Launcher
Q_OBJECT
public:
AdvancedPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings,
Settings::Manager &engineSettings, QWidget *parent = 0);
explicit AdvancedPage(Config::GameSettings &gameSettings, QWidget *parent = nullptr);
bool loadSettings();
void saveSettings();
@ -32,12 +29,11 @@ namespace Launcher
void on_skipMenuCheckBox_stateChanged(int state);
void on_runScriptAfterStartupBrowseButton_clicked();
void slotAnimSourcesToggled(bool checked);
void slotViewOverShoulderToggled(bool checked);
void slotPostProcessToggled(bool checked);
void slotSkyBlendingToggled(bool checked);
private:
Files::ConfigurationManager &mCfgMgr;
Config::GameSettings &mGameSettings;
Settings::Manager &mEngineSettings;
QCompleter mCellNameCompleter;
QStringListModel mCellNameCompleterModel;
@ -46,8 +42,12 @@ namespace Launcher
* @param filePaths the file paths of the content files to be examined
*/
void loadCellsForAutocomplete(QStringList filePaths);
void loadSettingBool(QCheckBox *checkbox, const std::string& setting, const std::string& group);
void saveSettingBool(QCheckBox *checkbox, const std::string& setting, const std::string& group);
static void loadSettingBool(QCheckBox *checkbox, const std::string& setting, const std::string& group);
static void saveSettingBool(QCheckBox *checkbox, const std::string& setting, const std::string& group);
static void loadSettingInt(QComboBox *comboBox, const std::string& setting, const std::string& group);
static void saveSettingInt(QComboBox *comboBox, const std::string& setting, const std::string& group);
static void loadSettingInt(QSpinBox *spinBox, const std::string& setting, const std::string& group);
static void saveSettingInt(QSpinBox *spinBox, const std::string& setting, const std::string& group);
};
}
#endif

@ -1,41 +1,131 @@
#include "datafilespage.hpp"
#include "maindialog.hpp"
#include <QDebug>
#include <QPushButton>
#include <QMessageBox>
#include <QCheckBox>
#include <QMenu>
#include <QSortFilterProxyModel>
#include <QFileDialog>
#include <thread>
#include <mutex>
#include <algorithm>
#include <apps/launcher/utils/cellnameloader.hpp>
#include <components/files/configurationmanager.hpp>
#include <components/contentselector/model/esmfile.hpp>
#include <components/contentselector/model/naturalsort.hpp>
#include <components/contentselector/view/contentselector.hpp>
#include <components/config/gamesettings.hpp>
#include <components/config/launchersettings.hpp>
#include <iostream>
#include <components/settings/settings.hpp>
#include <components/bsa/compressedbsafile.hpp>
#include <components/navmeshtool/protocol.hpp>
#include <components/vfs/bsaarchive.hpp>
#include "utils/textinputdialog.hpp"
#include "utils/profilescombobox.hpp"
#include "ui_directorypicker.h"
const char *Launcher::DataFilesPage::mDefaultContentListName = "Default";
Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, Config::LauncherSettings &launcherSettings, QWidget *parent)
namespace
{
void contentSubdirs(const QString& path, QStringList& dirs)
{
QStringList fileFilter {"*.esm", "*.esp", "*.omwaddon", "*.bsa"};
QStringList dirFilter {"bookart", "icons", "meshes", "music", "sound", "textures"};
QDir currentDir(path);
if (!currentDir.entryInfoList(fileFilter, QDir::Files).empty()
|| !currentDir.entryInfoList(dirFilter, QDir::Dirs | QDir::NoDotAndDotDot).empty())
dirs.push_back(currentDir.canonicalPath());
for (const auto& subdir : currentDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot))
contentSubdirs(subdir.canonicalFilePath(), dirs);
}
}
namespace Launcher
{
namespace
{
struct HandleNavMeshToolMessage
{
int mCellsCount;
int mExpectedMaxProgress;
int mMaxProgress;
int mProgress;
HandleNavMeshToolMessage operator()(NavMeshTool::ExpectedCells&& message) const
{
return HandleNavMeshToolMessage {
static_cast<int>(message.mCount),
mExpectedMaxProgress,
static_cast<int>(message.mCount) * 100,
mProgress
};
}
HandleNavMeshToolMessage operator()(NavMeshTool::ProcessedCells&& message) const
{
return HandleNavMeshToolMessage {
mCellsCount,
mExpectedMaxProgress,
mMaxProgress,
std::max(mProgress, static_cast<int>(message.mCount))
};
}
HandleNavMeshToolMessage operator()(NavMeshTool::ExpectedTiles&& message) const
{
const int expectedMaxProgress = mCellsCount + static_cast<int>(message.mCount);
return HandleNavMeshToolMessage {
mCellsCount,
expectedMaxProgress,
std::max(mMaxProgress, expectedMaxProgress),
mProgress
};
}
HandleNavMeshToolMessage operator()(NavMeshTool::GeneratedTiles&& message) const
{
int progress = mCellsCount + static_cast<int>(message.mCount);
if (mExpectedMaxProgress < mMaxProgress)
progress += static_cast<int>(std::round(
(mMaxProgress - mExpectedMaxProgress)
* (static_cast<float>(progress) / static_cast<float>(mExpectedMaxProgress))
));
return HandleNavMeshToolMessage {
mCellsCount,
mExpectedMaxProgress,
mMaxProgress,
std::max(mProgress, progress)
};
}
};
int getMaxNavMeshDbFileSizeMiB()
{
return static_cast<int>(Settings::Manager::getInt64("max navmeshdb file size", "Navigator") / (1024 * 1024));
}
}
}
Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings,
Config::LauncherSettings &launcherSettings, MainDialog *parent)
: QWidget(parent)
, mMainDialog(parent)
, mCfgMgr(cfg)
, mGameSettings(gameSettings)
, mLauncherSettings(launcherSettings)
, mNavMeshToolInvoker(new Process::ProcessInvoker(this))
{
ui.setupUi (this);
setObjectName ("DataFilesPage");
mSelector = new ContentSelectorView::ContentSelector (ui.contentSelectorWidget);
mSelector = new ContentSelectorView::ContentSelector (ui.contentSelectorWidget, /*showOMWScripts=*/true);
const QString encoding = mGameSettings.value("encoding", "win1252");
mSelector->setEncoding(encoding);
@ -46,6 +136,14 @@ Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config:
this, SLOT(updateNewProfileOkButton(QString)));
connect(mCloneProfileDialog->lineEdit(), SIGNAL(textChanged(QString)),
this, SLOT(updateCloneProfileOkButton(QString)));
connect(ui.directoryAddSubdirsButton, &QPushButton::released, this, [this]() { this->addSubdirectories(true); });
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.archiveUpButton, &QPushButton::released, this, [this]() { this->moveArchive(-1); });
connect(ui.archiveDownButton, &QPushButton::released, this, [this]() { this->moveArchive(1); });
connect(ui.directoryListWidget->model(), &QAbstractItemModel::rowsMoved, this, [this]() { this->sortDirectories(); });
buildView();
loadSettings();
@ -60,15 +158,12 @@ Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config:
void Launcher::DataFilesPage::buildView()
{
ui.verticalLayout->insertWidget (0, mSelector->uiWidget());
QToolButton * refreshButton = mSelector->refreshButton();
QToolButton * refreshButton = mSelector->refreshButton();
//tool buttons
ui.newProfileButton->setToolTip ("Create a new Content List");
ui.cloneProfileButton->setToolTip ("Clone the current Content List");
ui.deleteProfileButton->setToolTip ("Delete an existing Content List");
refreshButton->setToolTip("Refresh Data Files");
//combo box
ui.profilesComboBox->addItem(mDefaultContentListName);
@ -92,10 +187,19 @@ void Launcher::DataFilesPage::buildView()
this, SLOT (slotProfileChangedByUser(QString, QString)));
connect(ui.refreshDataFilesAction, SIGNAL(triggered()),this, SLOT(slotRefreshButtonClicked()));
connect(ui.updateNavMeshButton, SIGNAL(clicked()), this, SLOT(startNavMeshTool()));
connect(ui.cancelNavMeshButton, SIGNAL(clicked()), this, SLOT(killNavMeshTool()));
connect(mNavMeshToolInvoker->getProcess(), SIGNAL(readyReadStandardOutput()), this, SLOT(readNavMeshToolStdout()));
connect(mNavMeshToolInvoker->getProcess(), SIGNAL(readyReadStandardError()), this, SLOT(readNavMeshToolStderr()));
connect(mNavMeshToolInvoker->getProcess(), SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(navMeshToolFinished(int, QProcess::ExitStatus)));
}
bool Launcher::DataFilesPage::loadSettings()
{
ui.navMeshMaxSizeSpinBox->setValue(getMaxNavMeshDbFileSizeMiB());
QStringList profiles = mLauncherSettings.getContentLists();
QString currentProfile = mLauncherSettings.getCurrentContentListName();
@ -113,19 +217,96 @@ bool Launcher::DataFilesPage::loadSettings()
void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
{
QStringList paths = mGameSettings.getDataDirs();
mSelector->clearFiles();
ui.archiveListWidget->clear();
ui.directoryListWidget->clear();
mDataLocal = mGameSettings.getDataLocal();
QStringList directories = mLauncherSettings.getDataDirectoryList(contentModelName);
if (directories.isEmpty())
directories = mGameSettings.getDataDirs();
mDataLocal = mGameSettings.getDataLocal();
if (!mDataLocal.isEmpty())
paths.insert(0, mDataLocal);
directories.insert(0, mDataLocal);
mSelector->clearFiles();
const auto globalDataDir = QString(mGameSettings.getGlobalDataDir().c_str());
if (!globalDataDir.isEmpty())
directories.insert(0, globalDataDir);
// normalize user supplied directories: resolve symlink, convert to native separator, make absolute
for (auto& currentDir : directories)
currentDir = QDir(QDir::cleanPath(currentDir)).canonicalPath();
// add directories, archives and content files
directories.removeDuplicates();
for (const auto& currentDir : directories)
{
// add new achives files presents in current directory
addArchivesFromDir(currentDir);
QString tooltip;
// add content files presents in current directory
mSelector->addFiles(currentDir, mNewDataDirs.contains(currentDir));
// add current directory to list
ui.directoryListWidget->addItem(currentDir);
auto row = ui.directoryListWidget->count() - 1;
auto* item = ui.directoryListWidget->item(row);
// Display new content with green background
if (mNewDataDirs.contains(currentDir))
{
tooltip += "Will be added to the current profile\n";
item->setBackground(Qt::green);
item->setForeground(Qt::black);
}
// deactivate data-local and global data directory: they are always included
if (currentDir == mDataLocal || currentDir == globalDataDir)
{
auto flags = item->flags();
item->setFlags(flags & ~(Qt::ItemIsDragEnabled|Qt::ItemIsDropEnabled|Qt::ItemIsEnabled));
}
// Add a "data file" icon if the directory contains a content file
if (mSelector->containsDataFiles(currentDir))
{
item->setIcon(QIcon(":/images/openmw-plugin.png"));
tooltip += "Contains content file(s)";
}
else
{
// Pad to correct vertical alignment
QPixmap pixmap(QSize(200, 200)); // Arbitrary big number, will be scaled down to widget size
pixmap.fill(ui.directoryListWidget->palette().base().color());
auto emptyIcon = QIcon(pixmap);
item->setIcon(emptyIcon);
}
item->setToolTip(tooltip);
}
mSelector->sortFiles();
for (const QString &path : paths)
mSelector->addFiles(path);
QStringList selectedArchives = mLauncherSettings.getArchiveList(contentModelName);
if (selectedArchives.isEmpty())
selectedArchives = mGameSettings.getArchiveList();
PathIterator pathIterator(paths);
// sort and tick BSA according to profile
int row = 0;
for (const auto& archive : selectedArchives)
{
const auto match = ui.archiveListWidget->findItems(archive, Qt::MatchExactly);
if (match.isEmpty())
continue;
const auto name = match[0]->text();
const auto oldrow = ui.archiveListWidget->row(match[0]);
ui.archiveListWidget->takeItem(oldrow);
ui.archiveListWidget->insertItem(row, name);
ui.archiveListWidget->item(row)->setCheckState(Qt::Checked);
row++;
}
PathIterator pathIterator(directories);
mSelector->setProfileContent(filesInProfile(contentModelName, pathIterator));
}
@ -148,13 +329,19 @@ QStringList Launcher::DataFilesPage::filesInProfile(const QString& profileName,
void Launcher::DataFilesPage::saveSettings(const QString &profile)
{
QString profileName = profile;
if (const int value = ui.navMeshMaxSizeSpinBox->value(); value != getMaxNavMeshDbFileSizeMiB())
Settings::Manager::setInt64("max navmeshdb file size", "Navigator", static_cast<std::int64_t>(value) * 1024 * 1024);
QString profileName = profile;
if (profileName.isEmpty())
profileName = ui.profilesComboBox->currentText();
if (profileName.isEmpty())
profileName = ui.profilesComboBox->currentText();
//retrieve the data paths
auto dirList = selectedDirectoriesPaths();
//retrieve the files selected for the profile
ContentSelectorModel::ContentFileList items = mSelector->selectedFiles();
//retrieve the files selected for the profile
ContentSelectorModel::ContentFileList items = mSelector->selectedFiles();
//set the value of the current profile (not necessarily the profile being saved!)
mLauncherSettings.setCurrentContentListName(ui.profilesComboBox->currentText());
@ -164,11 +351,36 @@ void Launcher::DataFilesPage::saveSettings(const QString &profile)
{
fileNames.append(item->fileName());
}
mLauncherSettings.setContentList(profileName, fileNames);
mGameSettings.setContentList(fileNames);
mLauncherSettings.setContentList(profileName, dirList, selectedArchivePaths(), fileNames);
mGameSettings.setContentList(dirList, selectedArchivePaths(), fileNames);
}
QStringList Launcher::DataFilesPage::selectedFilePaths()
QStringList Launcher::DataFilesPage::selectedDirectoriesPaths() const
{
QStringList dirList;
for (int i = 0; i < ui.directoryListWidget->count(); ++i)
{
if (ui.directoryListWidget->item(i)->flags() & Qt::ItemIsEnabled)
dirList.append(ui.directoryListWidget->item(i)->text());
}
return dirList;
}
QStringList Launcher::DataFilesPage::selectedArchivePaths(bool all) const
{
QStringList archiveList;
for (int i = 0; i < ui.archiveListWidget->count(); ++i)
{
const auto* item = ui.archiveListWidget->item(i);
const auto archive = ui.archiveListWidget->item(i)->text();
if (all ||item->checkState() == Qt::Checked)
archiveList.append(item->text());
}
return archiveList;
}
QStringList Launcher::DataFilesPage::selectedFilePaths() const
{
//retrieve the files selected for the profile
ContentSelectorModel::ContentFileList items = mSelector->selectedFiles();
@ -176,15 +388,8 @@ QStringList Launcher::DataFilesPage::selectedFilePaths()
for (const ContentSelectorModel::EsmFile *item : items)
{
QFile file(item->filePath());
if(file.exists())
{
filePaths.append(item->filePath());
}
else
{
slotRefreshButtonClicked();
}
}
return filePaths;
}
@ -228,8 +433,17 @@ void Launcher::DataFilesPage::setProfile (const QString &previous, const QString
ui.profilesComboBox->setCurrentProfile (ui.profilesComboBox->findText (current));
mNewDataDirs.clear();
mKnownArchives.clear();
populateFileViews(current);
// save list of "old" bsa to be able to display "new" bsa in a different colour
for (int i = 0; i < ui.archiveListWidget->count(); ++i)
{
auto* item = ui.archiveListWidget->item(i);
mKnownArchives.push_back(item->text());
}
checkForDefaultProfile();
}
@ -318,7 +532,7 @@ void Launcher::DataFilesPage::on_cloneProfileAction_triggered()
if (profile.isEmpty())
return;
mLauncherSettings.setContentList(profile, selectedFilePaths());
mLauncherSettings.setContentList(profile, selectedDirectoriesPaths(), selectedArchivePaths(), selectedFilePaths());
addProfile(profile, true);
}
@ -356,6 +570,158 @@ void Launcher::DataFilesPage::updateCloneProfileOkButton(const QString &text)
mCloneProfileDialog->setOkButtonEnabled(!text.isEmpty() && ui.profilesComboBox->findText(text) == -1);
}
QString Launcher::DataFilesPage::selectDirectory()
{
QFileDialog fileDialog(this);
fileDialog.setFileMode(QFileDialog::Directory);
fileDialog.setOptions(QFileDialog::Option::ShowDirsOnly | QFileDialog::Option::ReadOnly);
if (fileDialog.exec() == QDialog::Rejected)
return {};
return QDir(fileDialog.selectedFiles()[0]).canonicalPath();
}
void Launcher::DataFilesPage::addSubdirectories(bool append)
{
int selectedRow = append ? ui.directoryListWidget->count() : ui.directoryListWidget->currentRow();
if (selectedRow == -1)
return;
const auto rootDir = selectDirectory();
if (rootDir.isEmpty())
return;
QStringList subdirs;
contentSubdirs(rootDir, subdirs);
if (subdirs.empty())
{
// we didn't find anything that looks like a content directory, add directory selected by user
if (ui.directoryListWidget->findItems(rootDir, Qt::MatchFixedString).isEmpty())
{
ui.directoryListWidget->addItem(rootDir);
mNewDataDirs.push_back(rootDir);
refreshDataFilesView();
}
return;
}
QDialog dialog;
Ui::SelectSubdirs select;
select.setupUi(&dialog);
for (const auto& dir : subdirs)
{
if (!ui.directoryListWidget->findItems(dir, Qt::MatchFixedString).isEmpty())
continue;
const auto lastRow = select.dirListWidget->count();
select.dirListWidget->addItem(dir);
select.dirListWidget->item(lastRow)->setCheckState(Qt::Unchecked);
}
dialog.show();
if (dialog.exec() == QDialog::Rejected)
return;
for (int i = 0; i < select.dirListWidget->count(); ++i)
{
const auto* dir = select.dirListWidget->item(i);
if (dir->checkState() == Qt::Checked)
{
ui.directoryListWidget->insertItem(selectedRow++, dir->text());
mNewDataDirs.push_back(dir->text());
}
}
refreshDataFilesView();
}
void Launcher::DataFilesPage::sortDirectories()
{
// Ensure disabled entries (aka default directories) are always at the top.
for (auto i = 1; i < ui.directoryListWidget->count(); ++i)
{
if (!(ui.directoryListWidget->item(i)->flags() & Qt::ItemIsEnabled) &&
(ui.directoryListWidget->item(i - 1)->flags() & Qt::ItemIsEnabled))
{
const auto item = ui.directoryListWidget->takeItem(i);
ui.directoryListWidget->insertItem(i - 1, item);
ui.directoryListWidget->setCurrentRow(i);
}
}
}
void Launcher::DataFilesPage::moveDirectory(int step)
{
int selectedRow = ui.directoryListWidget->currentRow();
int newRow = selectedRow + step;
if (selectedRow == -1 || newRow < 0 || newRow > ui.directoryListWidget->count() - 1)
return;
if (!(ui.directoryListWidget->item(newRow)->flags() & Qt::ItemIsEnabled))
return;
const auto item = ui.directoryListWidget->takeItem(selectedRow);
ui.directoryListWidget->insertItem(newRow, item);
ui.directoryListWidget->setCurrentRow(newRow);
}
void Launcher::DataFilesPage::removeDirectory()
{
for (const auto& path : ui.directoryListWidget->selectedItems())
ui.directoryListWidget->takeItem(ui.directoryListWidget->row(path));
refreshDataFilesView();
}
void Launcher::DataFilesPage::moveArchive(int step)
{
int selectedRow = ui.archiveListWidget->currentRow();
int newRow = selectedRow + step;
if (selectedRow == -1 || newRow < 0 || newRow > ui.archiveListWidget->count() - 1)
return;
const auto* item = ui.archiveListWidget->takeItem(selectedRow);
addArchive(item->text(), item->checkState(), newRow);
ui.archiveListWidget->setCurrentRow(newRow);
}
void Launcher::DataFilesPage::addArchive(const QString& name, Qt::CheckState selected, int row)
{
if (row == -1)
row = ui.archiveListWidget->count();
ui.archiveListWidget->insertItem(row, name);
ui.archiveListWidget->item(row)->setCheckState(selected);
if (mKnownArchives.filter(name).isEmpty()) // XXX why contains doesn't work here ???
{
ui.archiveListWidget->item(row)->setBackground(Qt::green);
ui.archiveListWidget->item(row)->setForeground(Qt::black);
}
}
void Launcher::DataFilesPage::addArchivesFromDir(const QString& path)
{
QDir dir(path, "*.bsa");
for (const auto& fileinfo : dir.entryInfoList())
{
const auto absPath = fileinfo.absoluteFilePath();
if (Bsa::CompressedBSAFile::detectVersion(absPath.toStdString()) == Bsa::BSAVER_UNKNOWN)
continue;
const auto fileName = fileinfo.fileName();
const auto currentList = selectedArchivePaths(true);
if (!currentList.contains(fileName, Qt::CaseInsensitive))
addArchive(fileName, Qt::Unchecked);
}
}
void Launcher::DataFilesPage::checkForDefaultProfile()
{
//don't allow deleting "Default" profile
@ -394,13 +760,13 @@ void Launcher::DataFilesPage::slotAddonDataChanged()
}
// Mutex lock to run reloadCells synchronously.
std::mutex _reloadCellsMutex;
static std::mutex reloadCellsMutex;
void Launcher::DataFilesPage::reloadCells(QStringList selectedFiles)
{
// Use a mutex lock so that we can prevent two threads from executing the rest of this code at the same time
// Based on https://stackoverflow.com/a/5429695/531762
std::unique_lock<std::mutex> lock(_reloadCellsMutex);
std::unique_lock<std::mutex> lock(reloadCellsMutex);
// The following code will run only if there is not another thread currently running it
CellNameLoader cellNameLoader;
@ -413,3 +779,89 @@ void Launcher::DataFilesPage::reloadCells(QStringList selectedFiles)
std::sort(cellNamesList.begin(), cellNamesList.end());
emit signalLoadedCellsChanged(cellNamesList);
}
void Launcher::DataFilesPage::startNavMeshTool()
{
mMainDialog->writeSettings();
ui.navMeshLogPlainTextEdit->clear();
ui.navMeshProgressBar->setValue(0);
ui.navMeshProgressBar->setMaximum(1);
mNavMeshToolProgress = NavMeshToolProgress {};
QStringList arguments({"--write-binary-log"});
if (ui.navMeshRemoveUnusedTilesCheckBox->checkState() == Qt::Checked)
arguments.append("--remove-unused-tiles");
if (!mNavMeshToolInvoker->startProcess(QLatin1String("openmw-navmeshtool"), arguments))
return;
ui.cancelNavMeshButton->setEnabled(true);
ui.navMeshProgressBar->setEnabled(true);
}
void Launcher::DataFilesPage::killNavMeshTool()
{
mNavMeshToolInvoker->killProcess();
}
void Launcher::DataFilesPage::readNavMeshToolStderr()
{
updateNavMeshProgress(4096);
}
void Launcher::DataFilesPage::updateNavMeshProgress(int minDataSize)
{
QProcess& process = *mNavMeshToolInvoker->getProcess();
mNavMeshToolProgress.mMessagesData.append(process.readAllStandardError());
if (mNavMeshToolProgress.mMessagesData.size() < minDataSize)
return;
const std::byte* const begin = reinterpret_cast<const std::byte*>(mNavMeshToolProgress.mMessagesData.constData());
const std::byte* const end = begin + mNavMeshToolProgress.mMessagesData.size();
const std::byte* position = begin;
HandleNavMeshToolMessage handle {
mNavMeshToolProgress.mCellsCount,
mNavMeshToolProgress.mExpectedMaxProgress,
ui.navMeshProgressBar->maximum(),
ui.navMeshProgressBar->value(),
};
while (true)
{
NavMeshTool::Message message;
const std::byte* const nextPosition = NavMeshTool::deserialize(position, end, message);
if (nextPosition == position)
break;
position = nextPosition;
handle = std::visit(handle, NavMeshTool::decode(message));
}
if (position != begin)
mNavMeshToolProgress.mMessagesData = mNavMeshToolProgress.mMessagesData.mid(position - begin);
mNavMeshToolProgress.mCellsCount = handle.mCellsCount;
mNavMeshToolProgress.mExpectedMaxProgress = handle.mExpectedMaxProgress;
ui.navMeshProgressBar->setMaximum(handle.mMaxProgress);
ui.navMeshProgressBar->setValue(handle.mProgress);
}
void Launcher::DataFilesPage::readNavMeshToolStdout()
{
QProcess& process = *mNavMeshToolInvoker->getProcess();
QByteArray& logData = mNavMeshToolProgress.mLogData;
logData.append(process.readAllStandardOutput());
const int lineEnd = logData.lastIndexOf('\n');
if (lineEnd == -1)
return;
const int size = logData.size() >= lineEnd && logData[lineEnd - 1] == '\r' ? lineEnd - 1 : lineEnd;
ui.navMeshLogPlainTextEdit->appendPlainText(QString::fromUtf8(logData.data(), size));
logData = logData.mid(lineEnd + 1);
}
void Launcher::DataFilesPage::navMeshToolFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
updateNavMeshProgress(0);
ui.navMeshLogPlainTextEdit->appendPlainText(QString::fromUtf8(mNavMeshToolInvoker->getProcess()->readAllStandardOutput()));
if (exitCode == 0 && exitStatus == QProcess::ExitStatus::NormalExit)
ui.navMeshProgressBar->setValue(ui.navMeshProgressBar->maximum());
ui.cancelNavMeshButton->setEnabled(false);
ui.navMeshProgressBar->setEnabled(false);
}

@ -2,11 +2,11 @@
#define DATAFILESPAGE_H
#include "ui_datafilespage.h"
#include <QWidget>
#include <components/process/processinvoker.hpp>
#include <QWidget>
#include <QDir>
#include <QFile>
#include <QStringList>
class QSortFilterProxyModel;
@ -20,6 +20,7 @@ namespace Config { class GameSettings;
namespace Launcher
{
class MainDialog;
class TextInputDialog;
class ProfilesComboBox;
@ -32,7 +33,7 @@ namespace Launcher
public:
explicit DataFilesPage (Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings,
Config::LauncherSettings &launcherSettings, QWidget *parent = 0);
Config::LauncherSettings &launcherSettings, MainDialog *parent = nullptr);
QAbstractItemModel* profilesModel() const;
@ -42,12 +43,6 @@ namespace Launcher
void saveSettings(const QString &profile = "");
bool loadSettings();
/**
* Returns the file paths of all selected content files
* @return the file paths of all selected content files
*/
QStringList selectedFilePaths();
signals:
void signalProfileChanged (int index);
void signalLoadedCellsChanged(QStringList selectedFiles);
@ -65,17 +60,37 @@ namespace Launcher
void updateNewProfileOkButton(const QString &text);
void updateCloneProfileOkButton(const QString &text);
void addSubdirectories(bool append);
void sortDirectories();
void removeDirectory();
void moveArchive(int step);
void moveDirectory(int step);
void on_newProfileAction_triggered();
void on_cloneProfileAction_triggered();
void on_deleteProfileAction_triggered();
void startNavMeshTool();
void killNavMeshTool();
void readNavMeshToolStdout();
void readNavMeshToolStderr();
void navMeshToolFinished(int exitCode, QProcess::ExitStatus exitStatus);
public:
/// Content List that is always present
const static char *mDefaultContentListName;
private:
struct NavMeshToolProgress
{
QByteArray mLogData;
QByteArray mMessagesData;
std::map<std::uint64_t, std::string> mWorldspaces;
int mCellsCount = 0;
int mExpectedMaxProgress = 0;
};
MainDialog *mMainDialog;
TextInputDialog *mNewProfileDialog;
TextInputDialog *mCloneProfileDialog;
@ -87,12 +102,15 @@ namespace Launcher
QString mPreviousProfile;
QStringList previousSelectedFiles;
QString mDataLocal;
QStringList mKnownArchives;
QStringList mNewDataDirs;
void setPluginsCheckstates(Qt::CheckState state);
Process::ProcessInvoker* mNavMeshToolInvoker;
NavMeshToolProgress mNavMeshToolProgress;
void addArchive(const QString& name, Qt::CheckState selected, int row = -1);
void addArchivesFromDir(const QString& dir);
void buildView();
void setupConfig();
void readConfig();
void setProfile (int index, bool savePrevious);
void setProfile (const QString &previous, const QString &current, bool savePrevious);
void removeProfile (const QString &profile);
@ -102,6 +120,16 @@ namespace Launcher
void populateFileViews(const QString& contentModelName);
void reloadCells(QStringList selectedFiles);
void refreshDataFilesView ();
void updateNavMeshProgress(int minDataSize);
QString selectDirectory();
/**
* Returns the file paths of all selected content files
* @return the file paths of all selected content files
*/
QStringList selectedFilePaths() const;
QStringList selectedArchivePaths(bool all=false) const;
QStringList selectedDirectoriesPaths() const;
class PathIterator
{

@ -1,8 +1,6 @@
#include "graphicspage.hpp"
#include <QDesktopWidget>
#include <QMessageBox>
#include <QDir>
#include <QScreen>
#ifdef MAC_OS_X_VERSION_MIN_REQUIRED
@ -14,12 +12,14 @@
#include <SDL_video.h>
#include <numeric>
#include <components/files/configurationmanager.hpp>
#include <array>
QString getAspect(int x, int y)
{
int gcd = std::gcd (x, y);
if (gcd == 0)
return QString();
int xaspect = x / gcd;
int yaspect = y / gcd;
// special case: 8 : 5 is usually referred to as 16:10
@ -29,10 +29,8 @@ QString getAspect(int x, int y)
return QString(QString::number(xaspect) + ":" + QString::number(yaspect));
}
Launcher::GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent)
Launcher::GraphicsPage::GraphicsPage(QWidget *parent)
: QWidget(parent)
, mCfgMgr(cfg)
, mEngineSettings(engineSettings)
{
setObjectName ("GraphicsPage");
setupUi(this);
@ -42,12 +40,11 @@ Launcher::GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, Settings:
customWidthSpinBox->setMaximum(res.width());
customHeightSpinBox->setMaximum(res.height());
connect(fullScreenCheckBox, SIGNAL(stateChanged(int)), this, SLOT(slotFullScreenChanged(int)));
connect(windowModeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotFullScreenChanged(int)));
connect(standardRadioButton, SIGNAL(toggled(bool)), this, SLOT(slotStandardToggled(bool)));
connect(screenComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(screenChanged(int)));
connect(framerateLimitCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotFramerateLimitToggled(bool)));
connect(shadowDistanceCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotShadowDistLimitToggled(bool)));
}
bool Launcher::GraphicsPage::setupSDL()
@ -91,26 +88,29 @@ bool Launcher::GraphicsPage::loadSettings()
if (!setupSDL())
return false;
if (mEngineSettings.getBool("vsync", "Video"))
// Visuals
if (Settings::Manager::getBool("vsync", "Video"))
vSyncCheckBox->setCheckState(Qt::Checked);
if (mEngineSettings.getBool("fullscreen", "Video"))
fullScreenCheckBox->setCheckState(Qt::Checked);
size_t windowMode = static_cast<size_t>(Settings::Manager::getInt("window mode", "Video"));
if (windowMode > static_cast<size_t>(Settings::WindowMode::Windowed))
windowMode = 0;
windowModeComboBox->setCurrentIndex(windowMode);
if (mEngineSettings.getBool("window border", "Video"))
if (Settings::Manager::getBool("window border", "Video"))
windowBorderCheckBox->setCheckState(Qt::Checked);
// aaValue is the actual value (0, 1, 2, 4, 8, 16)
int aaValue = mEngineSettings.getInt("antialiasing", "Video");
int aaValue = Settings::Manager::getInt("antialiasing", "Video");
// aaIndex is the index into the allowed values in the pull down.
int aaIndex = antiAliasingComboBox->findText(QString::number(aaValue));
if (aaIndex != -1)
antiAliasingComboBox->setCurrentIndex(aaIndex);
int width = mEngineSettings.getInt("resolution x", "Video");
int height = mEngineSettings.getInt("resolution y", "Video");
int width = Settings::Manager::getInt("resolution x", "Video");
int height = Settings::Manager::getInt("resolution y", "Video");
QString resolution = QString::number(width) + QString(" x ") + QString::number(height);
screenComboBox->setCurrentIndex(mEngineSettings.getInt("screen", "Video"));
screenComboBox->setCurrentIndex(Settings::Manager::getInt("screen", "Video"));
int resIndex = resolutionComboBox->findText(resolution, Qt::MatchStartsWith);
@ -123,40 +123,49 @@ bool Launcher::GraphicsPage::loadSettings()
customHeightSpinBox->setValue(height);
}
float fpsLimit = mEngineSettings.getFloat("framerate limit", "Video");
float fpsLimit = Settings::Manager::getFloat("framerate limit", "Video");
if (fpsLimit != 0)
{
framerateLimitCheckBox->setCheckState(Qt::Checked);
framerateLimitSpinBox->setValue(fpsLimit);
}
if (mEngineSettings.getBool("actor shadows", "Shadows"))
// Lighting
int lightingMethod = 1;
if (Settings::Manager::getString("lighting method", "Shaders") == "legacy")
lightingMethod = 0;
else if (Settings::Manager::getString("lighting method", "Shaders") == "shaders")
lightingMethod = 2;
lightingMethodComboBox->setCurrentIndex(lightingMethod);
// Shadows
if (Settings::Manager::getBool("actor shadows", "Shadows"))
actorShadowsCheckBox->setCheckState(Qt::Checked);
if (mEngineSettings.getBool("player shadows", "Shadows"))
if (Settings::Manager::getBool("player shadows", "Shadows"))
playerShadowsCheckBox->setCheckState(Qt::Checked);
if (mEngineSettings.getBool("terrain shadows", "Shadows"))
if (Settings::Manager::getBool("terrain shadows", "Shadows"))
terrainShadowsCheckBox->setCheckState(Qt::Checked);
if (mEngineSettings.getBool("object shadows", "Shadows"))
if (Settings::Manager::getBool("object shadows", "Shadows"))
objectShadowsCheckBox->setCheckState(Qt::Checked);
if (mEngineSettings.getBool("enable indoor shadows", "Shadows"))
if (Settings::Manager::getBool("enable indoor shadows", "Shadows"))
indoorShadowsCheckBox->setCheckState(Qt::Checked);
shadowComputeSceneBoundsComboBox->setCurrentIndex(
shadowComputeSceneBoundsComboBox->findText(
QString(tr(mEngineSettings.getString("compute scene bounds", "Shadows").c_str()))));
QString(tr(Settings::Manager::getString("compute scene bounds", "Shadows").c_str()))));
int shadowDistLimit = mEngineSettings.getInt("maximum shadow map distance", "Shadows");
int shadowDistLimit = Settings::Manager::getInt("maximum shadow map distance", "Shadows");
if (shadowDistLimit > 0)
{
shadowDistanceCheckBox->setCheckState(Qt::Checked);
shadowDistanceSpinBox->setValue(shadowDistLimit);
}
float shadowFadeStart = mEngineSettings.getFloat("shadow fade start", "Shadows");
float shadowFadeStart = Settings::Manager::getFloat("shadow fade start", "Shadows");
if (shadowFadeStart != 0)
fadeStartSpinBox->setValue(shadowFadeStart);
int shadowRes = mEngineSettings.getInt("shadow map resolution", "Shadows");
int shadowRes = Settings::Manager::getInt("shadow map resolution", "Shadows");
int shadowResIndex = shadowResolutionComboBox->findText(QString::number(shadowRes));
if (shadowResIndex != -1)
shadowResolutionComboBox->setCurrentIndex(shadowResIndex);
@ -166,23 +175,25 @@ bool Launcher::GraphicsPage::loadSettings()
void Launcher::GraphicsPage::saveSettings()
{
// Visuals
// Ensure we only set the new settings if they changed. This is to avoid cluttering the
// user settings file (which by definition should only contain settings the user has touched)
bool cVSync = vSyncCheckBox->checkState();
if (cVSync != mEngineSettings.getBool("vsync", "Video"))
mEngineSettings.setBool("vsync", "Video", cVSync);
if (cVSync != Settings::Manager::getBool("vsync", "Video"))
Settings::Manager::setBool("vsync", "Video", cVSync);
bool cFullScreen = fullScreenCheckBox->checkState();
if (cFullScreen != mEngineSettings.getBool("fullscreen", "Video"))
mEngineSettings.setBool("fullscreen", "Video", cFullScreen);
int cWindowMode = windowModeComboBox->currentIndex();
if (cWindowMode != Settings::Manager::getInt("window mode", "Video"))
Settings::Manager::setInt("window mode", "Video", cWindowMode);
bool cWindowBorder = windowBorderCheckBox->checkState();
if (cWindowBorder != mEngineSettings.getBool("window border", "Video"))
mEngineSettings.setBool("window border", "Video", cWindowBorder);
if (cWindowBorder != Settings::Manager::getBool("window border", "Video"))
Settings::Manager::setBool("window border", "Video", cWindowBorder);
int cAAValue = antiAliasingComboBox->currentText().toInt();
if (cAAValue != mEngineSettings.getInt("antialiasing", "Video"))
mEngineSettings.setInt("antialiasing", "Video", cAAValue);
if (cAAValue != Settings::Manager::getInt("antialiasing", "Video"))
Settings::Manager::setInt("antialiasing", "Video", cAAValue);
int cWidth = 0;
int cHeight = 0;
@ -197,33 +208,40 @@ void Launcher::GraphicsPage::saveSettings()
cHeight = customHeightSpinBox->value();
}
if (cWidth != mEngineSettings.getInt("resolution x", "Video"))
mEngineSettings.setInt("resolution x", "Video", cWidth);
if (cWidth != Settings::Manager::getInt("resolution x", "Video"))
Settings::Manager::setInt("resolution x", "Video", cWidth);
if (cHeight != mEngineSettings.getInt("resolution y", "Video"))
mEngineSettings.setInt("resolution y", "Video", cHeight);
if (cHeight != Settings::Manager::getInt("resolution y", "Video"))
Settings::Manager::setInt("resolution y", "Video", cHeight);
int cScreen = screenComboBox->currentIndex();
if (cScreen != mEngineSettings.getInt("screen", "Video"))
mEngineSettings.setInt("screen", "Video", cScreen);
if (cScreen != Settings::Manager::getInt("screen", "Video"))
Settings::Manager::setInt("screen", "Video", cScreen);
if (framerateLimitCheckBox->checkState())
if (framerateLimitCheckBox->checkState() != Qt::Unchecked)
{
float cFpsLimit = framerateLimitSpinBox->value();
if (cFpsLimit != mEngineSettings.getFloat("framerate limit", "Video"))
mEngineSettings.setFloat("framerate limit", "Video", cFpsLimit);
if (cFpsLimit != Settings::Manager::getFloat("framerate limit", "Video"))
Settings::Manager::setFloat("framerate limit", "Video", cFpsLimit);
}
else if (mEngineSettings.getFloat("framerate limit", "Video") != 0)
else if (Settings::Manager::getFloat("framerate limit", "Video") != 0)
{
mEngineSettings.setFloat("framerate limit", "Video", 0);
Settings::Manager::setFloat("framerate limit", "Video", 0);
}
int cShadowDist = shadowDistanceCheckBox->checkState() ? shadowDistanceSpinBox->value() : 0;
if (mEngineSettings.getInt("maximum shadow map distance", "Shadows") != cShadowDist)
mEngineSettings.setInt("maximum shadow map distance", "Shadows", cShadowDist);
// Lighting
static std::array<std::string, 3> lightingMethodMap = {"legacy", "shaders compatibility", "shaders"};
const std::string& cLightingMethod = lightingMethodMap[lightingMethodComboBox->currentIndex()];
if (cLightingMethod != Settings::Manager::getString("lighting method", "Shaders"))
Settings::Manager::setString("lighting method", "Shaders", cLightingMethod);
// Shadows
int cShadowDist = shadowDistanceCheckBox->checkState() != Qt::Unchecked ? shadowDistanceSpinBox->value() : 0;
if (Settings::Manager::getInt("maximum shadow map distance", "Shadows") != cShadowDist)
Settings::Manager::setInt("maximum shadow map distance", "Shadows", cShadowDist);
float cFadeStart = fadeStartSpinBox->value();
if (cShadowDist > 0 && mEngineSettings.getFloat("shadow fade start", "Shadows") != cFadeStart)
mEngineSettings.setFloat("shadow fade start", "Shadows", cFadeStart);
if (cShadowDist > 0 && Settings::Manager::getFloat("shadow fade start", "Shadows") != cFadeStart)
Settings::Manager::setFloat("shadow fade start", "Shadows", cFadeStart);
bool cActorShadows = actorShadowsCheckBox->checkState();
bool cObjectShadows = objectShadowsCheckBox->checkState();
@ -231,42 +249,42 @@ void Launcher::GraphicsPage::saveSettings()
bool cPlayerShadows = playerShadowsCheckBox->checkState();
if (cActorShadows || cObjectShadows || cTerrainShadows || cPlayerShadows)
{
if (!mEngineSettings.getBool("enable shadows", "Shadows"))
mEngineSettings.setBool("enable shadows", "Shadows", true);
if (mEngineSettings.getBool("actor shadows", "Shadows") != cActorShadows)
mEngineSettings.setBool("actor shadows", "Shadows", cActorShadows);
if (mEngineSettings.getBool("player shadows", "Shadows") != cPlayerShadows)
mEngineSettings.setBool("player shadows", "Shadows", cPlayerShadows);
if (mEngineSettings.getBool("object shadows", "Shadows") != cObjectShadows)
mEngineSettings.setBool("object shadows", "Shadows", cObjectShadows);
if (mEngineSettings.getBool("terrain shadows", "Shadows") != cTerrainShadows)
mEngineSettings.setBool("terrain shadows", "Shadows", cTerrainShadows);
if (!Settings::Manager::getBool("enable shadows", "Shadows"))
Settings::Manager::setBool("enable shadows", "Shadows", true);
if (Settings::Manager::getBool("actor shadows", "Shadows") != cActorShadows)
Settings::Manager::setBool("actor shadows", "Shadows", cActorShadows);
if (Settings::Manager::getBool("player shadows", "Shadows") != cPlayerShadows)
Settings::Manager::setBool("player shadows", "Shadows", cPlayerShadows);
if (Settings::Manager::getBool("object shadows", "Shadows") != cObjectShadows)
Settings::Manager::setBool("object shadows", "Shadows", cObjectShadows);
if (Settings::Manager::getBool("terrain shadows", "Shadows") != cTerrainShadows)
Settings::Manager::setBool("terrain shadows", "Shadows", cTerrainShadows);
}
else
{
if (mEngineSettings.getBool("enable shadows", "Shadows"))
mEngineSettings.setBool("enable shadows", "Shadows", false);
if (mEngineSettings.getBool("actor shadows", "Shadows"))
mEngineSettings.setBool("actor shadows", "Shadows", false);
if (mEngineSettings.getBool("player shadows", "Shadows"))
mEngineSettings.setBool("player shadows", "Shadows", false);
if (mEngineSettings.getBool("object shadows", "Shadows"))
mEngineSettings.setBool("object shadows", "Shadows", false);
if (mEngineSettings.getBool("terrain shadows", "Shadows"))
mEngineSettings.setBool("terrain shadows", "Shadows", false);
if (Settings::Manager::getBool("enable shadows", "Shadows"))
Settings::Manager::setBool("enable shadows", "Shadows", false);
if (Settings::Manager::getBool("actor shadows", "Shadows"))
Settings::Manager::setBool("actor shadows", "Shadows", false);
if (Settings::Manager::getBool("player shadows", "Shadows"))
Settings::Manager::setBool("player shadows", "Shadows", false);
if (Settings::Manager::getBool("object shadows", "Shadows"))
Settings::Manager::setBool("object shadows", "Shadows", false);
if (Settings::Manager::getBool("terrain shadows", "Shadows"))
Settings::Manager::setBool("terrain shadows", "Shadows", false);
}
bool cIndoorShadows = indoorShadowsCheckBox->checkState();
if (mEngineSettings.getBool("enable indoor shadows", "Shadows") != cIndoorShadows)
mEngineSettings.setBool("enable indoor shadows", "Shadows", cIndoorShadows);
if (Settings::Manager::getBool("enable indoor shadows", "Shadows") != cIndoorShadows)
Settings::Manager::setBool("enable indoor shadows", "Shadows", cIndoorShadows);
int cShadowRes = shadowResolutionComboBox->currentText().toInt();
if (cShadowRes != mEngineSettings.getInt("shadow map resolution", "Shadows"))
mEngineSettings.setInt("shadow map resolution", "Shadows", cShadowRes);
if (cShadowRes != Settings::Manager::getInt("shadow map resolution", "Shadows"))
Settings::Manager::setInt("shadow map resolution", "Shadows", cShadowRes);
auto cComputeSceneBounds = shadowComputeSceneBoundsComboBox->currentText().toStdString();
if (cComputeSceneBounds != mEngineSettings.getString("compute scene bounds", "Shadows"))
mEngineSettings.setString("compute scene bounds", "Shadows", cComputeSceneBounds);
if (cComputeSceneBounds != Settings::Manager::getString("compute scene bounds", "Shadows"))
Settings::Manager::setString("compute scene bounds", "Shadows", cComputeSceneBounds);
}
QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen)
@ -299,9 +317,9 @@ QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen)
return result;
}
QString aspect = getAspect(mode.w, mode.h);
QString resolution = QString::number(mode.w) + QString(" x ") + QString::number(mode.h);
QString aspect = getAspect(mode.w, mode.h);
if (aspect == QLatin1String("16:9") || aspect == QLatin1String("16:10")) {
resolution.append(tr("\t(Wide ") + aspect + ")");
@ -339,9 +357,9 @@ void Launcher::GraphicsPage::screenChanged(int screen)
}
}
void Launcher::GraphicsPage::slotFullScreenChanged(int state)
void Launcher::GraphicsPage::slotFullScreenChanged(int mode)
{
if (state == Qt::Checked) {
if (mode == static_cast<int>(Settings::WindowMode::Fullscreen) || mode == static_cast<int>(Settings::WindowMode::WindowedFullscreen)) {
standardRadioButton->toggle();
customRadioButton->setEnabled(false);
customWidthSpinBox->setEnabled(false);

@ -1,8 +1,6 @@
#ifndef GRAPHICSPAGE_H
#define GRAPHICSPAGE_H
#include <QWidget>
#include "ui_graphicspage.h"
#include <components/settings/settings.hpp>
@ -20,7 +18,7 @@ namespace Launcher
Q_OBJECT
public:
GraphicsPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent = 0);
explicit GraphicsPage(QWidget *parent = nullptr);
void saveSettings();
bool loadSettings();
@ -35,9 +33,6 @@ namespace Launcher
void slotShadowDistLimitToggled(bool checked);
private:
Files::ConfigurationManager &mCfgMgr;
Settings::Manager &mEngineSettings;
QVector<QStringList> mResolutionsPerScreen;
static QStringList getAvailableResolutions(int screen);

@ -1,10 +1,10 @@
#include <iostream>
#include <QApplication>
#include <QTranslator>
#include <QTextCodec>
#include <QDir>
#include <QDebug>
#include <components/debug/debugging.hpp>
#include <components/platform/platform.hpp>
#ifdef MAC_OS_X_VERSION_MIN_REQUIRED
#undef MAC_OS_X_VERSION_MIN_REQUIRED
@ -14,8 +14,10 @@
#include "maindialog.hpp"
int main(int argc, char *argv[])
int runLauncher(int argc, char *argv[])
{
Platform::init();
try
{
QApplication app(argc, argv);
@ -51,3 +53,8 @@ int main(int argc, char *argv[])
return 0;
}
}
int main(int argc, char *argv[])
{
return wrapApplication(runLauncher, argc, argv, "Launcher");
}

@ -3,16 +3,15 @@
#include <components/version/version.hpp>
#include <components/misc/helpviewer.hpp>
#include <QDate>
#include <QTime>
#include <QDir>
#include <QDebug>
#include <QMessageBox>
#include <QPushButton>
#include <QFontDatabase>
#include <QInputDialog>
#include <QFileDialog>
#include <QCloseEvent>
#include <QTextCodec>
#include <QDebug>
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/variables_map.hpp>
#include "playpage.hpp"
#include "graphicspage.hpp"
@ -55,8 +54,8 @@ Launcher::MainDialog::MainDialog(QWidget *parent)
iconWidget->setCurrentRow(0);
iconWidget->setFlow(QListView::LeftToRight);
QPushButton *helpButton = new QPushButton(tr("Help"));
QPushButton *playButton = new QPushButton(tr("Play"));
auto *helpButton = new QPushButton(tr("Help"));
auto *playButton = new QPushButton(tr("Play"));
buttonBox->button(QDialogButtonBox::Close)->setText(tr("Close"));
buttonBox->addButton(helpButton, QDialogButtonBox::HelpRole);
buttonBox->addButton(playButton, QDialogButtonBox::AcceptRole);
@ -82,31 +81,31 @@ void Launcher::MainDialog::createIcons()
if (!QIcon::hasThemeIcon("document-new"))
QIcon::setThemeName("tango");
QListWidgetItem *playButton = new QListWidgetItem(iconWidget);
auto *playButton = new QListWidgetItem(iconWidget);
playButton->setIcon(QIcon(":/images/openmw.png"));
playButton->setText(tr("Play"));
playButton->setTextAlignment(Qt::AlignCenter);
playButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
QListWidgetItem *dataFilesButton = new QListWidgetItem(iconWidget);
auto *dataFilesButton = new QListWidgetItem(iconWidget);
dataFilesButton->setIcon(QIcon(":/images/openmw-plugin.png"));
dataFilesButton->setText(tr("Data Files"));
dataFilesButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom);
dataFilesButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
QListWidgetItem *graphicsButton = new QListWidgetItem(iconWidget);
auto *graphicsButton = new QListWidgetItem(iconWidget);
graphicsButton->setIcon(QIcon(":/images/preferences-video.png"));
graphicsButton->setText(tr("Graphics"));
graphicsButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom | Qt::AlignAbsolute);
graphicsButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
QListWidgetItem *settingsButton = new QListWidgetItem(iconWidget);
auto *settingsButton = new QListWidgetItem(iconWidget);
settingsButton->setIcon(QIcon(":/images/preferences.png"));
settingsButton->setText(tr("Settings"));
settingsButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom);
settingsButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
QListWidgetItem *advancedButton = new QListWidgetItem(iconWidget);
auto *advancedButton = new QListWidgetItem(iconWidget);
advancedButton->setIcon(QIcon(":/images/preferences-advanced.png"));
advancedButton->setText(tr("Advanced"));
advancedButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom);
@ -126,9 +125,9 @@ void Launcher::MainDialog::createPages()
mPlayPage = new PlayPage(this);
mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this);
mGraphicsPage = new GraphicsPage(mCfgMgr, mEngineSettings, this);
mGraphicsPage = new GraphicsPage(this);
mSettingsPage = new SettingsPage(mCfgMgr, mGameSettings, mLauncherSettings, this);
mAdvancedPage = new AdvancedPage(mCfgMgr, mGameSettings, mEngineSettings, this);
mAdvancedPage = new AdvancedPage(mGameSettings, this);
// Set the combobox of the play page to imitate the combobox on the datafilespage
mPlayPage->setProfilesModel(mDataFilesPage->profilesModel());
@ -150,7 +149,6 @@ void Launcher::MainDialog::createPages()
connect(mDataFilesPage, SIGNAL(signalProfileChanged(int)), mPlayPage, SLOT(setProfilesIndex(int)));
// Using Qt::QueuedConnection because signal is emitted in a subthread and slot is in the main thread
connect(mDataFilesPage, SIGNAL(signalLoadedCellsChanged(QStringList)), mAdvancedPage, SLOT(slotLoadedCellsChanged(QStringList)), Qt::QueuedConnection);
}
Launcher::FirstRunDialogResult Launcher::MainDialog::showFirstRunDialog()
@ -158,6 +156,20 @@ Launcher::FirstRunDialogResult Launcher::MainDialog::showFirstRunDialog()
if (!setupLauncherSettings())
return FirstRunDialogResultFailure;
// Dialog wizard and setup will fail if the config directory does not already exist
QDir userConfigDir = QDir(QString::fromStdString(mCfgMgr.getUserConfigPath().string()));
if ( ! userConfigDir.exists() ) {
if ( ! userConfigDir.mkpath(".") )
{
cfgError(tr("Error opening OpenMW configuration file"),
tr("<br><b>Could not create directory %0</b><br><br> \
Please make sure you have the right permissions \
and try again.<br>").arg(userConfigDir.canonicalPath())
);
return FirstRunDialogResultFailure;
}
}
if (mLauncherSettings.value(QString("General/firstrun"), QString("true")) == QLatin1String("true"))
{
QMessageBox msgBox;
@ -322,54 +334,46 @@ bool Launcher::MainDialog::setupGameSettings()
QString userPath = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str());
QString globalPath = QString::fromUtf8(mCfgMgr.getGlobalPath().string().c_str());
// Load the user config file first, separately
// So we can write it properly, uncontaminated
QString path = userPath + QLatin1String("openmw.cfg");
QFile file(path);
QFile file;
qDebug() << "Loading config file:" << path.toUtf8().constData();
if (file.exists()) {
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
cfgError(tr("Error opening OpenMW configuration file"),
tr("<br><b>Could not open %0 for reading</b><br><br> \
Please make sure you have the right permissions \
and try again.<br>").arg(file.fileName()));
return false;
}
QTextStream stream(&file);
stream.setCodec(QTextCodec::codecForName("UTF-8"));
mGameSettings.readUserFile(stream);
file.close();
}
// Now the rest - priority: user > local > global
QStringList paths;
paths.append(globalPath + QString("openmw.cfg"));
paths.append(localPath + QString("openmw.cfg"));
paths.append(userPath + QString("openmw.cfg"));
for (const QString &path2 : paths)
auto loadFile = [&] (const QString& path, bool(Config::GameSettings::*reader)(QTextStream&, bool), bool ignoreContent = false) -> std::optional<bool>
{
qDebug() << "Loading config file:" << path2.toUtf8().constData();
file.setFileName(path2);
qDebug() << "Loading config file:" << path.toUtf8().constData();
file.setFileName(path);
if (file.exists()) {
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
cfgError(tr("Error opening OpenMW configuration file"),
tr("<br><b>Could not open %0 for reading</b><br><br> \
Please make sure you have the right permissions \
and try again.<br>").arg(file.fileName()));
return false;
tr("<br><b>Could not open %0 for reading</b><br><br> \
Please make sure you have the right permissions \
and try again.<br>").arg(file.fileName()));
return {};
}
QTextStream stream(&file);
stream.setCodec(QTextCodec::codecForName("UTF-8"));
mGameSettings.readFile(stream);
(mGameSettings.*reader)(stream, ignoreContent);
file.close();
return true;
}
return false;
};
// Load the user config file first, separately
// So we can write it properly, uncontaminated
if(!loadFile(userPath + QLatin1String("openmw.cfg"), &Config::GameSettings::readUserFile))
return false;
// Now the rest - priority: user > local > global
if(auto result = loadFile(localPath + QString("openmw.cfg"), &Config::GameSettings::readFile, true))
{
// Load global if local wasn't found
if(!*result && !loadFile(globalPath + QString("openmw.cfg"), &Config::GameSettings::readFile, true))
return false;
}
else
return false;
if(!loadFile(userPath + QString("openmw.cfg"), &Config::GameSettings::readFile))
return false;
return true;
}
@ -419,57 +423,23 @@ bool Launcher::MainDialog::setupGameData()
bool Launcher::MainDialog::setupGraphicsSettings()
{
// This method is almost a copy of OMW::Engine::loadSettings(). They should definitely
// remain consistent, and possibly be merged into a shared component. At the very least
// the filenames should be in the CfgMgr component.
// Ensure to clear previous settings in case we had already loaded settings.
mEngineSettings.clear();
// Create the settings manager and load default settings file
const std::string localDefault = (mCfgMgr.getLocalPath() / "settings-default.cfg").string();
const std::string globalDefault = (mCfgMgr.getGlobalPath() / "settings-default.cfg").string();
std::string defaultPath;
// Prefer the settings-default.cfg in the current directory.
if (boost::filesystem::exists(localDefault))
defaultPath = localDefault;
else if (boost::filesystem::exists(globalDefault))
defaultPath = globalDefault;
// Something's very wrong if we can't find the file at all.
else {
cfgError(tr("Error reading OpenMW configuration file"),
tr("<br><b>Could not find settings-default.cfg</b><br><br> \
The problem may be due to an incomplete installation of OpenMW.<br> \
Reinstalling OpenMW may resolve the problem."));
return false;
}
// Load the default settings, report any parsing errors.
try {
mEngineSettings.loadDefault(defaultPath);
}
catch (std::exception& e) {
std::string msg = std::string("<br><b>Error reading settings-default.cfg</b><br><br>") + e.what();
cfgError(tr("Error reading OpenMW configuration file"), tr(msg.c_str()));
return false;
}
// Load user settings if they exist
const std::string userPath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string();
// User settings are not required to exist, so if they don't we're done.
if (!boost::filesystem::exists(userPath)) return true;
try {
mEngineSettings.loadUser(userPath);
Settings::Manager::clear(); // Ensure to clear previous settings in case we had already loaded settings.
try
{
boost::program_options::variables_map variables;
boost::program_options::options_description desc;
mCfgMgr.addCommonOptions(desc);
mCfgMgr.readConfiguration(variables, desc, true);
Settings::Manager::load(mCfgMgr);
return true;
}
catch (std::exception& e) {
std::string msg = std::string("<br><b>Error reading settings.cfg</b><br><br>") + e.what();
cfgError(tr("Error reading OpenMW configuration file"), tr(msg.c_str()));
catch (std::exception& e)
{
cfgError(tr("Error reading OpenMW configuration files"),
tr("<br>The problem may be due to an incomplete installation of OpenMW.<br> \
Reinstalling OpenMW may resolve the problem.<br>") + e.what());
return false;
}
return true;
}
void Launcher::MainDialog::loadSettings()
@ -543,7 +513,7 @@ bool Launcher::MainDialog::writeSettings()
// Graphics settings
const std::string settingsPath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string();
try {
mEngineSettings.saveUser(settingsPath);
Settings::Manager::saveUser(settingsPath);
}
catch (std::exception& e) {
std::string msg = "<br><b>Error writing settings.cfg</b><br><br>" +
@ -609,7 +579,7 @@ void Launcher::MainDialog::play()
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setText(tr("<br><b>You do not have a game file selected.</b><br><br> \
OpenMW will not start without a game file selected.<br>"));
msgBox.exec();
msgBox.exec();
return;
}

@ -1,19 +1,15 @@
#ifndef MAINDIALOG_H
#define MAINDIALOG_H
#include <QMainWindow>
#include <QProcess>
#ifndef Q_MOC_RUN
#include <components/files/configurationmanager.hpp>
#include <components/process/processinvoker.hpp>
#include <components/config/gamesettings.hpp>
#include <components/config/launchersettings.hpp>
#include <components/settings/settings.hpp>
#endif
#include "ui_mainwindow.h"
@ -48,8 +44,8 @@ namespace Launcher
Q_OBJECT
public:
explicit MainDialog(QWidget *parent = 0);
~MainDialog();
explicit MainDialog(QWidget *parent = nullptr);
~MainDialog() override;
FirstRunDialogResult showFirstRunDialog();
@ -98,7 +94,6 @@ namespace Launcher
Files::ConfigurationManager mCfgMgr;
Config::GameSettings mGameSettings;
Settings::Manager mEngineSettings;
Config::LauncherSettings mLauncherSettings;
};

@ -1,8 +1,6 @@
#ifndef PLAYPAGE_H
#define PLAYPAGE_H
#include <QWidget>
#include "ui_playpage.h"
class QComboBox;
@ -16,7 +14,7 @@ namespace Launcher
Q_OBJECT
public:
PlayPage(QWidget *parent = 0);
PlayPage(QWidget *parent = nullptr);
void setProfilesModel(QAbstractItemModel *model);
signals:

@ -1,7 +1,6 @@
#include <signal.h>
#include <SDL.h>
#include <SDL_video.h>
bool initSDL()
{

@ -5,11 +5,6 @@
#include <QDebug>
#include <QDir>
#include <components/files/configurationmanager.hpp>
#include <components/config/gamesettings.hpp>
#include <components/config/launchersettings.hpp>
#include "utils/textinputdialog.hpp"
#include "datafilespage.hpp"

@ -1,9 +1,6 @@
#ifndef SETTINGSPAGE_HPP
#define SETTINGSPAGE_HPP
#include <QWidget>
#include <QProcess>
#include <components/process/processinvoker.hpp>
#include "ui_settingspage.h"
@ -24,8 +21,8 @@ namespace Launcher
public:
SettingsPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings,
Config::LauncherSettings &launcherSettings, MainDialog *parent = 0);
~SettingsPage();
Config::LauncherSettings &launcherSettings, MainDialog *parent = nullptr);
~SettingsPage() override;
void saveSettings();
bool loadSettings();

@ -1,6 +1,6 @@
#include "cellnameloader.hpp"
#include <components/esm/loadcell.hpp>
#include <components/esm3/loadcell.hpp>
#include <components/contentselector/view/contentselector.hpp>
QSet<QString> CellNameLoader::getCellNames(QStringList &contentPaths)
@ -10,6 +10,8 @@ QSet<QString> CellNameLoader::getCellNames(QStringList &contentPaths)
// Loop through all content files
for (auto &contentPath : contentPaths) {
if (contentPath.endsWith(".omwscripts", Qt::CaseInsensitive))
continue;
esmReader.open(contentPath.toStdString());
// Loop through all records
@ -35,7 +37,7 @@ QSet<QString> CellNameLoader::getCellNames(QStringList &contentPaths)
bool CellNameLoader::isCellRecord(ESM::NAME &recordName)
{
return recordName.intval == ESM::REC_CELL;
return recordName.toInt() == ESM::REC_CELL;
}
QString CellNameLoader::getCellName(ESM::ESMReader &esmReader)
@ -45,4 +47,5 @@ QString CellNameLoader::getCellName(ESM::ESMReader &esmReader)
cell.loadNameAndData(esmReader, isDeleted);
return QString::fromStdString(cell.mName);
}
}

@ -1,11 +1,10 @@
#ifndef OPENMW_CELLNAMELOADER_H
#define OPENMW_CELLNAMELOADER_H
#include <QComboBox>
#include <QSet>
#include <QString>
#include <components/esm/esmreader.hpp>
#include <components/esm3/esmreader.hpp>
namespace ESM {class ESMReader; struct Cell;}
namespace ContentSelectorView {class ContentSelector;}

@ -11,7 +11,6 @@
#define LINEEDIT_H
#include <QLineEdit>
#include <QStyle>
#include <QStylePainter>
#include <QToolButton>
@ -24,7 +23,7 @@ class LineEdit : public QLineEdit
QString mPlaceholderText;
public:
LineEdit(QWidget *parent = 0);
LineEdit(QWidget *parent = nullptr);
protected:
void resizeEvent(QResizeEvent *) override;

@ -0,0 +1,60 @@
#include <cstring>
#include <vector>
#include <apps/openmw/mwsound/alext.h>
#include "openalutil.hpp"
#ifndef ALC_ALL_DEVICES_SPECIFIER
#define ALC_ALL_DEVICES_SPECIFIER 0x1013
#endif
std::vector<std::string> Launcher::enumerateOpenALDevices()
{
std::vector<std::string> devlist;
const ALCchar *devnames;
if(alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT"))
{
devnames = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER);
}
else
{
devnames = alcGetString(nullptr, ALC_DEVICE_SPECIFIER);
}
while(devnames && *devnames)
{
devlist.emplace_back(devnames);
devnames += strlen(devnames)+1;
}
return devlist;
}
std::vector<std::string> Launcher::enumerateOpenALDevicesHrtf()
{
std::vector<std::string> ret;
ALCdevice *device = alcOpenDevice(nullptr);
if(device)
{
if(alcIsExtensionPresent(device, "ALC_SOFT_HRTF"))
{
LPALCGETSTRINGISOFT alcGetStringiSOFT = nullptr;
void* funcPtr = alcGetProcAddress(device, "alcGetStringiSOFT");
memcpy(&alcGetStringiSOFT, &funcPtr, sizeof(funcPtr));
ALCint num_hrtf;
alcGetIntegerv(device, ALC_NUM_HRTF_SPECIFIERS_SOFT, 1, &num_hrtf);
ret.reserve(num_hrtf);
for(ALCint i = 0;i < num_hrtf;++i)
{
const ALCchar *entry = alcGetStringiSOFT(device, ALC_HRTF_SPECIFIER_SOFT, i);
if(strcmp(entry, "") == 0)
break;
ret.emplace_back(entry);
}
}
alcCloseDevice(device);
}
return ret;
}

@ -0,0 +1,8 @@
#include <vector>
#include <string>
namespace Launcher
{
std::vector<std::string> enumerateOpenALDevices();
std::vector<std::string> enumerateOpenALDevicesHrtf();
}

@ -1,8 +1,5 @@
#include <QRegExpValidator>
#include <QLineEdit>
#include <QString>
#include <QApplication>
#include <QKeyEvent>
#include "profilescombobox.hpp"
@ -30,10 +27,10 @@ void ProfilesComboBox::setEditEnabled(bool editable)
setEditable(true);
setValidator(mValidator);
ComboBoxLineEdit *edit = new ComboBoxLineEdit(this);
auto *edit = new ComboBoxLineEdit(this);
setLineEdit(edit);
setCompleter(0);
setCompleter(nullptr);
connect(lineEdit(), SIGNAL(editingFinished()), this,
SLOT(slotEditingFinished()));

@ -4,7 +4,7 @@
#include "components/contentselector/view/combobox.hpp"
#include "lineedit.hpp"
#include <QDebug>
#include <QWidget>
class QString;
@ -16,12 +16,12 @@ public:
class ComboBoxLineEdit : public LineEdit
{
public:
explicit ComboBoxLineEdit (QWidget *parent = 0);
explicit ComboBoxLineEdit (QWidget *parent = nullptr);
};
public:
explicit ProfilesComboBox(QWidget *parent = 0);
explicit ProfilesComboBox(QWidget *parent = nullptr);
void setEditEnabled(bool editable);
void setCurrentProfile(int index)
{

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

Loading…
Cancel
Save