Merge progress, still lots to do and to continue up the change

pull/649/head
Max Henzerling 10 months ago
parent 37a4b2a103
commit 17a2c81b28

@ -0,0 +1,105 @@
---
Language: Cpp
AccessModifierOffset: -4
AlignAfterOpenBracket: DontAlign
AlignConsecutiveAssignments: None
AlignConsecutiveDeclarations: None
AlignEscapedNewlines: Right
AlignOperands: false
AlignTrailingComments: false
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Inline
AllowShortIfStatementsOnASingleLine: false
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterCaseLabel: true
AfterClass: true
AfterControlStatement: Always
AfterEnum: true
AfterFunction: true
AfterNamespace: true
AfterObjCDeclaration: true
AfterStruct: true
AfterUnion: true
AfterExternBlock: true
BeforeCatch: true
BeforeElse: true
BeforeLambdaBody: false
IndentBraces: false
BreakBeforeBinaryOperators: All
BreakBeforeBraces: Custom
BreakBeforeInheritanceComma: false
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: true
BreakStringLiterals: true
ColumnLimit: 120
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: false
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: false
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
Priority: 3
- Regex: '.*'
Priority: 1
IncludeIsMainRegex: '(Test)?$'
IndentCaseLabels: true
IndentExternBlock: AfterExternBlock
IndentPPDirectives: None
IndentWidth: 4
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: true
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: All
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Left
ReflowComments: true
SortIncludes: CaseSensitive
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: c++20
TabWidth: 4
UseTab: Never
StatementMacros:
- META_Object
- META_StateAttribute
- META_Node

@ -0,0 +1,18 @@
Checks: >
-*,
boost-*,
portability-*,
clang-analyzer-*,
-clang-analyzer-optin*,
-clang-analyzer-cplusplus.NewDeleteLeaks,
-clang-analyzer-core.CallAndMessage,
-modernize-avoid-bind
WarningsAsErrors: >
-*,
boost-*,
portability-*,
clang-analyzer-*,
-clang-analyzer-optin*,
-clang-analyzer-cplusplus.NewDeleteLeaks,
-clang-analyzer-core.CallAndMessage
HeaderFilterRegex: '^(apps|components)'

@ -10,7 +10,12 @@ indent_style = space
indent_size = 4
insert_final_newline = true
[*.glsl]
[*.{glsl,vert,tesc,tese,geom,frag,comp}]
indent_style = space
indent_size = 4
insert_final_newline = false
insert_final_newline = true
[{CMakeLists.txt,*.cmake}]
indent_style = space
indent_size = 4
insert_final_newline = true

@ -0,0 +1,19 @@
# This file lists revisions meant to be ignored by `git blame`.
# Pass `--ignore-revs-file .git-blame-ignore-revs` to `git blame` to make your life easier.
# Author: Alexei Kotov <alexdobrohotov@yandex.ru>
# Date: Fri Sep 2 02:52:49 2022 +0000
# Reformat NIF record type mapping
8df0587793a07ec556dc9cb575cd2af4204c456b
# Author: AnyOldName3 <krizdjali@gmail.com>
# Date: Fri Sep 16 00:53:24 2022 +0100
# Renormalise line endings
84f8a6848a8b05502d7618ca7af8cca74f2c3bae
# Author: clang-format-bot <bot@clang-format>
# Date: 9/22/2022 9:26:05 PM
# Apply clang-format to code base
ddb0522bbf2aa8aa7c9e139ff7395fb8ed6a841f
88ec8a95231341e7962b85716510d414e9f0c424

@ -0,0 +1,80 @@
name: CMake
on:
push:
branches:
- 'master'
pull_request:
branches: [ master ]
env:
BUILD_TYPE: RelWithDebInfo
jobs:
Ubuntu:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Add OpenMW PPA Dependencies
run: sudo add-apt-repository ppa:openmw/openmw; sudo apt-get update
- name: Install Building Dependencies
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: Configure
run: cmake . -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DOPENMW_USE_SYSTEM_RECASTNAVIGATION=1 -DUSE_SYSTEM_TINYXML=1 -DBUILD_UNITTESTS=1 -DCMAKE_INSTALL_PREFIX=install
- name: Build
run: make -j3
- 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 Dependencies
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

6
.gitignore vendored

@ -35,7 +35,6 @@ CMakeLists.txt.user*
.vscode
## resources
data
resources
/*.cfg
/*.desktop
@ -73,6 +72,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
@ -85,3 +85,7 @@ moc_*.cxx
*.[ao]
*.so
venv/
## operating system files
.DS_Store
Thumbs.db

@ -1,131 +1,483 @@
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:
- checks
- build
- test
.Debian_Image:
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
when: never
- if: $CI_COMMIT_BRANCH
# 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"
.Ubuntu_Image:
tags:
- docker
- linux
image: debian:bullseye
image: ubuntu:22.04
rules:
- if: $CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "merge_request_event"
.Debian:
extends: .Debian_Image
Ubuntu_GCC_preprocess:
extends: .Ubuntu_Image
cache:
key: Ubuntu_GCC_preprocess.ubuntu_22.04.v1
paths:
- apt-cache/
- .cache/pip/
stage: build
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
before_script:
- CI/install_debian_deps.sh openmw-deps openmw-deps-dynamic gcc_preprocess
- pip3 install --user click termtables
script:
- CI/ubuntu_gcc_preprocess.sh
.Ubuntu:
extends: .Ubuntu_Image
cache:
paths:
- apt-cache/
- ccache/
stage: build
variables:
CMAKE_EXE_LINKER_FLAGS: -fuse-ld=mold
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_detournavigator_navmeshtilescache_benchmark; fi
- if [[ "${BUILD_TESTS_ONLY}" ]]; then ./openmw_test_suite --gtest_output="xml:openmw_tests.xml"; fi
- if [[ "${BUILD_TESTS_ONLY}" ]]; then ./openmw-cs-tests --gtest_output="xml:openmw_cs_tests.xml"; fi
- if [[ "${BUILD_TESTS_ONLY}" && ! "${BUILD_WITH_CODE_COVERAGE}" ]]; then ./openmw_detournavigator_navmeshtilescache_benchmark; fi
- if [[ "${BUILD_TESTS_ONLY}" && ! "${BUILD_WITH_CODE_COVERAGE}" ]]; then ./openmw_esm_refid_benchmark; fi
- if [[ "${BUILD_TESTS_ONLY}" && ! "${BUILD_WITH_CODE_COVERAGE}" ]]; then ./openmw_settings_access_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 '^openmw_tests.xml$' -e '^openmw_cs_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/
Coverity:
extends: .Debian_Image
tags:
- docker
- linux
image: ubuntu:20.04
stage: build
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'
- if: $CI_PIPELINE_SOURCE == "schedule"
cache:
key: Coverity.ubuntu_20.04.v1
paths:
- apt-cache/
- ccache/
variables:
CCACHE_SIZE: 2G
CC: clang-11
CXX: clang++-11
CMAKE_BUILD_TYPE: Debug
CMAKE_CXX_FLAGS_DEBUG: -O0
before_script:
- CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic coverity
- curl -o /tmp/cov-analysis-linux64.tgz https://scan.coverity.com/download/linux64 --form project=$COVERITY_SCAN_PROJECT_NAME --form token=$COVERITY_SCAN_TOKEN
- CI/install_debian_deps.sh coverity 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:
- export CCACHE_BASEDIR="$(pwd)"
- export CCACHE_DIR="$(pwd)/ccache"
- mkdir -pv "${CCACHE_DIR}"
- ccache -z -M "${CCACHE_SIZE}"
- CI/before_script.linux.sh
# Add more than just `openmw` once we can build everything under 3h
- cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build build -- -j $(nproc) openmw
- cov-analysis-linux64-*/bin/cov-configure --template --comptype prefix --compiler ccache
# 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)
- ccache -s
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="`git describe --tags`"
--form description="`git describe --tags` / $CI_COMMIT_TITLE / $CI_COMMIT_REF_NAME:$CI_PIPELINE_ID"
--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"
artifacts:
paths:
- /builds/OpenMW/openmw/cov-int/build-log.txt
Ubuntu_GCC:
extends: .Ubuntu
cache:
key: Ubuntu_GCC.ubuntu_22.04.v1
before_script:
- CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic
variables:
CC: gcc
CXX: g++
timeout: 8h
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:
extends: .Debian
Ubuntu_GCC_asan:
extends: Ubuntu_GCC
cache:
key: Ubuntu_GCC_asan.ubuntu_22.04.v1
variables:
CMAKE_BUILD_TYPE: Debug
CMAKE_CXX_FLAGS_DEBUG: -g -O1 -fno-omit-frame-pointer -fsanitize=address -fsanitize=pointer-subtract -fsanitize=leak
CMAKE_EXE_LINKER_FLAGS: -fsanitize=address -fsanitize=pointer-subtract -fsanitize=leak -fuse-ld=mold
BUILD_OPENMW_ONLY: 1
Clang_Format:
extends: .Ubuntu_Image
stage: checks
cache:
key: Debian_GCC.v2
key: Ubuntu_Clang_Format.ubuntu_22.04.v1
paths:
- apt-cache/
variables:
CLANG_FORMAT: clang-format-14
before_script:
- CI/install_debian_deps.sh openmw-clang-format
script:
- CI/check_cmake_format.sh
- CI/check_file_names.sh
- CI/check_clang_format.sh
Teal:
stage: checks
extends: .Ubuntu_Image
before_script:
- apt-get update
- apt-get -y install curl wget make build-essential libreadline-dev git-core zip unzip
script:
- CI/teal_ci.sh
artifacts:
paths:
- teal_declarations.zip
Ubuntu_GCC_Debug:
extends: .Ubuntu
cache:
key: Ubuntu_GCC_Debug.ubuntu_22.04.v1
before_script:
- CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic
variables:
CC: gcc
CXX: g++
CCACHE_SIZE: 3G
CCACHE_SIZE: 4G
CMAKE_BUILD_TYPE: Debug
CMAKE_CXX_FLAGS_DEBUG: -O0
# 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_GCC
Ubuntu_GCC_tests:
extends: Ubuntu_GCC
cache:
key: Ubuntu_GCC_tests.ubuntu_22.04.v1
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
.Ubuntu_GCC_tests_Debug:
extends: Ubuntu_GCC
cache:
key: Ubuntu_GCC_tests_Debug.ubuntu_22.04.v1
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: Debian_GCC_tests.v2
key: Ubuntu_GCC_tests_asan.ubuntu_22.04.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-subtract -fsanitize=leak
CMAKE_EXE_LINKER_FLAGS: -fsanitize=address -fsanitize=pointer-subtract -fsanitize=leak -fuse-ld=mold
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
Debian_GCC_Static_Deps:
extends: Debian_GCC
Ubuntu_GCC_tests_ubsan:
extends: Ubuntu_GCC
cache:
key: Debian_GCC_Static_Deps
key: Ubuntu_GCC_tests_ubsan.ubuntu_22.04.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_tests_tsan.ubuntu_22.04.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 -fuse-ld=mold
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.ubuntu_22.04.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" || $CI_PIPELINE_SOURCE == "merge_request_event"
changes:
- "**/CMakeLists.txt"
- "cmake/**/*"
- "CI/**/*"
- ".gitlab-ci.yml"
cache:
key: Ubuntu_Static_Deps.ubuntu_22.04.v1
paths:
- apt-cache/
- ccache/
- build/extern/fetched/
- apt-cache/
- ccache/
- build/extern/fetched/
before_script:
- CI/install_debian_deps.sh gcc openmw-deps openmw-deps-static
- CI/install_debian_deps.sh clang openmw-deps openmw-deps-static
variables:
CI_OPENMW_USE_STATIC_DEPS: 1
CC: clang
CXX: clang++
CXXFLAGS: -O0
timeout: 3h
Debian_GCC_Static_Deps_tests:
extends: Debian_GCC_Static_Deps
.Ubuntu_Static_Deps_tests:
extends: .Ubuntu_Static_Deps
cache:
key: Debian_GCC_Static_Deps_tests
key: Ubuntu_Static_Deps_tests.ubuntu_22.04.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
Debian_Clang:
extends: .Debian
Ubuntu_Clang:
extends: .Ubuntu
before_script:
- CI/install_debian_deps.sh clang openmw-deps openmw-deps-dynamic
cache:
key: Debian_Clang.v2
key: Ubuntu_Clang.ubuntu_22.04.v2
variables:
CC: clang
CXX: clang++
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
timeout: 3h
.Ubuntu_Clang_Tidy_Base:
extends: Ubuntu_Clang
before_script:
- CI/install_debian_deps.sh clang clang-tidy openmw-deps openmw-deps-dynamic
cache:
key: Ubuntu_Clang_Tidy.ubuntu_22.04.v1
variables:
CMAKE_BUILD_TYPE: Debug
CMAKE_CXX_FLAGS_DEBUG: -O0
CI_CLANG_TIDY: 1
CCACHE_BASEDIR: $CI_PROJECT_DIR
CCACHE_DIR: $CI_PROJECT_DIR/ccache
script:
- mkdir -pv "${CCACHE_DIR}"
- ccache -z -M "${CCACHE_SIZE}"
- CI/before_script.linux.sh
- cd build
- find . -name *.o -exec touch {} \;
- cmake --build . -- -j $(nproc) ${BUILD_TARGETS}
- ccache -s
artifacts:
paths:
- build/
expire_in: 12h
timeout: 3h
Ubuntu_Clang_Tidy_components:
extends: .Ubuntu_Clang_Tidy_Base
variables:
BUILD_TARGETS: components components_qt oics osg-ffmpeg-videoplayer osgQt
timeout: 3h
Ubuntu_Clang_Tidy_openmw:
extends: .Ubuntu_Clang_Tidy_Base
needs:
- Ubuntu_Clang_Tidy_components
variables:
BUILD_TARGETS: openmw
timeout: 3h
Ubuntu_Clang_Tidy_openmw-cs:
extends: .Ubuntu_Clang_Tidy_Base
needs:
- Ubuntu_Clang_Tidy_components
variables:
BUILD_TARGETS: openmw-cs openmw-cs-tests
timeout: 3h
Ubuntu_Clang_Tidy_other:
extends: .Ubuntu_Clang_Tidy_Base
needs:
- Ubuntu_Clang_Tidy_components
variables:
BUILD_TARGETS: bsatool esmtool openmw-launcher openmw-iniimporter openmw-essimporter openmw-wizard niftest openmw_test_suite openmw-navmeshtool openmw-bulletobjecttool
timeout: 3h
Debian_Clang_tests:
extends: Debian_Clang
.Ubuntu_Clang_tests:
extends: Ubuntu_Clang
cache:
key: Debian_Clang_tests.v2
key: Ubuntu_Clang_tests.ubuntu_22.04.v1
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
Ubuntu_Clang_tests_Debug:
extends: Ubuntu_Clang
cache:
key: Ubuntu_Clang_tests_Debug.ubuntu_22.04.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_integration_tests_base:
extends: .Ubuntu_Image
stage: test
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
cache:
paths:
- .cache/pip
- apt-cache/
before_script:
- CI/install_debian_deps.sh $OPENMW_DEPS
- pip3 install --user numpy matplotlib termtables click
script:
- CI/run_integration_tests.sh
after_script:
- if [[ -f /tmp/openmw-crash.log ]]; then cat /tmp/openmw-crash.log; fi
Ubuntu_Clang_integration_tests:
extends: .Ubuntu_integration_tests_base
needs:
- Ubuntu_Clang
cache:
key: Ubuntu_Clang_integration_tests.ubuntu_22.04.v2
variables:
OPENMW_DEPS: openmw-integration-tests
Ubuntu_GCC_integration_tests_asan:
extends: .Ubuntu_integration_tests_base
needs:
- Ubuntu_GCC_asan
cache:
key: Ubuntu_GCC_integration_tests_asan.ubuntu_22.04.v1
variables:
OPENMW_DEPS: openmw-integration-tests libasan6
ASAN_OPTIONS: halt_on_error=1:strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1:detect_leaks=0
.MacOS:
image: macos-11-xcode-12
tags:
- shared-macos-amd64
stage: build
only:
variables:
- $CI_PROJECT_ID == "7107382"
cache:
paths:
- ccache/
@ -138,78 +490,89 @@ Debian_Clang_tests:
- ccache -z -M "${CCACHE_SIZE}"
- CI/before_script.osx.sh
- 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
- 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"
macOS11_Xcode12:
extends: .MacOS
image: macos-11-xcode-12
allow_failure: true
cache:
key: macOS11_Xcode12.v1
variables:
CCACHE_SIZE: 3G
macOS10.15_Xcode11:
macOS13_Xcode14_arm64:
extends: .MacOS
image: macos-10.15-xcode-11
image: macos-12-xcode-14
tags:
- saas-macos-medium-m1
cache:
key: macOS10.15_Xcode11.v1
key: macOS12_Xcode14_arm64.v1
variables:
CCACHE_SIZE: 3G
variables: &engine-targets
targets: "openmw,openmw-essimporter,openmw-iniimporter,openmw-launcher,openmw-wizard"
package: "Engine"
variables: &cs-targets
targets: "openmw-cs,bsatool,esmtool,niftest"
package: "CS"
variables: &tests-targets
targets: "openmw_test_suite,openmw_detournavigator_navmeshtilescache_benchmark"
package: "Tests"
.Windows_Ninja_Base:
tags:
- windows
rules:
- if: $CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "merge_request_event"
before_script:
- Get-Volume
- 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 cmake.install --installargs 'ADD_CMAKE_TO_PATH=System' -y
- choco install ccache -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:
- Get-Volume
- $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 -b -t
- $env:CCACHE_BASEDIR = Get-Location
- $env:CCACHE_DIR = "$(Get-Location)\ccache"
- New-Item -Type Directory -Force -Path $env:CCACHE_DIR
- New-Item -Type File -Force -Path MSVC2019_64_Ninja\.cmake\api\v1\query\codemodel-v2
- sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -N -b -t -C $multiview -E
- Get-Volume
- 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_${package}_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' CI-ID.txt
7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt
Push-Location ..
..\CI\Store-Symbols.ps1
7z a -tzip "..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_sym_store.zip"))" '.\SymStore\*' $config\CI-ID.txt
Pop-Location
Get-ChildItem -Recurse *.pdb | Remove-Item
}
- 7z a -tzip ..\..\OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}.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:
- Get-Volume
- Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log
cache:
key: ninja-v2
key: ninja-v7
paths:
- ccache
- deps
- MSVC2019_64_Ninja/deps/Qt
artifacts:
@ -225,54 +588,32 @@ variables: &tests-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:
extends:
- .Windows_Ninja_Base
variables:
<<: *engine-targets
config: "Debug"
Windows_Ninja_Engine_RelWithDebInfo:
.Windows_Ninja_Release_MultiView:
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"
Windows_Ninja_Tests_RelWithDebInfo:
extends: .Windows_Ninja_Base
stage: build
variables:
<<: *tests-targets
config: "RelWithDebInfo"
# Gitlab can't successfully execute following binaries due to unknown reason
# executables: "openmw_test_suite.exe,openmw_detournavigator_navmeshtilescache_benchmark.exe"
@ -280,37 +621,67 @@ Windows_Ninja_Tests_RelWithDebInfo:
.Windows_MSBuild_Base:
tags:
- windows
rules:
- if: $CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "merge_request_event"
before_script:
- Get-Volume
- 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 cmake.install --installargs 'ADD_CMAKE_TO_PATH=System' -y
- choco install ccache -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:
- Get-Volume
- $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 -b -t
- $env:CCACHE_BASEDIR = Get-Location
- $env:CCACHE_DIR = "$(Get-Location)\ccache"
- New-Item -Type Directory -Force -Path $env:CCACHE_DIR
- New-Item -Type File -Force -Path MSVC2019_64\.cmake\api\v1\query\codemodel-v2
- sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -b -t -C $multiview -E
- cd MSVC2019_64
- cmake --build . --config $config --target ($targets.Split(','))
- Get-Volume
- cmake --build . --config $config
- ccache --show-stats
- cd $config
- echo "CI_COMMIT_REF_NAME ${CI_COMMIT_REF_NAME}`nCI_JOB_ID ${CI_JOB_ID}`nCI_COMMIT_SHA ${CI_COMMIT_SHA}" | Out-File -Encoding UTF8 CI-ID.txt
- Get-ChildItem -Recurse *.ilk | Remove-Item
- |
if (Get-ChildItem -Recurse *.pdb) {
7z a -tzip ..\..\OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' CI-ID.txt
7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt
Push-Location ..
..\CI\Store-Symbols.ps1
7z a -tzip "..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_sym_store.zip"))" '.\SymStore\*' $config\CI-ID.txt
Pop-Location
Get-ChildItem -Recurse *.pdb | Remove-Item
}
- 7z a -tzip ..\..\OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}.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:
- Get-Volume
- Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log
cache:
key: msbuild-v2
key: msbuild-v7
paths:
- ccache
- deps
- MSVC2019_64/deps/Qt
artifacts:
@ -326,78 +697,52 @@ Windows_Ninja_Tests_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:
extends:
- .Windows_MSBuild_Base
variables:
<<: *engine-targets
config: "Release"
Windows_MSBuild_Engine_Debug:
extends:
- .Windows_MSBuild_Base
variables:
<<: *engine-targets
config: "Debug"
Windows_MSBuild_Engine_RelWithDebInfo:
extends:
- .Windows_MSBuild_Base
variables:
<<: *engine-targets
config: "RelWithDebInfo"
Windows_MSBuild_CS_Release:
.Windows_MSBuild_Release:
extends:
- .Windows_MSBuild_Base
variables:
<<: *cs-targets
config: "Release"
Windows_MSBuild_CS_Debug:
.Windows_MSBuild_Debug:
extends:
- .Windows_MSBuild_Base
variables:
<<: *cs-targets
config: "Debug"
Windows_MSBuild_CS_RelWithDebInfo:
Windows_MSBuild_RelWithDebInfo:
extends:
- .Windows_MSBuild_Base
variables:
<<: *cs-targets
config: "RelWithDebInfo"
Windows_MSBuild_Tests_RelWithDebInfo:
extends: .Windows_MSBuild_Base
stage: build
variables:
<<: *tests-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" || $CI_PIPELINE_SOURCE == "merge_request_event" || $CI_PIPELINE_SOURCE == "schedule"
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" || $CI_PIPELINE_SOURCE == "merge_request_event"
variables:
CCACHE_SIZE: 3G
cache:
key: Debian_AndroidNDK_arm64-v8a.v3
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 -q -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 android
stage: build
script:
- df -h
- export CCACHE_BASEDIR="`pwd`"
- export CCACHE_DIR="`pwd`/ccache" && mkdir -pv "$CCACHE_DIR"
- ccache -z -M "${CCACHE_SIZE}"
@ -405,10 +750,55 @@ 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
.flatpak:
image: 'docker.io/bilelmoussaoui/flatpak-github-actions'
stage: build
script:
- flatpak install -y flathub org.kde.Platform/x86_64/5.15-21.08
- flatpak install -y flathub org.kde.Sdk/x86_64/5.15-21.08
- flatpak-builder --ccache --force-clean --repo=repo build CI/org.openmw.OpenMW.devel.yaml
- flatpak build-bundle ./repo openmw.flatpak org.openmw.OpenMW.devel
cache:
key: flatpak
paths:
- ".flatpak-builder"
artifacts:
untracked: false
paths:
- "openmw.flatpak"
# When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks.
# Flatpak Builds compile all dependencies aswell so need more time. Build results of libraries are cached
timeout: 4h

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

@ -1,100 +0,0 @@
language: cpp
branches:
only:
- master
- coverity_scan
- /openmw-.*$/
- /^[0-9]+\.[0-9]+\.[0-9]+.*$/
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: "1QK0yVyoOB+gf2I7XzvhXu9w/5lq4stBXIwJbVCTjz4Q4XVHCosURaW1MAgKzMrPnbFEwjyn5uQ8BwsvvfkuN1AZD0YXITgc7gyI+J1wQ/p/ljxRxglakU6WEgsTs2J5z9UmGac4YTXg+quK7YP3rv+zuGim2I2rhzImejyzp0Ym3kRCnNcy+SGBsiRaevRJMe00Ch8zGAbEhduQGeSoS6W0rcu02DNlQKiq5NktWsXR+TWWWVfIeIlQR/lbPsCd0pdxMaMv2QCY0rVbwrYxWJwr/Qe45dAdWp+8/C3PbXpeMSGxlLa33nJNX4Lf/djxbjm8KWk6edaXPajrjR/0iwcpwq0jg2Jt6XfEdnJt35F1gpXlc04sxStjG45uloOKCFYT0wdhIO1Lq+hDP54wypQl+JInd5qC001O7pwhVxO36EgKWqo8HD+BqGDBwsNj2engy9Qcp3wO6G0rLBPB3CrZsk9wrHVv5cSiQSLMhId3Xviu3ZI2qEDA+kgTvxrKrsnMj4bILVCyG5Ka2Mj22wIDW9e8oIab9oTdujax3DTN1GkD6QuOAGzwDsNwGASsgfoeZ+FUhgM75RlBWGMilgkmnF7EJ0oAXLEpjtABnEr2d4qHv+y08kOuTDBLB9ExzCIj024dYYYNLZrqPKx0ncHuCMG2QNj2aJAJEZtj1rQ="
cache: ccache
addons:
apt:
sources:
- sourceline: 'ppa:openmw/openmw'
- ubuntu-toolchain-r-test
packages: [
# Dev
cmake, clang-tools-7, gcc-8, g++-8, 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-3.4-dev, libmygui-dev,
# tes3mp stuff
libboost-dev, libqt5opengl5-dev, libluajit-5.1-dev
]
coverity_scan:
project:
name: "TES3MP/openmw-tes3mp"
description: "<Your project description here>"
branch_pattern: coverity_scan
notification_email: koncord@tes3mp.com
build_command_prepend: "cov-configure --comptype gcc --compiler gcc-8 --template; cmake . -DBUILD_UNITTESTS=FALSE -DBUILD_OPENCS=FALSE -DBUILD_BSATOOL=FALSE -DBUILD_ESMTOOL=FALSE -DBUILD_MWINIIMPORTER=FALSE -DBUILD_LAUNCHER=FALSE"
build_command: "make VERBOSE=1 -j3"
matrix:
include:
- os: linux
env:
- ANALYZE="scan-build-7 --use-cc clang-7 --use-c++ clang++-7 "
- MATRIX_CC="CC=clang-7 && CXX=clang++-7"
compiler: clang
sudo: required
dist: bionic
- os: linux
env:
- MATRIX_CC="CC=gcc-8 && CXX=g++-8"
sudo: required
dist: bionic
- os: linux
env:
- MATRIX_CC="CC=clang-7 && CXX=clang++-7"
sudo: required
dist: bionic
allow_failures:
- env:
- MATRIX_CC="CC=clang-7 && CXX=clang++-7"
- env:
- ANALYZE="scan-build-7 --use-cc clang-7 --use-c++ clang++-7 "
- MATRIX_CC="CC=clang-7 && CXX=clang++-7"
before_install:
- ./CI/before_install.${TRAVIS_OS_NAME}.sh
before_script:
- ccache -z
- ./CI/before_script.${TRAVIS_OS_NAME}.sh
script:
- cd ./build
- ${ANALYZE} make -j3; fi
- if [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi
- if [ "${TRAVIS_OS_NAME}" = "osx" ]; then ../CI/check_package.osx.sh; fi
- if [ "${TRAVIS_OS_NAME}" = "linux" ] && [ "${BUILD_TESTS_ONLY}" ]; then ./openmw_test_suite; fi
- if [ "${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: TES3MP/openmw-tes3mp
#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

@ -27,8 +27,12 @@ Programmers
Alexander Olofsson (Ananace)
Alex Rice
Alex S (docwest)
Alexey Yaryshev (skeevert)
Allofich
Andreas Stöckel
Andrei Kortunov (akortunov)
Andrew Appuhamy (andrew-app)
Andrzej Głuszak (agluszak)
AnyOldName3
Ardekantur
Armin Preiml
@ -42,6 +46,7 @@ Programmers
Austin Salgat (Salgat)
Ben Shealy (bentsherman)
Berulacks
Bo Svensson
Britt Mathis (galdor557)
Capostrophic
Carl Maxwell
@ -54,8 +59,9 @@ Programmers
Cory F. Cohen (cfcohen)
Cris Mihalache (Mirceam)
crussell187
DanielVukelich
Dan Vukelich (sanchezman)
darkf
Dave Corley (S3ctor)
David Cernat (davidcernat)
Declan Millar (declan-millar)
devnexen
@ -67,16 +73,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)
@ -88,15 +97,18 @@ Programmers
Haoda Wang (h313)
hristoast
Internecine
Ivan Beloborodov (myrix)
Jackerty
Jacob Essex (Yacoby)
Jacob Turnbull (Tankinfrank)
Jake Westrip (16bitint)
James Carty (MrTopCat)
James Deciutiis (JamesDeciutiis)
James Moore (moore.work)
James Stephens (james-h-stephens)
Jan-Peter Nilsson (peppe)
Jan Borsodi (am0s)
JanuarySnow
Jason Hooks (jhooks)
jeaye
jefetienne
@ -108,6 +120,7 @@ Programmers
John Blomberg (fstp)
Jordan Ayers
Jordan Milne
Josquin Frei
Josua Grawitter
Jules Blok (Armada651)
julianko
@ -118,6 +131,7 @@ Programmers
Kurnevsky Evgeny (kurnevsky)
Lars Söderberg (Lazaroth)
lazydev
Léo Peltier
Leon Krieg (lkrieg)
Leon Saunders (emoose)
logzero
@ -146,6 +160,7 @@ Programmers
Miroslav Puda (pakanek)
MiroslavR
Mitchell Schwitzer (schwitzerm)
Mitten.O
naclander
Narmo
Nat Meo (Utopium)
@ -154,8 +169,10 @@ Programmers
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)
@ -170,6 +187,7 @@ Programmers
PlutonicOverkill
Radu-Marius Popovici (rpopovici)
Rafael Moura (dhustkoder)
Randy Davin (Kindi)
rdimesio
rexelion
riothamus
@ -186,6 +204,7 @@ Programmers
Sergey Shambir (sergey-shambir)
sergoz
ShadowRadiance
Shihan42
Siimacore
Simon Meulenbeek (simonmb)
sir_herrbatka
@ -207,16 +226,20 @@ Programmers
tlmullis
tri4ng1e
Thoronador
Tobias Tribble (zackhasacat)
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
@ -237,6 +260,7 @@ Documentation
Joakim Berg (lysol90)
Ryan Tucker (Ravenwing)
sir_herrbatka
David Nagy (zuzaman)
Packagers
---------

@ -1,3 +1,313 @@
0.49.0
------
Bug #2623: Snowy Granius doesn't prioritize conjuration spells
Bug #3842: Body part skeletons override the main skeleton
Bug #4127: Weapon animation looks choppy
Bug #4204: Dead slaughterfish doesn't float to water surface after loading saved game
Bug #4382: Sound output device does not change when it should
Bug #4610: Casting a Bound Weapon spell cancels the casting animation by equipping the weapon prematurely
Bug #4754: Stack of ammunition cannot be equipped partially
Bug #4816: GetWeaponDrawn returns 1 before weapon is attached
Bug #5057: Weapon swing sound plays at same pitch whether it hits or misses
Bug #5129: Stuttering animation on Centurion Archer
Bug #5371: Keyframe animation tracks are used for any file that begins with an X
Bug #5714: Touch spells cast using ExplodeSpell don't always explode
Bug #5849: Paralysis breaks landing
Bug #5870: Disposing of actors who were selected in the console doesn't deselect them like vanilla
Bug #5883: Immobile creatures don't cause water ripples
Bug #5977: Fatigueless NPCs' corpse underwater changes animation on game load
Bug #6027: Collisionshape becomes spiderweb-like when the mesh is too complex
Bug #6313: Followers with high Fight can turn hostile
Bug #6427: Enemy health bar disappears before damaging effect ends
Bug #6550: Cloned body parts don't inherit texture effects
Bug #6645: Enemy block sounds align with animation instead of blocked hits
Bug #6657: Distant terrain tiles become black when using FWIW mod
Bug #6661: Saved games that have no preview screenshot cause issues or crashes
Bug #6716: mwscript comparison operator handling is too restrictive
Bug #6807: Ultimate Galleon is not working properly
Bug #6893: Lua: Inconsistent behavior with actors affected by Disable and SetDelete commands
Bug #6894: Added item combines with equipped stack instead of creating a new unequipped stack
Bug #6939: OpenMW-CS: ID columns are too short
Bug #6949: Sun Damage effect doesn't work in quasi exteriors
Bug #6964: Nerasa Dralor Won't Follow
Bug #6973: Fade in happens after the scene load and is shown
Bug #6974: Only harmful effects are reflected
Bug #6977: Sun damage implementation does not match research
Bug #6986: Sound magic effect does not make noise
Bug #6987: Set/Mod Blindness should not darken the screen
Bug #6992: Crossbow reloading doesn't look the same as in Morrowind
Bug #6993: Shooting your last round of ammunition causes the attack animation to cancel
Bug #7009: Falling actors teleport to the ground without receiving any damage on cell loading
Bug #7034: Misc items defined in one content file are not treated as keys if another content file uses them as such
Bug #7042: Weapon follow animations that immediately follow the hit animations cause multiple hits
Bug #7044: Changing a class' services does not affect autocalculated NPCs
Bug #7054: Quests aren't sorted by name
Bug #7064: NPCs don't report crime if the player is casting offensive spells on them while sneaking
Bug #7077: OpenMW fails to load certain particle effects in .osgt format
Bug #7084: Resurrecting an actor doesn't take into account base record changes
Bug #7088: Deleting last save game of last character doesn't clear character name/details
Bug #7092: BSA archives from higher priority directories don't take priority
Bug #7122: Teleportation to underwater should cancel active water walking effect
Bug #7131: MyGUI log spam when post processing HUD is open
Bug #7163: Myar Aranath: Wheat breaks the GUI
Bug #7172: Current music playlist continues playing indefinitely if next playlist is empty
Bug #7229: Error marker loading failure is not handled
Bug #7243: Supporting loading external files from VFS from esm files
Bug #7298: Water ripples from projectiles sometimes are not spawned
Bug #7307: Alchemy "Magic Effect" search string does not match on tool tip for effects related to attributes
Bug #7413: Generated wilderness cells don't spawn fish
Bug #7415: Unbreakable lock discrepancies
Bug #7428: AutoCalc flag is not used to calculate enchantment costs
Feature #3537: Shader-based water ripples
Feature #5492: Let rain and snow collide with statics
Feature #6447: Add LOD support to Object Paging
Feature #6491: Add support for Qt6
Feature #6726: Lua API for creating new objects
Feature #6922: Improve launcher appearance
Feature #6933: Support high-resolution cursor textures
Feature #6945: Support S3TC-compressed and BGR/BGRA NiPixelData
Feature #6979: Add support of loading and displaying LOD assets purely based on their filename extension
Feature #6983: PCVisionBonus script functions
Feature #6995: Localize the "show effect duration" option
Feature #7058: Implement TestModels (T3D) console command
Feature #7087: Block resolution change in the Windowed Fullscreen mode
Feature #7125: Remembering console commands between sessions
Feature #7129: Add support for non-adaptive VSync
Feature #7130: Ability to set MyGUI logging verbosity
Feature #7148: Optimize string literal lookup in mwscript
Feature #7194: Ori to show texture paths
Feature #7214: Searching in the in-game console
Task #7113: Move from std::atoi to std::from_char
Task #7117: Replace boost::scoped_array with std::vector
Task #7151: Do not use std::strerror to get errno error message
Task #7394: Drop support for --fs-strict
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 #4175: Objects "vibrate" when extremely far from (0,0)
Bug #4203: Resurrecting an actor doesn't close the loot GUI
Bug #4227: Spellcasting restrictions are checked before spellcasting animations are played
Bug #4310: Spell description is centered
Bug #4374: Player rotation reset when nearing area that hasn't been loaded yet
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 #4526: Crash when additional maps are applied over a model with out of bounds UV
Bug #4602: Robert's Bodies: crash inside createInstance()
Bug #4700: OpenMW-CS: 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 #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 #5976: Invisibility is broken when the attack starts instead of when it ends
Bug #5978: NPCs and Creatures talk to and headtrack a player character with a 75% chameleon effect or more
Bug #5989: Simple water isn't affected by texture filter settings
Bug #6037: Launcher: Morrowind content language cannot be set to English
Bug #6049: Main Theme on OpenMW should begin on the second video like Vanilla.
Bug #6051: NaN water height in ESM file is not handled gracefully
Bug #6054: Hotkey items can be equipped while in ready to attack stance
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 #6109: Crash when playing a custom made menu_background file
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 #6154: Levitating player character is floating rather than on the floor when teleported back from Magas Volar
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: OpenMW-CS: 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 #6545: Player character momentum is preserved when going to a different cell
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 #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 #6705: OpenMW CS: A typo in the Creature levelled list
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
Bug #6860: Sinnammu randomly strafes while running on water
Bug #6869: Hits queue stagger during swing animation
Bug #6890: SDL_PeepEvents errors are not handled
Bug #6895: Removing a negative number of items from a script, makes the script terminate with an error
Bug #6896: Sounds played using PlaySound3D are cut off as the emitter leaves the cell
Bug #6898: Accessing the Quick Inventory menu does not work while in menu mode
Bug #6901: Morrowind.exe soul gem usage discrepancy
Bug #6909: Using enchanted items has no animation
Bug #6910: Torches should not be extinguished when not being held
Bug #6913: Constant effect enchanted items don't break invisibility
Bug #6923: Dispose of corpse prevents respawning after load
Bug #6937: Divided by Nix Hounds quest is broken
Bug #7008: Race condition on initializing a vector of reserved node names
Bug #7121: Crash on TimeStamp construction with invalid hour value
Bug #7251: Force shaders setting still renders some drawables with FFP
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 #3180: Support uncompressed colour-mapped TGA files
Feature #3245: OpenMW-CS: Instance editing grid
Feature #3616: Allow Zoom levels on the World Map
Feature #3668: Support palettized DDS files
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: OpenMW-CS: 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 #6078: Do not clear depth buffer for first-person meshes
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: OpenMW-CS: Preserve "blocked" record flags when saving
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 #6631: Support FFMPEG 5
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 #6823: Animation layering for osgAnimation formats
Feature #6867: Add a way to localize hardcoded strings in GUI
Feature #6888: Add switch for armor degradation fix
Feature #6925: Allow to use a mouse wheel to rotate a head in the race selection menu
Feature #6941: Allow users to easily change font size and ttf resolution
Feature #7434: Exponential fog
Feature #7435: Sky blending
Task #5534: Remove support for OSG 3.4
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 #6435: Add support for MSVC 2022
Task #6564: Remove predefined data paths `data="?global?data"`, `data=./data`
0.47.0
------
@ -180,6 +490,7 @@
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
Feature #7161: OpenMW-CS: Make adding and filtering TopicInfos easier
Task #5480: Drop Qt4 support
Task #5520: Improve cell name autocompleter implementation
@ -1907,6 +2218,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

@ -1,53 +0,0 @@
*** PLEASE PUT YOUR ISSUE DESCRIPTION FOR DUMMIES HERE FOR REVIEW ***
- I'm just a placeholder description (#1337)
- I'm also just a placeholder description, but I'm a more recent one (#42)
***
0.47.0
------
The OpenMW team is proud to announce the release of version 0.47.0! Grab it from our Downloads Page for all operating systems. ***short summary: XXX ***
Check out the release video (***add link***) and the OpenMW-CS release video (***add link***) by the ***add flattering adjective*** Atahualpa, and see below for the full list of changes.
Known Issues:
- To use generic Linux binaries, Qt4 and libpng12 must be installed on your system
- On macOS, launching OpenMW from OpenMW-CS requires OpenMW.app and OpenMW-CS.app to be siblings
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)
- Targetting non-unique actors in scripts is now supported (#2311)
- Guards no longer ignore attacks of invisible players but rather initiate dialogue and flee if the player resists being arrested (#4774)
- Changing the dialogue window without closing it no longer clears the dialogue history in order to allow, e.g., emulation of three-way dialogue via ForceGreeting (#5358)
- Scripts which try to start a non-existent global script now skip that step and continue execution instead of breaking (#5364)
- Selecting already equipped spells or magic items via hotkey no longer triggers the equip sound to play (#5367)
- 'Scale' argument in levelled creature lists is now taken into account when spawning creatures from such lists (#5369)
- 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)

@ -0,0 +1,52 @@
if (-Not (Test-Path CMakeCache.txt))
{
Write-Error "This script must be run from the build directory."
}
if (-Not (Test-Path .cmake\api\v1\reply))
{
New-Item -Type File -Force .cmake\api\v1\query\codemodel-v2
cmake .
}
try
{
Push-Location .cmake\api\v1\reply
$index = Get-Content -Raw index-*.json | ConvertFrom-Json
$codemodel = Get-Content -Raw $index.reply."codemodel-v2".jsonFile | ConvertFrom-Json
$targets = @()
$codemodel.configurations | ForEach-Object {
$_.targets | ForEach-Object {
$target = Get-Content -Raw $_.jsonFile | ConvertFrom-Json
if ($target.type -eq "EXECUTABLE" -or $target.type -eq "SHARED_LIBRARY")
{
$targets += $target
}
}
}
$artifacts = @()
$targets | ForEach-Object {
$_.artifacts | ForEach-Object {
$artifacts += $_.path
}
}
}
finally
{
Pop-Location
}
if (-not (Test-Path symstore-venv))
{
python -m venv symstore-venv
}
if (-not (Test-Path symstore-venv\Scripts\symstore.exe))
{
symstore-venv\Scripts\pip install symstore==0.3.3
}
$artifacts = $artifacts | Where-Object { Test-Path $_ }
symstore-venv\Scripts\symstore --compress .\SymStore @artifacts

@ -23,11 +23,11 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
fi
command -v unixPathAsWindows >/dev/null 2>&1 || function unixPathAsWindows {
if command -v cygpath >/dev/null 2>&1; then
cygpath -w $1
else
echo "$1" | sed "s,^/\([^/]\)/,\\1:/," | sed "s,/,\\\\,g"
fi
if command -v cygpath >/dev/null 2>&1; then
cygpath -w $1
else
echo "$1" | sed "s,^/\([^/]\)/,\\1:/," | sed "s,/,\\\\,g"
fi
}

@ -1,4 +1,4 @@
#!/bin/sh -ex
curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/android/openmw-android-deps-20201129.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,22 +1,39 @@
#!/bin/sh -ex
# workaround python issue on travis
[ -z "${TRAVIS}" ] && HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies python@3.8 || true
[ -z "${TRAVIS}" ] && HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies python@3.9 || true
[ -z "${TRAVIS}" ] && HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies qt@6 || true
export HOMEBREW_NO_EMOJI=1
brew uninstall --ignore-dependencies python@3.8 || true
brew uninstall --ignore-dependencies python@3.9 || true
brew uninstall --ignore-dependencies qt@5 || true
brew uninstall --ignore-dependencies jpeg || true
brew tap --repair
brew update --quiet
# Some of these tools can come from places other than brew, so check before installing
brew reinstall xquartz fontconfig freetype harfbuzz brotli
# Fix: can't open file: @loader_path/libbrotlicommon.1.dylib (No such file or directory)
BREW_LIB_PATH="$(brew --prefix)/lib"
install_name_tool -change "@loader_path/libbrotlicommon.1.dylib" "${BREW_LIB_PATH}/libbrotlicommon.1.dylib" ${BREW_LIB_PATH}/libbrotlidec.1.dylib
install_name_tool -change "@loader_path/libbrotlicommon.1.dylib" "${BREW_LIB_PATH}/libbrotlicommon.1.dylib" ${BREW_LIB_PATH}/libbrotlienc.1.dylib
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@5
export PATH="/usr/local/opt/qt@5/bin:$PATH" # needed to use qmake in none default path as qt now points to qt6
command -v qmake >/dev/null 2>&1 || brew install qt@6
# Install deps
brew install icu4c yaml-cpp sqlite
ccache --version
cmake --version
qmake --version
curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20210617.zip -o ~/openmw-deps.zip
unzip -o ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null
if [[ "${MACOS_AMD64}" ]]; then
curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20221113.zip -o ~/openmw-deps.zip
else
curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20230223_arm64.zip -o ~/openmw-deps.zip
fi
unzip -o ~/openmw-deps.zip -d /tmp > /dev/null
# additional libraries
[ -z "${TRAVIS}" ] && HOMEBREW_NO_AUTO_UPDATE=1 brew install fontconfig

@ -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
# 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,7 +39,11 @@ cmake \
-DBUILD_ESSIMPORTER=0 \
-DBUILD_OPENCS=0 \
-DBUILD_WIZARD=0 \
-DBUILD_NAVMESHTOOL=OFF \
-DBUILD_BULLETOBJECTTOOL=OFF \
-DOPENMW_USE_SYSTEM_MYGUI=OFF \
-DOPENMW_USE_SYSTEM_OSG=OFF \
-DOPENMW_USE_SYSTEM_BULLET=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" \
..

@ -4,35 +4,81 @@ 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"
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
<<<<<<< HEAD
-DCMAKE_INSTALL_PREFIX=install
-DRakNet_LIBRARY_RELEASE=~/CrabNet/lib/libRakNetLibStatic.a
-DRakNet_LIBRARY_DEBUG=~/CrabNet/lib/libRakNetLibStatic.a
=======
-DOPENMW_USE_SYSTEM_RECASTNAVIGATION=ON
-DOPENMW_CXX_FLAGS="-Werror -Werror=implicit-fallthrough" # flags specific to OpenMW project
>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7
)
if [[ "${CMAKE_EXE_LINKER_FLAGS}" ]]; then
CMAKE_CONF_OPTS+=(
-DCMAKE_EXE_LINKER_FLAGS="${CMAKE_EXE_LINKER_FLAGS}"
)
fi
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=*"
-DBUILD_UNITTESTS=ON
-DBUILD_OPENCS_TESTS=ON
-DBUILD_BENCHMARKS=ON
)
fi
if [[ "${CMAKE_BUILD_TYPE}" ]]; then
CMAKE_CONF_OPTS+=(
-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
)
else
CMAKE_CONF_OPTS+=(
-DCMAKE_BUILD_TYPE=RelWithDebInfo
)
fi
if [[ "${CMAKE_CXX_FLAGS_DEBUG}" ]]; then
CMAKE_CONF_OPTS+=(
-DCMAKE_CXX_FLAGS_DEBUG="${CMAKE_CXX_FLAGS_DEBUG}"
)
fi
if [[ "${BUILD_WITH_CODE_COVERAGE}" ]]; then
CMAKE_CONF_OPTS+=(
-DBUILD_WITH_CODE_COVERAGE="${BUILD_WITH_CODE_COVERAGE}"
)
fi
@ -47,6 +93,16 @@ fi
export RAKNET_ROOT=~/CrabNet
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 \
"${CMAKE_CONF_OPTS[@]}" \
-DBUILD_OPENMW=OFF \
@ -59,10 +115,27 @@ if [[ "${BUILD_TESTS_ONLY}" ]]; then
-DBUILD_ESSIMPORTER=OFF \
-DBUILD_OPENCS=OFF \
-DBUILD_WIZARD=OFF \
-DBUILD_NAVMESHTOOL=OFF \
-DBUILD_BULLETOBJECTTOOL=OFF \
-DBUILD_NIFTEST=OFF \
-DBUILD_UNITTESTS=${BUILD_UNITTESTS} \
-DBUILD_OPENCS_TESTS=${BUILD_UNITTESTS} \
-DBUILD_BENCHMARKS=${BUILD_BENCHMARKS} \
-DGTEST_ROOT="${GOOGLETEST_DIR}" \
-DGMOCK_ROOT="${GOOGLETEST_DIR}" \
..
elif [[ "${BUILD_OPENMW_ONLY}" ]]; then
${ANALYZE} cmake \
"${CMAKE_CONF_OPTS[@]}" \
-DBUILD_OPENMW=ON \
-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_BULLETOBJECTTOOL=OFF \
-DBUILD_NIFTEST=OFF \
..
else
${ANALYZE} cmake \

@ -62,6 +62,7 @@ VERBOSE=""
STRIP=""
SKIP_DOWNLOAD=""
SKIP_EXTRACT=""
USE_CCACHE=""
KEEP=""
UNITY_BUILD=""
VS_VERSION=""
@ -74,6 +75,9 @@ TEST_FRAMEWORK=""
GOOGLE_INSTALL_ROOT=""
INSTALL_PREFIX="."
BUILD_BENCHMARKS=""
OSG_MULTIVIEW_BUILD=""
USE_WERROR=""
USE_CLANG_TIDY=""
ACTIVATE_MSVC=""
SINGLE_CONFIG=""
@ -100,6 +104,9 @@ while [ $# -gt 0 ]; do
e )
SKIP_EXTRACT=true ;;
C )
USE_CCACHE=true ;;
k )
KEEP=true ;;
@ -112,7 +119,7 @@ while [ $# -gt 0 ]; do
n )
NMAKE=true ;;
N )
NINJA=true ;;
@ -137,6 +144,15 @@ while [ $# -gt 0 ]; do
b )
BUILD_BENCHMARKS=true ;;
M )
OSG_MULTIVIEW_BUILD=true ;;
E )
USE_WERROR=true ;;
T )
USE_CLANG_TIDY=true ;;
h )
cat <<EOF
Usage: $0 [-cdehkpuvVi]
@ -145,6 +161,8 @@ 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.
-e
@ -159,7 +177,7 @@ Options:
Build unit tests / Google test
-u
Configure for unity builds.
-v <2017/2019>
-v <2019/2022>
Choose the Visual Studio version to use.
-n
Produce NMake makefiles instead of a Visual Studio solution. Cannot be used with -N.
@ -173,6 +191,12 @@ Options:
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
;;
@ -258,10 +282,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
@ -338,34 +362,49 @@ if [ -z $PLATFORM ]; then
fi
if [ -z $VS_VERSION ]; then
VS_VERSION="2017"
VS_VERSION="2019"
fi
case $VS_VERSION in
17|17.0|2022 )
GENERATOR="Visual Studio 17 2022"
TOOLSET="vc143"
MSVC_REAL_VER="17"
MSVC_VER="14.3"
MSVC_DISPLAY_YEAR="2022"
OSG_MSVC_YEAR="2019"
MYGUI_MSVC_YEAR="2019"
LUA_MSVC_YEAR="2019"
QT_MSVC_YEAR="2019"
BULLET_MSVC_YEAR="2019"
BOOST_VER="1.80.0"
BOOST_VER_URL="1_80_0"
BOOST_VER_SDK="108000"
;;
16|16.0|2019 )
GENERATOR="Visual Studio 16 2019"
TOOLSET="vc142"
MSVC_REAL_VER="16"
MSVC_VER="14.2"
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"
OSG_MSVC_YEAR="2019"
MYGUI_MSVC_YEAR="2019"
LUA_MSVC_YEAR="2019"
QT_MSVC_YEAR="2019"
BULLET_MSVC_YEAR="2019"
BOOST_VER="1.80.0"
BOOST_VER_URL="1_80_0"
BOOST_VER_SDK="108000"
;;
15|15.0|2017 )
GENERATOR="Visual Studio 15 2017"
TOOLSET="vc141"
MSVC_REAL_VER="15"
MSVC_VER="14.1"
MSVC_YEAR="2015"
MSVC_REAL_YEAR="2017"
MSVC_DISPLAY_YEAR="2017"
BOOST_VER="1.67.0"
BOOST_VER_URL="1_67_0"
BOOST_VER_SDK="106700"
echo "Visual Studio 2017 is no longer supported"
wrappedExit 1
;;
14|14.0|2015 )
@ -398,10 +437,6 @@ case $PLATFORM in
;;
esac
if [ $BITS -eq 64 ] && [ $MSVC_REAL_VER -lt 16 ]; then
GENERATOR="${GENERATOR} Win64"
fi
if [ -n "$NMAKE" ]; then
GENERATOR="NMake Makefiles"
SINGLE_CONFIG=true
@ -485,7 +520,7 @@ for i in ${!CONFIGURATIONS[@]}; do
esac
done
if [ $MSVC_REAL_VER -ge 16 ] && [ -z "$NMAKE" ] && [ -z "$NINJA" ]; then
if [ -z "$NMAKE" ] && [ -z "$NINJA" ]; then
if [ $BITS -eq 64 ]; then
add_cmake_opts "-G\"$GENERATOR\" -A x64"
else
@ -503,6 +538,39 @@ if ! [ -z $UNITY_BUILD ]; then
add_cmake_opts "-DOPENMW_UNITY_BUILD=True"
fi
if ! [ -z $USE_CCACHE ]; then
add_cmake_opts "-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache"
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
BULLET_VER="2.89"
FFMPEG_VER="4.2.2"
ICU_VER="70_1"
LUAJIT_VER="v2.1.0-beta3-452-g7a0cf5fd"
LZ4_VER="1.9.2"
OPENAL_VER="1.23.0"
QT_VER="5.15.2"
OSG_ARCHIVE_NAME="OSGoS 3.6.5"
OSG_ARCHIVE="OSGoS-3.6.5-123-g68c5c573d-msvc${OSG_MSVC_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-d2ee5aa8-msvc${OSG_MSVC_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}"
@ -527,63 +595,67 @@ if [ -z $SKIP_DOWNLOAD ]; then
fi
# Bullet
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"
download "Bullet ${BULLET_VER}" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/Bullet-${BULLET_VER}-msvc${BULLET_MSVC_YEAR}-win${BITS}-double-mt.7z" \
"Bullet-${BULLET_VER}-msvc${BULLET_MSVC_YEAR}-win${BITS}-double-mt.7z"
# FFmpeg
download "FFmpeg 4.2.2" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/ffmpeg-4.2.2-win${BITS}.zip" \
"ffmpeg-4.2.2-win${BITS}.zip" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/ffmpeg-4.2.2-dev-win${BITS}.zip" \
"ffmpeg-4.2.2-dev-win${BITS}.zip"
download "FFmpeg ${FFMPEG_VER}" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/ffmpeg-${FFMPEG_VER}-win${BITS}.zip" \
"ffmpeg-${FFMPEG_VER}-win${BITS}.zip" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/ffmpeg-${FFMPEG_VER}-dev-win${BITS}.zip" \
"ffmpeg-${FFMPEG_VER}-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${MYGUI_MSVC_YEAR}-win${BITS}.7z" \
"MyGUI-3.4.1-msvc${MYGUI_MSVC_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${MYGUI_MSVC_YEAR}-win${BITS}-sym.7z" \
"MyGUI-3.4.1-msvc${MYGUI_MSVC_YEAR}-win${BITS}-sym.7z"
fi
# OpenAL
download "OpenAL-Soft 1.20.1" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/OpenAL-Soft-1.20.1.zip" \
"OpenAL-Soft-1.20.1.zip"
download "OpenAL-Soft ${OPENAL_VER}" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/OpenAL-Soft-${OPENAL_VER}.zip" \
"OpenAL-Soft-${OPENAL_VER}.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
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.24.0" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/SDL2-devel-2.24.0-VC.zip" \
"SDL2-devel-2.24.0-VC.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"
# Google test and mock
if [ ! -z $TEST_FRAMEWORK ]; then
echo "Google test 1.10.0..."
if [ -d googletest ]; then
printf " Google test exists, skipping."
else
git clone -b release-1.10.0 https://github.com/google/googletest.git
fi
fi
download "LZ4 ${LZ4_VER}" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/lz4_win${BITS}_v${LZ4_VER//./_}.7z" \
"lz4_win${BITS}_v${LZ4_VER//./_}.7z"
# LuaJIT
download "LuaJIT ${LUAJIT_VER}" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/LuaJIT-${LUAJIT_VER}-msvc${LUA_MSVC_YEAR}-win${BITS}.7z" \
"LuaJIT-${LUAJIT_VER}-msvc${LUA_MSVC_YEAR}-win${BITS}.7z"
# 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"
download "zlib 1.2.11"\
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/zlib-1.2.11-msvc2017-win64.7z" \
"zlib-1.2.11-msvc2017-win64.7z"
fi
cd .. #/..
@ -620,7 +692,6 @@ echo "---------------------------------------------------"
echo
# Boost
if [ -z $APPVEYOR ]; then
printf "Boost ${BOOST_VER}... "
else
@ -668,35 +739,33 @@ fi
}
cd $DEPS
echo
# Bullet
printf "Bullet 2.89... "
printf "Bullet ${BULLET_VER}... "
{
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}-double.7z" $STRIP
mv "Bullet-2.89-msvc${MSVC_YEAR}-win${BITS}-double" Bullet
eval 7z x -y "${DEPS}/Bullet-${BULLET_VER}-msvc${BULLET_MSVC_YEAR}-win${BITS}-double-mt.7z" $STRIP
mv "Bullet-${BULLET_VER}-msvc${BULLET_MSVC_YEAR}-win${BITS}-double-mt" Bullet
fi
add_cmake_opts -DBULLET_ROOT="$(real_pwd)/Bullet"
echo Done.
}
cd $DEPS
echo
# FFmpeg
printf "FFmpeg 4.2.2... "
printf "FFmpeg ${FFMPEG_VER}... "
{
cd $DEPS_INSTALL
if [ -d FFmpeg ] && grep "4.2.2" FFmpeg/README.txt > /dev/null; then
if [ -d FFmpeg ] && grep "${FFMPEG_VER}" FFmpeg/README.txt > /dev/null; then
printf "Exists. "
elif [ -z $SKIP_EXTRACT ]; then
rm -rf FFmpeg
eval 7z x -y "${DEPS}/ffmpeg-4.2.2-win${BITS}.zip" $STRIP
eval 7z x -y "${DEPS}/ffmpeg-4.2.2-dev-win${BITS}.zip" $STRIP
mv "ffmpeg-4.2.2-win${BITS}-shared" FFmpeg
cp -r "ffmpeg-4.2.2-win${BITS}-dev/"* FFmpeg/
rm -rf "ffmpeg-4.2.2-win${BITS}-dev"
eval 7z x -y "${DEPS}/ffmpeg-${FFMPEG_VER}-win${BITS}.zip" $STRIP
eval 7z x -y "${DEPS}/ffmpeg-${FFMPEG_VER}-dev-win${BITS}.zip" $STRIP
mv "ffmpeg-${FFMPEG_VER}-win${BITS}-shared" FFmpeg
cp -r "ffmpeg-${FFMPEG_VER}-win${BITS}-dev/"* FFmpeg/
rm -rf "ffmpeg-${FFMPEG_VER}-win${BITS}-dev"
fi
export FFMPEG_HOME="$(real_pwd)/FFmpeg"
for config in ${CONFIGURATIONS[@]}; do
@ -709,21 +778,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${MYGUI_MSVC_YEAR}-win${BITS}.7z" $STRIP
[ -n "$PDBS" ] && eval 7z x -y "${DEPS}/MyGUI-3.4.1-msvc${MYGUI_MSVC_YEAR}-win${BITS}-sym.7z" $STRIP
mv "MyGUI-3.4.1-msvc${MYGUI_MSVC_YEAR}-win${BITS}" MyGUI
fi
export MYGUI_HOME="$(real_pwd)/MyGUI"
for CONFIGURATION in ${CONFIGURATIONS[@]}; do
@ -740,27 +808,25 @@ printf "MyGUI 3.4.0... "
}
cd $DEPS
echo
# OpenAL
printf "OpenAL-Soft 1.20.1... "
printf "OpenAL-Soft ${OPENAL_VER}... "
{
if [ -d openal-soft-1.20.1-bin ]; then
if [ -d openal-soft-${OPENAL_VER}-bin ]; then
printf "Exists. "
elif [ -z $SKIP_EXTRACT ]; then
rm -rf openal-soft-1.20.1-bin
eval 7z x -y OpenAL-Soft-1.20.1.zip $STRIP
rm -rf openal-soft-${OPENAL_VER}-bin
eval 7z x -y OpenAL-Soft-${OPENAL_VER}.zip $STRIP
fi
OPENAL_SDK="$(real_pwd)/openal-soft-1.20.1-bin"
OPENAL_SDK="$(real_pwd)/openal-soft-${OPENAL_VER}-bin"
add_cmake_opts -DOPENAL_INCLUDE_DIR="${OPENAL_SDK}/include/AL" \
-DOPENAL_LIBRARY="${OPENAL_SDK}/libs/Win${BITS}/OpenAL32.lib"
for config in ${CONFIGURATIONS[@]}; do
add_runtime_dlls $config "$(pwd)/openal-soft-1.20.1-bin/bin/WIN${BITS}/soft_oal.dll:OpenAL32.dll"
add_runtime_dlls $config "$(pwd)/openal-soft-${OPENAL_VER}-bin/bin/WIN${BITS}/soft_oal.dll:OpenAL32.dll"
done
echo Done.
}
cd $DEPS
echo
# OSG
printf "OSG 3.6.5... "
printf "${OSG_ARCHIVE_NAME}... "
{
cd $DEPS_INSTALL
if [ -d OSG ] && \
@ -768,157 +834,147 @@ printf "OSG 3.6.5... "
grep "OPENSCENEGRAPH_MINOR_VERSION 6" OSG/include/osg/Version > /dev/null && \
grep "OPENSCENEGRAPH_PATCH_VERSION 5" OSG/include/osg/Version > /dev/null
then
printf "Exists. "
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"
for CONFIGURATION in ${CONFIGURATIONS[@]}; do
if [ $CONFIGURATION == "Debug" ]; then
SUFFIX="d"
SUFFIX_UPCASE="D"
else
SUFFIX=""
SUFFIX_UPCASE=""
fi
if ! [ -z $OSG_MULTIVIEW_BUILD ]; then
add_runtime_dlls $CONFIGURATION "$(pwd)/OSG/bin/"{ot21-OpenThreads,libpng16}${SUFFIX}.dll \
"$(pwd)/OSG/bin/osg162-osg"{,Animation,DB,FX,GA,Particle,Text,Util,Viewer,Shadow,Sim}${SUFFIX}.dll
else
add_runtime_dlls $CONFIGURATION "$(pwd)/OSG/bin/"{OpenThreads,icuuc58,libpng16}${SUFFIX}.dll \
"$(pwd)/OSG/bin/libxml2"${SUFFIX_UPCASE}.dll \
"$(pwd)/OSG/bin/osg"{,Animation,DB,FX,GA,Particle,Text,Util,Viewer,Shadow,Sim}${SUFFIX}.dll
add_runtime_dlls $CONFIGURATION "$(pwd)/OSG/bin/icudt58.dll"
if [ $CONFIGURATION == "Debug" ]; then
add_runtime_dlls $CONFIGURATION "$(pwd)/OSG/bin/"{boost_system-vc141-mt-gd-1_63,collada-dom2.4-dp-vc141-mt-d}.dll
else
add_runtime_dlls $CONFIGURATION "$(pwd)/OSG/bin/"{boost_system-vc141-mt-1_63,collada-dom2.4-dp-vc141-mt}.dll
fi
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
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_"{bmp,dae,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
echo Done.
}
cd $DEPS
echo
# Qt
if [ -z $APPVEYOR ]; then
printf "Qt 5.15.0... "
else
printf "Qt 5.13 AppVeyor... "
fi
printf "Qt ${QT_VER}... "
{
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}"
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_VER}/msvc${QT_MSVC_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_VER}" ]; then
printf "Exists. "
elif [ -z $SKIP_EXTRACT ]; then
if [ $MISSINGPYTHON -ne 0 ]; then
echo "Can't be automatically installed without Python."
wrappedExit 1
fi
# 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
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_VER} windows desktop "win${BITS}_msvc${QT_MSVC_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"
add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qwindowsvistastyle${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"
add_qt_style_dlls $CONFIGURATION "${DIR}/plugins/styles/qwindowsvistastyle${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
if [ "${QT_VER:0:1}" -eq "6" ]; then
add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt${QT_VER:0:1}"{Core,Gui,Network,OpenGL,OpenGLWidgets,Widgets}${DLLSUFFIX}.dll
else
add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt${QT_VER:0:1}"{Core,Gui,Network,OpenGL,Widgets}${DLLSUFFIX}.dll
fi
add_qt_platform_dlls $CONFIGURATION "$(pwd)/plugins/platforms/qwindows${DLLSUFFIX}.dll"
add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll"
done
echo Done.
}
cd $DEPS
echo
# SDL2
printf "SDL 2.0.12... "
printf "SDL 2.24.0... "
{
if [ -d SDL2-2.0.12 ]; then
if [ -d SDL2-2.24.0 ]; 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.24.0
eval 7z x -y SDL2-devel-2.24.0-VC.zip $STRIP
fi
export SDL2DIR="$(real_pwd)/SDL2-2.0.12"
export SDL2DIR="$(real_pwd)/SDL2-2.24.0"
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.24.0/lib/x${ARCHSUFFIX}/SDL2.dll"
done
echo Done.
}
cd $DEPS
echo
# LZ4
printf "LZ4 1.9.2... "
printf "LZ4 ${LZ4_VER}... "
{
if [ -d LZ4_1.9.2 ]; then
if [ -d LZ4_${LZ4_VER} ]; then
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$(real_pwd)/LZ4_1.9.2 $STRIP
rm -rf LZ4_${LZ4_VER}
eval 7z x -y lz4_win${BITS}_v${LZ4_VER//./_}.7z -o$(real_pwd)/LZ4_${LZ4_VER} $STRIP
fi
export LZ4DIR="$(real_pwd)/LZ4_1.9.2"
export LZ4DIR="$(real_pwd)/LZ4_${LZ4_VER}"
add_cmake_opts -DLZ4_INCLUDE_DIR="${LZ4DIR}/include" \
-DLZ4_LIBRARY="${LZ4DIR}/lib/liblz4.lib"
for CONFIGURATION in ${CONFIGURATIONS[@]}; do
@ -928,65 +984,74 @@ printf "LZ4 1.9.2... "
SUFFIX=""
LZ4_CONFIGURATION="Release"
fi
add_runtime_dlls $CONFIGURATION "$(pwd)/LZ4_1.9.2/bin/${LZ4_CONFIGURATION}/liblz4.dll"
add_runtime_dlls $CONFIGURATION "$(pwd)/LZ4_${LZ4_VER}/bin/${LZ4_CONFIGURATION}/liblz4.dll"
done
echo Done.
}
cd $DEPS
echo
# Google Test and Google Mock
if [ ! -z $TEST_FRAMEWORK ]; then
printf "Google test 1.10.0 ..."
printf "LuaJIT ${LUAJIT_VER}... "
{
if [ -d LuaJIT ]; then
printf "Exists. "
elif [ -z $SKIP_EXTRACT ]; then
rm -rf LuaJIT
eval 7z x -y LuaJIT-${LUAJIT_VER}-msvc${LUA_MSVC_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 googletest
mkdir -p build${MSVC_REAL_YEAR}
cd $DEPS
echo
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
cd build${MSVC_REAL_YEAR}
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.
}
GOOGLE_INSTALL_ROOT="${DEPS_INSTALL}/GoogleTest"
for CONFIGURATION in ${CONFIGURATIONS[@]}; do
# FindGMock.cmake mentions Release explicitly, but not RelWithDebInfo. Only one optimised library config can be used, so go for the safer one.
GTEST_CONFIG=$([ $CONFIGURATION == "RelWithDebInfo" ] && echo "Release" || echo "$CONFIGURATION" )
if [ $GTEST_CONFIG == "Debug" ]; then
DEBUG_SUFFIX="d"
cd $DEPS
echo
printf "zlib 1.2.11... "
{
if [ -d zlib-1.2.11-msvc2017-win64 ]; then
printf "Exists. "
elif [ -z $SKIP_EXTRACT ]; then
rm -rf zlib-1.2.11-msvc2017-win64
eval 7z x -y zlib-1.2.11-msvc2017-win64.7z $STRIP
fi
add_cmake_opts -DZLIB_ROOT="$(real_pwd)/zlib-1.2.11-msvc2017-win64"
for config in ${CONFIGURATIONS[@]}; do
if [ $CONFIGURATION == "Debug" ]; then
add_runtime_dlls $config "$(pwd)/zlib-1.2.11-msvc2017-win64/bin/zlibd.dll"
else
DEBUG_SUFFIX=""
add_runtime_dlls $config "$(pwd)/zlib-1.2.11-msvc2017-win64/bin/zlib.dll"
fi
if [ ! -f "$GOOGLE_INSTALL_ROOT/lib/gtest${DEBUG_SUFFIX}.lib" ]; then
# Always use MSBuild solution files as they don't need the environment activating
cmake .. -DCMAKE_USE_WIN32_THREADS_INIT=1 -G "Visual Studio $MSVC_REAL_VER $MSVC_REAL_YEAR$([ $BITS -eq 64 ] && [ $MSVC_REAL_VER -lt 16 ] && echo " Win64")" $([ $MSVC_REAL_VER -ge 16 ] && echo "-A $([ $BITS -eq 64 ] && echo "x64" || echo "Win32")") -DBUILD_SHARED_LIBS=1
cmake --build . --config "${GTEST_CONFIG}"
cmake --install . --config "${GTEST_CONFIG}" --prefix "${GOOGLE_INSTALL_ROOT}"
fi
add_runtime_dlls $CONFIGURATION "${GOOGLE_INSTALL_ROOT}\bin\gtest_main${DEBUG_SUFFIX}.dll"
add_runtime_dlls $CONFIGURATION "${GOOGLE_INSTALL_ROOT}\bin\gtest${DEBUG_SUFFIX}.dll"
add_runtime_dlls $CONFIGURATION "${GOOGLE_INSTALL_ROOT}\bin\gmock_main${DEBUG_SUFFIX}.dll"
add_runtime_dlls $CONFIGURATION "${GOOGLE_INSTALL_ROOT}\bin\gmock${DEBUG_SUFFIX}.dll"
done
add_cmake_opts -DBUILD_UNITTESTS=yes
# FindGTest and FindGMock do not work perfectly on Windows
# but we can help them by telling them everything we know about installation
add_cmake_opts -DGMOCK_ROOT="$GOOGLE_INSTALL_ROOT"
add_cmake_opts -DGTEST_ROOT="$GOOGLE_INSTALL_ROOT"
add_cmake_opts -DGTEST_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gtest.lib"
add_cmake_opts -DGTEST_MAIN_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gtest_main.lib"
add_cmake_opts -DGMOCK_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gmock.lib"
add_cmake_opts -DGMOCK_MAIN_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gmock_main.lib"
add_cmake_opts -DGTEST_LIBRARY_DEBUG="$GOOGLE_INSTALL_ROOT/lib/gtestd.lib"
add_cmake_opts -DGTEST_MAIN_LIBRARY_DEBUG="$GOOGLE_INSTALL_ROOT/lib/gtest_maind.lib"
add_cmake_opts -DGMOCK_LIBRARY_DEBUG="$GOOGLE_INSTALL_ROOT/lib/gmockd.lib"
add_cmake_opts -DGMOCK_MAIN_LIBRARY_DEBUG="$GOOGLE_INSTALL_ROOT/lib/gmock_maind.lib"
add_cmake_opts -DGTEST_LINKED_AS_SHARED_LIBRARY=True
add_cmake_opts -DGTEST_LIBRARY_TYPE=SHARED
add_cmake_opts -DGTEST_MAIN_LIBRARY_TYPE=SHARED
echo Done.
fi
}
echo
cd $DEPS_INSTALL/..
@ -994,6 +1059,8 @@ 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 )
@ -1077,6 +1144,11 @@ if [ "${BUILD_BENCHMARKS}" ]; then
add_cmake_opts -DBUILD_BENCHMARKS=ON
fi
if [ -n "${TEST_FRAMEWORK}" ]; then
add_cmake_opts -DBUILD_UNITTESTS=ON
add_cmake_opts -DBUILD_OPENCS_TESTS=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; }

@ -3,8 +3,13 @@
export CXX=clang++
export CC=clang
DEPENDENCIES_ROOT="/private/tmp/openmw-deps/openmw-deps"
QT_PATH=$(brew --prefix qt@5)
# Silence a git warning
git config --global advice.detachedHead false
DEPENDENCIES_ROOT="/tmp/openmw-deps"
QT_PATH=$(brew --prefix qt@6)
ICU_PATH=$(brew --prefix icu4c)
CCACHE_EXECUTABLE=$(brew --prefix ccache)/bin/ccache
mkdir build
cd build
@ -16,14 +21,18 @@ 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.14" \
-D CMAKE_OSX_DEPLOYMENT_TARGET="10.15" \
-D CMAKE_BUILD_TYPE=RELEASE \
-D OPENMW_OSX_DEPLOYMENT=TRUE \
-D OPENMW_USE_SYSTEM_RECASTNAVIGATION=TRUE \
-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 BUILD_NAVMESHTOOL=TRUE \
-D BUILD_BULLETOBJECTTOOL=TRUE \
-D ICU_ROOT="$ICU_PATH" \
-G"Unix Makefiles" \
..

@ -1,17 +0,0 @@
#!/bin/sh -ex
git clone -b release-1.10.0 https://github.com/google/googletest.git
cd googletest
mkdir build
cd build
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_BUILD_TYPE="${CONFIGURATION}" \
-D CMAKE_INSTALL_PREFIX="${GOOGLETEST_DIR}" \
-G "${GENERATOR}" \
..
cmake --build . --config "${CONFIGURATION}" -- -j $(nproc)
cmake --install . --config "${CONFIGURATION}"

@ -0,0 +1,8 @@
#!/bin/bash -ex
set -o pipefail
CLANG_FORMAT="${CLANG_FORMAT:-clang-format}"
git ls-files -- ':(exclude)extern/' '*.cpp' '*.hpp' '*.h' |
xargs -I '{}' -P $(nproc) bash -ec "\"${CLANG_FORMAT:?}\" --dry-run -Werror \"\${0:?}\" &> /dev/null || \"${CLANG_FORMAT:?}\" \"\${0:?}\" | git diff --color=always --no-index \"\${0:?}\" -" '{}' ||
( echo "clang-format differences detected"; exit -1 )

@ -0,0 +1,6 @@
#!/bin/bash -ex
git ls-files -- ':(exclude)extern/' 'CMakeLists.txt' '*.cmake' |
xargs grep -P '^\s*\t' &&
( echo 'CMake files contain leading tab character. Use only spaces for indentation'; exit -1 )
exit 0

@ -0,0 +1,7 @@
#!/bin/bash -ex
git ls-files -- ':(exclude)extern/' '*.cpp' '*.hpp' '*.h' |
grep -vP '/[a-z0-9]+\.(cpp|hpp|h)$' |
grep -vFf CI/file_name_exceptions.txt &&
( echo 'File names do not follow the naming convention, see https://wiki.openmw.org/index.php?title=Naming_Conventions#Files'; exit -1 )
exit 0

@ -0,0 +1,48 @@
apps/openmw/android_main.cpp
apps/openmw/mwsound/efx-presets.h
apps/openmw/mwsound/ffmpeg_decoder.cpp
apps/openmw/mwsound/ffmpeg_decoder.hpp
apps/openmw/mwsound/openal_output.cpp
apps/openmw/mwsound/openal_output.hpp
apps/openmw/mwsound/sound_buffer.cpp
apps/openmw/mwsound/sound_buffer.hpp
apps/openmw/mwsound/sound_decoder.hpp
apps/openmw/mwsound/sound_output.hpp
apps/openmw_test_suite/esm/test_fixed_string.cpp
apps/openmw_test_suite/files/conversion_tests.cpp
apps/openmw_test_suite/lua/test_async.cpp
apps/openmw_test_suite/lua/test_configuration.cpp
apps/openmw_test_suite/lua/test_l10n.cpp
apps/openmw_test_suite/lua/test_lua.cpp
apps/openmw_test_suite/lua/test_scriptscontainer.cpp
apps/openmw_test_suite/lua/test_serialization.cpp
apps/openmw_test_suite/lua/test_storage.cpp
apps/openmw_test_suite/lua/test_ui_content.cpp
apps/openmw_test_suite/lua/test_utilpackage.cpp
apps/openmw_test_suite/misc/test_endianness.cpp
apps/openmw_test_suite/misc/test_resourcehelpers.cpp
apps/openmw_test_suite/misc/test_stringops.cpp
apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp
apps/openmw_test_suite/mwscript/test_scripts.cpp
apps/openmw_test_suite/mwscript/test_utils.hpp
apps/openmw_test_suite/mwworld/test_store.cpp
apps/openmw_test_suite/openmw_test_suite.cpp
apps/openmw_test_suite/testing_util.hpp
components/bsa/bsa_file.cpp
components/bsa/bsa_file.hpp
components/crashcatcher/windows_crashcatcher.cpp
components/crashcatcher/windows_crashcatcher.hpp
components/crashcatcher/windows_crashmonitor.cpp
components/crashcatcher/windows_crashmonitor.hpp
components/crashcatcher/windows_crashshm.hpp
components/fx/lexer_types.hpp
components/fx/parse_constants.hpp
components/platform/file.posix.cpp
components/platform/file.stdio.cpp
components/platform/file.win32.cpp
components/sdlutil/gl4es_init.cpp
components/sdlutil/gl4es_init.h
components/to_utf8/gen_iconv.cpp
components/to_utf8/tables_gen.hpp
components/to_utf8/to_utf8.cpp
components/to_utf8/to_utf8.hpp

@ -9,26 +9,38 @@ print_help() {
}
declare -rA GROUPED_DEPS=(
[gcc]="binutils gcc g++ libc-dev"
[clang]="binutils clang"
[gcc]="binutils gcc build-essential cmake ccache curl unzip git pkg-config mold"
[clang]="binutils clang make cmake ccache curl unzip git pkg-config mold"
[coverity]="binutils clang-11 make cmake ccache curl unzip git pkg-config"
[gcc_preprocess]="
binutils
build-essential
clang
cmake
curl
gcc
git
libclang-dev
ninja-build
python3-clang
python3-pip
unzip
"
# Common dependencies for building OpenMW.
[openmw-deps]="
make cmake ccache git pkg-config
libboost-filesystem-dev libboost-program-options-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
ca-certificates
libbullet-dev liblz4-dev libpng-dev libjpeg-dev libluajit-5.1-dev
librecast-dev libsqlite3-dev ca-certificates libicu-dev libyaml-cpp-dev
"
# TODO: add librecastnavigation-dev when debian is ready
# These dependencies can alternatively be built and linked statically.
[openmw-deps-dynamic]="libmygui-dev libopenscenegraph-dev"
[coverity]="curl"
[openmw-deps-dynamic]="libmygui-dev libopenscenegraph-dev libsqlite3-dev libcollada-dom-dev"
[clang-tidy]="clang-tidy"
# Pre-requisites for building MyGUI and OSG for static linking.
#
@ -40,10 +52,53 @@ declare -rA GROUPED_DEPS=(
# * JPEG: libjpeg-dev
# * PNG: libpng-dev
[openmw-deps-static]="
make cmake
ccache curl unzip libcollada-dom-dev libfreetype6-dev libjpeg-dev libpng-dev
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
gdb
git
git-lfs
libavcodec58
libavformat58
libavutil56
libboost-iostreams1.74.0
libboost-program-options1.74.0
libboost-system1.74.0
libbullet3.24
libcollada-dom2.5-dp0
libicu70
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.7
python3-pip
xvfb
"
[libasan6]="libasan6"
[android]="binutils build-essential cmake ccache curl unzip git pkg-config"
[openmw-clang-format]="
clang-format-14
git-core
"
)
if [[ $# -eq 0 ]]; then
@ -61,7 +116,10 @@ for group in "$@"; do
done
export APT_CACHE_DIR="${PWD}/apt-cache"
export DEBIAN_FRONTEND=noninteractive
set -x
mkdir -pv "$APT_CACHE_DIR"
apt-get update -yq
apt-get -q -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends "${deps[@]}"
apt-get update -yqq
apt-get -qq -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends software-properties-common gnupg >/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,200 @@
---
app-id: org.openmw.OpenMW.devel
runtime: org.kde.Platform
runtime-version: '5.15-21.08'
sdk: org.kde.Sdk
command: openmw-launcher
rename-appdata-file: openmw.appdata.xml
finish-args:
- "--share=ipc"
- "--socket=x11"
- "--device=all"
- "--filesystem=host"
- "--socket=pulseaudio"
build-options:
cflags: "-O2 -g"
cxxflags: "-O2 -g"
cleanup:
- "/include"
- "/lib/pkgconfig"
- "/lib/cmake"
- "/share/pkgconfig"
- "/share/aclocal"
- "/share/doc"
- "/man"
- "/share/man"
- "/share/gtk-doc"
- "/share/vala"
- "*.la"
- "*.a"
modules:
- name: boost
buildsystem: simple
build-commands:
- ./bootstrap.sh --prefix=/app --with-libraries=filesystem,iostreams,program_options,system
- ./b2 headers
- ./b2 install
sources:
- type: archive
url: https://boostorg.jfrog.io/artifactory/main/release/1.75.0/source/boost_1_75_0.tar.gz
sha256: aeb26f80e80945e82ee93e5939baebdca47b9dee80a07d3144be1e1a6a66dd6a
- name: collada-dom
buildsystem: cmake-ninja
config-opts:
- "-DOPT_COLLADA14=1"
- "-DOPT_COLLADA15=0"
sources:
- type: archive
url: https://github.com/rdiankov/collada-dom/archive/c1e20b7d6ff806237030fe82f126cb86d661f063.zip
sha256: 6c51cd068c7d6760b587391884942caaac8a515d138535041e42d00d3e5c9152
- name: ffmpeg
config-opts:
- "--disable-static"
- "--enable-shared"
- "--disable-programs"
- "--disable-doc"
- "--disable-avdevice"
- "--disable-avfilter"
- "--disable-postproc"
- "--disable-encoders"
- "--disable-muxers"
- "--disable-protocols"
- "--disable-indevs"
- "--disable-devices"
- "--disable-filters"
sources:
- type: archive
url: http://ffmpeg.org/releases/ffmpeg-4.3.2.tar.xz
sha256: 46e4e64f1dd0233cbc0934b9f1c0da676008cad34725113fb7f802cfa84ccddb
cleanup:
- "/share/ffmpeg"
- name: openscenegraph
buildsystem: cmake-ninja
config-opts:
- "-DBUILD_OSG_PLUGINS_BY_DEFAULT=0"
- "-DBUILD_OSG_PLUGIN_OSG=1"
- "-DBUILD_OSG_PLUGIN_DDS=1"
- "-DBUILD_OSG_PLUGIN_DAE=1"
- "-DBUILD_OSG_PLUGIN_TGA=1"
- "-DBUILD_OSG_PLUGIN_BMP=1"
- "-DBUILD_OSG_PLUGIN_JPEG=1"
- "-DBUILD_OSG_PLUGIN_PNG=1"
- "-DBUILD_OSG_DEPRECATED_SERIALIZERS=0"
- "-DBUILD_OSG_APPLICATIONS=0"
- "-DCMAKE_BUILD_TYPE=Release"
build-options:
env:
COLLADA_DIR: /app/include/collada-dom2.5
sources:
- type: archive
url: https://github.com/openmw/osg/archive/76e061739610bc9a3420a59e7c9395e742ce2f97.zip
sha256: fa1100362eae260192819d65d90b29ec0b88fdf80e30cee677730b7a0d68637e
- name: bullet
# The cmake + ninja buildsystem doesn't install the required binaries correctly
buildsystem: cmake
config-opts:
- "-DBUILD_BULLET2_DEMOS=0"
- "-DBUILD_BULLET3=0"
- "-DBUILD_CPU_DEMOS=0"
- "-DBUILD_EXTRAS=0"
- "-DBUILD_OPENGL3_DEMOS=0"
- "-DBUILD_UNIT_TESTS=0"
- "-DCMAKE_BUILD_TYPE=Release"
- "-DUSE_GLUT=0"
- "-DUSE_GRAPHICAL_BENCHMARK=0"
- "-DUSE_DOUBLE_PRECISION=on"
- "-DBULLET2_MULTITHREADING=on"
sources:
- type: archive
url: https://github.com/bulletphysics/bullet3/archive/93be7e644024e92df13b454a4a0b0fcd02b21b10.zip
sha256: 82968fbf20a92c51bc71ac9ee8f6381ecf3420c7cbb881ffb7bb633fa13b27f9
- name: mygui
buildsystem: cmake-ninja
config-opts:
- "-DCMAKE_BUILD_TYPE=Release"
- "-DMYGUI_RENDERSYSTEM=1"
- "-DMYGUI_BUILD_DEMOS=0"
- "-DMYGUI_BUILD_TOOLS=0"
- "-DMYGUI_BUILD_PLUGINS=0"
sources:
- type: archive
url: https://github.com/MyGUI/mygui/archive/refs/tags/MyGUI3.4.1.tar.gz
sha256: bdf730bdeb4ad89e6b8223967db01aa5274d2b93adc2c0d6aa4842faeed4de1a
- name: libunshield
buildsystem: cmake-ninja
config-opts:
- "-DCMAKE_BUILD_TYPE=Release"
sources:
- type: archive
url: https://github.com/twogood/unshield/archive/1.4.3.tar.gz
sha256: aa8c978dc0eb1158d266eaddcd1852d6d71620ddfc82807fe4bf2e19022b7bab
- name: lz4
buildsystem: simple
build-commands:
- "make lib"
- "PREFIX=/app make install"
sources:
- type: archive
url: https://github.com/lz4/lz4/archive/refs/tags/v1.9.3.tar.gz
sha256: 030644df4611007ff7dc962d981f390361e6c97a34e5cbc393ddfbe019ffe2c1
- name: recastnavigation
buildsystem: cmake-ninja
config-opts:
- "-DCMAKE_BUILD_TYPE=Release"
- "-DRECASTNAVIGATION_DEMO=no"
- "-DRECASTNAVIGATION_TESTS=no"
- "-DRECASTNAVIGATION_EXAMPLES=no"
sources:
- type: archive
url: https://github.com/recastnavigation/recastnavigation/archive/c5cbd53024c8a9d8d097a4371215e3342d2fdc87.zip
sha256: 53dacfd7bead4d3b0c9a04a648caed3e7c3900e0aba765c15dee26b50f6103c6
- name: yaml-cpp
buildsystem: cmake-ninja
sources:
- type: archive
url: https://github.com/jbeder/yaml-cpp/archive/refs/tags/yaml-cpp-0.7.0.zip
sha256: 4d5e664a7fb2d7445fc548cc8c0e1aa7b1a496540eb382d137e2cc263e6d3ef5
- name: LuaJIT
buildsystem: simple
build-commands:
- make install PREFIX=/app
sources:
- type: archive
url: https://github.com/LuaJIT/LuaJIT/archive/refs/tags/v2.0.5.zip
sha256: 2adbe397a5b6b8ab22fa8396507ce852a2495db50e50734b3daa1ffcadd9eeb4
- name: openmw
builddir: true
buildsystem: cmake-ninja
config-opts:
- "-DBUILD_BSATOOL=no"
- "-DBUILD_ESMTOOL=no"
- "-DCMAKE_BUILD_TYPE=Release"
- "-DICONDIR=/app/share/icons"
- "-DOPENMW_USE_SYSTEM_RECASTNAVIGATION=yes"
sources:
- type: dir
path: ..
- type: shell
commands:
- "sed -i 's:/wiki:/old-wiki:' ./files/openmw.appdata.xml"
- "sed -i 's:>org.openmw.launcher.desktop<:>org.openmw.OpenMW.devel.desktop<:' ./files/openmw.appdata.xml"
- "sed -i 's:Icon=openmw:Icon=org.openmw.OpenMW.devel.png:' ./files/org.openmw.launcher.desktop"
- "sed -i 's:Icon=openmw-cs:Icon=org.openmw.OpenMW.OpenCS.devel.png:' ./files/org.openmw.cs.desktop"
post-install:
- "mv /app/share/applications/org.openmw.launcher.desktop /app/share/applications/org.openmw.OpenMW.devel.desktop"
- "mv /app/share/applications/org.openmw.cs.desktop /app/share/applications/org.openmw.OpenMW.OpenCS.devel.desktop"
- "mv /app/share/icons/openmw.png /app/share/icons/org.openmw.OpenMW.devel.png"
- "mv /app/share/icons/openmw-cs.png /app/share/icons/org.openmw.OpenMW.OpenCS.devel.png"

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

@ -0,0 +1,12 @@
docs/source/install_luadocumentor_in_docker.sh
PATH=$PATH:~/luarocks/bin
pushd .
echo "Install Teal Cyan"
git clone https://github.com/teal-language/cyan.git --depth 1
cd cyan
luarocks make cyan-dev-1.rockspec
popd
scripts/generate_teal_declarations.sh ./teal_declarations
zip teal_declarations.zip -r teal_declarations

@ -0,0 +1,74 @@
#!/bin/bash
set -xeo pipefail
SRC="${PWD:?}"
VERSION=$(git rev-parse HEAD)
mkdir -p build
cd build
cmake \
-G Ninja \
-D CMAKE_C_COMPILER=gcc \
-D CMAKE_CXX_COMPILER=g++ \
-D USE_SYSTEM_TINYXML=ON \
-D OPENMW_USE_SYSTEM_RECASTNAVIGATION=ON \
-D CMAKE_BUILD_TYPE=Release \
-D CMAKE_C_FLAGS_RELEASE='-DNDEBUG -E -w' \
-D CMAKE_CXX_FLAGS_RELEASE='-DNDEBUG -E -w' \
-D CMAKE_EXPORT_COMPILE_COMMANDS=ON \
-D BUILD_BENCHMARKS=ON \
-D BUILD_BSATOOL=ON \
-D BUILD_BULLETOBJECTTOOL=ON \
-D BUILD_ESMTOOL=ON \
-D BUILD_ESSIMPORTER=ON \
-D BUILD_LAUNCHER=ON \
-D BUILD_LAUNCHER_TESTS=ON \
-D BUILD_MWINIIMPORTER=ON \
-D BUILD_NAVMESHTOOL=ON \
-D BUILD_NIFTEST=ON \
-D BUILD_OPENCS=ON \
-D BUILD_OPENCS_TESTS=ON \
-D BUILD_OPENMW=ON \
-D BUILD_UNITTESTS=ON \
-D BUILD_WIZARD=ON \
"${SRC}"
cmake --build . --parallel
cd ..
scripts/preprocessed_file_size_stats.py --remove_prefix "${SRC}/" build > "${VERSION:?}.json"
ls -al "${VERSION:?}.json"
if [[ "${GENERATE_ONLY}" ]]; then
exit 0
fi
git remote add target "${CI_MERGE_REQUEST_PROJECT_URL:-https://gitlab.com/OpenMW/openmw.git}"
TARGET_BRANCH="${CI_MERGE_REQUEST_TARGET_BRANCH_NAME:-master}"
git fetch target "${TARGET_BRANCH:?}"
if [[ "${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME}" ]]; then
git remote add source "${CI_MERGE_REQUEST_SOURCE_PROJECT_URL}"
git fetch --unshallow source "${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME}"
elif [[ "${CI_COMMIT_BRANCH}" ]]; then
git fetch origin "${CI_COMMIT_BRANCH:?}"
else
git fetch origin
fi
BASE_VERSION=$(git merge-base "target/${TARGET_BRANCH:?}" "${VERSION:?}")
# Save and use scripts from this commit because they may be absent or different in the base version
cp scripts/preprocessed_file_size_stats.py scripts/preprocessed_file_size_stats.bak.py
cp CI/ubuntu_gcc_preprocess.sh CI/ubuntu_gcc_preprocess.bak.sh
git checkout "${BASE_VERSION:?}"
mv scripts/preprocessed_file_size_stats.bak.py scripts/preprocessed_file_size_stats.py
mv CI/ubuntu_gcc_preprocess.bak.sh CI/ubuntu_gcc_preprocess.sh
env GENERATE_ONLY=1 CI/ubuntu_gcc_preprocess.sh
git checkout --force "${VERSION:?}"
scripts/preprocessed_file_size_stats_diff.py "${BASE_VERSION:?}.json" "${VERSION:?}.json"

@ -1,18 +1,38 @@
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) # LTO
cmake_policy(SET CMP0069 NEW)
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)
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()
# set the timestamps of extracted contents to the time of extraction
if(POLICY CMP0135)
cmake_policy(SET CMP0135 NEW)
endif()
project(OpenMW)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
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)
@ -32,6 +52,7 @@ 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(BUILD_BENCHMARKS "Build benchmarks with Google Benchmark" OFF)
<<<<<<< HEAD
option(BUILD_OPENMW_MP "Build OpenMW-MP" ON)
option(BUILD_BROWSER "Build tes3mp Server Browser" ON)
option(BUILD_MASTER "Build tes3mp Master Server" OFF)
@ -40,8 +61,18 @@ set(OpenGL_GL_PREFERENCE LEGACY) # Use LEGACY as we use GL2; GLNVD is for GL3 a
if (NOT BUILD_LAUNCHER AND NOT BUILD_BROWSER AND NOT BUILD_OPENCS AND NOT BUILD_WIZARD)
set(USE_QT FALSE)
=======
option(BUILD_NAVMESHTOOL "Build navmesh tool" ON)
option(BUILD_BULLETOBJECTTOOL "Build Bullet object tool" ON)
option(BUILD_OPENCS_TESTS "Build OpenMW Construction Set tests" OFF)
set(OpenGL_GL_PREFERENCE LEGACY) # Use LEGACY as we use GL2; GLNVD is for GL3 and up.
if (BUILD_LAUNCHER OR BUILD_OPENCS OR BUILD_WIZARD OR BUILD_OPENCS_TESTS)
set(USE_QT TRUE)
>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7
else()
set(USE_QT TRUE)
set(USE_QT FALSE)
endif()
# If the user doesn't supply a CMAKE_BUILD_TYPE via command line, choose one for them.
@ -68,7 +99,7 @@ endif()
message(STATUS "Configuring OpenMW...")
set(OPENMW_VERSION_MAJOR 0)
set(OPENMW_VERSION_MINOR 47)
set(OPENMW_VERSION_MINOR 49)
set(OPENMW_VERSION_RELEASE 0)
set(OPENMW_VERSION_COMMITHASH "")
@ -112,7 +143,7 @@ configure_file ("${OpenMW_SOURCE_DIR}/docs/mainpage.hpp.cmake" "${OpenMW_BINARY_
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(QT_STATIC "Link static build of QT 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)
@ -147,6 +178,11 @@ else()
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_USE_SYSTEM_GOOGLETEST "Use system Google Test 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)
@ -162,18 +198,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.")
@ -184,10 +229,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)
@ -195,8 +238,14 @@ 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()
<<<<<<< HEAD
find_package(RakNet REQUIRED)
include_directories(${RakNet_INCLUDES})
=======
if(MSVC)
add_compile_options("/utf-8")
endif()
>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7
# Dependencies
find_package(OpenGL REQUIRED)
@ -204,12 +253,22 @@ find_package(OpenGL REQUIRED)
find_package(LZ4 REQUIRED)
if (USE_QT)
<<<<<<< HEAD
find_package(Qt5Core 5.9 REQUIRED) # Temporary adjustment for TES3MP CI
find_package(Qt5Widgets REQUIRED)
find_package(Qt5Network REQUIRED)
find_package(Qt5OpenGL REQUIRED)
# Instruct CMake to run moc automatically when needed.
#set(CMAKE_AUTOMOC ON)
=======
find_package(QT REQUIRED COMPONENTS Core NAMES Qt6 Qt5)
if (QT_VERSION_MAJOR VERSION_EQUAL 5)
find_package(Qt5 5.15 COMPONENTS Core Widgets Network OpenGL REQUIRED)
else()
find_package(Qt6 COMPONENTS Core Widgets Network OpenGL OpenGLWidgets REQUIRED)
endif()
message(STATUS "Using Qt${QT_VERSION}")
>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7
endif()
# Start of tes3mp addition
@ -218,17 +277,20 @@ endif()
IF(BUILD_OPENMW OR BUILD_OPENCS)
# End of tes3mp addition
set(USED_OSG_COMPONENTS
osgAnimation
osgDB
osgViewer
osgText
osgGA
osgFX
osgParticle
osgText
osgUtil
osgFX
osgShadow
osgAnimation)
osgSim
osgViewer
)
set(USED_OSG_PLUGINS
osgdb_bmp
osgdb_dae
osgdb_dds
osgdb_freetype
osgdb_jpeg
@ -242,6 +304,37 @@ set(USED_OSG_PLUGINS
ENDIF(BUILD_OPENMW OR BUILD_OPENCS)
# End of tes3mp addition
if(NOT COLLADA_DOM_VERSION_MAJOR)
set(COLLADA_DOM_VERSION_MAJOR 2)
endif()
if(NOT COLLADA_DOM_VERSION_MINOR)
set(COLLADA_DOM_VERSION_MINOR 5)
endif()
find_package(collada_dom 2.5)
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()
if ((BUILD_UNITTESTS OR BUILD_OPENCS_TESTS) AND OPENMW_USE_SYSTEM_GOOGLETEST)
find_package(GTest 1.10 REQUIRED)
find_package(GMock 1.10 REQUIRED)
endif()
add_subdirectory(extern)
# Sound setup
@ -326,7 +419,12 @@ 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()
# Start of tes3mp addition
@ -397,10 +495,7 @@ endif()
IF(BUILD_OPENMW OR BUILD_OPENCS)
# End of tes3mp addition
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.6.5 REQUIRED ${USED_OSG_COMPONENTS}) # Bump to 3.6.6 when released
if(OSG_STATIC)
find_package(OSGPlugins REQUIRED COMPONENTS ${USED_OSG_PLUGINS})
@ -421,7 +516,12 @@ ELSE(BUILD_OPENMW OR BUILD_OPENCS)
ENDIF(BUILD_OPENMW OR BUILD_OPENCS)
# End of tes3mp addition
set(BOOST_COMPONENTS system filesystem program_options iostreams)
include(cmake/CheckOsgMultiview.cmake)
if(HAVE_MULTIVIEW)
add_definitions(-DOSG_HAS_MULTIVIEW)
endif(HAVE_MULTIVIEW)
set(BOOST_COMPONENTS system program_options iostreams)
if(WIN32)
set(BOOST_COMPONENTS ${BOOST_COMPONENTS} locale)
if(MSVC)
@ -438,13 +538,21 @@ endif()
set(Boost_NO_BOOST_CMAKE ON)
find_package(Boost 1.6.2 REQUIRED COMPONENTS ${BOOST_COMPONENTS} OPTIONAL_COMPONENTS ${BOOST_OPTIONAL_COMPONENTS})
<<<<<<< HEAD
# Start of tes3mp addition
#
# Don't require certain dependencies for the server
IF(BUILD_OPENMW OR BUILD_OPENCS)
# End of tes3mp addition
=======
if(Boost_VERSION_STRING VERSION_GREATER_EQUAL 1.77.0)
find_package(Boost 1.77.0 REQUIRED COMPONENTS atomic)
endif()
>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7
if(OPENMW_USE_SYSTEM_MYGUI)
find_package(MyGUI 3.2.2 REQUIRED)
find_package(MyGUI 3.4.1 REQUIRED)
endif()
# End of tes3mp addition
#
@ -458,11 +566,32 @@ find_package(SDL2 2.0.9 REQUIRED)
IF(BUILD_OPENMW OR BUILD_OPENCS)
# End of tes3mp addition
find_package(OpenAL REQUIRED)
<<<<<<< HEAD
# End of tes3mp addition
#
# Don't require certain dependencies for the server
ENDIF(BUILD_OPENMW OR BUILD_OPENCS)
# End of tes3mp addition
=======
find_package(ZLIB REQUIRED)
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)
if (NOT WIN32)
include(cmake/CheckLuaCustomAllocator.cmake)
endif()
# C++ library binding to Lua
set(SOL_INCLUDE_DIR ${OpenMW_SOURCE_DIR}/extern/sol3)
set(SOL_CONFIG_DIR ${OpenMW_SOURCE_DIR}/extern/sol_config)
>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7
include_directories(
BEFORE SYSTEM
@ -473,12 +602,16 @@ include_directories(
${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})
link_directories(${SDL2_LIBRARY_DIRS} ${Boost_LIBRARY_DIRS} ${COLLADA_DOM_LIBRARY_DIRS})
if(MYGUI_STATIC)
add_definitions(-DMYGUI_STATIC)
add_definitions(-DMYGUI_STATIC)
endif (MYGUI_STATIC)
if (APPLE)
@ -489,9 +622,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/)
@ -575,7 +707,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(OPENMW_CXX_FLAGS "-Wall -Wextra -Wundef -Wextra-semi -Wno-unused-parameter -pedantic -Wno-long-long -Wnon-virtual-dtor -Wunused ${OPENMW_CXX_FLAGS}")
add_definitions( -DBOOST_NO_CXX11_SCOPED_ENUMS=ON )
if (APPLE)
@ -585,12 +717,12 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang)
if (CMAKE_CXX_COMPILER_ID STREQUAL Clang AND NOT APPLE)
if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 3.6 OR CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 3.6)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-potentially-evaluated-expression")
set(OPENMW_CXX_FLAGS "${OPENMW_CXX_FLAGS} -Wno-potentially-evaluated-expression")
endif ()
endif()
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")
if (CMAKE_CXX_COMPILER_ID STREQUAL GNU)
set(OPENMW_CXX_FLAGS "${OPENMW_CXX_FLAGS} -Wno-unused-but-set-parameter -Wduplicated-branches -Wduplicated-cond -Wlogical-op")
endif()
endif (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang)
@ -608,12 +740,16 @@ add_subdirectory (extern/oics)
ENDIF(BUILD_OPENMW OR BUILD_OPENCS)
# End of tes3mp addition
add_subdirectory (extern/Base64)
<<<<<<< HEAD
# Start of tes3mp addition
#
# Don't require certain dependencies for the server
IF(BUILD_OPENMW OR BUILD_OPENCS)
# End of tes3mp addition
if (BUILD_OPENCS)
=======
if (BUILD_OPENCS OR BUILD_OPENCS_TESTS)
>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7
add_subdirectory (extern/osgQt)
endif()
# Start of tes3mp addition
@ -622,9 +758,12 @@ endif()
ENDIF(BUILD_OPENMW OR BUILD_OPENCS)
# End of tes3mp addition
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_MP)
@ -640,15 +779,15 @@ if (BUILD_OPENMW)
endif()
if (BUILD_BSATOOL)
add_subdirectory( apps/bsatool )
add_subdirectory( apps/bsatool )
endif()
if (BUILD_ESMTOOL)
add_subdirectory( apps/esmtool )
add_subdirectory( apps/esmtool )
endif()
if (BUILD_LAUNCHER)
add_subdirectory( apps/launcher )
add_subdirectory( apps/launcher )
endif()
if (BUILD_BROWSER)
@ -656,19 +795,19 @@ if (BUILD_BROWSER)
endif()
if (BUILD_MWINIIMPORTER)
add_subdirectory( apps/mwiniimporter )
add_subdirectory( apps/mwiniimporter )
endif()
if (BUILD_ESSIMPORTER)
add_subdirectory (apps/essimporter )
add_subdirectory (apps/essimporter )
endif()
if (BUILD_OPENCS)
add_subdirectory (apps/opencs)
if (BUILD_OPENCS OR BUILD_OPENCS_TESTS)
add_subdirectory (apps/opencs)
endif()
if (BUILD_WIZARD)
add_subdirectory(apps/wizard)
add_subdirectory(apps/wizard)
endif()
if (BUILD_NIFTEST)
@ -677,19 +816,34 @@ endif(BUILD_NIFTEST)
# UnitTests
if (BUILD_UNITTESTS)
add_subdirectory( apps/openmw_test_suite )
add_subdirectory( apps/openmw_test_suite )
endif()
if (BUILD_BENCHMARKS)
add_subdirectory(apps/benchmarks)
add_subdirectory(apps/benchmarks)
endif()
if (WIN32)
if (MSVC)
if (OPENMW_MP_BUILD)
set( MT_BUILD "/MP")
endif()
if (BUILD_NAVMESHTOOL)
add_subdirectory(apps/navmeshtool)
endif()
if (BUILD_BULLETOBJECTTOOL)
add_subdirectory( apps/bulletobjecttool )
endif()
if (BUILD_OPENCS_TESTS)
add_subdirectory(apps/opencs_tests)
endif()
if (WIN32)
if (MSVC)
foreach( OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES} )
string( TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG )
set( CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} "$(SolutionDir)$(Configuration)" )
set( CMAKE_LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIG} "$(ProjectDir)$(Configuration)" )
endforeach( OUTPUTCONFIG )
<<<<<<< HEAD
foreach( OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES} )
string( TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG )
set( CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} "$(SolutionDir)$(Configuration)" )
@ -781,41 +935,154 @@ if (WIN32)
set_target_properties(tes3mp PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD} /bigobj")
else()
set_target_properties(tes3mp PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
=======
if (USE_DEBUG_CONSOLE AND BUILD_OPENMW)
set_target_properties(openmw PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE")
set_target_properties(openmw PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:CONSOLE")
set_target_properties(openmw PROPERTIES COMPILE_DEFINITIONS $<$<CONFIG:Debug>:_CONSOLE>)
elseif (BUILD_OPENMW)
# Turn off debug console, debug output will be written to visual studio output instead
set_target_properties(openmw PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:WINDOWS")
set_target_properties(openmw PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:WINDOWS")
>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7
endif()
endif()
if (BUILD_WIZARD)
set_target_properties(openmw-wizard PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
endif()
if (BUILD_OPENMW)
# Release builds don't use the debug console
set_target_properties(openmw PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS")
set_target_properties(openmw PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:WINDOWS")
endif()
if (BUILD_BENCHMARKS)
set_target_properties(openmw_detournavigator_navmeshtilescache_benchmark PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
endif()
endif(MSVC)
# Play a bit with the warning levels
<<<<<<< HEAD
# TODO: At some point release builds should not use the console but rather write to a log file
#set_target_properties(tes3mp PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS")
#set_target_properties(tes3mp PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:WINDOWS")
=======
set(WARNINGS "/W4")
set(WARNINGS_DISABLE
4100 # Unreferenced formal parameter (-Wunused-parameter)
4127 # Conditional expression is constant
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( "${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()
foreach(d ${WARNINGS_DISABLE})
set(WARNINGS "${WARNINGS} /wd${d}")
endforeach(d)
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}")
endif()
if (BUILD_ESMTOOL)
set_target_properties(esmtool PROPERTIES COMPILE_FLAGS "${WARNINGS}")
endif()
if (BUILD_ESSIMPORTER)
set_target_properties(openmw-essimporter PROPERTIES COMPILE_FLAGS "${WARNINGS}")
endif()
if (BUILD_LAUNCHER)
set_target_properties(openmw-launcher PROPERTIES COMPILE_FLAGS "${WARNINGS}")
endif()
if (BUILD_MWINIIMPORTER)
set_target_properties(openmw-iniimporter PROPERTIES COMPILE_FLAGS "${WARNINGS}")
endif()
if (BUILD_OPENCS)
set_target_properties(openmw-cs PROPERTIES COMPILE_FLAGS "${WARNINGS}")
endif()
if (BUILD_OPENMW)
set_target_properties(openmw PROPERTIES COMPILE_FLAGS "${WARNINGS}")
endif()
if (BUILD_WIZARD)
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)
# TODO: At some point release builds should not use the console but rather write to a log file
#set_target_properties(openmw PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS")
#set_target_properties(openmw PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:WINDOWS")
endif()
if (BUILD_OPENMW AND APPLE)
target_compile_definitions(components PRIVATE GL_SILENCE_DEPRECATION=1)
target_compile_definitions(openmw PRIVATE GL_SILENCE_DEPRECATION=1)
>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7
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)
message(FATAL_ERROR "macOS packaging is broken in early CMake 3.13 releases, see https://gitlab.com/OpenMW/openmw/issues/4767. Please use at least 3.13.4 or an older version like 3.12.4")
if (CMAKE_VERSION VERSION_LESS 3.19)
message(FATAL_ERROR "macOS packaging requires CMake 3.19 or higher to sign macOS app bundles.")
endif ()
get_property(QT_COCOA_PLUGIN_PATH TARGET Qt5::QCocoaIntegrationPlugin PROPERTY LOCATION_RELEASE)
get_property(QT_COCOA_PLUGIN_PATH TARGET Qt${QT_VERSION_MAJOR}::QCocoaIntegrationPlugin PROPERTY LOCATION_RELEASE)
get_filename_component(QT_COCOA_PLUGIN_DIR "${QT_COCOA_PLUGIN_PATH}" DIRECTORY)
get_filename_component(QT_COCOA_PLUGIN_GROUP "${QT_COCOA_PLUGIN_DIR}" NAME)
get_filename_component(QT_COCOA_PLUGIN_NAME "${QT_COCOA_PLUGIN_PATH}" NAME)
configure_file("${QT_COCOA_PLUGIN_PATH}" "${APP_BUNDLE_DIR}/Contents/PlugIns/${QT_COCOA_PLUGIN_GROUP}/${QT_COCOA_PLUGIN_NAME}" COPYONLY)
get_property(QT_QMACSTYLE_PLUGIN_PATH TARGET Qt${QT_VERSION_MAJOR}::QMacStylePlugin PROPERTY LOCATION_RELEASE)
get_filename_component(QT_QMACSTYLE_PLUGIN_DIR "${QT_QMACSTYLE_PLUGIN_PATH}" DIRECTORY)
get_filename_component(QT_QMACSTYLE_PLUGIN_GROUP "${QT_QMACSTYLE_PLUGIN_DIR}" NAME)
get_filename_component(QT_QMACSTYLE_PLUGIN_NAME "${QT_QMACSTYLE_PLUGIN_PATH}" NAME)
configure_file("${QT_QMACSTYLE_PLUGIN_PATH}" "${APP_BUNDLE_DIR}/Contents/PlugIns/${QT_QMACSTYLE_PLUGIN_GROUP}/${QT_QMACSTYLE_PLUGIN_NAME}" COPYONLY)
configure_file("${OpenMW_SOURCE_DIR}/files/mac/qt.conf" "${APP_BUNDLE_DIR}/Contents/Resources/qt.conf" COPYONLY)
if (BUILD_OPENCS)
get_property(OPENCS_BUNDLE_NAME_TMP TARGET openmw-cs PROPERTY OUTPUT_NAME)
set(OPENCS_BUNDLE_NAME "${OPENCS_BUNDLE_NAME_TMP}.app")
configure_file("${QT_COCOA_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/PlugIns/${QT_COCOA_PLUGIN_GROUP}/${QT_COCOA_PLUGIN_NAME}" COPYONLY)
configure_file("${OpenMW_SOURCE_DIR}/files/mac/qt.conf" "${OPENCS_BUNDLE_NAME}/Contents/Resources/qt.conf" COPYONLY)
get_property(OPENCS_BUNDLE_NAME_TMP TARGET openmw-cs PROPERTY OUTPUT_NAME)
set(OPENCS_BUNDLE_NAME "${OPENCS_BUNDLE_NAME_TMP}.app")
configure_file("${QT_COCOA_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/PlugIns/${QT_COCOA_PLUGIN_GROUP}/${QT_COCOA_PLUGIN_NAME}" COPYONLY)
configure_file("${QT_QMACSTYLE_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/PlugIns/${QT_QMACSTYLE_PLUGIN_GROUP}/${QT_QMACSTYLE_PLUGIN_NAME}" COPYONLY)
configure_file("${OpenMW_SOURCE_DIR}/files/mac/qt.conf" "${OPENCS_BUNDLE_NAME}/Contents/Resources/qt.conf" COPYONLY)
endif ()
install(DIRECTORY "${APP_BUNDLE_DIR}" USE_SOURCE_PERMISSIONS DESTINATION "." COMPONENT Runtime)
@ -884,7 +1151,9 @@ if (OPENMW_OSX_DEPLOYMENT AND APPLE)
install_plugins_for_bundle("${OPENCS_BUNDLE_NAME}" OPENCS_PLUGINS)
set(PLUGINS ${PLUGINS} "${INSTALLED_OPENMW_APP}/Contents/PlugIns/${QT_COCOA_PLUGIN_GROUP}/${QT_COCOA_PLUGIN_NAME}")
set(PLUGINS ${PLUGINS} "${INSTALLED_OPENMW_APP}/Contents/PlugIns/${QT_QMACSTYLE_PLUGIN_GROUP}/${QT_QMACSTYLE_PLUGIN_NAME}")
set(OPENCS_PLUGINS ${OPENCS_PLUGINS} "${INSTALLED_OPENCS_APP}/Contents/PlugIns/${QT_COCOA_PLUGIN_GROUP}/${QT_COCOA_PLUGIN_NAME}")
set(OPENCS_PLUGINS ${OPENCS_PLUGINS} "${INSTALLED_OPENCS_APP}/Contents/PlugIns/${QT_QMACSTYLE_PLUGIN_GROUP}/${QT_QMACSTYLE_PLUGIN_NAME}")
install(CODE "
function(gp_item_default_embedded_path_override item default_embedded_path_var)
@ -897,6 +1166,9 @@ if (OPENMW_OSX_DEPLOYMENT AND APPLE)
fixup_bundle(\"${INSTALLED_OPENMW_APP}\" \"${PLUGINS}\" \"\")
fixup_bundle(\"${INSTALLED_OPENCS_APP}\" \"${OPENCS_PLUGINS}\" \"\")
" COMPONENT Runtime)
set(CPACK_PRE_BUILD_SCRIPTS ${CMAKE_SOURCE_DIR}/cmake/SignMacApplications.cmake)
include(CPack)
elseif(NOT APPLE)
get_generator_is_multi_config(multi_config)
@ -929,7 +1201,6 @@ 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}/defaults.bin" DESTINATION ".")
# Start of tes3mp addition
INSTALL(FILES "${INSTALL_SOURCE}/tes3mp-client-default.cfg" DESTINATION ".")
@ -1053,9 +1324,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")

@ -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,6 +1,7 @@
TES3MP
======
<<<<<<< HEAD
Copyright (c) 2008-2015, OpenMW Team
Copyright (c) 2016-2022, David Cernat & Stanislav Zhukov
@ -12,11 +13,32 @@ TES3MP is a project adding multiplayer functionality to [OpenMW](https://github.
Font Licenses:
* DejaVuLGCSansMono.ttf: custom (see [files/mygui/DejaVuFontLicense.txt](https://github.com/TES3MP/TES3MP/blob/master/files/mygui/DejaVuFontLicense.txt) for more information)
=======
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.49.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.libera.chat
* Discord: https://discord.gg/bWuqq2e
Font Licenses:
* DejaVuLGCSansMono.ttf: custom (see [files/data/fonts/DejaVuFontLicense.txt](https://gitlab.com/OpenMW/openmw/-/raw/master/files/data/fonts/DejaVuFontLicense.txt) for more information)
* DemonicLetters.ttf: SIL Open Font License (see [files/data/fonts/DemonicLettersFontLicense.txt](https://gitlab.com/OpenMW/openmw/-/raw/master/files/data/fonts/DemonicLettersFontLicense.txt) for more information)
* MysticCards.ttf: SIL Open Font License (see [files/data/fonts/MysticCardsFontLicense.txt](https://gitlab.com/OpenMW/openmw/-/raw/master/files/data/fonts/MysticCardsFontLicense.txt) for more information)
>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7
Project status
--------------
<<<<<<< HEAD
[Version changelog](https://github.com/TES3MP/TES3MP/blob/master/tes3mp-changelog.md)
=======
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.
>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7
As of version 0.8.1, TES3MP is fully playable, providing very extensive player, NPC, world and quest synchronization, as well as state saving and loading, all of which are highly customizable via [serverside Lua scripts](https://github.com/TES3MP/CoreScripts).
@ -27,7 +49,17 @@ TES3MP now also has a [VR branch](https://github.com/TES3MP/TES3MP/tree/0.8.1-vr
Donations
---------------
<<<<<<< HEAD
You can benefit the project by donating on Patreon to our two developers, [David Cernat](https://www.patreon.com/davidcernat) and [Koncord](https://www.patreon.com/Koncord), as well as by supporting [OpenMW](https://openmw.org).
=======
* [Official forums](https://forum.openmw.org/)
* [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)
* [Report a bug](https://gitlab.com/OpenMW/openmw/issues) - read the [guidelines](https://wiki.openmw.org/index.php?title=Bug_Reporting_Guidelines) before submitting your first bug!
* [Known issues](https://gitlab.com/OpenMW/openmw/issues?label_name%5B%5D=Bug)
>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7
Contributing
---------------
@ -36,7 +68,54 @@ Helping us with documentation, bug hunting and video showcases is always greatly
For code contributions, it's best to start out with modestly sized fixes and features and work your way up. There are so many different possible implementations of more major features many of which would cause undesirable code or vision conflicts with OpenMW that those should be talked over in advance with the existing developers before effort is spent on them.
<<<<<<< HEAD
Feel free to contact the [team members](https://github.com/TES3MP/TES3MP/blob/master/tes3mp-credits.md) for any questions you might have.
=======
Syntax: openmw <options>
Allowed options:
--help print help message
--version print version information and quit
--data arg (=data) set data directories (later directories
have higher priority)
--data-local arg set local data directory (highest
priority)
--fallback-archive arg (=fallback-archive)
set fallback BSA archives (later
archives have higher priority)
--resources arg (=resources) set resources directory
--start arg set initial cell
--content arg content file(s): esm/esp, or
omwgame/omwaddon
--no-sound [=arg(=1)] (=0) disable all sounds
--script-verbose [=arg(=1)] (=0) verbose script output
--script-all [=arg(=1)] (=0) compile all scripts (excluding dialogue
scripts) at startup
--script-all-dialogue [=arg(=1)] (=0) compile all dialogue scripts at startup
--script-console [=arg(=1)] (=0) enable console-only script
functionality
--script-run arg select a file containing a list of
console commands that is executed on
startup
--script-warn [=arg(=1)] (=1) handling of warnings when compiling
scripts
0 - ignore warning
1 - show warning but consider script as
correctly compiled anyway
2 - treat warnings as errors
--script-blacklist arg ignore the specified script (if the use
of the blacklist is enabled)
--script-blacklist-use [=arg(=1)] (=1)
enable script blacklisting
--load-savegame arg load a save game file on game startup
(specify an absolute filename or a
filename relative to the current
working directory)
--skip-menu [=arg(=1)] (=0) skip main menu on game startup
--new-game [=arg(=1)] (=0) run new game sequence (ignored if
skip-menu=0)
--encoding arg (=win1252) Character encoding used in OpenMW game
messages:
>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7
Getting started
---------------

@ -1,27 +1,7 @@
cmake_minimum_required(VERSION 3.11)
set(BENCHMARK_ENABLE_TESTING OFF)
set(BENCHMARK_ENABLE_INSTALL OFF)
set(BENCHMARK_ENABLE_GTEST_TESTS OFF)
set(SAVED_CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
string(REPLACE "-Wsuggest-override" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
string(REPLACE "-Wundef" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
include(FetchContent)
FetchContent_Declare(benchmark
URL https://github.com/google/benchmark/archive/refs/tags/v1.5.2.zip
URL_HASH MD5=49395b757a7c4656d70f1328d93efd00
SOURCE_DIR fetched/benchmark
)
FetchContent_MakeAvailableExcludeFromAll(benchmark)
set(CMAKE_CXX_FLAGS "${SAVED_CMAKE_CXX_FLAGS}")
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})
if(OPENMW_USE_SYSTEM_BENCHMARK)
find_package(benchmark REQUIRED)
endif()
add_subdirectory(detournavigator)
add_subdirectory(esm)
add_subdirectory(settings)

@ -0,0 +1,15 @@
openmw_add_executable(openmw_detournavigator_navmeshtilescache_benchmark navmeshtilescache.cpp)
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()
if (BUILD_WITH_CODE_COVERAGE)
target_compile_options(openmw_detournavigator_navmeshtilescache_benchmark PRIVATE --coverage)
target_link_libraries(openmw_detournavigator_navmeshtilescache_benchmark gcov)
endif()

@ -1,10 +1,11 @@
#include <benchmark/benchmark.h>
#include <components/detournavigator/navmeshtilescache.hpp>
#include <components/detournavigator/stats.hpp>
#include <components/esm3/loadland.hpp>
#include <algorithm>
#include <random>
#include <iostream>
namespace
{
@ -12,23 +13,22 @@ namespace
struct Key
{
osg::Vec3f mAgentHalfExtents;
AgentBounds mAgentBounds;
TilePosition mTilePosition;
RecastMesh mRecastMesh;
std::vector<OffMeshConnection> mOffMeshConnections;
};
struct Item
{
Key mKey;
NavMeshData mValue;
PreparedNavMeshData mValue;
};
template <typename Random>
TilePosition generateTilePosition(int max, Random& random)
osg::Vec2i generateVec2i(int max, Random& random)
{
std::uniform_int_distribution<int> distribution(0, max);
return TilePosition(distribution(random), distribution(random));
return osg::Vec2i(distribution(random), distribution(random));
}
template <typename Random>
@ -56,11 +56,16 @@ namespace
{
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;
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;
}
@ -69,7 +74,7 @@ namespace
AreaType generateAreaType(Random& random)
{
std::uniform_int_distribution<int> distribution(0, 4);
return toAreaType(distribution(random));;
return toAreaType(distribution(random));
}
template <typename OutputIterator, typename Random>
@ -81,47 +86,77 @@ namespace
template <typename OutputIterator, typename Random>
void generateWater(OutputIterator out, std::size_t count, Random& random)
{
std::uniform_real_distribution<btScalar> distribution(0.0, 1.0);
std::uniform_real_distribution<float> distribution(0.0, 1.0);
std::generate_n(out, count, [&] {
const btVector3 shift(distribution(random), distribution(random), distribution(random));
return RecastMesh::Water {1, btTransform(btMatrix3x3::getIdentity(), shift)};
return CellWater{ generateVec2i(1000, random), Water{ ESM::Land::REAL_SIZE, distribution(random) } };
});
}
template <typename OutputIterator, typename Random>
void generateOffMeshConnection(OutputIterator out, std::size_t count, Random& random)
template <class Random>
Mesh generateMesh(std::size_t triangles, Random& random)
{
std::uniform_real_distribution<btScalar> distribution(0.0, 1.0);
std::generate_n(out, count, [&] {
const osg::Vec3f start(distribution(random), distribution(random), distribution(random));
const osg::Vec3f end(distribution(random), distribution(random), distribution(random));
return OffMeshConnection {start, end, generateAreaType(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 = generateTilePosition(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);
std::vector<float> vertices;
generateVertices(std::back_inserter(vertices), triangles * 1.98, random);
std::vector<int> indices;
generateIndices(std::back_inserter(indices), static_cast<int>(vertices.size() / 3) - 1, vertices.size() * 1.53, random);
std::vector<AreaType> areaTypes;
generateAreaTypes(std::back_inserter(areaTypes), indices.size() / 3, random);
std::vector<RecastMesh::Water> water;
generateWater(std::back_inserter(water), 2, random);
RecastMesh recastMesh(generation, revision, std::move(indices), std::move(vertices),
std::move(areaTypes), std::move(water));
std::vector<OffMeshConnection> offMeshConnections;
generateOffMeshConnection(std::back_inserter(offMeshConnections), 300, random);
return Key {agentHalfExtents, tilePosition, std::move(recastMesh), std::move(offMeshConnections)};
const TilePosition tilePosition = generateVec2i(10000, random);
const Version version{
.mGeneration = std::uniform_int_distribution<std::size_t>(0, 100)(random),
.mRevision = 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(version, std::move(mesh), std::move(water), { generateHeightfield(random) },
{ generateFlatHeightfield(random) }, {});
return Key{ AgentBounds{ agentShapeType, agentHalfExtents }, tilePosition, std::move(recastMesh) };
}
constexpr std::size_t trianglesPerTile = 310;
constexpr std::size_t trianglesPerTile = 239;
template <typename OutputIterator, typename Random>
void generateKeys(OutputIterator out, std::size_t count, Random& random)
@ -137,7 +172,7 @@ namespace
while (true)
{
Key key = generateKey(trianglesPerTile, random);
cache.set(key.mAgentHalfExtents, key.mTilePosition, key.mRecastMesh, key.mOffMeshConnections, NavMeshData());
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)
@ -156,22 +191,53 @@ namespace
generateKeys(std::back_inserter(keys), keys.size() * (100 - hitPercentage) / 100, random);
std::size_t n = 0;
while (state.KeepRunning())
for (auto _ : state)
{
const auto& key = keys[n++ % keys.size()];
const auto result = cache.get(key.mAgentHalfExtents, key.mTilePosition, key.mRecastMesh, key.mOffMeshConnections);
const auto result = cache.get(key.mAgentBounds, key.mTilePosition, key.mRecastMesh);
benchmark::DoNotOptimize(result);
}
}
constexpr auto getFromFilledCache_1m_100hit = getFromFilledCache<1 * 1024 * 1024, 100>;
constexpr auto getFromFilledCache_4m_100hit = getFromFilledCache<4 * 1024 * 1024, 100>;
constexpr auto getFromFilledCache_16m_100hit = getFromFilledCache<16 * 1024 * 1024, 100>;
constexpr auto getFromFilledCache_64m_100hit = getFromFilledCache<64 * 1024 * 1024, 100>;
constexpr auto getFromFilledCache_1m_70hit = getFromFilledCache<1 * 1024 * 1024, 70>;
constexpr auto getFromFilledCache_4m_70hit = getFromFilledCache<4 * 1024 * 1024, 70>;
constexpr auto getFromFilledCache_16m_70hit = getFromFilledCache<16 * 1024 * 1024, 70>;
constexpr auto getFromFilledCache_64m_70hit = getFromFilledCache<64 * 1024 * 1024, 70>;
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)
@ -187,15 +253,31 @@ namespace
while (state.KeepRunning())
{
const auto& key = keys[n++ % keys.size()];
const auto result = cache.set(key.mAgentHalfExtents, key.mTilePosition, key.mRecastMesh, key.mOffMeshConnections, NavMeshData());
const auto result = cache.set(
key.mAgentBounds, key.mTilePosition, key.mRecastMesh, std::make_unique<PreparedNavMeshData>());
benchmark::DoNotOptimize(result);
}
}
constexpr auto setToBoundedNonEmptyCache_1m = setToBoundedNonEmptyCache<1 * 1024 * 1024>;
constexpr auto setToBoundedNonEmptyCache_4m = setToBoundedNonEmptyCache<4 * 1024 * 1024>;
constexpr auto setToBoundedNonEmptyCache_16m = setToBoundedNonEmptyCache<16 * 1024 * 1024>;
constexpr auto setToBoundedNonEmptyCache_64m = setToBoundedNonEmptyCache<64 * 1024 * 1024>;
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);

@ -0,0 +1,15 @@
openmw_add_executable(openmw_esm_refid_benchmark benchrefid.cpp)
target_link_libraries(openmw_esm_refid_benchmark benchmark::benchmark components)
if (UNIX AND NOT APPLE)
target_link_libraries(openmw_esm_refid_benchmark ${CMAKE_THREAD_LIBS_INIT})
endif()
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC)
target_precompile_headers(openmw_esm_refid_benchmark PRIVATE <algorithm>)
endif()
if (BUILD_WITH_CODE_COVERAGE)
target_compile_options(openmw_esm_refid_benchmark PRIVATE --coverage)
target_link_libraries(openmw_esm_refid_benchmark gcov)
endif()

@ -0,0 +1,249 @@
#include <benchmark/benchmark.h>
#include "components/esm/refid.hpp"
#include <algorithm>
#include <cstddef>
#include <random>
#include <string>
#include <vector>
namespace
{
constexpr std::size_t refIdsCount = 64 * 1024;
template <class Random>
std::string generateText(std::size_t size, Random& random)
{
std::uniform_int_distribution<int> distribution('A', 'z');
std::string result;
result.reserve(size);
std::generate_n(std::back_inserter(result), size, [&] { return distribution(random); });
return result;
}
template <class Random>
std::vector<ESM::RefId> generateStringRefIds(std::size_t size, Random& random)
{
std::vector<ESM::RefId> result;
result.reserve(refIdsCount);
std::generate_n(
std::back_inserter(result), refIdsCount, [&] { return ESM::StringRefId(generateText(size, random)); });
return result;
}
template <class Serialize>
std::vector<std::string> generateSerializedRefIds(const std::vector<ESM::RefId>& generated, Serialize&& serialize)
{
std::vector<std::string> result;
result.reserve(generated.size());
for (ESM::RefId refId : generated)
result.push_back(serialize(refId));
return result;
}
template <class Random, class Serialize>
std::vector<std::string> generateSerializedStringRefIds(std::size_t size, Random& random, Serialize&& serialize)
{
return generateSerializedRefIds(generateStringRefIds(size, random), serialize);
}
template <class Random>
std::vector<ESM::RefId> generateIndexRefIds(Random& random)
{
std::vector<ESM::RefId> result;
result.reserve(refIdsCount);
std::uniform_int_distribution<std::uint32_t> distribution(0, std::numeric_limits<std::uint32_t>::max());
std::generate_n(std::back_inserter(result), refIdsCount,
[&] { return ESM::IndexRefId(ESM::REC_ARMO, distribution(random)); });
return result;
}
template <class Random, class Serialize>
std::vector<std::string> generateSerializedIndexRefIds(Random& random, Serialize&& serialize)
{
return generateSerializedRefIds(generateIndexRefIds(random), serialize);
}
template <class Random>
std::vector<ESM::RefId> generateGeneratedRefIds(Random& random)
{
std::vector<ESM::RefId> result;
result.reserve(refIdsCount);
std::uniform_int_distribution<std::uint64_t> distribution(0, std::numeric_limits<std::uint64_t>::max());
std::generate_n(
std::back_inserter(result), refIdsCount, [&] { return ESM::GeneratedRefId(distribution(random)); });
return result;
}
template <class Random, class Serialize>
std::vector<std::string> generateSerializedGeneratedRefIds(Random& random, Serialize&& serialize)
{
return generateSerializedRefIds(generateGeneratedRefIds(random), serialize);
}
template <class Random>
std::vector<ESM::RefId> generateESM3ExteriorCellRefIds(Random& random)
{
std::vector<ESM::RefId> result;
result.reserve(refIdsCount);
std::uniform_int_distribution<std::int32_t> distribution(-100, 100);
std::generate_n(std::back_inserter(result), refIdsCount,
[&] { return ESM::ESM3ExteriorCellRefId(distribution(random), distribution(random)); });
return result;
}
template <class Random, class Serialize>
std::vector<std::string> generateSerializedESM3ExteriorCellRefIds(Random& random, Serialize&& serialize)
{
return generateSerializedRefIds(generateESM3ExteriorCellRefIds(random), serialize);
}
void serializeRefId(benchmark::State& state)
{
std::minstd_rand random;
std::vector<ESM::RefId> refIds = generateStringRefIds(state.range(0), random);
std::size_t i = 0;
for (auto _ : state)
{
benchmark::DoNotOptimize(refIds[i].serialize());
if (++i >= refIds.size())
i = 0;
}
}
void deserializeRefId(benchmark::State& state)
{
std::minstd_rand random;
std::vector<std::string> serializedRefIds
= generateSerializedStringRefIds(state.range(0), random, [](ESM::RefId v) { return v.serialize(); });
std::size_t i = 0;
for (auto _ : state)
{
benchmark::DoNotOptimize(ESM::RefId::deserialize(serializedRefIds[i]));
if (++i >= serializedRefIds.size())
i = 0;
}
}
void serializeTextStringRefId(benchmark::State& state)
{
std::minstd_rand random;
std::vector<ESM::RefId> refIds = generateStringRefIds(state.range(0), random);
std::size_t i = 0;
for (auto _ : state)
{
benchmark::DoNotOptimize(refIds[i].serializeText());
if (++i >= refIds.size())
i = 0;
}
}
void deserializeTextStringRefId(benchmark::State& state)
{
std::minstd_rand random;
std::vector<std::string> serializedRefIds
= generateSerializedStringRefIds(state.range(0), random, [](ESM::RefId v) { return v.serializeText(); });
std::size_t i = 0;
for (auto _ : state)
{
benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i]));
if (++i >= serializedRefIds.size())
i = 0;
}
}
void serializeTextGeneratedRefId(benchmark::State& state)
{
std::minstd_rand random;
std::vector<ESM::RefId> refIds = generateGeneratedRefIds(random);
std::size_t i = 0;
for (auto _ : state)
{
benchmark::DoNotOptimize(refIds[i].serializeText());
if (++i >= refIds.size())
i = 0;
}
}
void deserializeTextGeneratedRefId(benchmark::State& state)
{
std::minstd_rand random;
std::vector<std::string> serializedRefIds
= generateSerializedGeneratedRefIds(random, [](ESM::RefId v) { return v.serializeText(); });
std::size_t i = 0;
for (auto _ : state)
{
benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i]));
if (++i >= serializedRefIds.size())
i = 0;
}
}
void serializeTextIndexRefId(benchmark::State& state)
{
std::minstd_rand random;
std::vector<ESM::RefId> refIds = generateIndexRefIds(random);
std::size_t i = 0;
for (auto _ : state)
{
benchmark::DoNotOptimize(refIds[i].serializeText());
if (++i >= refIds.size())
i = 0;
}
}
void deserializeTextIndexRefId(benchmark::State& state)
{
std::minstd_rand random;
std::vector<std::string> serializedRefIds
= generateSerializedIndexRefIds(random, [](ESM::RefId v) { return v.serializeText(); });
std::size_t i = 0;
for (auto _ : state)
{
benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i]));
if (++i >= serializedRefIds.size())
i = 0;
}
}
void serializeTextESM3ExteriorCellRefId(benchmark::State& state)
{
std::minstd_rand random;
std::vector<ESM::RefId> refIds = generateESM3ExteriorCellRefIds(random);
std::size_t i = 0;
for (auto _ : state)
{
benchmark::DoNotOptimize(refIds[i].serializeText());
if (++i >= refIds.size())
i = 0;
}
}
void deserializeTextESM3ExteriorCellRefId(benchmark::State& state)
{
std::minstd_rand random;
std::vector<std::string> serializedRefIds
= generateSerializedESM3ExteriorCellRefIds(random, [](ESM::RefId v) { return v.serializeText(); });
std::size_t i = 0;
for (auto _ : state)
{
benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i]));
if (++i >= serializedRefIds.size())
i = 0;
}
}
}
BENCHMARK(serializeRefId)->RangeMultiplier(4)->Range(8, 64);
BENCHMARK(deserializeRefId)->RangeMultiplier(4)->Range(8, 64);
BENCHMARK(serializeTextStringRefId)->RangeMultiplier(4)->Range(8, 64);
BENCHMARK(deserializeTextStringRefId)->RangeMultiplier(4)->Range(8, 64);
BENCHMARK(serializeTextGeneratedRefId);
BENCHMARK(deserializeTextGeneratedRefId);
BENCHMARK(serializeTextIndexRefId);
BENCHMARK(deserializeTextIndexRefId);
BENCHMARK(serializeTextESM3ExteriorCellRefId);
BENCHMARK(deserializeTextESM3ExteriorCellRefId);
BENCHMARK_MAIN();

@ -0,0 +1,18 @@
openmw_add_executable(openmw_settings_access_benchmark access.cpp)
target_link_libraries(openmw_settings_access_benchmark benchmark::benchmark components)
target_compile_definitions(openmw_settings_access_benchmark
PRIVATE OPENMW_PROJECT_SOURCE_DIR=u8"${PROJECT_SOURCE_DIR}")
if (UNIX AND NOT APPLE)
target_link_libraries(openmw_settings_access_benchmark ${CMAKE_THREAD_LIBS_INIT})
endif()
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC)
target_precompile_headers(openmw_settings_access_benchmark PRIVATE <algorithm>)
endif()
if (BUILD_WITH_CODE_COVERAGE)
target_compile_options(openmw_settings_access_benchmark PRIVATE --coverage)
target_link_libraries(openmw_settings_access_benchmark gcov)
endif()

@ -0,0 +1,162 @@
#include <benchmark/benchmark.h>
#include "components/misc/strings/conversion.hpp"
#include "components/settings/parser.hpp"
#include "components/settings/settings.hpp"
#include "components/settings/values.hpp"
namespace
{
void settingsManager(benchmark::State& state)
{
for (auto _ : state)
{
benchmark::DoNotOptimize(Settings::Manager::getFloat("sky blending start", "Fog"));
}
}
void settingsManager2(benchmark::State& state)
{
for (auto _ : state)
{
benchmark::DoNotOptimize(Settings::Manager::getFloat("near clip", "Camera"));
benchmark::DoNotOptimize(Settings::Manager::getBool("transparent postpass", "Post Processing"));
}
}
void settingsManager3(benchmark::State& state)
{
for (auto _ : state)
{
benchmark::DoNotOptimize(Settings::Manager::getFloat("near clip", "Camera"));
benchmark::DoNotOptimize(Settings::Manager::getBool("transparent postpass", "Post Processing"));
benchmark::DoNotOptimize(Settings::Manager::getInt("reflection detail", "Water"));
}
}
void localStatic(benchmark::State& state)
{
for (auto _ : state)
{
static const float v = Settings::Manager::getFloat("sky blending start", "Fog");
benchmark::DoNotOptimize(v);
}
}
void localStatic2(benchmark::State& state)
{
for (auto _ : state)
{
static const float v1 = Settings::Manager::getFloat("near clip", "Camera");
static const bool v2 = Settings::Manager::getBool("transparent postpass", "Post Processing");
benchmark::DoNotOptimize(v1);
benchmark::DoNotOptimize(v2);
}
}
void localStatic3(benchmark::State& state)
{
for (auto _ : state)
{
static const float v1 = Settings::Manager::getFloat("near clip", "Camera");
static const bool v2 = Settings::Manager::getBool("transparent postpass", "Post Processing");
static const int v3 = Settings::Manager::getInt("reflection detail", "Water");
benchmark::DoNotOptimize(v1);
benchmark::DoNotOptimize(v2);
benchmark::DoNotOptimize(v3);
}
}
void settingsStorage(benchmark::State& state)
{
for (auto _ : state)
{
benchmark::DoNotOptimize(Settings::fog().mSkyBlendingStart.get());
}
}
void settingsStorage2(benchmark::State& state)
{
for (auto _ : state)
{
benchmark::DoNotOptimize(Settings::postProcessing().mTransparentPostpass.get());
benchmark::DoNotOptimize(Settings::camera().mNearClip.get());
}
}
void settingsStorage3(benchmark::State& state)
{
for (auto _ : state)
{
benchmark::DoNotOptimize(Settings::postProcessing().mTransparentPostpass.get());
benchmark::DoNotOptimize(Settings::camera().mNearClip.get());
benchmark::DoNotOptimize(Settings::water().mReflectionDetail.get());
}
}
void settingsStorageGet(benchmark::State& state)
{
for (auto _ : state)
{
benchmark::DoNotOptimize(Settings::get<float>("Fog", "sky blending start"));
}
}
void settingsStorageGet2(benchmark::State& state)
{
for (auto _ : state)
{
benchmark::DoNotOptimize(Settings::get<bool>("Post Processing", "transparent postpass"));
benchmark::DoNotOptimize(Settings::get<float>("Camera", "near clip"));
}
}
void settingsStorageGet3(benchmark::State& state)
{
for (auto _ : state)
{
benchmark::DoNotOptimize(Settings::get<bool>("Post Processing", "transparent postpass"));
benchmark::DoNotOptimize(Settings::get<float>("Camera", "near clip"));
benchmark::DoNotOptimize(Settings::get<int>("Water", "reflection detail"));
}
}
}
BENCHMARK(settingsManager);
BENCHMARK(localStatic);
BENCHMARK(settingsStorage);
BENCHMARK(settingsStorageGet);
BENCHMARK(settingsManager2);
BENCHMARK(localStatic2);
BENCHMARK(settingsStorage2);
BENCHMARK(settingsStorageGet2);
BENCHMARK(settingsManager3);
BENCHMARK(localStatic3);
BENCHMARK(settingsStorage3);
BENCHMARK(settingsStorageGet3);
int main(int argc, char* argv[])
{
const std::filesystem::path settingsDefaultPath = std::filesystem::path{ OPENMW_PROJECT_SOURCE_DIR } / "files"
/ Misc::StringUtils::stringToU8String("settings-default.cfg");
Settings::SettingsFileParser parser;
parser.loadSettingsFile(settingsDefaultPath, Settings::Manager::mDefaultSettings);
Settings::StaticValues::initDefaults();
Settings::Manager::mUserSettings = Settings::Manager::mDefaultSettings;
Settings::Manager::mUserSettings.erase({ "Camera", "near clip" });
Settings::Manager::mUserSettings.erase({ "Post Processing", "transparent postpass" });
Settings::Manager::mUserSettings.erase({ "Water", "reflection detail" });
Settings::StaticValues::init();
benchmark::Initialize(&argc, argv);
benchmark::RunSpecifiedBenchmarks();
benchmark::Shutdown();
return 0;
}

@ -1,20 +1,27 @@
set(BSATOOL
bsatool.cpp
bsatool.cpp
)
source_group(apps\\bsatool FILES ${BSATOOL})
# Main executable
openmw_add_executable(bsatool
${BSATOOL}
${BSATOOL}
)
target_link_libraries(bsatool
${Boost_PROGRAM_OPTIONS_LIBRARY}
${Boost_FILESYSTEM_LIBRARY}
components
${Boost_PROGRAM_OPTIONS_LIBRARY}
components
)
if (BUILD_WITH_CODE_COVERAGE)
add_definitions (--coverage)
target_link_libraries(bsatool gcov)
target_compile_options(bsatool PRIVATE --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,63 +1,69 @@
#include <iostream>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <vector>
#include <boost/program_options.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <components/bsa/ba2dx10file.hpp>
#include <components/bsa/ba2gnrlfile.hpp>
#include <components/bsa/compressedbsafile.hpp>
#include <components/misc/stringops.hpp>
#include <components/files/configurationmanager.hpp>
#include <components/files/conversion.hpp>
#include <components/misc/strings/algorithm.hpp>
#include <components/misc/strings/conversion.hpp>
#define BSATOOL_VERSION 1.1
// 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;
std::filesystem::path filename;
std::filesystem::path extractfile;
std::filesystem::path addfile;
std::filesystem::path outdir;
bool longformat;
bool fullpath;
};
bool parseOptions (int argc, char** argv, Arguments &info)
bool parseOptions(int argc, char** argv, Arguments& info)
{
bpo::options_description desc("Inspect and extract files from Bethesda BSA archives\n\n"
"Usages:\n"
" bsatool list [-l] archivefile\n"
" List the files presents in the input archive.\n\n"
" bsatool extract [-f] archivefile [file_to_extract] [output_directory]\n"
" 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()
("help,h", "print help message.")
("version,v", "print version information and quit.")
("long,l", "Include extra information in archive listing.")
("full-path,f", "Create directory hierarchy on file extraction "
"(always true for extractall).")
;
bpo::options_description desc(R"(Inspect and extract files from Bethesda BSA archives
Usages:
bsatool list [-l] archivefile\n
List the files presents in the input archive.
bsatool extract [-f] archivefile [file_to_extract] [output_directory]
Extract a file from the input archive.
bsatool extractall archivefile [output_directory]
Extract all files from the input archive.
bsatool add [-a] archivefile file_to_add
Add a file to the input archive.
bsatool create [-c] archivefile
Create an archive.
Allowed options)");
auto addOption = desc.add_options();
addOption("help,h", "print help message.");
addOption("version,v", "print version information and quit.");
addOption("long,l", "Include extra information in archive listing.");
addOption("full-path,f", "Create directory hierarchy on file extraction (always true for extractall).");
// input-file is hidden and used as a positional argument
bpo::options_description hidden("Hidden Options");
hidden.add_options()
( "mode,m", bpo::value<std::string>(), "bsatool mode")
( "input-file,i", bpo::value< std::vector<std::string> >(), "input file")
;
auto addHiddenOption = hidden.add_options();
addHiddenOption("mode,m", bpo::value<std::string>(), "bsatool mode");
addHiddenOption("input-file,i", bpo::value<Files::MaybeQuotedPathContainer>(), "input file");
bpo::positional_options_description p;
p.add("mode", 1).add("input-file", 3);
@ -69,81 +75,82 @@ bool parseOptions (int argc, char** argv, Arguments &info)
bpo::variables_map variables;
try
{
bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv)
.options(all).positional(p).run();
bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv).options(all).positional(p).run();
bpo::store(valid_opts, variables);
}
catch(std::exception &e)
catch (std::exception& e)
{
std::cout << "ERROR parsing arguments: " << e.what() << "\n\n"
<< desc << std::endl;
std::cout << "ERROR parsing arguments: " << e.what() << "\n\n" << desc << std::endl;
return false;
}
bpo::notify(variables);
if (variables.count ("help"))
if (variables.count("help"))
{
std::cout << desc << std::endl;
return false;
}
if (variables.count ("version"))
if (variables.count("version"))
{
std::cout << "BSATool version " << BSATOOL_VERSION << std::endl;
return false;
}
if (!variables.count("mode"))
{
std::cout << "ERROR: no mode specified!\n\n"
<< desc << std::endl;
std::cout << "ERROR: no mode specified!\n\n" << desc << std::endl;
return false;
}
info.mode = variables["mode"].as<std::string>();
if (!(info.mode == "list" || info.mode == "extract" || info.mode == "extractall" || info.mode == "add" || info.mode == "create"))
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;
std::cout << std::endl << "ERROR: invalid mode \"" << info.mode << "\"\n\n" << desc << std::endl;
return false;
}
if (!variables.count("input-file"))
{
std::cout << "\nERROR: missing BSA archive\n\n"
<< desc << std::endl;
std::cout << "\nERROR: missing BSA archive\n\n" << desc << std::endl;
return false;
}
info.filename = variables["input-file"].as< std::vector<std::string> >()[0];
auto inputFiles = variables["input-file"].as<Files::MaybeQuotedPathContainer>();
info.filename = inputFiles[0].u8string(); // This call to u8string is redundant, but required to build on MSVC 14.26
// due to implementation bugs.
// Default output to the working directory
info.outdir = ".";
info.outdir = std::filesystem::current_path();
if (info.mode == "extract")
{
if (variables["input-file"].as< std::vector<std::string> >().size() < 2)
if (inputFiles.size() < 2)
{
std::cout << "\nERROR: file to extract unspecified\n\n"
<< desc << std::endl;
std::cout << "\nERROR: file to extract unspecified\n\n" << desc << std::endl;
return false;
}
if (variables["input-file"].as< std::vector<std::string> >().size() > 1)
info.extractfile = variables["input-file"].as< std::vector<std::string> >()[1];
if (variables["input-file"].as< std::vector<std::string> >().size() > 2)
info.outdir = variables["input-file"].as< std::vector<std::string> >()[2];
if (inputFiles.size() > 1)
info.extractfile = inputFiles[1].u8string(); // This call to u8string is redundant, but required to build on
// MSVC 14.26 due to implementation bugs.
if (inputFiles.size() > 2)
info.outdir = inputFiles[2].u8string(); // This call to u8string is redundant, but required to build on
// MSVC 14.26 due to implementation bugs.
}
else if (info.mode == "add")
{
if (variables["input-file"].as< std::vector<std::string> >().size() < 1)
if (inputFiles.empty())
{
std::cout << "\nERROR: file to add unspecified\n\n"
<< desc << std::endl;
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];
if (inputFiles.size() > 1)
info.addfile = inputFiles[1].u8string(); // This call to u8string is redundant, but required to build on
// MSVC 14.26 due to implementation bugs.
}
else if (variables["input-file"].as< std::vector<std::string> >().size() > 1)
info.outdir = variables["input-file"].as< std::vector<std::string> >()[1];
else if (inputFiles.size() > 1)
info.outdir = inputFiles[1].u8string(); // This call to u8string is redundant, but required to build on
// MSVC 14.26 due to implementation bugs.
info.longformat = variables.count("long") != 0;
info.fullpath = variables.count("full-path") != 0;
@ -151,65 +158,14 @@ 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 add(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());
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;
}
}
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)
if (info.longformat)
{
// Long format
std::ios::fmtflags f(std::cout.flags());
@ -225,48 +181,57 @@ int list(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info)
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, "/", "\\");
auto archivePath = info.extractfile.u8string();
Misc::StringUtils::replaceAll(archivePath, u8"/", u8"\\");
std::string extractPath = info.extractfile;
Misc::StringUtils::replaceAll(extractPath, "\\", "/");
auto extractPath = info.extractfile.u8string();
Misc::StringUtils::replaceAll(extractPath, u8"\\", u8"/");
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(Misc::StringUtils::stringToU8String(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;
std::cout << "ERROR: file '" << Misc::StringUtils::u8StringToString(archivePath) << "' not found\n";
std::cout << "In archive: " << Files::pathToUnicodeString(info.filename) << std::endl;
return 3;
}
// 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);
bfs::path target;
std::filesystem::path target;
if (info.fullpath)
target = outdir / relPath;
target = info.outdir / relPath;
else
target = outdir / relPath.filename();
target = info.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;
std::cout << "ERROR: " << Files::pathToUnicodeString(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;
std::cout << "Extracting " << Files::pathToUnicodeString(info.extractfile) << " to "
<< Files::pathToUnicodeString(target) << std::endl;
out << stream->rdbuf();
out.close();
@ -274,34 +239,34 @@ 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())
for (const auto& file : bsa->getList())
{
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);
target /= extractPath;
auto target = info.outdir;
target /= Misc::StringUtils::stringToU8String(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;
std::cout << "Extracting " << Files::pathToUnicodeString(target) << std::endl;
out << data->rdbuf();
out.close();
}
@ -309,10 +274,71 @@ int extractAll(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info)
return 0;
}
int add(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info)
template <typename File>
int add(std::unique_ptr<File>& bsa, Arguments& info)
{
boost::filesystem::fstream stream(info.addfile, std::ios_base::binary | std::ios_base::out | std::ios_base::in);
bsa->addFile(info.addfile, stream);
std::fstream stream(info.addfile, std::ios_base::binary | std::ios_base::out | std::ios_base::in);
bsa->addFile(Files::pathToUnicodeString(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::BSAFile::detectVersion(info.filename);
switch (bsaVersion)
{
case Bsa::BSAVER_COMPRESSED:
return call<Bsa::CompressedBSAFile>(info);
case Bsa::BSAVER_BA2_GNRL:
return call<Bsa::BA2GNRLFile>(info);
case Bsa::BSAVER_BA2_DX10:
return call<Bsa::BA2DX10File>(info);
case Bsa::BSAVER_UNCOMPRESSED:
return call<Bsa::BSAFile>(info);
default:
throw std::runtime_error("Unrecognised BSA archive");
}
}
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)
target_compile_options(openmw-bulletobjecttool PRIVATE --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,206 @@
#include <components/debug/debugging.hpp>
#include <components/debug/debuglog.hpp>
#include <components/esm/defs.hpp>
#include <components/esm3/loadcell.hpp>
#include <components/esm3/readerscache.hpp>
#include <components/esmloader/esmdata.hpp>
#include <components/esmloader/load.hpp>
#include <components/fallback/fallback.hpp>
#include <components/fallback/validate.hpp>
#include <components/files/collections.hpp>
#include <components/files/configurationmanager.hpp>
#include <components/files/multidircollection.hpp>
#include <components/misc/strings/conversion.hpp>
#include <components/platform/platform.hpp>
#include <components/resource/bulletshape.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/to_utf8/to_utf8.hpp>
#include <components/version/version.hpp>
#include <components/vfs/manager.hpp>
#include <components/vfs/registerarchives.hpp>
#include <boost/program_options.hpp>
#include <charconv>
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <iomanip>
#include <limits>
#include <map>
#include <memory>
#include <ostream>
#include <string>
#include <string_view>
#include <system_error>
#include <type_traits>
#include <utility>
#include <vector>
namespace
{
namespace bpo = boost::program_options;
using StringsVector = std::vector<std::string>;
constexpr std::string_view applicationName = "BulletObjectTool";
bpo::options_description makeOptionsDescription()
{
using Fallback::FallbackMap;
bpo::options_description result;
auto addOption = result.add_options();
addOption("help", "print help message");
addOption("version", "print version information and quit");
addOption("data",
bpo::value<Files::MaybeQuotedPathContainer>()
->default_value(Files::MaybeQuotedPathContainer(), "data")
->multitoken()
->composing(),
"set data directories (later directories have higher priority)");
addOption("data-local",
bpo::value<Files::MaybeQuotedPathContainer::value_type>()->default_value(
Files::MaybeQuotedPathContainer::value_type(), ""),
"set local data directory (highest priority)");
addOption("fallback-archive",
bpo::value<StringsVector>()->default_value(StringsVector(), "fallback-archive")->multitoken()->composing(),
"set fallback BSA archives (later archives have higher priority)");
addOption("resources",
bpo::value<Files::MaybeQuotedPath>()->default_value(Files::MaybeQuotedPath(), "resources"),
"set resources directory");
addOption("content", bpo::value<StringsVector>()->default_value(StringsVector(), "")->multitoken()->composing(),
"content file(s): esm/esp, or omwgame/omwaddon/omwscripts");
addOption("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");
addOption("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];
}
};
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;
config.readConfiguration(variables, desc);
setupLogging(config.getLogPath(), applicationName);
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 resDir = variables["resources"].as<Files::MaybeQuotedPath>();
const auto v = Version::getOpenmwVersion(resDir);
Log(Debug::Info) << v.describe();
dataDirs.insert(dataDirs.begin(), resDir / "vfs");
const auto fileCollections = Files::Collections(dataDirs);
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;
VFS::registerArchives(&vfs, fileCollections, archives, true);
Settings::Manager::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=" << Misc::StringUtils::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, applicationName);
}

@ -1,23 +1,34 @@
set(ESMTOOL
esmtool.cpp
labels.hpp
labels.cpp
record.hpp
record.cpp
esmtool.cpp
labels.hpp
labels.cpp
record.hpp
record.cpp
arguments.hpp
tes4.hpp
tes4.cpp
)
source_group(apps\\esmtool FILES ${ESMTOOL})
# Main executable
openmw_add_executable(esmtool
${ESMTOOL}
${ESMTOOL}
)
target_link_libraries(esmtool
${Boost_PROGRAM_OPTIONS_LIBRARY}
components
${Boost_PROGRAM_OPTIONS_LIBRARY}
components
)
if (BUILD_WITH_CODE_COVERAGE)
add_definitions (--coverage)
target_link_libraries(esmtool gcov)
target_compile_options(esmtool PRIVATE --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,29 @@
#ifndef OPENMW_ESMTOOL_ARGUMENTS_H
#define OPENMW_ESMTOOL_ARGUMENTS_H
#include <filesystem>
#include <optional>
#include <vector>
#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::filesystem::path filename;
std::filesystem::path outname;
std::vector<std::string> types;
std::string name;
};
}
#endif

File diff suppressed because it is too large Load Diff

@ -1,25 +1,25 @@
#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/misc/stringops.hpp>
std::string bodyPartLabel(int idx)
#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/strings/format.hpp>
std::string_view bodyPartLabel(int idx)
{
if (idx >= 0 && idx <= 26)
{
static const char *bodyPartLabels[] = {
static constexpr std::string_view bodyPartLabels[] = {
"Head",
"Hair",
"Neck",
@ -46,7 +46,7 @@ std::string bodyPartLabel(int idx)
"Right Shoulder",
"Left Shoulder",
"Weapon",
"Tail"
"Tail",
};
return bodyPartLabels[idx];
}
@ -54,11 +54,11 @@ std::string bodyPartLabel(int idx)
return "Invalid";
}
std::string meshPartLabel(int idx)
std::string_view meshPartLabel(int idx)
{
if (idx >= 0 && idx <= ESM::BodyPart::MP_Tail)
{
static const char *meshPartLabels[] = {
static constexpr std::string_view meshPartLabels[] = {
"Head",
"Hair",
"Neck",
@ -73,7 +73,7 @@ std::string meshPartLabel(int idx)
"Knee",
"Upper Leg",
"Clavicle",
"Tail"
"Tail",
};
return meshPartLabels[idx];
}
@ -81,14 +81,14 @@ std::string meshPartLabel(int idx)
return "Invalid";
}
std::string meshTypeLabel(int idx)
std::string_view meshTypeLabel(int idx)
{
if (idx >= 0 && idx <= ESM::BodyPart::MT_Armor)
{
static const char *meshTypeLabels[] = {
static constexpr std::string_view meshTypeLabels[] = {
"Skin",
"Clothing",
"Armor"
"Armor",
};
return meshTypeLabels[idx];
}
@ -96,11 +96,11 @@ std::string meshTypeLabel(int idx)
return "Invalid";
}
std::string clothingTypeLabel(int idx)
std::string_view clothingTypeLabel(int idx)
{
if (idx >= 0 && idx <= 9)
{
static const char *clothingTypeLabels[] = {
static constexpr std::string_view clothingTypeLabels[] = {
"Pants",
"Shoes",
"Shirt",
@ -110,7 +110,7 @@ std::string clothingTypeLabel(int idx)
"Left Glove",
"Skirt",
"Ring",
"Amulet"
"Amulet",
};
return clothingTypeLabels[idx];
}
@ -118,11 +118,11 @@ std::string clothingTypeLabel(int idx)
return "Invalid";
}
std::string armorTypeLabel(int idx)
std::string_view armorTypeLabel(int idx)
{
if (idx >= 0 && idx <= 10)
{
static const char *armorTypeLabels[] = {
static constexpr std::string_view armorTypeLabels[] = {
"Helmet",
"Cuirass",
"Left Pauldron",
@ -133,7 +133,7 @@ std::string armorTypeLabel(int idx)
"Right Gauntlet",
"Shield",
"Left Bracer",
"Right Bracer"
"Right Bracer",
};
return armorTypeLabels[idx];
}
@ -141,16 +141,16 @@ std::string armorTypeLabel(int idx)
return "Invalid";
}
std::string dialogTypeLabel(int idx)
std::string_view dialogTypeLabel(int idx)
{
if (idx >= 0 && idx <= 4)
{
static const char *dialogTypeLabels[] = {
static constexpr std::string_view dialogTypeLabels[] = {
"Topic",
"Voice",
"Greeting",
"Persuasion",
"Journal"
"Journal",
};
return dialogTypeLabels[idx];
}
@ -160,16 +160,16 @@ std::string dialogTypeLabel(int idx)
return "Invalid";
}
std::string questStatusLabel(int idx)
std::string_view questStatusLabel(int idx)
{
if (idx >= 0 && idx <= 4)
{
static const char *questStatusLabels[] = {
static constexpr std::string_view questStatusLabels[] = {
"None",
"Name",
"Finished",
"Restart",
"Deleted"
"Deleted",
};
return questStatusLabels[idx];
}
@ -177,11 +177,11 @@ std::string questStatusLabel(int idx)
return "Invalid";
}
std::string creatureTypeLabel(int idx)
std::string_view creatureTypeLabel(int idx)
{
if (idx >= 0 && idx <= 3)
{
static const char *creatureTypeLabels[] = {
static constexpr std::string_view creatureTypeLabels[] = {
"Creature",
"Daedra",
"Undead",
@ -193,11 +193,11 @@ std::string creatureTypeLabel(int idx)
return "Invalid";
}
std::string soundTypeLabel(int idx)
std::string_view soundTypeLabel(int idx)
{
if (idx >= 0 && idx <= 7)
{
static const char *soundTypeLabels[] = {
static constexpr std::string_view soundTypeLabels[] = {
"Left Foot",
"Right Foot",
"Swim Left",
@ -205,7 +205,7 @@ std::string soundTypeLabel(int idx)
"Moan",
"Roar",
"Scream",
"Land"
"Land",
};
return soundTypeLabels[idx];
}
@ -213,11 +213,11 @@ std::string soundTypeLabel(int idx)
return "Invalid";
}
std::string weaponTypeLabel(int idx)
std::string_view weaponTypeLabel(int idx)
{
if (idx >= 0 && idx <= 13)
{
static const char *weaponTypeLabels[] = {
static constexpr std::string_view weaponTypeLabels[] = {
"Short Blade One Hand",
"Long Blade One Hand",
"Long Blade Two Hand",
@ -231,7 +231,7 @@ std::string weaponTypeLabel(int idx)
"Marksman Crossbow",
"Marksman Thrown",
"Arrow",
"Bolt"
"Bolt",
};
return weaponTypeLabels[idx];
}
@ -239,21 +239,29 @@ std::string weaponTypeLabel(int idx)
return "Invalid";
}
std::string aiTypeLabel(int type)
std::string_view aiTypeLabel(ESM::AiPackageType type)
{
if (type == ESM::AI_Wander) return "Wander";
else if (type == ESM::AI_Travel) return "Travel";
else if (type == ESM::AI_Follow) return "Follow";
else if (type == ESM::AI_Escort) return "Escort";
else if (type == ESM::AI_Activate) return "Activate";
else return "Invalid";
switch (type)
{
case ESM::AI_Wander:
return "Wander";
case ESM::AI_Travel:
return "Travel";
case ESM::AI_Follow:
return "Follow";
case ESM::AI_Escort:
return "Escort";
case ESM::AI_Activate:
return "Activate";
}
return "Invalid";
}
std::string magicEffectLabel(int idx)
std::string_view magicEffectLabel(int idx)
{
if (idx >= 0 && idx <= 142)
{
const char* magicEffectLabels [] = {
static constexpr std::string_view magicEffectLabels[] = {
"Water Breathing",
"Swift Swim",
"Water Walking",
@ -396,7 +404,7 @@ std::string magicEffectLabel(int idx)
"sEffectSummonCreature02",
"sEffectSummonCreature03",
"sEffectSummonCreature04",
"sEffectSummonCreature05"
"sEffectSummonCreature05",
};
return magicEffectLabels[idx];
}
@ -404,11 +412,11 @@ std::string magicEffectLabel(int idx)
return "Invalid";
}
std::string attributeLabel(int idx)
std::string_view attributeLabel(int idx)
{
if (idx >= 0 && idx <= 7)
{
const char* attributeLabels [] = {
static constexpr std::string_view attributeLabels[] = {
"Strength",
"Intelligence",
"Willpower",
@ -416,7 +424,7 @@ std::string attributeLabel(int idx)
"Speed",
"Endurance",
"Personality",
"Luck"
"Luck",
};
return attributeLabels[idx];
}
@ -424,17 +432,17 @@ std::string attributeLabel(int idx)
return "Invalid";
}
std::string spellTypeLabel(int idx)
std::string_view spellTypeLabel(int idx)
{
if (idx >= 0 && idx <= 5)
{
const char* spellTypeLabels [] = {
static constexpr std::string_view spellTypeLabels[] = {
"Spells",
"Abilities",
"Blight Disease",
"Disease",
"Curse",
"Powers"
"Powers",
};
return spellTypeLabels[idx];
}
@ -442,14 +450,14 @@ std::string spellTypeLabel(int idx)
return "Invalid";
}
std::string specializationLabel(int idx)
std::string_view specializationLabel(int idx)
{
if (idx >= 0 && idx <= 2)
{
const char* specializationLabels [] = {
static constexpr std::string_view specializationLabels[] = {
"Combat",
"Magic",
"Stealth"
"Stealth",
};
return specializationLabels[idx];
}
@ -457,11 +465,11 @@ std::string specializationLabel(int idx)
return "Invalid";
}
std::string skillLabel(int idx)
std::string_view skillLabel(int idx)
{
if (idx >= 0 && idx <= 26)
{
const char* skillLabels [] = {
static constexpr std::string_view skillLabels[] = {
"Block",
"Armorer",
"Medium Armor",
@ -488,7 +496,7 @@ std::string skillLabel(int idx)
"Marksman",
"Mercantile",
"Speechcraft",
"Hand-to-hand"
"Hand-to-hand",
};
return skillLabels[idx];
}
@ -496,11 +504,11 @@ std::string skillLabel(int idx)
return "Invalid";
}
std::string apparatusTypeLabel(int idx)
std::string_view apparatusTypeLabel(int idx)
{
if (idx >= 0 && idx <= 3)
{
const char* apparatusTypeLabels [] = {
static constexpr std::string_view apparatusTypeLabels[] = {
"Mortar",
"Alembic",
"Calcinator",
@ -512,14 +520,14 @@ std::string apparatusTypeLabel(int idx)
return "Invalid";
}
std::string rangeTypeLabel(int idx)
std::string_view rangeTypeLabel(int idx)
{
if (idx >= 0 && idx <= 2)
{
const char* rangeTypeLabels [] = {
static constexpr std::string_view rangeTypeLabels[] = {
"Self",
"Touch",
"Target"
"Target",
};
return rangeTypeLabels[idx];
}
@ -527,17 +535,17 @@ std::string rangeTypeLabel(int idx)
return "Invalid";
}
std::string schoolLabel(int idx)
std::string_view schoolLabel(int idx)
{
if (idx >= 0 && idx <= 5)
{
const char* schoolLabels [] = {
static constexpr std::string_view schoolLabels[] = {
"Alteration",
"Conjuration",
"Destruction",
"Illusion",
"Mysticism",
"Restoration"
"Restoration",
};
return schoolLabels[idx];
}
@ -545,15 +553,15 @@ std::string schoolLabel(int idx)
return "Invalid";
}
std::string enchantTypeLabel(int idx)
std::string_view enchantTypeLabel(int idx)
{
if (idx >= 0 && idx <= 3)
{
const char* enchantTypeLabels [] = {
static constexpr std::string_view enchantTypeLabels[] = {
"Cast Once",
"Cast When Strikes",
"Cast When Used",
"Constant Effect"
"Constant Effect",
};
return enchantTypeLabels[idx];
}
@ -561,11 +569,11 @@ std::string enchantTypeLabel(int idx)
return "Invalid";
}
std::string ruleFunction(int idx)
std::string_view ruleFunction(int idx)
{
if (idx >= 0 && idx <= 72)
{
std::string ruleFunctions[] = {
static constexpr std::string_view ruleFunctions[] = {
"Reaction Low",
"Reaction High",
"Rank Requirement",
@ -638,7 +646,7 @@ std::string ruleFunction(int idx)
"Alarm",
"Flee",
"Should Attack",
"Werewolf"
"Werewolf",
};
return ruleFunctions[idx];
}
@ -653,13 +661,15 @@ std::string ruleFunction(int idx)
std::string bodyPartFlags(int flags)
{
std::string properties;
if (flags == 0) properties += "[None] ";
if (flags & ESM::BodyPart::BPF_Female) properties += "Female ";
if (flags & ESM::BodyPart::BPF_NotPlayable) properties += "NotPlayable ";
int unused = (0xFFFFFFFF ^
(ESM::BodyPart::BPF_Female|
ESM::BodyPart::BPF_NotPlayable));
if (flags & unused) properties += "Invalid ";
if (flags == 0)
properties += "[None] ";
if (flags & ESM::BodyPart::BPF_Female)
properties += "Female ";
if (flags & ESM::BodyPart::BPF_NotPlayable)
properties += "NotPlayable ";
int unused = (0xFFFFFFFF ^ (ESM::BodyPart::BPF_Female | ESM::BodyPart::BPF_NotPlayable));
if (flags & unused)
properties += "Invalid ";
properties += Misc::StringUtils::format("(0x%08X)", flags);
return properties;
}
@ -667,20 +677,23 @@ std::string bodyPartFlags(int flags)
std::string cellFlags(int flags)
{
std::string properties;
if (flags == 0) properties += "[None] ";
if (flags & ESM::Cell::HasWater) properties += "HasWater ";
if (flags & ESM::Cell::Interior) properties += "Interior ";
if (flags & ESM::Cell::NoSleep) properties += "NoSleep ";
if (flags & ESM::Cell::QuasiEx) properties += "QuasiEx ";
if (flags == 0)
properties += "[None] ";
if (flags & ESM::Cell::HasWater)
properties += "HasWater ";
if (flags & ESM::Cell::Interior)
properties += "Interior ";
if (flags & ESM::Cell::NoSleep)
properties += "NoSleep ";
if (flags & ESM::Cell::QuasiEx)
properties += "QuasiEx ";
// This used value is not in the ESM component.
if (flags & 0x00000040) properties += "Unknown ";
int unused = (0xFFFFFFFF ^
(ESM::Cell::HasWater|
ESM::Cell::Interior|
ESM::Cell::NoSleep|
ESM::Cell::QuasiEx|
0x00000040));
if (flags & unused) properties += "Invalid ";
if (flags & 0x00000040)
properties += "Unknown ";
int unused = (0xFFFFFFFF
^ (ESM::Cell::HasWater | ESM::Cell::Interior | ESM::Cell::NoSleep | ESM::Cell::QuasiEx | 0x00000040));
if (flags & unused)
properties += "Invalid ";
properties += Misc::StringUtils::format("(0x%08X)", flags);
return properties;
}
@ -688,15 +701,17 @@ std::string cellFlags(int flags)
std::string containerFlags(int flags)
{
std::string properties;
if (flags == 0) properties += "[None] ";
if (flags & ESM::Container::Unknown) properties += "Unknown ";
if (flags & ESM::Container::Organic) properties += "Organic ";
if (flags & ESM::Container::Respawn) properties += "Respawn ";
int unused = (0xFFFFFFFF ^
(ESM::Container::Unknown|
ESM::Container::Organic|
ESM::Container::Respawn));
if (flags & unused) properties += "Invalid ";
if (flags == 0)
properties += "[None] ";
if (flags & ESM::Container::Unknown)
properties += "Unknown ";
if (flags & ESM::Container::Organic)
properties += "Organic ";
if (flags & ESM::Container::Respawn)
properties += "Respawn ";
int unused = (0xFFFFFFFF ^ (ESM::Container::Unknown | ESM::Container::Organic | ESM::Container::Respawn));
if (flags & unused)
properties += "Invalid ";
properties += Misc::StringUtils::format("(0x%08X)", flags);
return properties;
}
@ -704,25 +719,29 @@ std::string containerFlags(int flags)
std::string creatureFlags(int flags)
{
std::string properties;
if (flags == 0) properties += "[None] ";
if (flags & ESM::Creature::Base) properties += "Base ";
if (flags & ESM::Creature::Walks) properties += "Walks ";
if (flags & ESM::Creature::Swims) properties += "Swims ";
if (flags & ESM::Creature::Flies) properties += "Flies ";
if (flags & ESM::Creature::Bipedal) properties += "Bipedal ";
if (flags & ESM::Creature::Respawn) properties += "Respawn ";
if (flags & ESM::Creature::Weapon) properties += "Weapon ";
if (flags & ESM::Creature::Essential) properties += "Essential ";
int unused = (0xFFFFFFFF ^
(ESM::Creature::Base|
ESM::Creature::Walks|
ESM::Creature::Swims|
ESM::Creature::Flies|
ESM::Creature::Bipedal|
ESM::Creature::Respawn|
ESM::Creature::Weapon|
ESM::Creature::Essential));
if (flags & unused) properties += "Invalid ";
if (flags == 0)
properties += "[None] ";
if (flags & ESM::Creature::Base)
properties += "Base ";
if (flags & ESM::Creature::Walks)
properties += "Walks ";
if (flags & ESM::Creature::Swims)
properties += "Swims ";
if (flags & ESM::Creature::Flies)
properties += "Flies ";
if (flags & ESM::Creature::Bipedal)
properties += "Bipedal ";
if (flags & ESM::Creature::Respawn)
properties += "Respawn ";
if (flags & ESM::Creature::Weapon)
properties += "Weapon ";
if (flags & ESM::Creature::Essential)
properties += "Essential ";
int unused = (0xFFFFFFFF
^ (ESM::Creature::Base | ESM::Creature::Walks | ESM::Creature::Swims | ESM::Creature::Flies
| ESM::Creature::Bipedal | ESM::Creature::Respawn | ESM::Creature::Weapon | ESM::Creature::Essential));
if (flags & unused)
properties += "Invalid ";
properties += Misc::StringUtils::format("(0x%02X)", flags);
return properties;
}
@ -730,9 +749,12 @@ std::string creatureFlags(int flags)
std::string enchantmentFlags(int flags)
{
std::string properties;
if (flags == 0) properties += "[None] ";
if (flags & ESM::Enchantment::Autocalc) properties += "Autocalc ";
if (flags & (0xFFFFFFFF ^ ESM::Enchantment::Autocalc)) properties += "Invalid ";
if (flags == 0)
properties += "[None] ";
if (flags & ESM::Enchantment::Autocalc)
properties += "Autocalc ";
if (flags & (0xFFFFFFFF ^ ESM::Enchantment::Autocalc))
properties += "Invalid ";
properties += Misc::StringUtils::format("(0x%08X)", flags);
return properties;
}
@ -743,11 +765,16 @@ std::string landFlags(int flags)
// The ESM component says that this first four bits are used, but
// only the first three bits are used as far as I can tell.
// There's also no enumeration of the bit in the ESM component.
if (flags == 0) properties += "[None] ";
if (flags & 0x00000001) properties += "Unknown1 ";
if (flags & 0x00000004) properties += "Unknown3 ";
if (flags & 0x00000002) properties += "Unknown2 ";
if (flags & 0xFFFFFFF8) properties += "Invalid ";
if (flags == 0)
properties += "[None] ";
if (flags & 0x00000001)
properties += "Unknown1 ";
if (flags & 0x00000004)
properties += "Unknown3 ";
if (flags & 0x00000002)
properties += "Unknown2 ";
if (flags & 0xFFFFFFF8)
properties += "Invalid ";
properties += Misc::StringUtils::format("(0x%08X)", flags);
return properties;
}
@ -755,13 +782,15 @@ std::string landFlags(int flags)
std::string itemListFlags(int flags)
{
std::string properties;
if (flags == 0) properties += "[None] ";
if (flags & ESM::ItemLevList::AllLevels) properties += "AllLevels ";
if (flags & ESM::ItemLevList::Each) properties += "Each ";
int unused = (0xFFFFFFFF ^
(ESM::ItemLevList::AllLevels|
ESM::ItemLevList::Each));
if (flags & unused) properties += "Invalid ";
if (flags == 0)
properties += "[None] ";
if (flags & ESM::ItemLevList::AllLevels)
properties += "AllLevels ";
if (flags & ESM::ItemLevList::Each)
properties += "Each ";
int unused = (0xFFFFFFFF ^ (ESM::ItemLevList::AllLevels | ESM::ItemLevList::Each));
if (flags & unused)
properties += "Invalid ";
properties += Misc::StringUtils::format("(0x%08X)", flags);
return properties;
}
@ -769,10 +798,13 @@ std::string itemListFlags(int flags)
std::string creatureListFlags(int flags)
{
std::string properties;
if (flags == 0) properties += "[None] ";
if (flags & ESM::CreatureLevList::AllLevels) properties += "AllLevels ";
if (flags == 0)
properties += "[None] ";
if (flags & ESM::CreatureLevList::AllLevels)
properties += "AllLevels ";
int unused = (0xFFFFFFFF ^ ESM::CreatureLevList::AllLevels);
if (flags & unused) properties += "Invalid ";
if (flags & unused)
properties += "Invalid ";
properties += Misc::StringUtils::format("(0x%08X)", flags);
return properties;
}
@ -780,27 +812,31 @@ std::string creatureListFlags(int flags)
std::string lightFlags(int flags)
{
std::string properties;
if (flags == 0) properties += "[None] ";
if (flags & ESM::Light::Dynamic) properties += "Dynamic ";
if (flags & ESM::Light::Fire) properties += "Fire ";
if (flags & ESM::Light::Carry) properties += "Carry ";
if (flags & ESM::Light::Flicker) properties += "Flicker ";
if (flags & ESM::Light::FlickerSlow) properties += "FlickerSlow ";
if (flags & ESM::Light::Pulse) properties += "Pulse ";
if (flags & ESM::Light::PulseSlow) properties += "PulseSlow ";
if (flags & ESM::Light::Negative) properties += "Negative ";
if (flags & ESM::Light::OffDefault) properties += "OffDefault ";
int unused = (0xFFFFFFFF ^
(ESM::Light::Dynamic|
ESM::Light::Fire|
ESM::Light::Carry|
ESM::Light::Flicker|
ESM::Light::FlickerSlow|
ESM::Light::Pulse|
ESM::Light::PulseSlow|
ESM::Light::Negative|
ESM::Light::OffDefault));
if (flags & unused) properties += "Invalid ";
if (flags == 0)
properties += "[None] ";
if (flags & ESM::Light::Dynamic)
properties += "Dynamic ";
if (flags & ESM::Light::Fire)
properties += "Fire ";
if (flags & ESM::Light::Carry)
properties += "Carry ";
if (flags & ESM::Light::Flicker)
properties += "Flicker ";
if (flags & ESM::Light::FlickerSlow)
properties += "FlickerSlow ";
if (flags & ESM::Light::Pulse)
properties += "Pulse ";
if (flags & ESM::Light::PulseSlow)
properties += "PulseSlow ";
if (flags & ESM::Light::Negative)
properties += "Negative ";
if (flags & ESM::Light::OffDefault)
properties += "OffDefault ";
int unused = (0xFFFFFFFF
^ (ESM::Light::Dynamic | ESM::Light::Fire | ESM::Light::Carry | ESM::Light::Flicker | ESM::Light::FlickerSlow
| ESM::Light::Pulse | ESM::Light::PulseSlow | ESM::Light::Negative | ESM::Light::OffDefault));
if (flags & unused)
properties += "Invalid ";
properties += Misc::StringUtils::format("(0x%08X)", flags);
return properties;
}
@ -808,27 +844,47 @@ std::string lightFlags(int flags)
std::string magicEffectFlags(int flags)
{
std::string properties;
if (flags == 0) properties += "[None] ";
if (flags & ESM::MagicEffect::TargetAttribute) properties += "TargetAttribute ";
if (flags & ESM::MagicEffect::TargetSkill) properties += "TargetSkill ";
if (flags & ESM::MagicEffect::NoDuration) properties += "NoDuration ";
if (flags & ESM::MagicEffect::NoMagnitude) properties += "NoMagnitude ";
if (flags & ESM::MagicEffect::Harmful) properties += "Harmful ";
if (flags & ESM::MagicEffect::ContinuousVfx) properties += "ContinuousVFX ";
if (flags & ESM::MagicEffect::CastSelf) properties += "CastSelf ";
if (flags & ESM::MagicEffect::CastTouch) properties += "CastTouch ";
if (flags & ESM::MagicEffect::CastTarget) properties += "CastTarget ";
if (flags & ESM::MagicEffect::AppliedOnce) properties += "AppliedOnce ";
if (flags & ESM::MagicEffect::Stealth) properties += "Stealth ";
if (flags & ESM::MagicEffect::NonRecastable) properties += "NonRecastable ";
if (flags & ESM::MagicEffect::IllegalDaedra) properties += "IllegalDaedra ";
if (flags & ESM::MagicEffect::Unreflectable) properties += "Unreflectable ";
if (flags & ESM::MagicEffect::CasterLinked) properties += "CasterLinked ";
if (flags & ESM::MagicEffect::AllowSpellmaking) properties += "AllowSpellmaking ";
if (flags & ESM::MagicEffect::AllowEnchanting) properties += "AllowEnchanting ";
if (flags & ESM::MagicEffect::NegativeLight) properties += "NegativeLight ";
if (flags & 0xFFFC0000) properties += "Invalid ";
if (flags == 0)
properties += "[None] ";
if (flags & ESM::MagicEffect::TargetAttribute)
properties += "TargetAttribute ";
if (flags & ESM::MagicEffect::TargetSkill)
properties += "TargetSkill ";
if (flags & ESM::MagicEffect::NoDuration)
properties += "NoDuration ";
if (flags & ESM::MagicEffect::NoMagnitude)
properties += "NoMagnitude ";
if (flags & ESM::MagicEffect::Harmful)
properties += "Harmful ";
if (flags & ESM::MagicEffect::ContinuousVfx)
properties += "ContinuousVFX ";
if (flags & ESM::MagicEffect::CastSelf)
properties += "CastSelf ";
if (flags & ESM::MagicEffect::CastTouch)
properties += "CastTouch ";
if (flags & ESM::MagicEffect::CastTarget)
properties += "CastTarget ";
if (flags & ESM::MagicEffect::AppliedOnce)
properties += "AppliedOnce ";
if (flags & ESM::MagicEffect::Stealth)
properties += "Stealth ";
if (flags & ESM::MagicEffect::NonRecastable)
properties += "NonRecastable ";
if (flags & ESM::MagicEffect::IllegalDaedra)
properties += "IllegalDaedra ";
if (flags & ESM::MagicEffect::Unreflectable)
properties += "Unreflectable ";
if (flags & ESM::MagicEffect::CasterLinked)
properties += "CasterLinked ";
if (flags & ESM::MagicEffect::AllowSpellmaking)
properties += "AllowSpellmaking ";
if (flags & ESM::MagicEffect::AllowEnchanting)
properties += "AllowEnchanting ";
if (flags & ESM::MagicEffect::NegativeLight)
properties += "NegativeLight ";
if (flags & 0xFFFC0000)
properties += "Invalid ";
properties += Misc::StringUtils::format("(0x%08X)", flags);
return properties;
}
@ -836,21 +892,24 @@ std::string magicEffectFlags(int flags)
std::string npcFlags(int flags)
{
std::string properties;
if (flags == 0) properties += "[None] ";
if (flags & ESM::NPC::Base) properties += "Base ";
if (flags & ESM::NPC::Autocalc) properties += "Autocalc ";
if (flags & ESM::NPC::Female) properties += "Female ";
if (flags & ESM::NPC::Respawn) properties += "Respawn ";
if (flags & ESM::NPC::Essential) properties += "Essential ";
if (flags == 0)
properties += "[None] ";
if (flags & ESM::NPC::Base)
properties += "Base ";
if (flags & ESM::NPC::Autocalc)
properties += "Autocalc ";
if (flags & ESM::NPC::Female)
properties += "Female ";
if (flags & ESM::NPC::Respawn)
properties += "Respawn ";
if (flags & ESM::NPC::Essential)
properties += "Essential ";
// Whether corpses persist is a bit that is unaccounted for,
// however relatively few NPCs have this bit set.
int unused = (0xFF ^
(ESM::NPC::Base|
ESM::NPC::Autocalc|
ESM::NPC::Female|
ESM::NPC::Respawn|
ESM::NPC::Essential));
if (flags & unused) properties += "Invalid ";
int unused
= (0xFF ^ (ESM::NPC::Base | ESM::NPC::Autocalc | ESM::NPC::Female | ESM::NPC::Respawn | ESM::NPC::Essential));
if (flags & unused)
properties += "Invalid ";
properties += Misc::StringUtils::format("(0x%02X)", flags);
return properties;
}
@ -858,14 +917,16 @@ std::string npcFlags(int flags)
std::string raceFlags(int flags)
{
std::string properties;
if (flags == 0) properties += "[None] ";
if (flags == 0)
properties += "[None] ";
// All races have the playable flag set in Bethesda files.
if (flags & ESM::Race::Playable) properties += "Playable ";
if (flags & ESM::Race::Beast) properties += "Beast ";
int unused = (0xFFFFFFFF ^
(ESM::Race::Playable|
ESM::Race::Beast));
if (flags & unused) properties += "Invalid ";
if (flags & ESM::Race::Playable)
properties += "Playable ";
if (flags & ESM::Race::Beast)
properties += "Beast ";
int unused = (0xFFFFFFFF ^ (ESM::Race::Playable | ESM::Race::Beast));
if (flags & unused)
properties += "Invalid ";
properties += Misc::StringUtils::format("(0x%08X)", flags);
return properties;
}
@ -873,15 +934,17 @@ std::string raceFlags(int flags)
std::string spellFlags(int flags)
{
std::string properties;
if (flags == 0) properties += "[None] ";
if (flags & ESM::Spell::F_Autocalc) properties += "Autocalc ";
if (flags & ESM::Spell::F_PCStart) properties += "PCStart ";
if (flags & ESM::Spell::F_Always) properties += "Always ";
int unused = (0xFFFFFFFF ^
(ESM::Spell::F_Autocalc|
ESM::Spell::F_PCStart|
ESM::Spell::F_Always));
if (flags & unused) properties += "Invalid ";
if (flags == 0)
properties += "[None] ";
if (flags & ESM::Spell::F_Autocalc)
properties += "Autocalc ";
if (flags & ESM::Spell::F_PCStart)
properties += "PCStart ";
if (flags & ESM::Spell::F_Always)
properties += "Always ";
int unused = (0xFFFFFFFF ^ (ESM::Spell::F_Autocalc | ESM::Spell::F_PCStart | ESM::Spell::F_Always));
if (flags & unused)
properties += "Invalid ";
properties += Misc::StringUtils::format("(0x%08X)", flags);
return properties;
}
@ -889,16 +952,38 @@ std::string spellFlags(int flags)
std::string weaponFlags(int flags)
{
std::string properties;
if (flags == 0) properties += "[None] ";
if (flags == 0)
properties += "[None] ";
// The interpretation of the flags are still unclear to me.
// Apparently you can't be Silver without being Magical? Many of
// the "Magical" weapons don't have enchantments of any sort.
if (flags & ESM::Weapon::Magical) properties += "Magical ";
if (flags & ESM::Weapon::Silver) properties += "Silver ";
int unused = (0xFFFFFFFF ^
(ESM::Weapon::Magical|
ESM::Weapon::Silver));
if (flags & unused) properties += "Invalid ";
if (flags & ESM::Weapon::Magical)
properties += "Magical ";
if (flags & ESM::Weapon::Silver)
properties += "Silver ";
int unused = (0xFFFFFFFF ^ (ESM::Weapon::Magical | ESM::Weapon::Silver));
if (flags & unused)
properties += "Invalid ";
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;
}

@ -2,26 +2,29 @@
#define OPENMW_ESMTOOL_LABELS_H
#include <string>
#include <string_view>
std::string bodyPartLabel(int idx);
std::string meshPartLabel(int idx);
std::string meshTypeLabel(int idx);
std::string clothingTypeLabel(int idx);
std::string armorTypeLabel(int idx);
std::string dialogTypeLabel(int idx);
std::string questStatusLabel(int idx);
std::string creatureTypeLabel(int idx);
std::string soundTypeLabel(int idx);
std::string weaponTypeLabel(int idx);
#include <components/esm3/aipackage.hpp>
std::string_view bodyPartLabel(int idx);
std::string_view meshPartLabel(int idx);
std::string_view meshTypeLabel(int idx);
std::string_view clothingTypeLabel(int idx);
std::string_view armorTypeLabel(int idx);
std::string_view dialogTypeLabel(int idx);
std::string_view questStatusLabel(int idx);
std::string_view creatureTypeLabel(int idx);
std::string_view soundTypeLabel(int idx);
std::string_view weaponTypeLabel(int idx);
// This function's a bit different because the types are record types,
// not consecutive values.
std::string aiTypeLabel(int type);
std::string_view aiTypeLabel(ESM::AiPackageType type);
// This one's also a bit different, because it enumerates dialog
// select rule functions, not types. Structurally, it still converts
// indexes to strings for display.
std::string ruleFunction(int idx);
std::string_view ruleFunction(int idx);
// The labels below here can all be loaded from GMSTs, but are not
// currently because among other things, that requires loading the
@ -32,15 +35,15 @@ std::string ruleFunction(int idx);
// and the indexes for referencing the types in other records in the
// database. Then a single label function could work for all types.
std::string magicEffectLabel(int idx);
std::string attributeLabel(int idx);
std::string spellTypeLabel(int idx);
std::string specializationLabel(int idx);
std::string skillLabel(int idx);
std::string apparatusTypeLabel(int idx);
std::string rangeTypeLabel(int idx);
std::string schoolLabel(int idx);
std::string enchantTypeLabel(int idx);
std::string_view magicEffectLabel(int idx);
std::string_view attributeLabel(int idx);
std::string_view spellTypeLabel(int idx);
std::string_view specializationLabel(int idx);
std::string_view skillLabel(int idx);
std::string_view apparatusTypeLabel(int idx);
std::string_view rangeTypeLabel(int idx);
std::string_view schoolLabel(int idx);
std::string_view enchantTypeLabel(int idx);
// The are the flag functions that convert a bitmask into a list of
// human readble strings representing the set bits.
@ -60,6 +63,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

File diff suppressed because it is too large Load Diff

@ -1,9 +1,12 @@
#ifndef OPENMW_ESMTOOL_RECORD_H
#define OPENMW_ESMTOOL_RECORD_H
#include <memory>
#include <string>
#include <components/esm/records.hpp>
#include <components/esm3/cellstate.hpp>
#include <components/esm3/fogstate.hpp>
namespace ESM
{
@ -13,7 +16,8 @@ namespace ESM
namespace EsmTool
{
template <class T> class Record;
template <class T>
class Record;
class RecordBase
{
@ -24,45 +28,48 @@ namespace EsmTool
bool mPrintPlain;
public:
RecordBase ()
: mFlags(0)
, mPrintPlain(false)
RecordBase()
: mFlags(0)
, mPrintPlain(false)
{
}
virtual ~RecordBase() {}
virtual ~RecordBase() = default;
virtual std::string getId() const = 0;
uint32_t getFlags() const {
return mFlags;
}
uint32_t getFlags() const { return mFlags; }
void setFlags(uint32_t flags) {
mFlags = flags;
}
void setFlags(uint32_t flags) { mFlags = flags; }
ESM::NAME getType() const {
return mType;
}
ESM::NAME getType() const { return mType; }
void setPrintPlain(bool plain) {
mPrintPlain = plain;
}
void setPrintPlain(bool plain) { mPrintPlain = plain; }
virtual void load(ESM::ESMReader &esm) = 0;
virtual void save(ESM::ESMWriter &esm) = 0;
virtual void load(ESM::ESMReader& esm) = 0;
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>
Record<T> *cast() {
return static_cast<Record<T> *>(this);
Record<T>* cast()
{
return static_cast<Record<T>*>(this);
}
};
struct CellState
{
ESM::CellState mCellState;
ESM::FogState mFogState;
void load(ESM::ESMReader& reader, bool& deleted);
void save(ESM::ESMWriter& /*writer*/, bool /*deleted*/) {}
};
template <class T>
class Record : public RecordBase
{
@ -72,75 +79,119 @@ namespace EsmTool
public:
Record()
: mIsDeleted(false)
{}
std::string getId() const override {
return mData.mId;
{
}
T &get() {
return mData;
}
std::string getId() const override { return mData.mId.toDebugString(); }
void save(ESM::ESMWriter &esm) override {
mData.save(esm, mIsDeleted);
}
T& get() { return mData; }
void load(ESM::ESMReader &esm) override {
mData.load(esm, mIsDeleted);
}
void save(ESM::ESMWriter& esm) override { mData.save(esm, mIsDeleted); }
void load(ESM::ESMReader& esm) override { mData.load(esm, mIsDeleted); }
void print() override;
};
template<> std::string Record<ESM::Cell>::getId() const;
template<> std::string Record<ESM::Land>::getId() const;
template<> std::string Record<ESM::MagicEffect>::getId() const;
template<> std::string Record<ESM::Pathgrid>::getId() const;
template<> std::string Record<ESM::Skill>::getId() const;
template<> void Record<ESM::Activator>::print();
template<> void Record<ESM::Potion>::print();
template<> void Record<ESM::Armor>::print();
template<> void Record<ESM::Apparatus>::print();
template<> void Record<ESM::BodyPart>::print();
template<> void Record<ESM::Book>::print();
template<> void Record<ESM::BirthSign>::print();
template<> void Record<ESM::Cell>::print();
template<> void Record<ESM::Class>::print();
template<> void Record<ESM::Clothing>::print();
template<> void Record<ESM::Container>::print();
template<> void Record<ESM::Creature>::print();
template<> void Record<ESM::Dialogue>::print();
template<> void Record<ESM::Door>::print();
template<> void Record<ESM::Enchantment>::print();
template<> void Record<ESM::Faction>::print();
template<> void Record<ESM::Global>::print();
template<> void Record<ESM::GameSetting>::print();
template<> void Record<ESM::DialInfo>::print();
template<> void Record<ESM::Ingredient>::print();
template<> void Record<ESM::Land>::print();
template<> void Record<ESM::CreatureLevList>::print();
template<> void Record<ESM::ItemLevList>::print();
template<> void Record<ESM::Light>::print();
template<> void Record<ESM::Lockpick>::print();
template<> void Record<ESM::Probe>::print();
template<> void Record<ESM::Repair>::print();
template<> void Record<ESM::LandTexture>::print();
template<> void Record<ESM::MagicEffect>::print();
template<> void Record<ESM::Miscellaneous>::print();
template<> void Record<ESM::NPC>::print();
template<> void Record<ESM::Pathgrid>::print();
template<> void Record<ESM::Race>::print();
template<> void Record<ESM::Region>::print();
template<> void Record<ESM::Script>::print();
template<> void Record<ESM::Skill>::print();
template<> void Record<ESM::SoundGenerator>::print();
template<> void Record<ESM::Sound>::print();
template<> void Record<ESM::Spell>::print();
template<> void Record<ESM::StartScript>::print();
template<> void Record<ESM::Static>::print();
template<> void Record<ESM::Weapon>::print();
template <>
std::string Record<ESM::Cell>::getId() const;
template <>
std::string Record<ESM::Land>::getId() const;
template <>
std::string Record<ESM::MagicEffect>::getId() const;
template <>
std::string Record<ESM::Pathgrid>::getId() const;
template <>
std::string Record<ESM::Skill>::getId() const;
template <>
std::string Record<CellState>::getId() const;
template <>
void Record<ESM::Activator>::print();
template <>
void Record<ESM::Potion>::print();
template <>
void Record<ESM::Armor>::print();
template <>
void Record<ESM::Apparatus>::print();
template <>
void Record<ESM::BodyPart>::print();
template <>
void Record<ESM::Book>::print();
template <>
void Record<ESM::BirthSign>::print();
template <>
void Record<ESM::Cell>::print();
template <>
void Record<ESM::Class>::print();
template <>
void Record<ESM::Clothing>::print();
template <>
void Record<ESM::Container>::print();
template <>
void Record<ESM::Creature>::print();
template <>
void Record<ESM::Dialogue>::print();
template <>
void Record<ESM::Door>::print();
template <>
void Record<ESM::Enchantment>::print();
template <>
void Record<ESM::Faction>::print();
template <>
void Record<ESM::Global>::print();
template <>
void Record<ESM::GameSetting>::print();
template <>
void Record<ESM::DialInfo>::print();
template <>
void Record<ESM::Ingredient>::print();
template <>
void Record<ESM::Land>::print();
template <>
void Record<ESM::CreatureLevList>::print();
template <>
void Record<ESM::ItemLevList>::print();
template <>
void Record<ESM::Light>::print();
template <>
void Record<ESM::Lockpick>::print();
template <>
void Record<ESM::Probe>::print();
template <>
void Record<ESM::Repair>::print();
template <>
void Record<ESM::LandTexture>::print();
template <>
void Record<ESM::MagicEffect>::print();
template <>
void Record<ESM::Miscellaneous>::print();
template <>
void Record<ESM::NPC>::print();
template <>
void Record<ESM::Pathgrid>::print();
template <>
void Record<ESM::Race>::print();
template <>
void Record<ESM::Region>::print();
template <>
void Record<ESM::Script>::print();
template <>
void Record<ESM::Skill>::print();
template <>
void Record<ESM::SoundGenerator>::print();
template <>
void Record<ESM::Sound>::print();
template <>
void Record<ESM::Spell>::print();
template <>
void Record<ESM::StartScript>::print();
template <>
void Record<ESM::Static>::print();
template <>
void Record<ESM::Weapon>::print();
template <>
void Record<CellState>::print();
}
#endif

@ -0,0 +1,564 @@
#include "tes4.hpp"
#include "arguments.hpp"
#include "labels.hpp"
#include <fstream>
#include <iostream>
#include <type_traits>
#include <components/esm/esmcommon.hpp>
#include <components/esm/typetraits.hpp>
#include <components/esm4/reader.hpp>
#include <components/esm4/readerutils.hpp>
#include <components/esm4/records.hpp>
#include <components/esm4/typetraits.hpp>
#include <components/to_utf8/to_utf8.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>
struct WriteArray
{
std::string_view mPrefix;
const T& mValue;
explicit WriteArray(std::string_view prefix, const T& value)
: mPrefix(prefix)
, mValue(value)
{
}
};
template <class T>
struct WriteData
{
const T& mValue;
explicit WriteData(const T& value)
: mValue(value)
{
}
};
template <class T>
std::ostream& operator<<(std::ostream& stream, const WriteArray<T>& write)
{
for (const auto& value : write.mValue)
stream << write.mPrefix << value;
return stream;
}
template <class T>
std::ostream& operator<<(std::ostream& stream, const WriteData<T>& /*write*/)
{
return stream << " ?";
}
std::ostream& operator<<(std::ostream& stream, const WriteData<ESM4::GameSetting::Data>& write)
{
std::visit([&](const auto& v) { stream << v; }, write.mValue);
return stream;
}
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 (ESM4::hasFormId<T>)
std::cout << "\n FormId: 0x" << ESM4::formIdToString(value.mFormId);
if constexpr (ESM::hasId<T>)
{
if constexpr (std::is_same_v<decltype(value.mId), ESM4::FormId>)
std::cout << "\n FormId: 0x" << ESM4::formIdToString(value.mId);
else
std::cout << "\n Id: " << value.mId;
}
if constexpr (ESM4::hasFlags<T>)
std::cout << "\n Record flags: " << recordFlags(value.mFlags);
if constexpr (ESM4::hasParentFormId<T>)
std::cout << "\n ParentFormId: 0x" << ESM4::formIdToString(value.mParentFormId);
if constexpr (ESM4::hasParent<T>)
std::cout << "\n Parent: " << value.mParent;
if constexpr (ESM4::hasEditorId<T>)
std::cout << "\n EditorId: " << value.mEditorId;
if constexpr (ESM::hasModel<T>)
std::cout << "\n Model: " << value.mModel;
if constexpr (ESM4::hasNif<T>)
std::cout << "\n Nif:" << WriteArray("\n - ", value.mNif);
if constexpr (ESM4::hasKf<T>)
std::cout << "\n Kf:" << WriteArray("\n - ", value.mKf);
if constexpr (ESM4::hasType<T>)
std::cout << "\n Type: " << value.mType;
if constexpr (ESM4::hasValue<T>)
std::cout << "\n Value: " << value.mValue;
if constexpr (ESM4::hasData<T>)
std::cout << "\n Data: " << WriteData(value.mData);
std::cout << '\n';
}
bool 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:
readTypedRecord<ESM4::ActorCharacter>(params, reader);
return true;
case ESM4::REC_ACRE:
readTypedRecord<ESM4::ActorCreature>(params, reader);
return true;
case ESM4::REC_ACTI:
readTypedRecord<ESM4::Activator>(params, reader);
return true;
case ESM4::REC_ADDN:
break;
case ESM4::REC_ALCH:
readTypedRecord<ESM4::Potion>(params, reader);
return true;
case ESM4::REC_ALOC:
readTypedRecord<ESM4::MediaLocationController>(params, reader);
return true;
case ESM4::REC_AMMO:
readTypedRecord<ESM4::Ammunition>(params, reader);
return true;
case ESM4::REC_ANIO:
readTypedRecord<ESM4::AnimObject>(params, reader);
return true;
case ESM4::REC_APPA:
readTypedRecord<ESM4::Apparatus>(params, reader);
return true;
case ESM4::REC_ARMA:
readTypedRecord<ESM4::ArmorAddon>(params, reader);
return true;
case ESM4::REC_ARMO:
readTypedRecord<ESM4::Armor>(params, reader);
return true;
case ESM4::REC_ARTO:
break;
case ESM4::REC_ASPC:
readTypedRecord<ESM4::AcousticSpace>(params, reader);
return true;
case ESM4::REC_ASTP:
break;
case ESM4::REC_AVIF:
break;
case ESM4::REC_BOOK:
readTypedRecord<ESM4::Book>(params, reader);
return true;
case ESM4::REC_BPTD:
readTypedRecord<ESM4::BodyPartData>(params, reader);
return true;
case ESM4::REC_CAMS:
break;
case ESM4::REC_CCRD:
break;
case ESM4::REC_CELL:
readTypedRecord<ESM4::Cell>(params, reader);
return true;
case ESM4::REC_CLAS:
readTypedRecord<ESM4::Class>(params, reader);
return true;
case ESM4::REC_CLFM:
readTypedRecord<ESM4::Colour>(params, reader);
return true;
case ESM4::REC_CLMT:
break;
case ESM4::REC_CLOT:
readTypedRecord<ESM4::Clothing>(params, reader);
return true;
case ESM4::REC_CMNY:
break;
case ESM4::REC_COBJ:
break;
case ESM4::REC_COLL:
break;
case ESM4::REC_CONT:
readTypedRecord<ESM4::Container>(params, reader);
return true;
case ESM4::REC_CPTH:
break;
case ESM4::REC_CREA:
readTypedRecord<ESM4::Creature>(params, reader);
return true;
case ESM4::REC_CSTY:
break;
case ESM4::REC_DEBR:
break;
case ESM4::REC_DIAL:
readTypedRecord<ESM4::Dialogue>(params, reader);
return true;
case ESM4::REC_DLBR:
break;
case ESM4::REC_DLVW:
break;
case ESM4::REC_DOBJ:
readTypedRecord<ESM4::DefaultObj>(params, reader);
return true;
case ESM4::REC_DOOR:
readTypedRecord<ESM4::Door>(params, reader);
return true;
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:
readTypedRecord<ESM4::Eyes>(params, reader);
return true;
case ESM4::REC_FACT:
break;
case ESM4::REC_FLOR:
readTypedRecord<ESM4::Flora>(params, reader);
return true;
case ESM4::REC_FLST:
readTypedRecord<ESM4::FormIdList>(params, reader);
return true;
case ESM4::REC_FSTP:
break;
case ESM4::REC_FSTS:
break;
case ESM4::REC_FURN:
readTypedRecord<ESM4::Furniture>(params, reader);
return true;
case ESM4::REC_GLOB:
readTypedRecord<ESM4::GlobalVariable>(params, reader);
return true;
case ESM4::REC_GMST:
readTypedRecord<ESM4::GameSetting>(params, reader);
return true;
case ESM4::REC_GRAS:
readTypedRecord<ESM4::Grass>(params, reader);
return true;
case ESM4::REC_GRUP:
break;
case ESM4::REC_HAIR:
readTypedRecord<ESM4::Hair>(params, reader);
return true;
case ESM4::REC_HAZD:
break;
case ESM4::REC_HDPT:
readTypedRecord<ESM4::HeadPart>(params, reader);
return true;
case ESM4::REC_IDLE:
// FIXME: ESM4::IdleAnimation::load does not work with Oblivion.esm
// readTypedRecord<ESM4::IdleAnimation>(params, reader);
return true;
break;
case ESM4::REC_IDLM:
readTypedRecord<ESM4::IdleMarker>(params, reader);
return true;
case ESM4::REC_IMAD:
break;
case ESM4::REC_IMGS:
break;
case ESM4::REC_IMOD:
readTypedRecord<ESM4::ItemMod>(params, reader);
return true;
case ESM4::REC_INFO:
readTypedRecord<ESM4::DialogInfo>(params, reader);
return true;
case ESM4::REC_INGR:
readTypedRecord<ESM4::Ingredient>(params, reader);
return true;
case ESM4::REC_IPCT:
break;
case ESM4::REC_IPDS:
break;
case ESM4::REC_KEYM:
readTypedRecord<ESM4::Key>(params, reader);
return true;
case ESM4::REC_KYWD:
break;
case ESM4::REC_LAND:
readTypedRecord<ESM4::Land>(params, reader);
return true;
case ESM4::REC_LCRT:
break;
case ESM4::REC_LCTN:
break;
case ESM4::REC_LGTM:
readTypedRecord<ESM4::LightingTemplate>(params, reader);
return true;
case ESM4::REC_LIGH:
readTypedRecord<ESM4::Light>(params, reader);
return true;
case ESM4::REC_LSCR:
break;
case ESM4::REC_LTEX:
readTypedRecord<ESM4::LandTexture>(params, reader);
return true;
case ESM4::REC_LVLC:
readTypedRecord<ESM4::LevelledCreature>(params, reader);
return true;
case ESM4::REC_LVLI:
readTypedRecord<ESM4::LevelledItem>(params, reader);
return true;
case ESM4::REC_LVLN:
readTypedRecord<ESM4::LevelledNpc>(params, reader);
return true;
case ESM4::REC_LVSP:
break;
case ESM4::REC_MATO:
readTypedRecord<ESM4::Material>(params, reader);
return true;
case ESM4::REC_MATT:
break;
case ESM4::REC_MESG:
break;
case ESM4::REC_MGEF:
break;
case ESM4::REC_MISC:
readTypedRecord<ESM4::MiscItem>(params, reader);
return true;
case ESM4::REC_MOVT:
break;
case ESM4::REC_MSET:
readTypedRecord<ESM4::MediaSet>(params, reader);
return true;
case ESM4::REC_MSTT:
readTypedRecord<ESM4::MovableStatic>(params, reader);
return true;
case ESM4::REC_MUSC:
readTypedRecord<ESM4::Music>(params, reader);
return true;
case ESM4::REC_MUST:
break;
case ESM4::REC_NAVI:
readTypedRecord<ESM4::Navigation>(params, reader);
return true;
case ESM4::REC_NAVM:
readTypedRecord<ESM4::NavMesh>(params, reader);
return true;
case ESM4::REC_NOTE:
readTypedRecord<ESM4::Note>(params, reader);
return true;
case ESM4::REC_NPC_:
readTypedRecord<ESM4::Npc>(params, reader);
return true;
case ESM4::REC_OTFT:
readTypedRecord<ESM4::Outfit>(params, reader);
return true;
case ESM4::REC_PACK:
readTypedRecord<ESM4::AIPackage>(params, reader);
return true;
case ESM4::REC_PERK:
break;
case ESM4::REC_PGRD:
readTypedRecord<ESM4::Pathgrid>(params, reader);
return true;
case ESM4::REC_PGRE:
readTypedRecord<ESM4::PlacedGrenade>(params, reader);
return true;
case ESM4::REC_PHZD:
break;
case ESM4::REC_PROJ:
break;
case ESM4::REC_PWAT:
readTypedRecord<ESM4::PlaceableWater>(params, reader);
return true;
case ESM4::REC_QUST:
readTypedRecord<ESM4::Quest>(params, reader);
return true;
case ESM4::REC_RACE:
readTypedRecord<ESM4::Race>(params, reader);
return true;
case ESM4::REC_REFR:
readTypedRecord<ESM4::Reference>(params, reader);
return true;
case ESM4::REC_REGN:
readTypedRecord<ESM4::Region>(params, reader);
return true;
case ESM4::REC_RELA:
break;
case ESM4::REC_REVB:
break;
case ESM4::REC_RFCT:
break;
case ESM4::REC_ROAD:
readTypedRecord<ESM4::Road>(params, reader);
return true;
case ESM4::REC_SBSP:
readTypedRecord<ESM4::SubSpace>(params, reader);
return true;
case ESM4::REC_SCEN:
break;
case ESM4::REC_SCOL:
readTypedRecord<ESM4::StaticCollection>(params, reader);
return true;
case ESM4::REC_SCPT:
readTypedRecord<ESM4::Script>(params, reader);
return true;
case ESM4::REC_SCRL:
readTypedRecord<ESM4::Scroll>(params, reader);
return true;
case ESM4::REC_SGST:
readTypedRecord<ESM4::SigilStone>(params, reader);
return true;
case ESM4::REC_SHOU:
break;
case ESM4::REC_SLGM:
readTypedRecord<ESM4::SoulGem>(params, reader);
return true;
case ESM4::REC_SMBN:
break;
case ESM4::REC_SMEN:
break;
case ESM4::REC_SMQN:
break;
case ESM4::REC_SNCT:
break;
case ESM4::REC_SNDR:
readTypedRecord<ESM4::SoundReference>(params, reader);
return true;
case ESM4::REC_SOPM:
break;
case ESM4::REC_SOUN:
readTypedRecord<ESM4::Sound>(params, reader);
return true;
case ESM4::REC_SPEL:
break;
case ESM4::REC_SPGD:
break;
case ESM4::REC_STAT:
readTypedRecord<ESM4::Static>(params, reader);
return true;
case ESM4::REC_TACT:
readTypedRecord<ESM4::TalkingActivator>(params, reader);
return true;
case ESM4::REC_TERM:
readTypedRecord<ESM4::Terminal>(params, reader);
return true;
case ESM4::REC_TES4:
readTypedRecord<ESM4::Header>(params, reader);
return true;
case ESM4::REC_TREE:
readTypedRecord<ESM4::Tree>(params, reader);
return true;
case ESM4::REC_TXST:
readTypedRecord<ESM4::TextureSet>(params, reader);
return true;
case ESM4::REC_VTYP:
break;
case ESM4::REC_WATR:
break;
case ESM4::REC_WEAP:
readTypedRecord<ESM4::Weapon>(params, reader);
return true;
case ESM4::REC_WOOP:
break;
case ESM4::REC_WRLD:
readTypedRecord<ESM4::World>(params, reader);
return true;
case ESM4::REC_WTHR:
break;
}
if (!params.mQuite)
std::cout << "\n Unsupported record: " << ESM::NAME(reader.hdr().record.typeId).toStringView() << '\n';
return false;
}
}
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, nullptr, &encoder, true);
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";
}
}
auto visitorRec = [&params](ESM4::Reader& reader) { return readRecord(params, reader); };
auto visitorGroup = [&params](ESM4::Reader& reader) {
if (params.mQuite)
return;
auto groupType = static_cast<ESM4::GroupType>(reader.hdr().group.type);
std::cout << "\nGroup: " << toString(groupType) << " "
<< ESM::NAME(reader.hdr().group.typeId).toStringView() << '\n';
};
ESM4::ReaderUtils::readAll(reader, visitorRec, visitorGroup);
}
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

@ -5,7 +5,6 @@ set(ESSIMPORTER_FILES
importnpcc.cpp
importcrec.cpp
importcellref.cpp
importacdt.cpp
importinventory.cpp
importklst.cpp
importcntc.cpp
@ -36,15 +35,24 @@ openmw_add_executable(openmw-essimporter
target_link_libraries(openmw-essimporter
${Boost_PROGRAM_OPTIONS_LIBRARY}
${Boost_FILESYSTEM_LIBRARY}
components
)
if (BUILD_WITH_CODE_COVERAGE)
add_definitions (--coverage)
target_link_libraries(openmw-essimporter gcov)
target_compile_options(openmw-essimporter PRIVATE --coverage)
target_link_libraries(openmw-essimporter gcov)
endif()
if (WIN32)
INSTALL(TARGETS openmw-essimporter RUNTIME DESTINATION ".")
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,8 +1,8 @@
#include <string>
#include <iostream>
#include <limits>
#include <string>
#include <components/misc/stringops.hpp>
#include <components/misc/strings/lower.hpp>
#include "convertacdt.hpp"
@ -18,36 +18,36 @@ namespace ESSImport
return mwIndex;
}
void convertACDT (const ACDT& acdt, ESM::CreatureStats& cStats)
void convertACDT(const ACDT& acdt, ESM::CreatureStats& cStats)
{
for (int i=0; i<3; ++i)
for (int i = 0; i < 3; ++i)
{
int writeIndex = translateDynamicIndex(i);
cStats.mDynamic[writeIndex].mBase = acdt.mDynamic[i][1];
cStats.mDynamic[writeIndex].mMod = acdt.mDynamic[i][1];
cStats.mDynamic[writeIndex].mMod = 0.f;
cStats.mDynamic[writeIndex].mCurrent = acdt.mDynamic[i][0];
}
for (int i=0; i<8; ++i)
for (int i = 0; i < 8; ++i)
{
cStats.mAttributes[i].mBase = static_cast<int>(acdt.mAttributes[i][1]);
cStats.mAttributes[i].mMod = static_cast<int>(acdt.mAttributes[i][0]);
cStats.mAttributes[i].mCurrent = static_cast<int>(acdt.mAttributes[i][0]);
cStats.mAttributes[i].mBase = acdt.mAttributes[i][1];
cStats.mAttributes[i].mMod = 0.f;
cStats.mAttributes[i].mCurrent = acdt.mAttributes[i][0];
}
cStats.mGoldPool = acdt.mGoldPool;
cStats.mTalkedTo = (acdt.mFlags & TalkedToPlayer) != 0;
cStats.mAttacked = (acdt.mFlags & Attacked) != 0;
}
void convertACSC (const ACSC& acsc, ESM::CreatureStats& cStats)
void convertACSC(const ACSC& acsc, ESM::CreatureStats& cStats)
{
cStats.mDead = (acsc.mFlags & Dead) != 0;
}
void convertNpcData (const ActorData& actorData, ESM::NpcStats& npcStats)
void convertNpcData(const ActorData& actorData, ESM::NpcStats& npcStats)
{
for (int i=0; i<ESM::Skill::Length; ++i)
for (int i = 0; i < ESM::Skill::Length; ++i)
{
npcStats.mSkills[i].mMod = actorData.mSkills[i][1];
npcStats.mSkills[i].mMod = 0.f;
npcStats.mSkills[i].mCurrent = actorData.mSkills[i][1];
npcStats.mSkills[i].mBase = actorData.mSkills[i][0];
}
@ -55,32 +55,29 @@ namespace ESSImport
npcStats.mTimeToStartDrowning = actorData.mACDT.mBreathMeter;
}
void convertANIS (const ANIS& anis, ESM::AnimationState& state)
void convertANIS(const ANIS& anis, ESM::AnimationState& state)
{
static const char* animGroups[] =
{
"Idle", "Idle2", "Idle3", "Idle4", "Idle5", "Idle6", "Idle7", "Idle8", "Idle9", "Idlehh", "Idle1h", "Idle2c",
"Idle2w", "IdleSwim", "IdleSpell", "IdleCrossbow", "IdleSneak", "IdleStorm", "Torch", "Hit1", "Hit2", "Hit3",
"Hit4", "Hit5", "SwimHit1", "SwimHit2", "SwimHit3", "Death1", "Death2", "Death3", "Death4", "Death5",
"DeathKnockDown", "DeathKnockOut", "KnockDown", "KnockOut", "SwimDeath", "SwimDeath2", "SwimDeath3",
"SwimDeathKnockDown", "SwimDeathKnockOut", "SwimKnockOut", "SwimKnockDown", "SwimWalkForward",
"SwimWalkBack", "SwimWalkLeft", "SwimWalkRight", "SwimRunForward", "SwimRunBack", "SwimRunLeft",
"SwimRunRight", "SwimTurnLeft", "SwimTurnRight", "WalkForward", "WalkBack", "WalkLeft", "WalkRight",
"TurnLeft", "TurnRight", "RunForward", "RunBack", "RunLeft", "RunRight", "SneakForward", "SneakBack",
"SneakLeft", "SneakRight", "Jump", "WalkForwardhh", "WalkBackhh", "WalkLefthh", "WalkRighthh",
"TurnLefthh", "TurnRighthh", "RunForwardhh", "RunBackhh", "RunLefthh", "RunRighthh", "SneakForwardhh",
"SneakBackhh", "SneakLefthh", "SneakRighthh", "Jumphh", "WalkForward1h", "WalkBack1h", "WalkLeft1h",
"WalkRight1h", "TurnLeft1h", "TurnRight1h", "RunForward1h", "RunBack1h", "RunLeft1h", "RunRight1h",
"SneakForward1h", "SneakBack1h", "SneakLeft1h", "SneakRight1h", "Jump1h", "WalkForward2c", "WalkBack2c",
"WalkLeft2c", "WalkRight2c", "TurnLeft2c", "TurnRight2c", "RunForward2c", "RunBack2c", "RunLeft2c",
"RunRight2c", "SneakForward2c", "SneakBack2c", "SneakLeft2c", "SneakRight2c", "Jump2c", "WalkForward2w",
"WalkBack2w", "WalkLeft2w", "WalkRight2w", "TurnLeft2w", "TurnRight2w", "RunForward2w", "RunBack2w",
"RunLeft2w", "RunRight2w", "SneakForward2w", "SneakBack2w", "SneakLeft2w", "SneakRight2w", "Jump2w",
"SpellCast", "SpellTurnLeft", "SpellTurnRight", "Attack1", "Attack2", "Attack3", "SwimAttack1",
static const char* animGroups[] = { "Idle", "Idle2", "Idle3", "Idle4", "Idle5", "Idle6", "Idle7", "Idle8",
"Idle9", "Idlehh", "Idle1h", "Idle2c", "Idle2w", "IdleSwim", "IdleSpell", "IdleCrossbow", "IdleSneak",
"IdleStorm", "Torch", "Hit1", "Hit2", "Hit3", "Hit4", "Hit5", "SwimHit1", "SwimHit2", "SwimHit3", "Death1",
"Death2", "Death3", "Death4", "Death5", "DeathKnockDown", "DeathKnockOut", "KnockDown", "KnockOut",
"SwimDeath", "SwimDeath2", "SwimDeath3", "SwimDeathKnockDown", "SwimDeathKnockOut", "SwimKnockOut",
"SwimKnockDown", "SwimWalkForward", "SwimWalkBack", "SwimWalkLeft", "SwimWalkRight", "SwimRunForward",
"SwimRunBack", "SwimRunLeft", "SwimRunRight", "SwimTurnLeft", "SwimTurnRight", "WalkForward", "WalkBack",
"WalkLeft", "WalkRight", "TurnLeft", "TurnRight", "RunForward", "RunBack", "RunLeft", "RunRight",
"SneakForward", "SneakBack", "SneakLeft", "SneakRight", "Jump", "WalkForwardhh", "WalkBackhh", "WalkLefthh",
"WalkRighthh", "TurnLefthh", "TurnRighthh", "RunForwardhh", "RunBackhh", "RunLefthh", "RunRighthh",
"SneakForwardhh", "SneakBackhh", "SneakLefthh", "SneakRighthh", "Jumphh", "WalkForward1h", "WalkBack1h",
"WalkLeft1h", "WalkRight1h", "TurnLeft1h", "TurnRight1h", "RunForward1h", "RunBack1h", "RunLeft1h",
"RunRight1h", "SneakForward1h", "SneakBack1h", "SneakLeft1h", "SneakRight1h", "Jump1h", "WalkForward2c",
"WalkBack2c", "WalkLeft2c", "WalkRight2c", "TurnLeft2c", "TurnRight2c", "RunForward2c", "RunBack2c",
"RunLeft2c", "RunRight2c", "SneakForward2c", "SneakBack2c", "SneakLeft2c", "SneakRight2c", "Jump2c",
"WalkForward2w", "WalkBack2w", "WalkLeft2w", "WalkRight2w", "TurnLeft2w", "TurnRight2w", "RunForward2w",
"RunBack2w", "RunLeft2w", "RunRight2w", "SneakForward2w", "SneakBack2w", "SneakLeft2w", "SneakRight2w",
"Jump2w", "SpellCast", "SpellTurnLeft", "SpellTurnRight", "Attack1", "Attack2", "Attack3", "SwimAttack1",
"SwimAttack2", "SwimAttack3", "HandToHand", "Crossbow", "BowAndArrow", "ThrowWeapon", "WeaponOneHand",
"WeaponTwoHand", "WeaponTwoWide", "Shield", "PickProbe", "InventoryHandToHand", "InventoryWeaponOneHand",
"InventoryWeaponTwoHand", "InventoryWeaponTwoWide"
};
"InventoryWeaponTwoHand", "InventoryWeaponTwoWide" };
if (anis.mGroupIndex < (sizeof(animGroups) / sizeof(*animGroups)))
{

@ -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/animationstate.hpp>
#include <components/esm3/creaturestats.hpp>
#include <components/esm3/loadskil.hpp>
#include <components/esm3/npcstats.hpp>
#include "importacdt.hpp"
@ -14,13 +14,12 @@ namespace ESSImport
// OpenMW uses Health,Magicka,Fatigue, MW uses Health,Fatigue,Magicka
int translateDynamicIndex(int mwIndex);
void convertACDT(const ACDT& acdt, ESM::CreatureStats& cStats);
void convertACSC(const ACSC& acsc, ESM::CreatureStats& cStats);
void convertACDT (const ACDT& acdt, ESM::CreatureStats& cStats);
void convertACSC (const ACSC& acsc, ESM::CreatureStats& cStats);
void convertNpcData(const ActorData& actorData, ESM::NpcStats& npcStats);
void convertNpcData (const ActorData& actorData, ESM::NpcStats& npcStats);
void convertANIS (const ANIS& anis, ESM::AnimationState& state);
void convertANIS(const ANIS& anis, ESM::AnimationState& state);
}
#endif

@ -5,7 +5,7 @@
namespace ESSImport
{
void convertCNTC(const CNTC &cntc, ESM::ContainerState &state)
void convertCNTC(const CNTC& cntc, ESM::ContainerState& state)
{
convertInventory(cntc.mInventory, state.mInventory);
}

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

@ -5,7 +5,7 @@
namespace ESSImport
{
void convertCREC(const CREC &crec, ESM::CreatureState &state)
void convertCREC(const CREC& crec, ESM::CreatureState& state)
{
convertInventory(crec.mInventory, state.mInventory);
}

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

@ -1,17 +1,17 @@
#include "converter.hpp"
#include <stdexcept>
#include <algorithm>
#include <stdexcept>
#include <osgDB/WriteFile>
#include <components/esm/creaturestate.hpp>
#include <components/esm/containerstate.hpp>
#include <components/esm3/containerstate.hpp>
#include <components/esm3/creaturestate.hpp>
#include <components/misc/constants.hpp>
#include "convertcrec.hpp"
#include "convertcntc.hpp"
#include "convertcrec.hpp"
#include "convertscri.hpp"
namespace
@ -19,7 +19,7 @@ namespace
void convertImage(char* data, int size, int width, int height, GLenum pf, const std::string& out)
{
osg::ref_ptr<osg::Image> image (new osg::Image);
osg::ref_ptr<osg::Image> image(new osg::Image);
image->allocateImage(width, height, 1, pf, GL_UNSIGNED_BYTE);
memcpy(image->data(), data, size);
image->flipVertical();
@ -27,7 +27,6 @@ namespace
osgDB::writeImageFile(*image, out);
}
void convertCellRef(const ESSImport::CellRef& cellref, ESM::ObjectState& objstate)
{
objstate.mEnabled = cellref.mEnabled;
@ -35,11 +34,11 @@ namespace
objstate.mRef.mRefNum = cellref.mRefNum;
if (cellref.mDeleted)
objstate.mCount = 0;
convertSCRI(cellref.mSCRI, objstate.mLocals);
convertSCRI(cellref.mActorData.mSCRI, objstate.mLocals);
objstate.mHasLocals = !objstate.mLocals.mVariables.empty();
if (cellref.mHasANIS)
convertANIS(cellref.mANIS, objstate.mAnimationState);
if (cellref.mActorData.mHasANIS)
convertANIS(cellref.mActorData.mANIS, objstate.mAnimationState);
}
bool isIndexedRefId(const std::string& indexedRefId)
@ -51,28 +50,28 @@ namespace
return false; // entirely numeric refid, this is a reference to
// a dynamically created record e.g. player-enchanted weapon
std::string index = indexedRefId.substr(indexedRefId.size()-8);
std::string index = indexedRefId.substr(indexedRefId.size() - 8);
return index.find_first_not_of("0123456789ABCDEF") == std::string::npos;
}
void splitIndexedRefId(const std::string& indexedRefId, int& refIndex, std::string& refId)
{
std::stringstream stream;
stream << std::hex << indexedRefId.substr(indexedRefId.size()-8,8);
stream << std::hex << indexedRefId.substr(indexedRefId.size() - 8, 8);
stream >> refIndex;
refId = indexedRefId.substr(0,indexedRefId.size()-8);
refId = indexedRefId.substr(0, indexedRefId.size() - 8);
}
int convertActorId(const std::string& indexedRefId, ESSImport::Context& context)
{
if (isIndexedRefId(indexedRefId))
{
int refIndex;
int refIndex = 0;
std::string refId;
splitIndexedRefId(indexedRefId, refIndex, refId);
auto it = context.mActorIdMap.find(std::make_pair(refIndex, refId));
auto it = context.mActorIdMap.find(std::make_pair(refIndex, ESM::RefId::stringRefId(refId)));
if (it == context.mActorIdMap.end())
return -1;
return it->second;
@ -89,14 +88,13 @@ namespace
namespace ESSImport
{
struct MAPH
{
unsigned int size;
unsigned int value;
};
void ConvertFMAP::read(ESM::ESMReader &esm)
void ConvertFMAP::read(ESM::ESMReader& esm)
{
MAPH maph;
esm.getHNT(maph, "MAPH");
@ -104,63 +102,63 @@ namespace ESSImport
esm.getSubNameIs("MAPD");
esm.getSubHeader();
data.resize(esm.getSubSize());
esm.getExact(&data[0], data.size());
esm.getExact(data.data(), data.size());
mGlobalMapImage = new osg::Image;
mGlobalMapImage->allocateImage(maph.size, maph.size, 1, GL_RGB, GL_UNSIGNED_BYTE);
memcpy(mGlobalMapImage->data(), &data[0], data.size());
memcpy(mGlobalMapImage->data(), data.data(), data.size());
// to match openmw size
// FIXME: filtering?
mGlobalMapImage->scaleImage(maph.size*2, maph.size*2, 1, GL_UNSIGNED_BYTE);
mGlobalMapImage->scaleImage(maph.size * 2, maph.size * 2, 1, GL_UNSIGNED_BYTE);
}
void ConvertFMAP::write(ESM::ESMWriter &esm)
void ConvertFMAP::write(ESM::ESMWriter& esm)
{
int numcells = mGlobalMapImage->s() / 18; // NB truncating, doesn't divide perfectly
// with the 512x512 map the game has by default
int cellSize = mGlobalMapImage->s()/numcells;
// with the 512x512 map the game has by default
int cellSize = mGlobalMapImage->s() / numcells;
// Note the upper left corner of the (0,0) cell should be at (width/2, height/2)
mContext->mGlobalMapState.mBounds.mMinX = -numcells/2;
mContext->mGlobalMapState.mBounds.mMaxX = (numcells-1)/2;
mContext->mGlobalMapState.mBounds.mMinY = -(numcells-1)/2;
mContext->mGlobalMapState.mBounds.mMaxY = numcells/2;
mContext->mGlobalMapState.mBounds.mMinX = -numcells / 2;
mContext->mGlobalMapState.mBounds.mMaxX = (numcells - 1) / 2;
mContext->mGlobalMapState.mBounds.mMinY = -(numcells - 1) / 2;
mContext->mGlobalMapState.mBounds.mMaxY = numcells / 2;
osg::ref_ptr<osg::Image> image2 (new osg::Image);
int width = cellSize*numcells;
int height = cellSize*numcells;
osg::ref_ptr<osg::Image> image2(new osg::Image);
int width = cellSize * numcells;
int height = cellSize * numcells;
std::vector<unsigned char> data;
data.resize(width*height*4, 0);
data.resize(width * height * 4, 0);
image2->allocateImage(width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE);
memcpy(image2->data(), &data[0], data.size());
memcpy(image2->data(), data.data(), data.size());
for (const auto & exploredCell : mContext->mExploredCells)
for (const auto& exploredCell : mContext->mExploredCells)
{
if (exploredCell.first > mContext->mGlobalMapState.mBounds.mMaxX
|| exploredCell.first < mContext->mGlobalMapState.mBounds.mMinX
|| exploredCell.second > mContext->mGlobalMapState.mBounds.mMaxY
|| exploredCell.second < mContext->mGlobalMapState.mBounds.mMinY)
|| exploredCell.first < mContext->mGlobalMapState.mBounds.mMinX
|| exploredCell.second > mContext->mGlobalMapState.mBounds.mMaxY
|| exploredCell.second < mContext->mGlobalMapState.mBounds.mMinY)
{
// out of bounds, I think this could happen, since the original engine had a fixed-size map
continue;
}
int imageLeftSrc = mGlobalMapImage->s()/2;
int imageTopSrc = mGlobalMapImage->t()/2;
int imageLeftSrc = mGlobalMapImage->s() / 2;
int imageTopSrc = mGlobalMapImage->t() / 2;
imageLeftSrc += exploredCell.first * cellSize;
imageTopSrc -= exploredCell.second * cellSize;
int imageLeftDst = width/2;
int imageTopDst = height/2;
int imageLeftDst = width / 2;
int imageTopDst = height / 2;
imageLeftDst += exploredCell.first * cellSize;
imageTopDst -= exploredCell.second * cellSize;
for (int x=0; x<cellSize; ++x)
for (int y=0; y<cellSize; ++y)
for (int x = 0; x < cellSize; ++x)
for (int y = 0; y < cellSize; ++y)
{
unsigned int col = *(unsigned int*)mGlobalMapImage->data(imageLeftSrc+x, imageTopSrc+y, 0);
*(unsigned int*)image2->data(imageLeftDst+x, imageTopDst+y, 0) = col;
unsigned int col = *(unsigned int*)mGlobalMapImage->data(imageLeftSrc + x, imageTopSrc + y, 0);
*(unsigned int*)image2->data(imageLeftDst + x, imageTopDst + y, 0) = col;
}
}
@ -177,7 +175,8 @@ namespace ESSImport
osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*image2, ostream);
if (!result.success())
{
std::cerr << "Error: can't write global map image: " << result.message() << " code " << result.status() << std::endl;
std::cerr << "Error: can't write global map image: " << result.message() << " code " << result.status()
<< std::endl;
return;
}
@ -189,7 +188,7 @@ namespace ESSImport
esm.endRecord(ESM::REC_GMAP);
}
void ConvertCell::read(ESM::ESMReader &esm)
void ConvertCell::read(ESM::ESMReader& esm)
{
ESM::Cell cell;
bool isDeleted = false;
@ -203,9 +202,9 @@ namespace ESSImport
}
// note if the player is in a nameless exterior cell, we will assign the cellId later based on player position
if (cell.mName == mContext->mPlayerCellName)
if (Misc::StringUtils::ciEqual(cell.mName, mContext->mPlayerCellName))
{
mContext->mPlayer.mCellId = cell.getCellId();
mContext->mPlayer.mCellId = cell.mId;
}
Cell newcell;
@ -234,15 +233,15 @@ namespace ESSImport
esm.getExact(nam8, 32);
newcell.mFogOfWar.reserve(16*16);
for (int x=0; x<16; ++x)
newcell.mFogOfWar.reserve(16 * 16);
for (int x = 0; x < 16; ++x)
{
for (int y=0; y<16; ++y)
for (int y = 0; y < 16; ++y)
{
size_t pos = x*16+y;
size_t bytepos = pos/8;
assert(bytepos<32);
int bit = pos%8;
size_t pos = x * 16 + y;
size_t bytepos = pos / 8;
assert(bytepos < 32);
int bit = pos % 8;
newcell.mFogOfWar.push_back(((nam8[bytepos] >> bit) & (0x1)) ? 0xffffffff : 0x000000ff);
}
}
@ -252,7 +251,8 @@ namespace ESSImport
std::ostringstream filename;
filename << "fog_" << cell.mData.mX << "_" << cell.mData.mY << ".tga";
convertImage((char*)&newcell.mFogOfWar[0], newcell.mFogOfWar.size()*4, 16, 16, GL_RGBA, filename.str());
convertImage(
(char*)&newcell.mFogOfWar[0], newcell.mFogOfWar.size() * 4, 16, 16, GL_RGBA, filename.str());
}
}
@ -268,17 +268,17 @@ namespace ESSImport
}
std::vector<CellRef> cellrefs;
while (esm.hasMoreSubs() && esm.isNextSub("FRMR"))
while (esm.hasMoreSubs() && esm.peekNextSub("FRMR"))
{
CellRef ref;
ref.load (esm);
ref.load(esm);
cellrefs.push_back(ref);
}
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,
@ -301,35 +301,37 @@ namespace ESSImport
marker.mWorldX = notepos[0];
marker.mWorldY = notepos[1];
marker.mNote = note;
marker.mCell = cell.getCellId();
marker.mCell = cell.mId;
mMarkers.push_back(marker);
}
newcell.mRefs = cellrefs;
if (cell.isExterior())
mExtCells[std::make_pair(cell.mData.mX, cell.mData.mY)] = newcell;
else
mIntCells[cell.mName] = newcell;
}
void ConvertCell::writeCell(const Cell &cell, ESM::ESMWriter& esm)
void ConvertCell::writeCell(const Cell& cell, ESM::ESMWriter& esm)
{
ESM::Cell esmcell = cell.mCell;
esm.startRecord(ESM::REC_CSTA);
ESM::CellState csta;
csta.mHasFogOfWar = 0;
csta.mId = esmcell.getCellId();
csta.mId.save(esm);
csta.mLastRespawn.mDay = 0;
csta.mLastRespawn.mHour = 0;
csta.mId = esmcell.mId;
csta.mIsInterior = !esmcell.isExterior();
esm.writeCellId(csta.mId);
// TODO csta.mLastRespawn;
// shouldn't be needed if we respawn on global schedule like in original MW
csta.mWaterLevel = esmcell.mWater;
csta.save(esm);
for (const auto & cellref : cell.mRefs)
for (const auto& cellref : cell.mRefs)
{
ESM::CellRef out (cellref);
ESM::CellRef out(cellref);
// TODO: use mContext->mCreatures/mNpcs
@ -337,88 +339,90 @@ namespace ESSImport
{
// non-indexed RefNum, i.e. no CREC/NPCC/CNTC record associated with it
// this could be any type of object really (even creatures/npcs too)
out.mRefID = cellref.mIndexedRefId;
std::string idLower = Misc::StringUtils::lowerCase(out.mRefID);
out.mRefID = ESM::RefId::stringRefId(cellref.mIndexedRefId);
ESM::ObjectState objstate;
objstate.blank();
objstate.mRef = out;
objstate.mRef.mRefID = idLower;
objstate.mRef.mRefID = out.mRefID;
objstate.mHasCustomState = false;
convertCellRef(cellref, objstate);
esm.writeHNT ("OBJE", 0);
esm.writeHNT("OBJE", 0);
objstate.save(esm);
continue;
}
else
{
int refIndex;
splitIndexedRefId(cellref.mIndexedRefId, refIndex, out.mRefID);
std::string idLower = Misc::StringUtils::lowerCase(out.mRefID);
int refIndex = 0;
std::string outStringId;
splitIndexedRefId(cellref.mIndexedRefId, refIndex, outStringId);
out.mRefID = ESM::RefId::stringRefId(outStringId);
std::map<std::pair<int, std::string>, NPCC>::const_iterator npccIt = mContext->mNpcChanges.find(
std::make_pair(refIndex, out.mRefID));
auto npccIt = mContext->mNpcChanges.find(std::make_pair(refIndex, out.mRefID));
if (npccIt != mContext->mNpcChanges.end())
{
ESM::NpcState objstate;
objstate.blank();
objstate.mRef = out;
objstate.mRef.mRefID = idLower;
objstate.mRef.mRefID = out.mRefID;
// TODO: need more micromanagement here so we don't overwrite values
// from the ESM with default values
if (cellref.mHasACDT)
convertACDT(cellref.mACDT, objstate.mCreatureStats);
if (cellref.mHasACSC)
convertACSC(cellref.mACSC, objstate.mCreatureStats);
convertNpcData(cellref, objstate.mNpcStats);
if (cellref.mActorData.mHasACDT)
convertACDT(cellref.mActorData.mACDT, objstate.mCreatureStats);
else
objstate.mCreatureStats.mMissingACDT = true;
if (cellref.mActorData.mHasACSC)
convertACSC(cellref.mActorData.mACSC, objstate.mCreatureStats);
convertNpcData(cellref.mActorData, objstate.mNpcStats);
convertNPCC(npccIt->second, objstate);
convertCellRef(cellref, objstate);
objstate.mCreatureStats.mActorId = mContext->generateActorId();
mContext->mActorIdMap.insert(std::make_pair(std::make_pair(refIndex, out.mRefID), objstate.mCreatureStats.mActorId));
mContext->mActorIdMap.insert(
std::make_pair(std::make_pair(refIndex, out.mRefID), objstate.mCreatureStats.mActorId));
esm.writeHNT ("OBJE", ESM::REC_NPC_);
esm.writeHNT("OBJE", ESM::REC_NPC_);
objstate.save(esm);
continue;
}
std::map<std::pair<int, std::string>, CNTC>::const_iterator cntcIt = mContext->mContainerChanges.find(
std::make_pair(refIndex, out.mRefID));
auto cntcIt = mContext->mContainerChanges.find(std::make_pair(refIndex, out.mRefID));
if (cntcIt != mContext->mContainerChanges.end())
{
ESM::ContainerState objstate;
objstate.blank();
objstate.mRef = out;
objstate.mRef.mRefID = idLower;
objstate.mRef.mRefID = out.mRefID;
convertCNTC(cntcIt->second, objstate);
convertCellRef(cellref, objstate);
esm.writeHNT ("OBJE", ESM::REC_CONT);
esm.writeHNT("OBJE", ESM::REC_CONT);
objstate.save(esm);
continue;
}
std::map<std::pair<int, std::string>, CREC>::const_iterator crecIt = mContext->mCreatureChanges.find(
std::make_pair(refIndex, out.mRefID));
auto crecIt = mContext->mCreatureChanges.find(std::make_pair(refIndex, out.mRefID));
if (crecIt != mContext->mCreatureChanges.end())
{
ESM::CreatureState objstate;
objstate.blank();
objstate.mRef = out;
objstate.mRef.mRefID = idLower;
objstate.mRef.mRefID = out.mRefID;
// TODO: need more micromanagement here so we don't overwrite values
// from the ESM with default values
if (cellref.mHasACDT)
convertACDT(cellref.mACDT, objstate.mCreatureStats);
if (cellref.mHasACSC)
convertACSC(cellref.mACSC, objstate.mCreatureStats);
if (cellref.mActorData.mHasACDT)
convertACDT(cellref.mActorData.mACDT, objstate.mCreatureStats);
else
objstate.mCreatureStats.mMissingACDT = true;
if (cellref.mActorData.mHasACSC)
convertACSC(cellref.mActorData.mACSC, objstate.mCreatureStats);
convertCREC(crecIt->second, objstate);
convertCellRef(cellref, objstate);
objstate.mCreatureStats.mActorId = mContext->generateActorId();
mContext->mActorIdMap.insert(std::make_pair(std::make_pair(refIndex, out.mRefID), objstate.mCreatureStats.mActorId));
mContext->mActorIdMap.insert(
std::make_pair(std::make_pair(refIndex, out.mRefID), objstate.mCreatureStats.mActorId));
esm.writeHNT ("OBJE", ESM::REC_CREA);
esm.writeHNT("OBJE", ESM::REC_CREA);
objstate.save(esm);
continue;
}
@ -432,15 +436,15 @@ namespace ESSImport
esm.endRecord(ESM::REC_CSTA);
}
void ConvertCell::write(ESM::ESMWriter &esm)
void ConvertCell::write(ESM::ESMWriter& esm)
{
for (const auto & cell : mIntCells)
for (const auto& cell : mIntCells)
writeCell(cell.second, esm);
for (const auto & cell : mExtCells)
for (const auto& cell : mExtCells)
writeCell(cell.second, esm);
for (const auto & marker : mMarkers)
for (const auto& marker : mMarkers)
{
esm.startRecord(ESM::REC_MARK);
marker.save(esm);
@ -462,7 +466,7 @@ namespace ESSImport
ESM::ProjectileState out;
convertBaseState(out, pnam);
out.mBowId = pnam.mBowId.toString();
out.mBowId = ESM::RefId::stringRefId(pnam.mBowId.toString());
out.mVelocity = pnam.mVelocity;
out.mAttackStrength = pnam.mAttackStrength;
@ -476,16 +480,18 @@ namespace ESSImport
convertBaseState(out, pnam);
auto it = std::find_if(mContext->mActiveSpells.begin(), mContext->mActiveSpells.end(),
[&pnam](const SPLM::ActiveSpell& spell) -> bool { return spell.mIndex == pnam.mSplmIndex; });
[&pnam](const SPLM::ActiveSpell& spell) -> bool { return spell.mIndex == pnam.mSplmIndex; });
if (it == mContext->mActiveSpells.end())
{
std::cerr << "Warning: Skipped conversion for magic projectile \"" << pnam.mArrowId.toString() << "\" (invalid spell link)" << std::endl;
std::cerr << "Warning: Skipped conversion for magic projectile \"" << pnam.mArrowId.toString()
<< "\" (invalid spell link)" << std::endl;
continue;
}
out.mSpellId = it->mSPDT.mId.toString();
out.mSpellId = ESM::RefId::stringRefId(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);
@ -496,11 +502,11 @@ namespace ESSImport
void ConvertPROJ::convertBaseState(ESM::BaseProjectileState& base, const PROJ::PNAM& pnam)
{
base.mId = pnam.mArrowId.toString();
base.mId = ESM::RefId::stringRefId(pnam.mArrowId.toString());
base.mPosition = pnam.mPosition;
osg::Quat orient;
orient.makeRotate(osg::Vec3f(0,1,0), pnam.mVelocity);
orient.makeRotate(osg::Vec3f(0, 1, 0), pnam.mVelocity);
base.mOrientation = orient;
base.mActorId = convertActorId(pnam.mActorId.toString(), *mContext);

File diff suppressed because it is too large Load Diff

@ -1,22 +1,23 @@
#include "convertinventory.hpp"
#include <components/misc/stringops.hpp>
#include <components/misc/strings/lower.hpp>
#include <cstdlib>
namespace ESSImport
{
void convertInventory(const Inventory &inventory, ESM::InventoryState &state)
void convertInventory(const Inventory& inventory, ESM::InventoryState& state)
{
int index = 0;
for (const auto & item : inventory.mItems)
for (const auto& item : inventory.mItems)
{
ESM::ObjectState objstate;
objstate.blank();
objstate.mRef = item;
objstate.mRef.mRefID = Misc::StringUtils::lowerCase(item.mId);
objstate.mRef.mRefID = ESM::RefId::stringRefId(item.mId);
objstate.mCount = std::abs(item.mCount); // restocking items have negative count in the savefile
// openmw handles them differently, so no need to set any flags
// openmw handles them differently, so no need to set any flags
state.mItems.push_back(objstate);
if (item.mRelativeEquipmentSlot != -1)
// Note we should really write the absolute slot here, which we do not know about

@ -3,12 +3,12 @@
#include "importinventory.hpp"
#include <components/esm/inventorystate.hpp>
#include <components/esm3/inventorystate.hpp>
namespace ESSImport
{
void convertInventory (const Inventory& inventory, ESM::InventoryState& state);
void convertInventory(const Inventory& inventory, ESM::InventoryState& state);
}

@ -5,7 +5,7 @@
namespace ESSImport
{
void convertNPCC(const NPCC &npcc, ESM::NpcState &npcState)
void convertNPCC(const NPCC& npcc, ESM::NpcState& npcState)
{
npcState.mNpcStats.mDisposition = npcc.mNPDT.mDisposition;
npcState.mNpcStats.mReputation = npcc.mNPDT.mReputation;

@ -3,12 +3,12 @@
#include "importnpcc.hpp"
#include <components/esm/npcstate.hpp>
#include <components/esm3/npcstate.hpp>
namespace ESSImport
{
void convertNPCC (const NPCC& npcc, ESM::NpcState& npcState);
void convertNPCC(const NPCC& npcc, ESM::NpcState& npcState);
}

@ -1,28 +1,35 @@
#include "convertplayer.hpp"
#include <cmath>
#include <components/esm3/loadcell.hpp>
#include <components/misc/constants.hpp>
#include <components/misc/stringops.hpp>
#include <components/misc/strings/lower.hpp>
namespace ESSImport
{
void convertPCDT(const PCDT& pcdt, ESM::Player& out, std::vector<std::string>& outDialogueTopics, bool& firstPersonCam, bool& teleportingEnabled, bool& levitationEnabled, ESM::ControlsState& controls)
void convertPCDT(const PCDT& pcdt, ESM::Player& out, std::vector<ESM::RefId>& outDialogueTopics,
bool& firstPersonCam, bool& teleportingEnabled, bool& levitationEnabled, ESM::ControlsState& controls)
{
out.mBirthsign = pcdt.mBirthsign;
out.mObject.mPosition.rot[0]
= -atan2(pcdt.mPNAM.mVerticalRotation.mData[2][1], pcdt.mPNAM.mVerticalRotation.mData[2][2]);
out.mBirthsign = ESM::RefId::stringRefId(pcdt.mBirthsign);
out.mObject.mNpcStats.mBounty = pcdt.mBounty;
for (const auto & essFaction : pcdt.mFactions)
for (const auto& essFaction : pcdt.mFactions)
{
ESM::NpcStats::Faction faction;
faction.mExpelled = (essFaction.mFlags & 0x2) != 0;
faction.mRank = essFaction.mRank;
faction.mReputation = essFaction.mReputation;
out.mObject.mNpcStats.mFactions[Misc::StringUtils::lowerCase(essFaction.mFactionName.toString())] = faction;
out.mObject.mNpcStats.mFactions[ESM::RefId::stringRefId(essFaction.mFactionName.toString())] = faction;
}
for (int i=0; i<3; ++i)
for (size_t i = 0; i < out.mObject.mNpcStats.mSpecIncreases.size(); ++i)
out.mObject.mNpcStats.mSpecIncreases[i] = pcdt.mPNAM.mSpecIncreases[i];
for (int i=0; i<8; ++i)
for (size_t i = 0; i < out.mObject.mNpcStats.mSkillIncrease.size(); ++i)
out.mObject.mNpcStats.mSkillIncrease[i] = pcdt.mPNAM.mSkillIncreases[i];
for (int i=0; i<27; ++i)
for (size_t i = 0; i < out.mObject.mNpcStats.mSkills.size(); ++i)
out.mObject.mNpcStats.mSkills[i].mProgress = pcdt.mPNAM.mSkillProgress[i];
out.mObject.mNpcStats.mLevelProgress = pcdt.mPNAM.mLevelProgress;
@ -35,9 +42,9 @@ namespace ESSImport
teleportingEnabled = !(pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_TeleportingDisabled);
levitationEnabled = !(pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_LevitationDisabled);
for (const auto & knownDialogueTopic : pcdt.mKnownDialogueTopics)
for (const auto& knownDialogueTopic : pcdt.mKnownDialogueTopics)
{
outDialogueTopics.push_back(Misc::StringUtils::lowerCase(knownDialogueTopic));
outDialogueTopics.push_back(ESM::RefId::stringRefId(knownDialogueTopic));
}
controls.mViewSwitchDisabled = pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_ViewSwitchDisabled;
@ -54,19 +61,9 @@ namespace ESSImport
const PCDT::PNAM::MarkLocation& mark = pcdt.mPNAM.mMarkLocation;
ESM::CellId cell;
cell.mWorldspace = ESM::CellId::sDefaultWorldspace;
cell.mPaged = true;
cell.mIndex.mX = mark.mCellX;
cell.mIndex.mY = mark.mCellY;
// TODO: Figure out a better way to detect interiors. (0, 0) is a valid exterior cell.
if (mark.mCellX == 0 && mark.mCellY == 0)
{
cell.mWorldspace = pcdt.mMNAM;
cell.mPaged = false;
}
bool interior = mark.mCellX == 0 && mark.mCellY == 0;
ESM::RefId cell = ESM::Cell::generateIdForCell(!interior, pcdt.mMNAM, mark.mCellX, mark.mCellY);
out.mMarkedCell = cell;
out.mMarkedPosition.pos[0] = mark.mX;

@ -3,13 +3,14 @@
#include "importplayer.hpp"
#include <components/esm/player.hpp>
#include <components/esm/controlsstate.hpp>
#include <components/esm3/controlsstate.hpp>
#include <components/esm3/player.hpp>
namespace ESSImport
{
void convertPCDT(const PCDT& pcdt, ESM::Player& out, std::vector<std::string>& outDialogueTopics, bool& firstPersonCam, bool& teleportingEnabled, bool& levitationEnabled, ESM::ControlsState& controls);
void convertPCDT(const PCDT& pcdt, ESM::Player& out, std::vector<ESM::RefId>& outDialogueTopics,
bool& firstPersonCam, bool& teleportingEnabled, bool& levitationEnabled, ESM::ControlsState& controls);
}

@ -1,17 +1,17 @@
#include "convertscpt.hpp"
#include <components/misc/stringops.hpp>
#include "convertscri.hpp"
#include <components/misc/strings/lower.hpp>
namespace ESSImport
{
void convertSCPT(const SCPT &scpt, ESM::GlobalScript &out)
void convertSCPT(const SCPT& scpt, ESM::GlobalScript& out)
{
out.mId = Misc::StringUtils::lowerCase(scpt.mSCHD.mName.toString());
out.mId = ESM::RefId::stringRefId(scpt.mSCHD.mName.toString());
out.mRunning = scpt.mRunning;
out.mTargetRef.unset(); // TODO: convert target reference of global script
out.mTargetRef = ESM::RefNum{}; // TODO: convert target reference of global script
convertSCRI(scpt.mSCRI, out.mLocals);
}

@ -1,14 +1,14 @@
#ifndef OPENMW_ESSIMPORT_CONVERTSCPT_H
#define OPENMW_ESSIMPORT_CONVERTSCPT_H
#include <components/esm/globalscript.hpp>
#include <components/esm3/globalscript.hpp>
#include "importscpt.hpp"
namespace ESSImport
{
void convertSCPT(const SCPT& scpt, ESM::GlobalScript& out);
void convertSCPT(const SCPT& scpt, ESM::GlobalScript& out);
}

@ -19,12 +19,12 @@ namespace
namespace ESSImport
{
void convertSCRI(const SCRI &scri, ESM::Locals &locals)
void convertSCRI(const SCRI& scri, ESM::Locals& locals)
{
// order *is* important, as we do not have variable names available in this format
storeVariables<short, ESM::VT_Short> (scri.mShorts, locals, scri.mScript);
storeVariables<int, ESM::VT_Int> (scri.mLongs, locals, scri.mScript);
storeVariables<float, ESM::VT_Float> (scri.mFloats, locals, scri.mScript);
storeVariables<short, ESM::VT_Short>(scri.mShorts, locals, scri.mScript);
storeVariables<int, ESM::VT_Int>(scri.mLongs, locals, scri.mScript);
storeVariables<float, ESM::VT_Float>(scri.mFloats, locals, scri.mScript);
}
}

@ -3,13 +3,13 @@
#include "importscri.hpp"
#include <components/esm/locals.hpp>
#include <components/esm3/locals.hpp>
namespace ESSImport
{
/// Convert script variable assignments
void convertSCRI (const SCRI& scri, ESM::Locals& locals);
void convertSCRI(const SCRI& scri, ESM::Locals& locals);
}

@ -1,135 +0,0 @@
#include "importacdt.hpp"
#include <components/esm/esmreader.hpp>
#include <components/esm/cellref.hpp>
namespace ESSImport
{
void ActorData::load(ESM::ESMReader &esm)
{
if (esm.isNextSub("ACTN"))
{
/*
Activation flags:
ActivationFlag_UseEnabled = 1
ActivationFlag_OnActivate = 2
ActivationFlag_OnDeath = 10h
ActivationFlag_OnKnockout = 20h
ActivationFlag_OnMurder = 40h
ActivationFlag_DoorOpening = 100h
ActivationFlag_DoorClosing = 200h
ActivationFlag_DoorJammedOpening = 400h
ActivationFlag_DoorJammedClosing = 800h
*/
esm.skipHSub();
}
if (esm.isNextSub("STPR"))
esm.skipHSub();
if (esm.isNextSub("MNAM"))
esm.skipHSub();
bool isDeleted = false;
ESM::CellRef::loadData(esm, isDeleted);
mHasACDT = false;
if (esm.isNextSub("ACDT"))
{
mHasACDT = true;
esm.getHT(mACDT);
}
mHasACSC = false;
if (esm.isNextSub("ACSC"))
{
mHasACSC = true;
esm.getHT(mACSC);
}
if (esm.isNextSub("ACSL"))
esm.skipHSubSize(112);
if (esm.isNextSub("CSTN"))
esm.skipHSub(); // "PlayerSaveGame", link to some object?
if (esm.isNextSub("LSTN"))
esm.skipHSub(); // "PlayerSaveGame", link to some object?
// unsure at which point between LSTN and TGTN
if (esm.isNextSub("CSHN"))
esm.skipHSub(); // "PlayerSaveGame", link to some object?
// unsure if before or after CSTN/LSTN
if (esm.isNextSub("LSHN"))
esm.skipHSub(); // "PlayerSaveGame", link to some object?
while (esm.isNextSub("TGTN"))
esm.skipHSub(); // "PlayerSaveGame", link to some object?
while (esm.isNextSub("FGTN"))
esm.getHString(); // fight target?
// unsure at which point between TGTN and CRED
if (esm.isNextSub("AADT"))
{
// occurred when a creature was in the middle of its attack, 44 bytes
esm.skipHSub();
}
// unsure at which point between FGTN and CHRD
if (esm.isNextSub("PWPC"))
esm.skipHSub();
if (esm.isNextSub("PWPS"))
esm.skipHSub();
if (esm.isNextSub("WNAM"))
{
std::string id = esm.getHString();
if (esm.isNextSub("XNAM"))
mSelectedEnchantItem = esm.getHString();
else
mSelectedSpell = id;
if (esm.isNextSub("YNAM"))
esm.skipHSub(); // 4 byte, 0
}
while (esm.isNextSub("APUD"))
{
// used power
esm.getSubHeader();
std::string id = esm.getString(32);
(void)id;
// timestamp can't be used: this is the total hours passed, calculated by
// timestamp = 24 * (365 * year + cumulativeDays[month] + day)
// unfortunately cumulativeDays[month] is not clearly defined,
// in the (non-MCP) vanilla version the first month was missing, but MCP added it.
double timestamp;
esm.getT(timestamp);
}
// FIXME: not all actors have this, add flag
if (esm.isNextSub("CHRD")) // npc only
esm.getHExact(mSkills, 27*2*sizeof(int));
if (esm.isNextSub("CRED")) // creature only
esm.getHExact(mCombatStats, 3*2*sizeof(int));
mSCRI.load(esm);
if (esm.isNextSub("ND3D"))
esm.skipHSub();
mHasANIS = false;
if (esm.isNextSub("ANIS"))
{
mHasANIS = true;
esm.getHT(mANIS);
}
}
}

@ -3,8 +3,6 @@
#include <string>
#include <components/esm/cellref.hpp>
#include "importscri.hpp"
namespace ESM
@ -40,7 +38,8 @@ namespace ESSImport
float mDynamic[3][2];
unsigned char mUnknown3[16];
float mAttributes[8][2];
float mMagicEffects[27]; // Effect attributes: https://wiki.openmw.org/index.php?title=Research:Magic#Effect_attributes
float mMagicEffects[27]; // Effect attributes:
// https://wiki.openmw.org/index.php?title=Research:Magic#Effect_attributes
unsigned char mUnknown4[4];
unsigned int mGoldPool;
unsigned char mCountDown; // seen the same value as in ACSC.mCorpseClearCountdown, maybe
@ -63,7 +62,7 @@ namespace ESSImport
};
#pragma pack(pop)
struct ActorData : public ESM::CellRef
struct ActorData
{
bool mHasACDT;
ACDT mACDT;
@ -85,10 +84,6 @@ namespace ESSImport
bool mHasANIS;
ANIS mANIS; // scripted animation state
virtual void load(ESM::ESMReader& esm);
virtual ~ActorData() = default;
};
}

@ -1,32 +1,152 @@
#include "importcellref.hpp"
#include <components/esm/esmreader.hpp>
#include <components/esm3/esmreader.hpp>
namespace ESSImport
{
void CellRef::load(ESM::ESMReader &esm)
void CellRef::load(ESM::ESMReader& esm)
{
blank();
// (FRMR subrecord name is already read by the loop in ConvertCell)
esm.getHT(mRefNum.mIndex); // FRMR
esm.getHNT(mRefNum.mIndex, "FRMR");
// this is required since openmw supports more than 255 content files
int pluginIndex = (mRefNum.mIndex & 0xff000000) >> 24;
mRefNum.mContentFile = pluginIndex-1;
mRefNum.mContentFile = pluginIndex - 1;
mRefNum.mIndex &= 0x00ffffff;
mIndexedRefId = esm.getHNString("NAME");
ActorData::load(esm);
if (esm.isNextSub("ACTN"))
{
/*
Activation flags:
ActivationFlag_UseEnabled = 1
ActivationFlag_OnActivate = 2
ActivationFlag_OnDeath = 10h
ActivationFlag_OnKnockout = 20h
ActivationFlag_OnMurder = 40h
ActivationFlag_DoorOpening = 100h
ActivationFlag_DoorClosing = 200h
ActivationFlag_DoorJammedOpening = 400h
ActivationFlag_DoorJammedClosing = 800h
*/
esm.skipHSub();
}
if (esm.isNextSub("STPR"))
esm.skipHSub();
if (esm.isNextSub("MNAM"))
esm.skipHSub();
bool isDeleted = false;
ESM::CellRef::loadData(esm, isDeleted);
mActorData.mHasACDT = false;
if (esm.isNextSub("ACDT"))
{
mActorData.mHasACDT = true;
esm.getHT(mActorData.mACDT);
}
mActorData.mHasACSC = false;
if (esm.isNextSub("ACSC"))
{
mActorData.mHasACSC = true;
esm.getHT(mActorData.mACSC);
}
if (esm.isNextSub("ACSL"))
esm.skipHSubSize(112);
if (esm.isNextSub("CSTN"))
esm.skipHSub(); // "PlayerSaveGame", link to some object?
if (esm.isNextSub("LSTN"))
esm.skipHSub(); // "PlayerSaveGame", link to some object?
// unsure at which point between LSTN and TGTN
if (esm.isNextSub("CSHN"))
esm.skipHSub(); // "PlayerSaveGame", link to some object?
// unsure if before or after CSTN/LSTN
if (esm.isNextSub("LSHN"))
esm.skipHSub(); // "PlayerSaveGame", link to some object?
while (esm.isNextSub("TGTN"))
esm.skipHSub(); // "PlayerSaveGame", link to some object?
while (esm.isNextSub("FGTN"))
esm.getHString(); // fight target?
// unsure at which point between TGTN and CRED
if (esm.isNextSub("AADT"))
{
// occurred when a creature was in the middle of its attack, 44 bytes
esm.skipHSub();
}
// unsure at which point between FGTN and CHRD
if (esm.isNextSub("PWPC"))
esm.skipHSub();
if (esm.isNextSub("PWPS"))
esm.skipHSub();
if (esm.isNextSub("WNAM"))
{
std::string id = esm.getHString();
if (esm.isNextSub("XNAM"))
mActorData.mSelectedEnchantItem = esm.getHString();
else
mActorData.mSelectedSpell = id;
if (esm.isNextSub("YNAM"))
esm.skipHSub(); // 4 byte, 0
}
while (esm.isNextSub("APUD"))
{
// used power
esm.getSubHeader();
std::string id = esm.getMaybeFixedStringSize(32);
(void)id;
// timestamp can't be used: this is the total hours passed, calculated by
// timestamp = 24 * (365 * year + cumulativeDays[month] + day)
// unfortunately cumulativeDays[month] is not clearly defined,
// in the (non-MCP) vanilla version the first month was missing, but MCP added it.
double timestamp;
esm.getT(timestamp);
}
// FIXME: not all actors have this, add flag
if (esm.isNextSub("CHRD")) // npc only
esm.getHExact(mActorData.mSkills, 27 * 2 * sizeof(int));
if (esm.isNextSub("CRED")) // creature only
esm.getHExact(mActorData.mCombatStats, 3 * 2 * sizeof(int));
mActorData.mSCRI.load(esm);
if (esm.isNextSub("ND3D"))
esm.skipHSub();
mActorData.mHasANIS = false;
if (esm.isNextSub("ANIS"))
{
mActorData.mHasANIS = true;
esm.getHT(mActorData.mANIS);
}
if (esm.isNextSub("LVCR"))
{
// occurs on levelled creature spawner references
// probably some identifier for the creature that has been spawned?
unsigned char lvcr;
esm.getHT(lvcr);
//std::cout << "LVCR: " << (int)lvcr << std::endl;
// std::cout << "LVCR: " << (int)lvcr << std::endl;
}
mEnabled = true;
@ -35,8 +155,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"
@ -15,7 +15,7 @@ namespace ESM
namespace ESSImport
{
struct CellRef : public ActorData
struct CellRef : public ESM::CellRef
{
std::string mIndexedRefId;
@ -25,9 +25,11 @@ namespace ESSImport
bool mDeleted;
void load(ESM::ESMReader& esm) override;
ActorData mActorData;
virtual ~CellRef() = default;
void load(ESM::ESMReader& esm);
~CellRef() = default;
};
}

@ -1,11 +1,11 @@
#include "importcntc.hpp"
#include <components/esm/esmreader.hpp>
#include <components/esm3/esmreader.hpp>
namespace ESSImport
{
void CNTC::load(ESM::ESMReader &esm)
void CNTC::load(ESM::ESMReader& esm)
{
mIndex = 0;
esm.getHNT(mIndex, "INDX");

@ -1,11 +1,11 @@
#include "importcrec.hpp"
#include <components/esm/esmreader.hpp>
#include <components/esm3/esmreader.hpp>
namespace ESSImport
{
void CREC::load(ESM::ESMReader &esm)
void CREC::load(ESM::ESMReader& esm)
{
esm.getHNT(mIndex, "INDX");
@ -14,9 +14,8 @@ namespace ESSImport
float scale;
esm.getHNOT(scale, "XSCL");
while (esm.isNextSub("AI_W") || esm.isNextSub("AI_E") || esm.isNextSub("AI_T") || esm.isNextSub("AI_F")
|| esm.isNextSub("AI_A"))
|| esm.isNextSub("AI_A"))
mAiPackages.add(esm);
mInventory.load(esm);

@ -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,11 +1,11 @@
#include "importdial.hpp"
#include <components/esm/esmreader.hpp>
#include <components/esm3/esmreader.hpp>
namespace ESSImport
{
void DIAL::load(ESM::ESMReader &esm)
void DIAL::load(ESM::ESMReader& esm)
{
// See ESM::Dialogue::Type enum, not sure why we would need this here though
int type = 0;

@ -1,27 +1,27 @@
#include "importer.hpp"
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <osgDB/ReadFile>
#include <osg/ImageUtils>
#include <osgDB/ReadFile>
#include <components/esm/esmreader.hpp>
#include <components/esm/esmwriter.hpp>
#include <components/esm/defs.hpp>
#include <components/esm3/esmreader.hpp>
#include <components/esm3/esmwriter.hpp>
#include <components/esm/savedgame.hpp>
#include <components/esm/player.hpp>
#include <components/esm3/player.hpp>
#include <components/esm3/savedgame.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/cellid.hpp>
#include <components/esm3/loadalch.hpp>
#include <components/esm3/loadarmo.hpp>
#include <components/esm3/loadclot.hpp>
#include <components/esm3/loadench.hpp>
#include <components/esm3/loadlevlist.hpp>
#include <components/esm3/loadspel.hpp>
#include <components/esm3/loadweap.hpp>
#include <components/misc/constants.hpp>
@ -36,25 +36,25 @@ namespace
void writeScreenshot(const ESM::Header& fileHeader, ESM::SavedGame& out)
{
if (fileHeader.mSCRS.size() != 128*128*4)
if (fileHeader.mSCRS.size() != 128 * 128 * 4)
{
std::cerr << "Error: unexpected screenshot size " << std::endl;
return;
}
osg::ref_ptr<osg::Image> image (new osg::Image);
osg::ref_ptr<osg::Image> image(new osg::Image);
image->allocateImage(128, 128, 1, GL_RGB, GL_UNSIGNED_BYTE);
// need to convert pixel format from BGRA to RGB as the jpg readerwriter doesn't support it otherwise
auto it = fileHeader.mSCRS.begin();
for (int y=0; y<128; ++y)
for (int y = 0; y < 128; ++y)
{
for (int x=0; x<128; ++x)
for (int x = 0; x < 128; ++x)
{
assert(image->data(x,y));
*(image->data(x,y)+2) = *it++;
*(image->data(x,y)+1) = *it++;
*image->data(x,y) = *it++;
assert(image->data(x, y));
*(image->data(x, y) + 2) = *it++;
*(image->data(x, y) + 1) = *it++;
*image->data(x, y) = *it++;
++it; // skip alpha
}
}
@ -73,7 +73,8 @@ namespace
osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*image, ostream);
if (!result.success())
{
std::cerr << "Error: can't write screenshot: " << result.message() << " code " << result.status() << std::endl;
std::cerr << "Error: can't write screenshot: " << result.message() << " code " << result.status()
<< std::endl;
return;
}
@ -86,12 +87,12 @@ namespace
namespace ESSImport
{
Importer::Importer(const std::string &essfile, const std::string &outfile, const std::string &encoding)
Importer::Importer(
const std::filesystem::path& essfile, const std::filesystem::path& outfile, const std::string& encoding)
: mEssFile(essfile)
, mOutFile(outfile)
, mEncoding(encoding)
{
}
struct File
@ -113,7 +114,7 @@ namespace ESSImport
std::vector<Record> mRecords;
};
void read(const std::string& filename, File& file)
void read(const std::filesystem::path& filename, File& file)
{
ESM::ESMReader esm;
esm.open(filename);
@ -144,14 +145,14 @@ namespace ESSImport
void Importer::compare()
{
// data that always changes (and/or is already fully decoded) should be blacklisted
std::set<std::pair<std::string, std::string> > blacklist;
std::set<std::pair<std::string, std::string>> blacklist;
blacklist.insert(std::make_pair("GLOB", "FLTV")); // gamehour
blacklist.insert(std::make_pair("REFR", "DATA")); // player position
blacklist.insert(std::make_pair("CELL", "NAM8")); // fog of war
blacklist.insert(std::make_pair("GAME", "GMDT")); // weather data, current time always changes
blacklist.insert(std::make_pair("CELL", "DELE")); // first 3 bytes are uninitialized
// this changes way too often, name suggests some renderer internal data?
// this changes way too often, name suggests some renderer internal data?
blacklist.insert(std::make_pair("CELL", "ND3D"));
blacklist.insert(std::make_pair("REFR", "ND3D"));
@ -161,7 +162,7 @@ namespace ESSImport
read(mOutFile, file2); // todo rename variable
// FIXME: use max(size1, size2)
for (unsigned int i=0; i<file1.mRecords.size(); ++i)
for (size_t i = 0; i < file1.mRecords.size(); ++i)
{
File::Record rec = file1.mRecords[i];
@ -184,14 +185,15 @@ namespace ESSImport
}
// FIXME: use max(size1, size2)
for (unsigned int j=0; j<rec.mSubrecords.size(); ++j)
for (size_t j = 0; j < rec.mSubrecords.size(); ++j)
{
File::Subrecord sub = rec.mSubrecords[j];
if (j >= rec2.mSubrecords.size())
{
std::ios::fmtflags f(std::cout.flags());
std::cout << "Subrecord in file1 not present in file2: (1) 0x" << std::hex << sub.mFileOffset << std::endl;
std::cout << "Subrecord in file1 not present in file2: (1) 0x" << std::hex << sub.mFileOffset
<< std::endl;
std::cout.flags(f);
return;
}
@ -201,8 +203,9 @@ namespace ESSImport
if (sub.mName != sub2.mName)
{
std::ios::fmtflags f(std::cout.flags());
std::cout << "Different subrecord name (" << rec.mName << "." << sub.mName << " vs. " << sub2.mName << ") at (1) 0x" << std::hex << sub.mFileOffset
<< " (2) 0x" << sub2.mFileOffset << std::endl;
std::cout << "Different subrecord name (" << rec.mName << "." << sub.mName << " vs. " << sub2.mName
<< ") at (1) 0x" << std::hex << sub.mFileOffset << " (2) 0x" << sub2.mFileOffset
<< std::endl;
std::cout.flags(f);
break; // TODO: try to recover
}
@ -214,11 +217,11 @@ namespace ESSImport
std::ios::fmtflags f(std::cout.flags());
std::cout << "Different subrecord data for " << rec.mName << "." << sub.mName << " at (1) 0x" << std::hex << sub.mFileOffset
<< " (2) 0x" << sub2.mFileOffset << std::endl;
std::cout << "Different subrecord data for " << rec.mName << "." << sub.mName << " at (1) 0x"
<< std::hex << sub.mFileOffset << " (2) 0x" << sub2.mFileOffset << std::endl;
std::cout << "Data 1:" << std::endl;
for (unsigned int k=0; k<sub.mData.size(); ++k)
for (size_t k = 0; k < sub.mData.size(); ++k)
{
bool different = false;
if (k >= sub2.mData.size() || sub2.mData[k] != sub.mData[k])
@ -233,7 +236,7 @@ namespace ESSImport
std::cout << std::endl;
std::cout << "Data 2:" << std::endl;
for (unsigned int k=0; k<sub2.mData.size(); ++k)
for (size_t k = 0; k < sub2.mData.size(); ++k)
{
bool different = false;
if (k >= sub.mData.size() || sub.mData[k] != sub2.mData[k])
@ -264,48 +267,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?)
@ -314,7 +317,7 @@ namespace ESSImport
std::set<unsigned int> unknownRecords;
for (const auto & converter : converters)
for (const auto& converter : converters)
{
converter.second->setContext(context);
}
@ -324,17 +327,18 @@ 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;
std::cerr << "Error: unknown record " << n.toString() << " (0x" << std::hex << esm.getFileOffset()
<< ")" << std::endl;
std::cerr.flags(f);
}
@ -344,23 +348,23 @@ namespace ESSImport
ESM::ESMWriter writer;
writer.setFormat (ESM::SavedGame::sCurrentFormat);
writer.setFormatVersion(ESM::CurrentSaveGameFormatVersion);
boost::filesystem::ofstream stream(boost::filesystem::path(mOutFile), std::ios::out | std::ios::binary);
std::ofstream stream(mOutFile, std::ios::out | std::ios::binary);
// all unused
writer.setVersion(0);
writer.setType(0);
writer.setAuthor("");
writer.setDescription("");
writer.setRecordCount (0);
writer.setRecordCount(0);
for (const auto & master : header.mMaster)
for (const auto& master : header.mMaster)
writer.addMaster(master.name, 0); // not using the size information anyway -> use value of 0
writer.save (stream);
writer.save(stream);
ESM::SavedGame profile;
for (const auto & master : header.mMaster)
for (const auto& master : header.mMaster)
{
profile.mContentFiles.push_back(master.name);
}
@ -369,7 +373,8 @@ namespace ESSImport
profile.mInGameTime.mGameHour = context.mHour;
profile.mInGameTime.mMonth = context.mMonth;
profile.mInGameTime.mYear = context.mYear;
profile.mPlayerCell = header.mGameData.mCurrentCell.toString();
profile.mTimePlayed = 0;
profile.mPlayerCellName = context.mPlayerCellName;
if (context.mPlayerBase.mClass == "NEWCLASSID_CHARGEN")
profile.mPlayerClassName = context.mCustomPlayerClassName;
else
@ -379,14 +384,13 @@ namespace ESSImport
writeScreenshot(header, profile);
writer.startRecord (ESM::REC_SAVE);
profile.save (writer);
writer.endRecord (ESM::REC_SAVE);
writer.startRecord(ESM::REC_SAVE);
profile.save(writer);
writer.endRecord(ESM::REC_SAVE);
// 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();
it != converters.end(); ++it)
for (auto it = converters.begin(); it != converters.end(); ++it)
{
if (it->second->getStage() != 0)
continue;
@ -394,12 +398,11 @@ namespace ESSImport
}
writer.startRecord(ESM::REC_NPC_);
context.mPlayerBase.mId = "player";
context.mPlayerBase.mId = ESM::RefId::stringRefId("Player");
context.mPlayerBase.save(writer);
writer.endRecord(ESM::REC_NPC_);
for (std::map<unsigned int, std::shared_ptr<Converter> >::const_iterator it = converters.begin();
it != converters.end(); ++it)
for (auto it = converters.begin(); it != converters.end(); ++it)
{
if (it->second->getStage() != 1)
continue;
@ -407,14 +410,11 @@ namespace ESSImport
}
writer.startRecord(ESM::REC_PLAY);
if (context.mPlayer.mCellId.mPaged)
{
// exterior cell -> determine cell coordinates based on position
int cellX = static_cast<int>(std::floor(context.mPlayer.mObject.mPosition.pos[0] / Constants::CellSizeInUnits));
int cellY = static_cast<int>(std::floor(context.mPlayer.mObject.mPosition.pos[1] / Constants::CellSizeInUnits));
context.mPlayer.mCellId.mIndex.mX = cellX;
context.mPlayer.mCellId.mIndex.mY = cellY;
}
ESM::CellId cellId = ESM::CellId::extractFromRefId(context.mPlayer.mCellId);
int cellX = static_cast<int>(std::floor(context.mPlayer.mObject.mPosition.pos[0] / Constants::CellSizeInUnits));
int cellY = static_cast<int>(std::floor(context.mPlayer.mObject.mPosition.pos[1] / Constants::CellSizeInUnits));
context.mPlayer.mCellId = ESM::Cell::generateIdForCell(cellId.mPaged, cellId.mWorldspace, cellX, cellY);
context.mPlayer.save(writer);
writer.endRecord(ESM::REC_PLAY);
@ -423,15 +423,14 @@ 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();
it != converters.end(); ++it)
for (auto it = converters.begin(); it != converters.end(); ++it)
{
if (it->second->getStage() != 2)
continue;
it->second->write(writer);
}
writer.startRecord (ESM::REC_DIAS);
writer.startRecord(ESM::REC_DIAS);
context.mDialogueState.save(writer);
writer.endRecord(ESM::REC_DIAS);
@ -440,5 +439,4 @@ namespace ESSImport
writer.endRecord(ESM::REC_INPU);
}
}

@ -1,7 +1,7 @@
#ifndef OPENMW_ESSIMPORTER_IMPORTER_H
#define OPENMW_ESSIMPORTER_IMPORTER_H
#include <string>
#include <filesystem>
namespace ESSImport
{
@ -9,15 +9,16 @@ namespace ESSImport
class Importer
{
public:
Importer(const std::string& essfile, const std::string& outfile, const std::string& encoding);
Importer(
const std::filesystem::path& essfile, const std::filesystem::path& outfile, const std::string& encoding);
void run();
void compare();
private:
std::string mEssFile;
std::string mOutFile;
std::filesystem::path mEssFile;
std::filesystem::path mOutFile;
std::string mEncoding;
};

@ -3,22 +3,19 @@
#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/controlsstate.hpp>
#include <components/esm3/dialoguestate.hpp>
#include <components/esm3/globalmap.hpp>
#include <components/esm3/loadcell.hpp>
#include <components/esm3/loadcrea.hpp>
#include <components/esm3/loadnpc.hpp>
#include <components/esm3/player.hpp>
#include "importnpcc.hpp"
#include "importcrec.hpp"
#include "importcntc.hpp"
#include "importplayer.hpp"
#include "importcrec.hpp"
#include "importnpcc.hpp"
#include "importsplm.h"
namespace ESSImport
{
@ -36,7 +33,7 @@ namespace ESSImport
ESM::ControlsState mControlsState;
// cells which should show an explored overlay on the global map
std::set<std::pair<int, int> > mExploredCells;
std::set<std::pair<int, int>> mExploredCells;
ESM::GlobalMap mGlobalMapState;
@ -44,15 +41,15 @@ namespace ESSImport
float mHour;
// key <refIndex, refId>
std::map<std::pair<int, std::string>, CREC> mCreatureChanges;
std::map<std::pair<int, std::string>, NPCC> mNpcChanges;
std::map<std::pair<int, std::string>, CNTC> mContainerChanges;
std::map<std::pair<int, ESM::RefId>, CREC> mCreatureChanges;
std::map<std::pair<int, ESM::RefId>, NPCC> mNpcChanges;
std::map<std::pair<int, ESM::RefId>, CNTC> mContainerChanges;
std::map<std::pair<int, std::string>, int> mActorIdMap;
std::map<std::pair<int, ESM::RefId>, int> mActorIdMap;
int mNextActorId;
std::map<std::string, ESM::Creature> mCreatures;
std::map<std::string, ESM::NPC> mNpcs;
std::map<ESM::RefId, ESM::Creature> mCreatures;
std::map<ESM::RefId, ESM::NPC> mNpcs;
std::vector<SPLM::ActiveSpell> mActiveSpells;
@ -63,20 +60,15 @@ namespace ESSImport
, mHour(0.f)
, mNextActorId(0)
{
ESM::CellId playerCellId;
playerCellId.mPaged = true;
playerCellId.mIndex.mX = playerCellId.mIndex.mY = 0;
mPlayer.mCellId = playerCellId;
mPlayer.mLastKnownExteriorPosition[0]
= mPlayer.mLastKnownExteriorPosition[1]
= mPlayer.mLastKnownExteriorPosition[2]
= 0.0f;
mPlayer.mCellId = ESM::RefId::esm3ExteriorCell(0, 0);
mPlayer.mLastKnownExteriorPosition[0] = mPlayer.mLastKnownExteriorPosition[1]
= mPlayer.mLastKnownExteriorPosition[2] = 0.0f;
mPlayer.mHasMark = 0;
mPlayer.mCurrentCrimeId = -1; // TODO
mPlayer.mPaidCrimeId = -1;
mPlayer.mObject.blank();
mPlayer.mObject.mEnabled = true;
mPlayer.mObject.mRef.mRefID = "player"; // REFR.mRefID would be PlayerSaveGame
mPlayer.mObject.mRef.mRefID = ESM::RefId::stringRefId("player"); // REFR.mRefID would be PlayerSaveGame
mPlayer.mObject.mCreatureStats.mActorId = generateActorId();
mGlobalMapState.mBounds.mMinX = 0;
@ -87,10 +79,7 @@ namespace ESSImport
mPlayerBase.blank();
}
int generateActorId()
{
return mNextActorId++;
}
int generateActorId() { return mNextActorId++; }
};
}

@ -1,29 +1,29 @@
#include "importgame.hpp"
#include <components/esm/esmreader.hpp>
#include <components/esm3/esmreader.hpp>
namespace ESSImport
{
void GAME::load(ESM::ESMReader &esm)
{
esm.getSubNameIs("GMDT");
esm.getSubHeader();
if (esm.getSubSize() == 92)
{
esm.getExact(&mGMDT, 92);
mGMDT.mSecundaPhase = 0;
}
else if (esm.getSubSize() == 96)
void GAME::load(ESM::ESMReader& esm)
{
esm.getT(mGMDT);
}
else
esm.fail("unexpected subrecord size for GAME.GMDT");
esm.getSubNameIs("GMDT");
esm.getSubHeader();
if (esm.getSubSize() == 92)
{
esm.getExact(&mGMDT, 92);
mGMDT.mSecundaPhase = 0;
}
else if (esm.getSubSize() == 96)
{
esm.getT(mGMDT);
}
else
esm.fail("unexpected subrecord size for GAME.GMDT");
mGMDT.mWeatherTransition &= (0x000000ff);
mGMDT.mSecundaPhase &= (0x000000ff);
mGMDT.mMasserPhase &= (0x000000ff);
}
mGMDT.mWeatherTransition &= (0x000000ff);
mGMDT.mSecundaPhase &= (0x000000ff);
mGMDT.mMasserPhase &= (0x000000ff);
}
}

@ -14,13 +14,13 @@ namespace ESSImport
{
struct GMDT
{
char mCellName[64] {};
int mFogColour {0};
float mFogDensity {0.f};
int mCurrentWeather {0}, mNextWeather {0};
int mWeatherTransition {0}; // 0-100 transition between weathers, top 3 bytes may be garbage
float mTimeOfNextTransition {0.f}; // weather changes when gamehour == timeOfNextTransition
int mMasserPhase {0}, mSecundaPhase {0}; // top 3 bytes may be garbage
char mCellName[64]{};
int mFogColour{ 0 };
float mFogDensity{ 0.f };
int mCurrentWeather{ 0 }, mNextWeather{ 0 };
int mWeatherTransition{ 0 }; // 0-100 transition between weathers, top 3 bytes may be garbage
float mTimeOfNextTransition{ 0.f }; // weather changes when gamehour == timeOfNextTransition
int mMasserPhase{ 0 }, mSecundaPhase{ 0 }; // top 3 bytes may be garbage
};
GMDT mGMDT;

@ -1,11 +1,11 @@
#include "importinfo.hpp"
#include <components/esm/esmreader.hpp>
#include <components/esm3/esmreader.hpp>
namespace ESSImport
{
void INFO::load(ESM::ESMReader &esm)
void INFO::load(ESM::ESMReader& esm)
{
mInfo = esm.getHNString("INAM");
mActorRefId = esm.getHNString("ACDT");

@ -2,12 +2,12 @@
#include <stdexcept>
#include <components/esm/esmreader.hpp>
#include <components/esm3/esmreader.hpp>
namespace ESSImport
{
void Inventory::load(ESM::ESMReader &esm)
void Inventory::load(ESM::ESMReader& esm)
{
while (esm.isNextSub("NPCO"))
{
@ -19,11 +19,10 @@ 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;
for (unsigned int i=0;i<itemCount;++i)
for (unsigned int i = 0; i < itemCount; ++i)
{
bool newStack = esm.isNextSub("XIDX");
if (newStack)
@ -40,7 +39,7 @@ namespace ESSImport
bool isDeleted = false;
item.ESM::CellRef::loadData(esm, isDeleted);
int charge=-1;
int charge = -1;
esm.getHNOT(charge, "XHLT");
item.mChargeInt = charge;

@ -1,11 +1,11 @@
#ifndef OPENMW_ESSIMPORT_IMPORTINVENTORY_H
#define OPENMW_ESSIMPORT_IMPORTINVENTORY_H
#include <vector>
#include <string>
#include <vector>
#include <components/esm/cellref.hpp>
#include <components/esm/esmcommon.hpp>
#include <components/esm3/cellref.hpp>
#include "importscri.hpp"

@ -1,11 +1,11 @@
#include "importjour.hpp"
#include <components/esm/esmreader.hpp>
#include <components/esm3/esmreader.hpp>
namespace ESSImport
{
void JOUR::load(ESM::ESMReader &esm)
void JOUR::load(ESM::ESMReader& esm)
{
mText = esm.getHNString("NAME");
}

@ -1,11 +1,11 @@
#include "importklst.hpp"
#include <components/esm/esmreader.hpp>
#include <components/esm3/esmreader.hpp>
namespace ESSImport
{
void KLST::load(ESM::ESMReader &esm)
void KLST::load(ESM::ESMReader& esm)
{
while (esm.isNextSub("KNAM"))
{

@ -1,8 +1,8 @@
#ifndef OPENMW_ESSIMPORT_KLST_H
#define OPENMW_ESSIMPORT_KLST_H
#include <string>
#include <map>
#include <string>
namespace ESM
{

@ -1,16 +1,16 @@
#include "importnpcc.hpp"
#include <components/esm/esmreader.hpp>
#include <components/esm3/esmreader.hpp>
namespace ESSImport
{
void NPCC::load(ESM::ESMReader &esm)
void NPCC::load(ESM::ESMReader& esm)
{
esm.getHNT(mNPDT, "NPDT");
while (esm.isNextSub("AI_W") || esm.isNextSub("AI_E") || esm.isNextSub("AI_T") || esm.isNextSub("AI_F")
|| esm.isNextSub("AI_A"))
|| esm.isNextSub("AI_A"))
mAiPackages.add(esm);
mInventory.load(esm);

@ -1,9 +1,7 @@
#ifndef OPENMW_ESSIMPORT_NPCC_H
#define OPENMW_ESSIMPORT_NPCC_H
#include <components/esm/loadcont.hpp>
#include <components/esm/aipackage.hpp>
#include <components/esm3/aipackage.hpp>
#include "importinventory.hpp"
@ -29,7 +27,7 @@ namespace ESSImport
Inventory mInventory;
ESM::AIPackageList mAiPackages;
void load(ESM::ESMReader &esm);
void load(ESM::ESMReader& esm);
};
}

@ -1,22 +1,11 @@
#include "importplayer.hpp"
#include <components/esm/esmreader.hpp>
#include <components/esm3/esmreader.hpp>
namespace ESSImport
{
void REFR::load(ESM::ESMReader &esm)
{
esm.getHNT(mRefNum.mIndex, "FRMR");
mRefID = esm.getHNString("NAME");
mActorData.load(esm);
esm.getHNOT(mPos, "DATA", 24);
}
void PCDT::load(ESM::ESMReader &esm)
void PCDT::load(ESM::ESMReader& esm)
{
while (esm.isNextSub("DNAM"))
{

@ -1,15 +1,11 @@
#ifndef OPENMW_ESSIMPORT_PLAYER_H
#define OPENMW_ESSIMPORT_PLAYER_H
#include <vector>
#include <string>
#include <vector>
#include <components/esm/defs.hpp>
#include <components/esm/cellref.hpp>
#include <components/esm/esmcommon.hpp>
#include "importacdt.hpp"
namespace ESM
{
class ESMReader;
@ -18,108 +14,102 @@ namespace ESM
namespace ESSImport
{
/// Player-agnostic player data
struct REFR
{
ActorData mActorData;
std::string mRefID;
ESM::Position mPos;
ESM::RefNum mRefNum;
void load(ESM::ESMReader& esm);
};
/// Other player data
struct PCDT
{
int mBounty;
std::string mBirthsign;
/// Other player data
struct PCDT
{
int mBounty;
std::string mBirthsign;
std::vector<std::string> mKnownDialogueTopics;
std::vector<std::string> mKnownDialogueTopics;
enum PlayerFlags
{
PlayerFlags_ViewSwitchDisabled = 0x1,
PlayerFlags_ControlsDisabled = 0x4,
PlayerFlags_Sleeping = 0x10,
PlayerFlags_Waiting = 0x40,
PlayerFlags_WeaponDrawn = 0x80,
PlayerFlags_SpellDrawn = 0x100,
PlayerFlags_InJail = 0x200,
PlayerFlags_JumpingDisabled = 0x1000,
PlayerFlags_LookingDisabled = 0x2000,
PlayerFlags_VanityModeDisabled = 0x4000,
PlayerFlags_WeaponDrawingDisabled = 0x8000,
PlayerFlags_SpellDrawingDisabled = 0x10000,
PlayerFlags_ThirdPerson = 0x20000,
PlayerFlags_TeleportingDisabled = 0x40000,
PlayerFlags_LevitationDisabled = 0x80000
};
enum PlayerFlags
{
PlayerFlags_ViewSwitchDisabled = 0x1,
PlayerFlags_ControlsDisabled = 0x4,
PlayerFlags_Sleeping = 0x10,
PlayerFlags_Waiting = 0x40,
PlayerFlags_WeaponDrawn = 0x80,
PlayerFlags_SpellDrawn = 0x100,
PlayerFlags_InJail = 0x200,
PlayerFlags_JumpingDisabled = 0x1000,
PlayerFlags_LookingDisabled = 0x2000,
PlayerFlags_VanityModeDisabled = 0x4000,
PlayerFlags_WeaponDrawingDisabled = 0x8000,
PlayerFlags_SpellDrawingDisabled = 0x10000,
PlayerFlags_ThirdPerson = 0x20000,
PlayerFlags_TeleportingDisabled = 0x40000,
PlayerFlags_LevitationDisabled = 0x80000
};
#pragma pack(push)
#pragma pack(1)
struct FNAM
{
unsigned char mRank;
unsigned char mUnknown1[3];
int mReputation;
unsigned char mFlags; // 0x1: unknown, 0x2: expelled
unsigned char mUnknown2[3];
ESM::NAME32 mFactionName;
};
struct PNAM
{
struct MarkLocation
struct FNAM
{
float mX, mY, mZ; // worldspace position
float mRotZ; // Z angle in radians
int mCellX, mCellY; // grid coordinates; for interior cells this is always (0, 0)
unsigned char mRank;
unsigned char mUnknown1[3];
int mReputation;
unsigned char mFlags; // 0x1: unknown, 0x2: expelled
unsigned char mUnknown2[3];
ESM::NAME32 mFactionName;
};
int mPlayerFlags; // controls, camera and draw state
unsigned int mLevelProgress;
float mSkillProgress[27]; // skill progress, non-uniform scaled
unsigned char mSkillIncreases[8]; // number of skill increases for each attribute
int mTelekinesisRangeBonus; // in units; seems redundant
float mVisionBonus; // range: <0.0, 1.0>; affected by light spells and Get/Mod/SetPCVisionBonus
int mDetectKeyMagnitude; // seems redundant
int mDetectEnchantmentMagnitude; // seems redundant
int mDetectAnimalMagnitude; // seems redundant
MarkLocation mMarkLocation;
unsigned char mUnknown3[40];
unsigned char mSpecIncreases[3]; // number of skill increases for each specialization
unsigned char mUnknown4;
};
struct PNAM
{
struct MarkLocation
{
float mX, mY, mZ; // worldspace position
float mRotZ; // Z angle in radians
int mCellX, mCellY; // grid coordinates; for interior cells this is always (0, 0)
};
struct Rotation
{
float mData[3][3];
};
int mPlayerFlags; // controls, camera and draw state
unsigned int mLevelProgress;
float mSkillProgress[27]; // skill progress, non-uniform scaled
unsigned char mSkillIncreases[8]; // number of skill increases for each attribute
int mTelekinesisRangeBonus; // in units; seems redundant
float mVisionBonus; // range: <0.0, 1.0>; affected by light spells and Get/Mod/SetPCVisionBonus
int mDetectKeyMagnitude; // seems redundant
int mDetectEnchantmentMagnitude; // seems redundant
int mDetectAnimalMagnitude; // seems redundant
MarkLocation mMarkLocation;
unsigned char mUnknown3[4];
Rotation mVerticalRotation;
unsigned char mSpecIncreases[3]; // number of skill increases for each specialization
unsigned char mUnknown4;
};
struct ENAM
{
int mCellX;
int mCellY;
};
struct ENAM
{
int mCellX;
int mCellY;
};
struct AADT // 44 bytes
{
int animGroupIndex; // See convertANIS() for the mapping.
unsigned char mUnknown5[40];
};
struct AADT // 44 bytes
{
int animGroupIndex; // See convertANIS() for the mapping.
unsigned char mUnknown5[40];
};
#pragma pack(pop)
std::vector<FNAM> mFactions;
PNAM mPNAM;
std::vector<FNAM> mFactions;
PNAM mPNAM;
bool mHasMark;
std::string mMNAM; // mark cell name; can also be sDefaultCellname or region name
bool mHasMark;
std::string mMNAM; // mark cell name; can also be sDefaultCellname or region name
bool mHasENAM;
ENAM mENAM; // last exterior cell
bool mHasENAM;
ENAM mENAM; // last exterior cell
bool mHasAADT;
AADT mAADT;
bool mHasAADT;
AADT mAADT;
void load(ESM::ESMReader& esm);
};
void load(ESM::ESMReader& esm);
};
}

@ -1,18 +1,18 @@
#include "importproj.h"
#include <components/esm/esmreader.hpp>
#include <components/esm3/esmreader.hpp>
namespace ESSImport
{
void ESSImport::PROJ::load(ESM::ESMReader& esm)
{
while (esm.isNextSub("PNAM"))
void ESSImport::PROJ::load(ESM::ESMReader& esm)
{
PNAM pnam;
esm.getHT(pnam);
mProjectiles.push_back(pnam);
while (esm.isNextSub("PNAM"))
{
PNAM pnam;
esm.getHT(pnam);
mProjectiles.push_back(pnam);
}
}
}
}

@ -1,9 +1,9 @@
#ifndef OPENMW_ESSIMPORT_IMPORTPROJ_H
#define OPENMW_ESSIMPORT_IMPORTPROJ_H
#include <vector>
#include <components/esm/esmcommon.hpp>
#include <components/esm/util.hpp>
#include <vector>
namespace ESM
{
@ -13,34 +13,34 @@ namespace ESM
namespace ESSImport
{
struct PROJ
{
struct PROJ
{
#pragma pack(push)
#pragma pack(1)
struct PNAM // 184 bytes
{
float mAttackStrength;
float mSpeed;
unsigned char mUnknown[4*2];
float mFlightTime;
int mSplmIndex; // reference to a SPLM record (0 for ballistic projectiles)
unsigned char mUnknown2[4];
ESM::Vector3 mVelocity;
ESM::Vector3 mPosition;
unsigned char mUnknown3[4*9];
ESM::NAME32 mActorId; // indexed refID (with the exception of "PlayerSaveGame")
ESM::NAME32 mArrowId;
ESM::NAME32 mBowId;
bool isMagic() const { return mSplmIndex != 0; }
};
struct PNAM // 184 bytes
{
float mAttackStrength;
float mSpeed;
unsigned char mUnknown[4 * 2];
float mFlightTime;
int mSplmIndex; // reference to a SPLM record (0 for ballistic projectiles)
unsigned char mUnknown2[4];
ESM::Vector3 mVelocity;
ESM::Vector3 mPosition;
unsigned char mUnknown3[4 * 9];
ESM::NAME32 mActorId; // indexed refID (with the exception of "PlayerSaveGame")
ESM::NAME32 mArrowId;
ESM::NAME32 mBowId;
bool isMagic() const { return mSplmIndex != 0; }
};
#pragma pack(pop)
std::vector<PNAM> mProjectiles;
std::vector<PNAM> mProjectiles;
void load(ESM::ESMReader& esm);
};
void load(ESM::ESMReader& esm);
};
}

@ -1,11 +1,11 @@
#include "importques.hpp"
#include <components/esm/esmreader.hpp>
#include <components/esm3/esmreader.hpp>
namespace ESSImport
{
void QUES::load(ESM::ESMReader &esm)
void QUES::load(ESM::ESMReader& esm)
{
while (esm.isNextSub("DATA"))
mInfo.push_back(esm.getHString());

@ -1,13 +1,11 @@
#include "importscpt.hpp"
#include <components/esm/esmreader.hpp>
#include <components/esm3/esmreader.hpp>
namespace ESSImport
{
void SCPT::load(ESM::ESMReader &esm)
void SCPT::load(ESM::ESMReader& esm)
{
esm.getHNT(mSCHD, "SCHD");

@ -3,7 +3,8 @@
#include "importscri.hpp"
#include <components/esm/loadscpt.hpp>
#include <components/esm/esmcommon.hpp>
#include <components/esm3/loadscpt.hpp>
namespace ESM
{
@ -15,8 +16,8 @@ namespace ESSImport
struct SCHD
{
ESM::NAME32 mName;
ESM::Script::SCHDstruct mData;
ESM::NAME32 mName;
ESM::Script::SCHDstruct mData;
};
// A running global script

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

Loading…
Cancel
Save