Merge remote-tracking branch 'remotes/origin/master' into openmw-vr

pull/615/head
madsbuvi 4 years ago
commit f4e36f4be5

@ -16,6 +16,8 @@ stages:
- apt-cache/ - apt-cache/
- ccache/ - ccache/
stage: build stage: build
rules:
- if: '$CI_PIPELINE_SOURCE != "schedule"'
script: script:
- export CCACHE_BASEDIR="`pwd`" - export CCACHE_BASEDIR="`pwd`"
- export CCACHE_DIR="`pwd`/ccache" && mkdir -pv "$CCACHE_DIR" - export CCACHE_DIR="`pwd`/ccache" && mkdir -pv "$CCACHE_DIR"
@ -30,6 +32,30 @@ stages:
paths: paths:
- build/install/ - build/install/
Coverity:
extends: .Debian_Image
stage: build
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'
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
- tar xfz /tmp/cov-analysis-linux64.tgz
script:
- 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
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"
variables:
CC: gcc
CXX: g++
timeout: 8h
Debian_GCC: Debian_GCC:
extends: .Debian extends: .Debian
cache: cache:
@ -93,125 +119,152 @@ Debian_Clang_tests:
CCACHE_SIZE: 1G CCACHE_SIZE: 1G
BUILD_TESTS_ONLY: 1 BUILD_TESTS_ONLY: 1
MacOS: .MacOS:
image: macos-11-xcode-12
tags: tags:
- macos - shared-macos-amd64
stage: build stage: build
only: only:
variables: variables:
- $CI_PROJECT_ID == "7107382" - $CI_PROJECT_ID == "7107382"
cache:
paths:
- ccache/
script: script:
- rm -fr build/* # remove anything in the build directory - rm -fr build # remove the build directory
- CI/before_install.osx.sh - CI/before_install.osx.sh
- export CCACHE_BASEDIR="$(pwd)"
- export CCACHE_DIR="$(pwd)/ccache"
- mkdir -pv "${CCACHE_DIR}"
- ccache -z -M "${CCACHE_SIZE}"
- CI/before_script.osx.sh - CI/before_script.osx.sh
- cd build; make -j2 package - 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: artifacts:
paths: paths:
- build/OpenMW-*.dmg - build/OpenMW-*.dmg
- "build/**/*.log" - "build/**/*.log"
macOS11_Xcode12:
extends: .MacOS
image: macos-11-xcode-12
cache:
key: macOS11_Xcode12.v1
variables:
CCACHE_SIZE: 3G
macOS10.15_Xcode11:
extends: .MacOS
image: macos-10.15-xcode-11
allow_failure: true
cache:
key: macOS10.15_Xcode11.v1
variables:
CCACHE_SIZE: 3G
variables: &engine-targets variables: &engine-targets
targets: "openmw_vr,openmw-essimporter,openmw-iniimporter,openmw-launcher,openmw-wizard" targets: "openmw,openmw-essimporter,openmw-iniimporter,openmw-launcher,openmw-wizard"
package: "Engine" package: "Engine"
variables: &cs-targets variables: &cs-targets
targets: "openmw-cs,bsatool,esmtool,niftest" targets: "openmw-cs,bsatool,esmtool,niftest"
package: "CS" package: "CS"
#.Windows_Ninja_Base: .Windows_Ninja_Base:
# tags: tags:
# - windows - windows
# before_script: before_script:
# - Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1" - Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1"
# - choco source add -n=openmw-proxy -s="https://repo.openmw.org/repository/Chocolatey/" --priority=1 - choco source add -n=openmw-proxy -s="https://repo.openmw.org/repository/Chocolatey/" --priority=1
# - choco install git --force --params "/GitAndUnixToolsOnPath" -y - choco install git --force --params "/GitAndUnixToolsOnPath" -y
# - choco install 7zip -y - choco install 7zip -y
# - choco install cmake.install --installargs 'ADD_CMAKE_TO_PATH=System' -y - choco install cmake.install --installargs 'ADD_CMAKE_TO_PATH=System' -y
# - choco install vswhere -y - choco install vswhere -y
# - choco install ninja -y - choco install ninja -y
# - choco install python -y - choco install python -y
# - refreshenv - refreshenv
# stage: build stage: build
# script: rules:
# - $time = (Get-Date -Format "HH:mm:ss") - if: '$CI_PIPELINE_SOURCE != "schedule"'
# - echo ${time} script:
# - echo "started by ${GITLAB_USER_NAME}" - $time = (Get-Date -Format "HH:mm:ss")
# - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -N - echo ${time}
# - cd MSVC2019_64_Ninja - echo "started by ${GITLAB_USER_NAME}"
# - .\ActivateMSVC.ps1 - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -N
# - cmake --build . --config $config --target ($targets.Split(',')) - cd MSVC2019_64_Ninja
# - cd $config - .\ActivateMSVC.ps1
# - 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 - cmake --build . --config $config --target ($targets.Split(','))
# - | - cd $config
# if (Get-ChildItem -Recurse *.pdb) { - 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
# 7z a -tzip ..\..\OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' CI-ID.txt - |
# Get-ChildItem -Recurse *.pdb | 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 ..\..\OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}.zip '*' Get-ChildItem -Recurse *.pdb | Remove-Item
# after_script: }
# - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}.zip '*'
# cache: after_script:
# key: ninja-v2 - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log
# paths: cache:
# - deps key: ninja-v2
# - MSVC2019_64_Ninja/deps/Qt paths:
# artifacts: - deps
# when: always - MSVC2019_64_Ninja/deps/Qt
# paths: artifacts:
# - "*.zip" when: always
# - "*.log" paths:
# - MSVC2019_64_Ninja/*.log - "*.zip"
# - MSVC2019_64_Ninja/*/*.log - "*.log"
# - MSVC2019_64_Ninja/*/*/*.log - MSVC2019_64_Ninja/*.log
# - MSVC2019_64_Ninja/*/*/*/*.log - MSVC2019_64_Ninja/*/*.log
# - MSVC2019_64_Ninja/*/*/*/*/*.log - MSVC2019_64_Ninja/*/*/*.log
# - MSVC2019_64_Ninja/*/*/*/*/*/*.log - MSVC2019_64_Ninja/*/*/*/*.log
# - MSVC2019_64_Ninja/*/*/*/*/*/*/*.log - MSVC2019_64_Ninja/*/*/*/*/*.log
# - MSVC2019_64_Ninja/*/*/*/*/*/*/*/*.log - MSVC2019_64_Ninja/*/*/*/*/*/*.log
- MSVC2019_64_Ninja/*/*/*/*/*/*/*.log
- MSVC2019_64_Ninja/*/*/*/*/*/*/*/*.log
Windows_Ninja_Engine_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:
extends:
- .Windows_Ninja_Base
variables:
<<: *engine-targets
config: "RelWithDebInfo"
Windows_Ninja_CS_Release:
extends:
- .Windows_Ninja_Base
variables:
<<: *cs-targets
config: "Release"
Windows_Ninja_CS_Debug:
extends:
- .Windows_Ninja_Base
variables:
<<: *cs-targets
config: "Debug"
# Windows_Ninja_CS_RelWithDebInfo:
#Windows_Ninja_Engine_Release: extends:
# extends: - .Windows_Ninja_Base
# - .Windows_Ninja_Base variables:
# variables: <<: *cs-targets
# <<: *engine-targets config: "RelWithDebInfo"
# config: "Release"
#
#Windows_Ninja_Engine_Debug:
# extends:
# - .Windows_Ninja_Base
# variables:
# <<: *engine-targets
# config: "Debug"
#
#Windows_Ninja_Engine_RelWithDebInfo:
# extends:
# - .Windows_Ninja_Base
# variables:
# <<: *engine-targets
# config: "RelWithDebInfo"
#
#Windows_Ninja_CS_Release:
# extends:
# - .Windows_Ninja_Base
# variables:
# <<: *cs-targets
# config: "Release"
#
#Windows_Ninja_CS_Debug:
# extends:
# - .Windows_Ninja_Base
# variables:
# <<: *cs-targets
# config: "Debug"
#
#Windows_Ninja_CS_RelWithDebInfo:
# extends:
# - .Windows_Ninja_Base
# variables:
# <<: *cs-targets
# config: "RelWithDebInfo"
.Windows_MSBuild_Base: .Windows_MSBuild_Base:
tags: tags:
@ -226,6 +279,8 @@ variables: &cs-targets
- choco install python -y - choco install python -y
- refreshenv - refreshenv
stage: build stage: build
rules:
- if: '$CI_PIPELINE_SOURCE != "schedule"'
script: script:
- $time = (Get-Date -Format "HH:mm:ss") - $time = (Get-Date -Format "HH:mm:ss")
- echo ${time} - echo ${time}
@ -304,37 +359,37 @@ Windows_MSBuild_CS_RelWithDebInfo:
<<: *cs-targets <<: *cs-targets
config: "RelWithDebInfo" config: "RelWithDebInfo"
#Debian_AndroidNDK_arm64-v8a: Debian_AndroidNDK_arm64-v8a:
# tags: tags:
# - linux - linux
# image: debian:bullseye image: debian:bullseye
# variables: variables:
# CCACHE_SIZE: 3G CCACHE_SIZE: 3G
# cache: cache:
# key: Debian_AndroidNDK_arm64-v8a.v3 key: Debian_AndroidNDK_arm64-v8a.v3
# paths: paths:
# - apt-cache/ - apt-cache/
# - ccache/ - ccache/
# - build/extern/fetched/ - build/extern/fetched/
# before_script: before_script:
# - export APT_CACHE_DIR=`pwd`/apt-cache && mkdir -pv $APT_CACHE_DIR - 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 "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 - echo "google-android-ndk-installer google-android-installers/mirror select https://dl.google.com" | debconf-set-selections
# - apt-get update -yq - 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 - apt-get -q -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake ccache curl unzip git build-essential google-android-ndk-installer
# stage: build stage: build
# script: script:
# - export CCACHE_BASEDIR="`pwd`" - export CCACHE_BASEDIR="`pwd`"
# - export CCACHE_DIR="`pwd`/ccache" && mkdir -pv "$CCACHE_DIR" - export CCACHE_DIR="`pwd`/ccache" && mkdir -pv "$CCACHE_DIR"
# - ccache -z -M "${CCACHE_SIZE}" - ccache -z -M "${CCACHE_SIZE}"
# - CI/before_install.android.sh - CI/before_install.android.sh
# - CI/before_script.android.sh - CI/before_script.android.sh
# - cd build - cd build
# - cmake --build . -- -j $(nproc) - cmake --build . -- -j $(nproc)
# - cmake --install . - cmake --install .
# - ccache -s - ccache -s
# artifacts: artifacts:
# paths: paths:
# - build/install/ - 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. # 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 timeout: 1h30m

@ -10,7 +10,7 @@ addons:
- sourceline: 'ppa:openmw/openmw' - sourceline: 'ppa:openmw/openmw'
packages: [ packages: [
# Dev # Dev
build-essential, cmake, clang-tools, ccache, build-essential, cmake, clang-tools-9, ccache,
# Boost # Boost
libboost-filesystem-dev, libboost-iostreams-dev, libboost-program-options-dev, libboost-system-dev, libboost-filesystem-dev, libboost-iostreams-dev, libboost-program-options-dev, libboost-system-dev,
# FFmpeg # FFmpeg
@ -22,9 +22,9 @@ addons:
] ]
matrix: matrix:
include: include:
- name: OpenMW (all) on MacOS 10.15 with Xcode 11.6 - name: OpenMW (all) on MacOS 10.15 with Xcode 10.2
os: osx os: osx
osx_image: xcode11.6 osx_image: xcode10.2
- name: OpenMW (all) on Ubuntu Focal with GCC - name: OpenMW (all) on Ubuntu Focal with GCC
os: linux os: linux
dist: focal dist: focal
@ -37,8 +37,8 @@ matrix:
os: linux os: linux
dist: focal dist: focal
env: env:
- MATRIX_EVAL="CC=clang && CXX=clang++" - MATRIX_EVAL="CC=clang-9 && CXX=clang++-9"
- ANALYZE="scan-build --force-analyze-debug-code --use-cc clang --use-c++ clang++" - ANALYZE="scan-build-9 --force-analyze-debug-code --use-cc clang-9 --use-c++ clang++-9"
compiler: clang compiler: clang
before_install: before_install:
@ -66,11 +66,13 @@ deploy:
repo: OpenMW/openmw repo: OpenMW/openmw
notifications: notifications:
email: email:
if: repository_slug = OpenMW/openmw AND branch = master
recipients: recipients:
- corrmage+travis-ci@gmail.com - corrmage+travis-ci@gmail.com
on_success: change on_success: change
on_failure: always on_failure: always
irc: irc:
if: repository_slug = OpenMW/openmw AND branch = master
channels: channels:
- "chat.freenode.net#openmw" - "chat.freenode.net#openmw"
on_success: change on_success: change

@ -4,7 +4,7 @@ Contributors
The OpenMW project was started in 2008 by Nicolay Korslund. The OpenMW project was started in 2008 by Nicolay Korslund.
In the course of years many people have contributed to the project. In the course of years many people have contributed to the project.
If you feel your name is missing from this list, please notify a developer. If you feel your name is missing from this list, please add it to `AUTHORS.md`.
Programmers Programmers
@ -24,7 +24,7 @@ Programmers
Alex McKibben Alex McKibben
alexanderkjall alexanderkjall
Alexander Nadeau (wareya) Alexander Nadeau (wareya)
Alexander Olofsson (Ace) Alexander Olofsson (Ananace)
Alex Rice Alex Rice
Alex S (docwest) Alex S (docwest)
Allofich Allofich
@ -49,6 +49,7 @@ Programmers
Cédric Mocquillon Cédric Mocquillon
Chris Boyce (slothlife) Chris Boyce (slothlife)
Chris Robinson (KittyCat) Chris Robinson (KittyCat)
Cody Glassman (Wazabear)
Coleman Smith (olcoal) Coleman Smith (olcoal)
Cory F. Cohen (cfcohen) Cory F. Cohen (cfcohen)
Cris Mihalache (Mirceam) Cris Mihalache (Mirceam)
@ -89,6 +90,7 @@ Programmers
Internecine Internecine
Jackerty Jackerty
Jacob Essex (Yacoby) Jacob Essex (Yacoby)
Jacob Turnbull (Tankinfrank)
Jake Westrip (16bitint) Jake Westrip (16bitint)
James Carty (MrTopCat) James Carty (MrTopCat)
James Moore (moore.work) James Moore (moore.work)
@ -150,6 +152,7 @@ Programmers
Nathan Jeffords (blunted2night) Nathan Jeffords (blunted2night)
NeveHanter NeveHanter
Nialsy Nialsy
Nick Crawford (nighthawk469)
Nikolay Kasyanov (corristo) Nikolay Kasyanov (corristo)
nobrakal nobrakal
Nolan Poe (nopoe) Nolan Poe (nopoe)
@ -184,6 +187,7 @@ Programmers
sergoz sergoz
ShadowRadiance ShadowRadiance
Siimacore Siimacore
Simon Meulenbeek (simonmb)
sir_herrbatka sir_herrbatka
smbas smbas
Sophie Kirschner (pineapplemachine) Sophie Kirschner (pineapplemachine)
@ -197,6 +201,7 @@ Programmers
Sylvain Thesnieres (Garvek) Sylvain Thesnieres (Garvek)
t6 t6
terrorfisch terrorfisch
Tess (tescoShoppah)
thegriglat thegriglat
Thomas Luppi (Digmaster) Thomas Luppi (Digmaster)
tlmullis tlmullis
@ -235,7 +240,8 @@ Documentation
Packagers Packagers
--------- ---------
Alexander Olofsson (Ace) - Windows Alexander Olofsson (Ananace) - Windows and Flatpak
Alexey Sokolov (DarthGandalf) - Gentoo Linux
Bret Curtis (psi29a) - Debian and Ubuntu Linux Bret Curtis (psi29a) - Debian and Ubuntu Linux
Edmondo Tommasina (edmondo) - Gentoo Linux Edmondo Tommasina (edmondo) - Gentoo Linux
Julian Ospald (hasufell) - Gentoo Linux Julian Ospald (hasufell) - Gentoo Linux

@ -115,6 +115,9 @@
Bug #5906: Sunglare doesn't work with Mesa drivers and AMD GPUs Bug #5906: Sunglare doesn't work with Mesa drivers and AMD GPUs
Bug #5912: ImprovedBound mod doesn't work Bug #5912: ImprovedBound mod doesn't work
Bug #5914: BM: The Swimmer can't reach destination Bug #5914: BM: The Swimmer can't reach destination
Bug #5923: Clicking on empty spaces between journal entries might show random topics
Bug #5934: AddItem command doesn't accept negative values
Bug #5975: NIF controllers from sheath meshes are used
Feature #390: 3rd person look "over the shoulder" Feature #390: 3rd person look "over the shoulder"
Feature #832: OpenMW-CS: Handle deleted references Feature #832: OpenMW-CS: Handle deleted references
Feature #1536: Show more information about level on menu Feature #1536: Show more information about level on menu
@ -122,8 +125,10 @@
Feature #2404: Levelled List can not be placed into a container Feature #2404: Levelled List can not be placed into a container
Feature #2686: Timestamps in openmw.log Feature #2686: Timestamps in openmw.log
Feature #3171: OpenMW-CS: Instance drag selection Feature #3171: OpenMW-CS: Instance drag selection
Feature #3983: Wizard: Add link to buy Morrowind
Feature #4894: Consider actors as obstacles for pathfinding Feature #4894: Consider actors as obstacles for pathfinding
Feature #4899: Alpha-To-Coverage Anti-Aliasing for alpha testing Feature #4899: Alpha-To-Coverage Anti-Aliasing for alpha testing
Feature #4917: Do not trigger NavMesh update when RecastMesh update should not change NavMesh
Feature #4977: Use the "default icon.tga" when an item's icon is not found Feature #4977: Use the "default icon.tga" when an item's icon is not found
Feature #5043: Head Bobbing Feature #5043: Head Bobbing
Feature #5199: OpenMW-CS: Improve scene view colors Feature #5199: OpenMW-CS: Improve scene view colors
@ -133,6 +138,7 @@
Feature #5456: Basic collada animation support Feature #5456: Basic collada animation support
Feature #5457: Realistic diagonal movement Feature #5457: Realistic diagonal movement
Feature #5486: Fixes trainers to choose their training skills based on their base skill points Feature #5486: Fixes trainers to choose their training skills based on their base skill points
Feature #5511: Add in game option to toggle HRTF support in OpenMW
Feature #5519: Code Patch tab in launcher Feature #5519: Code Patch tab in launcher
Feature #5524: Resume failed script execution after reload Feature #5524: Resume failed script execution after reload
Feature #5545: Option to allow stealing from an unconscious NPC during combat Feature #5545: Option to allow stealing from an unconscious NPC during combat
@ -147,6 +153,8 @@
Feature #5730: Add graphic herbalism option to the launcher and documents Feature #5730: Add graphic herbalism option to the launcher and documents
Feature #5771: ori command should report where a mesh is loaded from and whether the x version is used. Feature #5771: ori command should report where a mesh is loaded from and whether the x version is used.
Feature #5813: Instanced groundcover support Feature #5813: Instanced groundcover support
Feature #5814: Bsatool should be able to create BSA archives, not only to extract it
Feature #5828: Support more than 8 lights
Feature #5910: Fall back to delta time when physics can't keep up Feature #5910: Fall back to delta time when physics can't keep up
Task #5480: Drop Qt4 support Task #5480: Drop Qt4 support
Task #5520: Improve cell name autocompleter implementation Task #5520: Improve cell name autocompleter implementation

@ -1,9 +1,9 @@
#!/bin/sh -ex #!/bin/sh -ex
# workaround python issue on travis # workaround python issue on 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.8 || true
HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies python@3.9 || true [-z "${TRAVIS}"] && HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies python@3.9 || true
HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies qt@6 || true [-z "${TRAVIS}"] && HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies qt@6 || true
# Some of these tools can come from places other than brew, so check before installing # Some of these tools can come from places other than brew, so check before installing
command -v ccache >/dev/null 2>&1 || brew install ccache command -v ccache >/dev/null 2>&1 || brew install ccache

@ -16,7 +16,7 @@ cmake \
-D CMAKE_CXX_FLAGS="-stdlib=libc++" \ -D CMAKE_CXX_FLAGS="-stdlib=libc++" \
-D CMAKE_C_FLAGS_RELEASE="-g -O0" \ -D CMAKE_C_FLAGS_RELEASE="-g -O0" \
-D CMAKE_CXX_FLAGS_RELEASE="-g -O0" \ -D CMAKE_CXX_FLAGS_RELEASE="-g -O0" \
-D CMAKE_OSX_DEPLOYMENT_TARGET="10.12" \ -D CMAKE_OSX_DEPLOYMENT_TARGET="10.14" \
-D CMAKE_BUILD_TYPE=RELEASE \ -D CMAKE_BUILD_TYPE=RELEASE \
-D OPENMW_OSX_DEPLOYMENT=TRUE \ -D OPENMW_OSX_DEPLOYMENT=TRUE \
-D BUILD_OPENMW=TRUE \ -D BUILD_OPENMW=TRUE \

@ -28,6 +28,8 @@ declare -rA GROUPED_DEPS=(
# These dependencies can alternatively be built and linked statically. # These dependencies can alternatively be built and linked statically.
[openmw-deps-dynamic]="libmygui-dev libopenscenegraph-dev" [openmw-deps-dynamic]="libmygui-dev libopenscenegraph-dev"
[coverity]="curl"
# Pre-requisites for building MyGUI and OSG for static linking. # Pre-requisites for building MyGUI and OSG for static linking.
# #

@ -612,8 +612,8 @@ if (WIN32)
# Warnings that aren't enabled normally and don't need to be enabled # Warnings that aren't enabled normally and don't need to be enabled
# They're unneeded and sometimes completely retarded warnings that /Wall enables # They're unneeded and sometimes completely retarded warnings that /Wall enables
# Not going to bother commenting them as they tend to warn on every standard library file # Not going to bother commenting them as they tend to warn on every standard library file
4061 4263 4264 4266 4350 4371 4435 4514 4548 4571 4610 4619 4623 4625 4061 4263 4264 4266 4350 4371 4435 4514 4548 4571 4582 4583 4610 4619 4623 4625
4626 4628 4640 4668 4710 4711 4768 4820 4826 4917 4946 5032 5039 5045 4626 4628 4640 4668 4710 4711 4768 4820 4826 4917 4946 5032 5039 5045 5219 5220
# Warnings that are thrown on standard libraries and not OpenMW # Warnings that are thrown on standard libraries and not OpenMW
4347 # Non-template function with same name and parameter count as template function 4347 # Non-template function with same name and parameter count as template function
@ -635,7 +635,6 @@ if (WIN32)
5204 # Class has virtual functions, but its trivial destructor is not virtual 5204 # Class has virtual functions, but its trivial destructor is not virtual
# caused by MyGUI # caused by MyGUI
4275 # non dll-interface class 'std::exception' used as base for dll-interface class 'MyGUI::Exception'
4297 # function assumed not to throw an exception but does 4297 # function assumed not to throw an exception but does
# OpenMW specific warnings # OpenMW specific warnings
@ -671,6 +670,12 @@ if (WIN32)
) )
endif() 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}) foreach(d ${WARNINGS_DISABLE})
set(WARNINGS "${WARNINGS} /wd${d}") set(WARNINGS "${WARNINGS} /wd${d}")
endforeach(d) endforeach(d)

@ -20,6 +20,7 @@ struct Arguments
std::string mode; std::string mode;
std::string filename; std::string filename;
std::string extractfile; std::string extractfile;
std::string addfile;
std::string outdir; std::string outdir;
bool longformat; bool longformat;
@ -36,6 +37,10 @@ bool parseOptions (int argc, char** argv, Arguments &info)
" Extract a file from the input archive.\n\n" " Extract a file from the input archive.\n\n"
" bsatool extractall archivefile [output_directory]\n" " bsatool extractall archivefile [output_directory]\n"
" Extract all files from the input archive.\n\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"); "Allowed options");
desc.add_options() desc.add_options()
@ -95,7 +100,7 @@ bool parseOptions (int argc, char** argv, Arguments &info)
} }
info.mode = variables["mode"].as<std::string>(); info.mode = variables["mode"].as<std::string>();
if (!(info.mode == "list" || info.mode == "extract" || info.mode == "extractall")) if (!(info.mode == "list" || info.mode == "extract" || info.mode == "extractall" || info.mode == "add" || info.mode == "create"))
{ {
std::cout << std::endl << "ERROR: invalid mode \"" << info.mode << "\"\n\n" std::cout << std::endl << "ERROR: invalid mode \"" << info.mode << "\"\n\n"
<< desc << std::endl; << desc << std::endl;
@ -126,6 +131,17 @@ bool parseOptions (int argc, char** argv, Arguments &info)
if (variables["input-file"].as< std::vector<std::string> >().size() > 2) if (variables["input-file"].as< std::vector<std::string> >().size() > 2)
info.outdir = variables["input-file"].as< std::vector<std::string> >()[2]; info.outdir = variables["input-file"].as< std::vector<std::string> >()[2];
} }
else if (info.mode == "add")
{
if (variables["input-file"].as< std::vector<std::string> >().size() < 1)
{
std::cout << "\nERROR: file to add unspecified\n\n"
<< desc << std::endl;
return false;
}
if (variables["input-file"].as< std::vector<std::string> >().size() > 1)
info.addfile = variables["input-file"].as< std::vector<std::string> >()[1];
}
else if (variables["input-file"].as< std::vector<std::string> >().size() > 1) else if (variables["input-file"].as< std::vector<std::string> >().size() > 1)
info.outdir = variables["input-file"].as< std::vector<std::string> >()[1]; info.outdir = variables["input-file"].as< std::vector<std::string> >()[1];
@ -138,6 +154,7 @@ bool parseOptions (int argc, char** argv, Arguments &info)
int list(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info); int list(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info);
int extract(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 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) int main(int argc, char** argv)
{ {
@ -157,6 +174,12 @@ int main(int argc, char** argv)
else else
bsa = std::make_unique<Bsa::BSAFile>(Bsa::BSAFile()); bsa = std::make_unique<Bsa::BSAFile>(Bsa::BSAFile());
if (info.mode == "create")
{
bsa->open(info.filename);
return 0;
}
bsa->open(info.filename); bsa->open(info.filename);
if (info.mode == "list") if (info.mode == "list")
@ -165,6 +188,8 @@ int main(int argc, char** argv)
return extract(bsa, info); return extract(bsa, info);
else if (info.mode == "extractall") else if (info.mode == "extractall")
return extractAll(bsa, info); return extractAll(bsa, info);
else if (info.mode == "add")
return add(bsa, info);
else else
{ {
std::cout << "Unsupported mode. That is not supposed to happen." << std::endl; std::cout << "Unsupported mode. That is not supposed to happen." << std::endl;
@ -188,13 +213,13 @@ int list(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info)
{ {
// Long format // Long format
std::ios::fmtflags f(std::cout.flags()); std::ios::fmtflags f(std::cout.flags());
std::cout << std::setw(50) << std::left << file.name; std::cout << std::setw(50) << std::left << file.name();
std::cout << std::setw(8) << std::left << std::dec << file.fileSize; std::cout << std::setw(8) << std::left << std::dec << file.fileSize;
std::cout << "@ 0x" << std::hex << file.offset << std::endl; std::cout << "@ 0x" << std::hex << file.offset << std::endl;
std::cout.flags(f); std::cout.flags(f);
} }
else else
std::cout << file.name << std::endl; std::cout << file.name() << std::endl;
} }
return 0; return 0;
@ -253,7 +278,7 @@ int extractAll(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info)
{ {
for (const auto &file : bsa->getList()) for (const auto &file : bsa->getList())
{ {
std::string extractPath(file.name); std::string extractPath(file.name());
Misc::StringUtils::replaceAll(extractPath, "\\", "/"); Misc::StringUtils::replaceAll(extractPath, "\\", "/");
// Get the target path (the path the file will be extracted to) // Get the target path (the path the file will be extracted to)
@ -272,7 +297,7 @@ int extractAll(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info)
// Get a stream for the file to extract // Get a stream for the file to extract
// (inefficient because getFile iter on the list again) // (inefficient because getFile iter on the list again)
Files::IStreamPtr data = bsa->getFile(file.name); Files::IStreamPtr data = bsa->getFile(file.name());
bfs::ofstream out(target, std::ios::binary); bfs::ofstream out(target, std::ios::binary);
// Write the file to disk // Write the file to disk
@ -283,3 +308,11 @@ int extractAll(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info)
return 0; return 0;
} }
int add(std::unique_ptr<Bsa::BSAFile>& 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);
return 0;
}

@ -322,7 +322,7 @@ int load(Arguments& info)
std::string filename = info.filename; std::string filename = info.filename;
std::cout << "Loading file: " << filename << std::endl; std::cout << "Loading file: " << filename << std::endl;
std::list<int> skipped; std::list<uint32_t> skipped;
try { try {

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

@ -19,6 +19,7 @@ namespace ESSImport
item.mCount = contItem.mCount; item.mCount = contItem.mCount;
item.mRelativeEquipmentSlot = -1; item.mRelativeEquipmentSlot = -1;
item.mLockLevel = 0; item.mLockLevel = 0;
item.mRefNum.unset();
unsigned int itemCount = std::abs(item.mCount); unsigned int itemCount = std::abs(item.mCount);
bool separateStacks = false; bool separateStacks = false;

@ -57,7 +57,7 @@ int main(int argc, char** argv)
else else
{ {
const std::string& ext = ".omwsave"; const std::string& ext = ".omwsave";
if (boost::filesystem::exists(boost::filesystem::path(outputFile)) if (bfs::exists(bfs::path(outputFile))
&& (outputFile.size() < ext.size() || outputFile.substr(outputFile.size()-ext.size()) != ext)) && (outputFile.size() < ext.size() || outputFile.substr(outputFile.size()-ext.size()) != ext))
{ {
throw std::runtime_error("Output file already exists and does not end in .omwsave. Did you mean to use --compare?"); throw std::runtime_error("Output file already exists and does not end in .omwsave. Did you mean to use --compare?");

@ -13,6 +13,7 @@ set(LAUNCHER
utils/profilescombobox.cpp utils/profilescombobox.cpp
utils/textinputdialog.cpp utils/textinputdialog.cpp
utils/lineedit.cpp utils/lineedit.cpp
utils/openalutil.cpp
${CMAKE_SOURCE_DIR}/files/windows/launcher.rc ${CMAKE_SOURCE_DIR}/files/windows/launcher.rc
) )
@ -31,6 +32,7 @@ set(LAUNCHER_HEADER
utils/profilescombobox.hpp utils/profilescombobox.hpp
utils/textinputdialog.hpp utils/textinputdialog.hpp
utils/lineedit.hpp utils/lineedit.hpp
utils/openalutil.hpp
) )
# Headers that must be pre-processed # Headers that must be pre-processed
@ -47,6 +49,7 @@ set(LAUNCHER_HEADER_MOC
utils/textinputdialog.hpp utils/textinputdialog.hpp
utils/profilescombobox.hpp utils/profilescombobox.hpp
utils/lineedit.hpp utils/lineedit.hpp
utils/openalutil.hpp
) )
@ -95,6 +98,7 @@ endif (WIN32)
target_link_libraries(openmw-launcher target_link_libraries(openmw-launcher
${SDL2_LIBRARY_ONLY} ${SDL2_LIBRARY_ONLY}
${OPENAL_LIBRARY}
components components
) )

@ -1,15 +1,20 @@
#include "advancedpage.hpp" #include "advancedpage.hpp"
#include <array>
#include <components/config/gamesettings.hpp> #include <components/config/gamesettings.hpp>
#include <components/config/launchersettings.hpp> #include <components/config/launchersettings.hpp>
#include <QFileDialog> #include <QFileDialog>
#include <QCompleter> #include <QCompleter>
#include <QProxyStyle> #include <QProxyStyle>
#include <QString>
#include <components/contentselector/view/contentselector.hpp> #include <components/contentselector/view/contentselector.hpp>
#include <components/contentselector/model/esmfile.hpp> #include <components/contentselector/model/esmfile.hpp>
#include <cmath> #include <cmath>
#include "utils/openalutil.hpp"
Launcher::AdvancedPage::AdvancedPage(Config::GameSettings &gameSettings, Launcher::AdvancedPage::AdvancedPage(Config::GameSettings &gameSettings,
Settings::Manager &engineSettings, QWidget *parent) Settings::Manager &engineSettings, QWidget *parent)
: QWidget(parent) : QWidget(parent)
@ -19,7 +24,17 @@ Launcher::AdvancedPage::AdvancedPage(Config::GameSettings &gameSettings,
setObjectName ("AdvancedPage"); setObjectName ("AdvancedPage");
setupUi(this); setupUi(this);
for(const char * name : Launcher::enumerateOpenALDevices())
{
audioDeviceSelectorComboBox->addItem(QString::fromUtf8(name), QString::fromUtf8(name));
}
for(const char * name : Launcher::enumerateOpenALDevicesHrtf())
{
hrtfProfileSelectorComboBox->addItem(QString::fromUtf8(name), QString::fromUtf8(name));
}
loadSettings(); loadSettings();
mCellNameCompleter.setModel(&mCellNameCompleterModel); mCellNameCompleter.setModel(&mCellNameCompleterModel);
startDefaultCharacterAtField->setCompleter(&mCellNameCompleter); startDefaultCharacterAtField->setCompleter(&mCellNameCompleter);
} }
@ -95,6 +110,7 @@ bool Launcher::AdvancedPage::loadSettings()
int numPhysicsThreads = mEngineSettings.getInt("async num threads", "Physics"); int numPhysicsThreads = mEngineSettings.getInt("async num threads", "Physics");
if (numPhysicsThreads >= 0) if (numPhysicsThreads >= 0)
physicsThreadsSpinBox->setValue(numPhysicsThreads); physicsThreadsSpinBox->setValue(numPhysicsThreads);
loadSettingBool(allowNPCToFollowOverWaterSurfaceCheckBox, "allow actors to follow over water surface", "Game");
} }
// Visuals // Visuals
@ -124,8 +140,43 @@ bool Launcher::AdvancedPage::loadSettings()
loadSettingBool(activeGridObjectPagingCheckBox, "object paging active grid", "Terrain"); loadSettingBool(activeGridObjectPagingCheckBox, "object paging active grid", "Terrain");
viewingDistanceComboBox->setValue(convertToCells(mEngineSettings.getInt("viewing distance", "Camera"))); viewingDistanceComboBox->setValue(convertToCells(mEngineSettings.getInt("viewing distance", "Camera")));
int lightingMethod = 1;
if (mEngineSettings.getString("lighting method", "Shaders") == "legacy")
lightingMethod = 0;
else if (mEngineSettings.getString("lighting method", "Shaders") == "shaders")
lightingMethod = 2;
lightingMethodComboBox->setCurrentIndex(lightingMethod);
} }
// Audio
{
std::string selectedAudioDevice = mEngineSettings.getString("device", "Sound");
if (selectedAudioDevice.empty() == false)
{
int audioDeviceIndex = audioDeviceSelectorComboBox->findData(QString::fromStdString(selectedAudioDevice));
if (audioDeviceIndex != -1)
{
audioDeviceSelectorComboBox->setCurrentIndex(audioDeviceIndex);
}
}
int hrtfEnabledIndex = mEngineSettings.getInt("hrtf enable", "Sound");
if (hrtfEnabledIndex >= -1 && hrtfEnabledIndex <= 1)
{
enableHRTFComboBox->setCurrentIndex(hrtfEnabledIndex + 1);
}
std::string selectedHRTFProfile = mEngineSettings.getString("hrtf", "Sound");
if (selectedHRTFProfile.empty() == false)
{
int hrtfProfileIndex = hrtfProfileSelectorComboBox->findData(QString::fromStdString(selectedHRTFProfile));
if (hrtfProfileIndex != -1)
{
hrtfProfileSelectorComboBox->setCurrentIndex(hrtfProfileIndex);
}
}
}
// Camera // Camera
{ {
loadSettingBool(viewOverShoulderCheckBox, "view over shoulder", "Camera"); loadSettingBool(viewOverShoulderCheckBox, "view over shoulder", "Camera");
@ -152,6 +203,7 @@ bool Launcher::AdvancedPage::loadSettings()
showOwnedComboBox->setCurrentIndex(showOwnedIndex); showOwnedComboBox->setCurrentIndex(showOwnedIndex);
loadSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI"); loadSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI");
loadSettingBool(graphicHerbalismCheckBox, "graphic herbalism", "Game"); loadSettingBool(graphicHerbalismCheckBox, "graphic herbalism", "Game");
scalingSpinBox->setValue(mEngineSettings.getFloat("scaling factor", "GUI"));
} }
// Bug fixes // Bug fixes
@ -266,6 +318,36 @@ void Launcher::AdvancedPage::saveSettings()
{ {
mEngineSettings.setInt("viewing distance", "Camera", convertToUnits(viewingDistance)); mEngineSettings.setInt("viewing distance", "Camera", convertToUnits(viewingDistance));
} }
static std::array<std::string, 3> lightingMethodMap = {"legacy", "shaders compatibility", "shaders"};
mEngineSettings.setString("lighting method", "Shaders", lightingMethodMap[lightingMethodComboBox->currentIndex()]);
}
// Audio
{
int audioDeviceIndex = audioDeviceSelectorComboBox->currentIndex();
if (audioDeviceIndex != 0)
{
mEngineSettings.setString("device", "Sound", audioDeviceSelectorComboBox->currentText().toUtf8().constData());
}
else
{
mEngineSettings.setString("device", "Sound", "");
}
int hrtfEnabledIndex = enableHRTFComboBox->currentIndex() - 1;
if (hrtfEnabledIndex != mEngineSettings.getInt("hrtf enable", "Sound"))
{
mEngineSettings.setInt("hrtf enable", "Sound", hrtfEnabledIndex);
}
int selectedHRTFProfileIndex = hrtfProfileSelectorComboBox->currentIndex();
if (selectedHRTFProfileIndex != 0)
{
mEngineSettings.setString("hrtf", "Sound", hrtfProfileSelectorComboBox->currentText().toUtf8().constData());
}
else
{
mEngineSettings.setString("hrtf", "Sound", "");
}
} }
// Camera // Camera
@ -299,6 +381,9 @@ void Launcher::AdvancedPage::saveSettings()
mEngineSettings.setInt("show owned", "Game", showOwnedCurrentIndex); mEngineSettings.setInt("show owned", "Game", showOwnedCurrentIndex);
saveSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI"); saveSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI");
saveSettingBool(graphicHerbalismCheckBox, "graphic herbalism", "Game"); saveSettingBool(graphicHerbalismCheckBox, "graphic herbalism", "Game");
float uiScalingFactor = scalingSpinBox->value();
if (uiScalingFactor != mEngineSettings.getFloat("scaling factor", "GUI"))
mEngineSettings.setFloat("scaling factor", "GUI", uiScalingFactor);
} }
// Bug fixes // Bug fixes

@ -20,6 +20,9 @@
QString getAspect(int x, int y) QString getAspect(int x, int y)
{ {
int gcd = std::gcd (x, y); int gcd = std::gcd (x, y);
if (gcd == 0)
return QString();
int xaspect = x / gcd; int xaspect = x / gcd;
int yaspect = y / gcd; int yaspect = y / gcd;
// special case: 8 : 5 is usually referred to as 16:10 // special case: 8 : 5 is usually referred to as 16:10
@ -298,9 +301,9 @@ QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen)
return result; return result;
} }
QString aspect = getAspect(mode.w, mode.h);
QString resolution = QString::number(mode.w) + QString(" x ") + QString::number(mode.h); QString resolution = QString::number(mode.w) + QString(" x ") + QString::number(mode.h);
QString aspect = getAspect(mode.w, mode.h);
if (aspect == QLatin1String("16:9") || aspect == QLatin1String("16:10")) { if (aspect == QLatin1String("16:9") || aspect == QLatin1String("16:10")) {
resolution.append(tr("\t(Wide ") + aspect + ")"); resolution.append(tr("\t(Wide ") + aspect + ")");

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

@ -0,0 +1,7 @@
#include <vector>
namespace Launcher
{
std::vector<const char *> enumerateOpenALDevices();
std::vector<const char *> enumerateOpenALDevicesHrtf();
}

@ -116,7 +116,7 @@ opencs_units (view/prefs
opencs_units (model/prefs opencs_units (model/prefs
state setting intsetting doublesetting boolsetting enumsetting coloursetting shortcut state setting intsetting doublesetting boolsetting enumsetting coloursetting shortcut
shortcuteventhandler shortcutmanager shortcutsetting modifiersetting shortcuteventhandler shortcutmanager shortcutsetting modifiersetting stringsetting
) )
opencs_units_noqt (model/prefs opencs_units_noqt (model/prefs

@ -13,8 +13,6 @@
namespace CSMPrefs namespace CSMPrefs
{ {
const int ShortcutSetting::MaxKeys;
ShortcutSetting::ShortcutSetting(Category* parent, Settings::Manager* values, QMutex* mutex, const std::string& key, ShortcutSetting::ShortcutSetting(Category* parent, Settings::Manager* values, QMutex* mutex, const std::string& key,
const std::string& label) const std::string& label)
: Setting(parent, values, mutex, key, label) : Setting(parent, values, mutex, key, label)

@ -421,6 +421,16 @@ void CSMPrefs::State::declare()
declareSubcategory ("Script Editor"); declareSubcategory ("Script Editor");
declareShortcut ("script-editor-comment", "Comment Selection", QKeySequence()); declareShortcut ("script-editor-comment", "Comment Selection", QKeySequence());
declareShortcut ("script-editor-uncomment", "Uncomment Selection", QKeySequence()); declareShortcut ("script-editor-uncomment", "Uncomment Selection", QKeySequence());
declareCategory ("Models");
declareString ("baseanim", "base animations", "meshes/base_anim.nif").
setTooltip("3rd person base model with textkeys-data");
declareString ("baseanimkna", "base animations, kna", "meshes/base_animkna.nif").
setTooltip("3rd person beast race base model with textkeys-data");
declareString ("baseanimfemale", "base animations, female", "meshes/base_anim_female.nif").
setTooltip("3rd person female base model with textkeys-data");
declareString ("wolfskin", "base animations, wolf", "meshes/wolf/skin.nif").
setTooltip("3rd person werewolf skin");
} }
void CSMPrefs::State::declareCategory (const std::string& key) void CSMPrefs::State::declareCategory (const std::string& key)
@ -557,6 +567,24 @@ CSMPrefs::ShortcutSetting& CSMPrefs::State::declareShortcut (const std::string&
return *setting; return *setting;
} }
CSMPrefs::StringSetting& CSMPrefs::State::declareString (const std::string& key, const std::string& label, std::string default_)
{
if (mCurrentCategory==mCategories.end())
throw std::logic_error ("no category for setting");
setDefault (key, default_);
default_ = mSettings.getString (key, mCurrentCategory->second.getKey());
CSMPrefs::StringSetting *setting =
new CSMPrefs::StringSetting (&mCurrentCategory->second, &mSettings, &mMutex, key, label,
default_);
mCurrentCategory->second.addSetting (setting);
return *setting;
}
CSMPrefs::ModifierSetting& CSMPrefs::State::declareModifier(const std::string& key, const std::string& label, CSMPrefs::ModifierSetting& CSMPrefs::State::declareModifier(const std::string& key, const std::string& label,
int default_) int default_)
{ {

@ -16,6 +16,7 @@
#include "category.hpp" #include "category.hpp"
#include "setting.hpp" #include "setting.hpp"
#include "enumsetting.hpp" #include "enumsetting.hpp"
#include "stringsetting.hpp"
#include "shortcutmanager.hpp" #include "shortcutmanager.hpp"
class QColor; class QColor;
@ -78,6 +79,8 @@ namespace CSMPrefs
ShortcutSetting& declareShortcut (const std::string& key, const std::string& label, ShortcutSetting& declareShortcut (const std::string& key, const std::string& label,
const QKeySequence& default_); const QKeySequence& default_);
StringSetting& declareString (const std::string& key, const std::string& label, std::string default_);
ModifierSetting& declareModifier(const std::string& key, const std::string& label, int modifier_); ModifierSetting& declareModifier(const std::string& key, const std::string& label, int modifier_);
void declareSeparator(); void declareSeparator();

@ -0,0 +1,54 @@
#include "stringsetting.hpp"
#include <QLineEdit>
#include <QMutexLocker>
#include <components/settings/settings.hpp>
#include "category.hpp"
#include "state.hpp"
CSMPrefs::StringSetting::StringSetting (Category *parent, Settings::Manager *values,
QMutex *mutex, const std::string& key, const std::string& label, std::string default_)
: Setting (parent, values, mutex, key, label), mDefault (default_), mWidget(nullptr)
{}
CSMPrefs::StringSetting& CSMPrefs::StringSetting::setTooltip (const std::string& tooltip)
{
mTooltip = tooltip;
return *this;
}
std::pair<QWidget *, QWidget *> CSMPrefs::StringSetting::makeWidgets (QWidget *parent)
{
mWidget = new QLineEdit (QString::fromUtf8 (mDefault.c_str()), parent);
if (!mTooltip.empty())
{
QString tooltip = QString::fromUtf8 (mTooltip.c_str());
mWidget->setToolTip (tooltip);
}
connect (mWidget, SIGNAL (textChanged (QString)), this, SLOT (textChanged (QString)));
return std::make_pair (static_cast<QWidget *> (nullptr), mWidget);
}
void CSMPrefs::StringSetting::updateWidget()
{
if (mWidget)
{
mWidget->setText(QString::fromStdString(getValues().getString(getKey(), getParent()->getKey())));
}
}
void CSMPrefs::StringSetting::textChanged (const QString& text)
{
{
QMutexLocker lock (getMutex());
getValues().setString (getKey(), getParent()->getKey(), text.toStdString());
}
getParent()->getState()->update (*this);
}

@ -0,0 +1,36 @@
#ifndef CSM_PREFS_StringSetting_H
#define CSM_PREFS_StringSetting_H
#include "setting.hpp"
class QLineEdit;
namespace CSMPrefs
{
class StringSetting : public Setting
{
Q_OBJECT
std::string mTooltip;
std::string mDefault;
QLineEdit* mWidget;
public:
StringSetting (Category *parent, Settings::Manager *values,
QMutex *mutex, const std::string& key, const std::string& label, std::string default_);
StringSetting& setTooltip (const std::string& tooltip);
/// Return label, input widget.
std::pair<QWidget *, QWidget *> makeWidgets (QWidget *parent) override;
void updateWidget() override;
private slots:
void textChanged (const QString& text);
};
}
#endif

@ -76,6 +76,7 @@ void CSMWorld::ImportLandTexturesCommand::redo()
} }
std::vector<std::string> oldTextures; std::vector<std::string> oldTextures;
oldTextures.reserve(texIndices.size());
for (int index : texIndices) for (int index : texIndices)
{ {
oldTextures.push_back(LandTexture::createUniqueRecordId(oldPlugin, index)); oldTextures.push_back(LandTexture::createUniqueRecordId(oldPlugin, index));

@ -83,6 +83,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, bool fsStrict, const Files::Pat
defines["clamp"] = "1"; // Clamp lighting defines["clamp"] = "1"; // Clamp lighting
defines["preLightEnv"] = "0"; // Apply environment maps after lighting like Morrowind defines["preLightEnv"] = "0"; // Apply environment maps after lighting like Morrowind
defines["radialFog"] = "0"; defines["radialFog"] = "0";
defines["lightingModel"] = "0";
for (const auto& define : shadowDefines) for (const auto& define : shadowDefines)
defines[define.first] = define.second; defines[define.first] = define.second;
mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(defines); mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(defines);
@ -985,20 +986,6 @@ int CSMWorld::Data::startLoading (const boost::filesystem::path& path, bool base
mMetaData.setRecord (0, Record<MetaData> (RecordBase::State_ModifiedOnly, nullptr, &metaData)); mMetaData.setRecord (0, Record<MetaData> (RecordBase::State_ModifiedOnly, nullptr, &metaData));
} }
// Fix uninitialized master data index
for (std::vector<ESM::Header::MasterData>::const_iterator masterData = mReader->getGameFiles().begin();
masterData != mReader->getGameFiles().end(); ++masterData)
{
std::map<std::string, int>::iterator nameResult = mContentFileNames.find(masterData->name);
if (nameResult != mContentFileNames.end())
{
ESM::Header::MasterData& hackedMasterData = const_cast<ESM::Header::MasterData&>(*masterData);
hackedMasterData.index = nameResult->second;
}
}
return mReader->getRecordCount(); return mReader->getRecordCount();
} }

@ -201,7 +201,7 @@ QModelIndex CSMWorld::IdTree::parent (const QModelIndex& index) const
const std::pair<int, int>& address(unfoldIndexAddress(id)); const std::pair<int, int>& address(unfoldIndexAddress(id));
if (address.first >= this->rowCount() || address.second >= this->columnCount()) if (address.first >= this->rowCount() || address.second >= this->columnCount())
throw "Parent index is not present in the model"; throw std::logic_error("Parent index is not present in the model");
return createIndex(address.first, address.second); return createIndex(address.first, address.second);
} }
@ -216,7 +216,7 @@ unsigned int CSMWorld::IdTree::foldIndexAddress (const QModelIndex& index) const
std::pair< int, int > CSMWorld::IdTree::unfoldIndexAddress (unsigned int id) const std::pair< int, int > CSMWorld::IdTree::unfoldIndexAddress (unsigned int id) const
{ {
if (id == 0) if (id == 0)
throw "Attempt to unfold index id of the top level data cell"; throw std::runtime_error("Attempt to unfold index id of the top level data cell");
--id; --id;
int row = id / this->columnCount(); int row = id / this->columnCount();

@ -56,7 +56,9 @@ void CSMWorld::PotionRefIdAdapter::setData (const RefIdColumn *column, RefIdData
CSMWorld::IngredientColumns::IngredientColumns (const InventoryColumns& columns) CSMWorld::IngredientColumns::IngredientColumns (const InventoryColumns& columns)
: InventoryColumns (columns) {} : InventoryColumns (columns)
, mEffects(nullptr)
{}
CSMWorld::IngredientRefIdAdapter::IngredientRefIdAdapter (const IngredientColumns& columns) CSMWorld::IngredientRefIdAdapter::IngredientRefIdAdapter (const IngredientColumns& columns)
: InventoryRefIdAdapter<ESM::Ingredient> (UniversalId::Type_Ingredient, columns), : InventoryRefIdAdapter<ESM::Ingredient> (UniversalId::Type_Ingredient, columns),
@ -585,7 +587,13 @@ void CSMWorld::DoorRefIdAdapter::setData (const RefIdColumn *column, RefIdData&
} }
CSMWorld::LightColumns::LightColumns (const InventoryColumns& columns) CSMWorld::LightColumns::LightColumns (const InventoryColumns& columns)
: InventoryColumns (columns) {} : InventoryColumns (columns)
, mTime(nullptr)
, mRadius(nullptr)
, mColor(nullptr)
, mSound(nullptr)
, mEmitterType(nullptr)
{}
CSMWorld::LightRefIdAdapter::LightRefIdAdapter (const LightColumns& columns) CSMWorld::LightRefIdAdapter::LightRefIdAdapter (const LightColumns& columns)
: InventoryRefIdAdapter<ESM::Light> (UniversalId::Type_Light, columns), mColumns (columns) : InventoryRefIdAdapter<ESM::Light> (UniversalId::Type_Light, columns), mColumns (columns)
@ -1454,7 +1462,15 @@ int CSMWorld::CreatureMiscRefIdAdapter::getNestedRowsCount(const RefIdColumn *co
} }
CSMWorld::WeaponColumns::WeaponColumns (const EnchantableColumns& columns) CSMWorld::WeaponColumns::WeaponColumns (const EnchantableColumns& columns)
: EnchantableColumns (columns) {} : EnchantableColumns (columns)
, mType(nullptr)
, mHealth(nullptr)
, mSpeed(nullptr)
, mReach(nullptr)
, mChop{nullptr}
, mSlash{nullptr}
, mThrust{nullptr}
{}
CSMWorld::WeaponRefIdAdapter::WeaponRefIdAdapter (const WeaponColumns& columns) CSMWorld::WeaponRefIdAdapter::WeaponRefIdAdapter (const WeaponColumns& columns)
: EnchantableRefIdAdapter<ESM::Weapon> (UniversalId::Type_Weapon, columns), mColumns (columns) : EnchantableRefIdAdapter<ESM::Weapon> (UniversalId::Type_Weapon, columns), mColumns (columns)

@ -178,7 +178,11 @@ namespace CSMWorld
const RefIdColumn *mName; const RefIdColumn *mName;
const RefIdColumn *mScript; const RefIdColumn *mScript;
NameColumns (const ModelColumns& base) : ModelColumns (base) {} NameColumns (const ModelColumns& base)
: ModelColumns (base)
, mName(nullptr)
, mScript(nullptr)
{}
}; };
/// \brief Adapter for IDs with names (all but levelled lists and statics) /// \brief Adapter for IDs with names (all but levelled lists and statics)
@ -247,7 +251,12 @@ namespace CSMWorld
const RefIdColumn *mWeight; const RefIdColumn *mWeight;
const RefIdColumn *mValue; const RefIdColumn *mValue;
InventoryColumns (const NameColumns& base) : NameColumns (base) {} InventoryColumns (const NameColumns& base)
: NameColumns (base)
, mIcon(nullptr)
, mWeight(nullptr)
, mValue(nullptr)
{}
}; };
/// \brief Adapter for IDs that can go into an inventory /// \brief Adapter for IDs that can go into an inventory
@ -405,7 +414,11 @@ namespace CSMWorld
const RefIdColumn *mEnchantment; const RefIdColumn *mEnchantment;
const RefIdColumn *mEnchantmentPoints; const RefIdColumn *mEnchantmentPoints;
EnchantableColumns (const InventoryColumns& base) : InventoryColumns (base) {} EnchantableColumns (const InventoryColumns& base)
: InventoryColumns (base)
, mEnchantment(nullptr)
, mEnchantmentPoints(nullptr)
{}
}; };
/// \brief Adapter for enchantable IDs /// \brief Adapter for enchantable IDs
@ -474,7 +487,11 @@ namespace CSMWorld
const RefIdColumn *mQuality; const RefIdColumn *mQuality;
const RefIdColumn *mUses; const RefIdColumn *mUses;
ToolColumns (const InventoryColumns& base) : InventoryColumns (base) {} ToolColumns (const InventoryColumns& base)
: InventoryColumns (base)
, mQuality(nullptr)
, mUses(nullptr)
{}
}; };
/// \brief Adapter for tools with limited uses IDs (lockpick, repair, probes) /// \brief Adapter for tools with limited uses IDs (lockpick, repair, probes)
@ -549,7 +566,17 @@ namespace CSMWorld
const RefIdColumn *mAiPackages; const RefIdColumn *mAiPackages;
std::map<const RefIdColumn *, unsigned int> mServices; std::map<const RefIdColumn *, unsigned int> mServices;
ActorColumns (const NameColumns& base) : NameColumns (base) {} ActorColumns (const NameColumns& base)
: NameColumns (base)
, mHello(nullptr)
, mFlee(nullptr)
, mFight(nullptr)
, mAlarm(nullptr)
, mInventory(nullptr)
, mSpells(nullptr)
, mDestinations(nullptr)
, mAiPackages(nullptr)
{}
}; };
/// \brief Adapter for actor IDs (handles common AI functionality) /// \brief Adapter for actor IDs (handles common AI functionality)
@ -2054,7 +2081,11 @@ namespace CSMWorld
const RefIdColumn *mLevList; const RefIdColumn *mLevList;
const RefIdColumn *mNestedListLevList; const RefIdColumn *mNestedListLevList;
LevListColumns (const BaseColumns& base) : BaseColumns (base) {} LevListColumns (const BaseColumns& base)
: BaseColumns (base)
, mLevList(nullptr)
, mNestedListLevList(nullptr)
{}
}; };
template<typename RecordT> template<typename RecordT>

@ -126,7 +126,7 @@ namespace CSVRender
{ {
// Try again without any mask // Try again without any mask
boundsVisitor.reset(); boundsVisitor.reset();
boundsVisitor.setTraversalMask(~0); boundsVisitor.setTraversalMask(~0u);
root->accept(boundsVisitor); root->accept(boundsVisitor);
// Last resort, set a default // Last resort, set a default
@ -458,7 +458,7 @@ namespace CSVRender
, mDown(false) , mDown(false)
, mRollLeft(false) , mRollLeft(false)
, mRollRight(false) , mRollRight(false)
, mPickingMask(~0) , mPickingMask(~0u)
, mCenter(0,0,0) , mCenter(0,0,0)
, mDistance(0) , mDistance(0)
, mOrbitSpeed(osg::PI / 4) , mOrbitSpeed(osg::PI / 4)

@ -151,7 +151,7 @@ void CSVRender::CellArrow::buildShape()
osg::Vec4Array *colours = new osg::Vec4Array; osg::Vec4Array *colours = new osg::Vec4Array;
for (int i=0; i<6; ++i) for (int i=0; i<6; ++i)
colours->push_back (osg::Vec4f (0.11, 0.6f, 0.95f, 1.0f)); colours->push_back (osg::Vec4f (0.11f, 0.6f, 0.95f, 1.0f));
for (int i=0; i<6; ++i) for (int i=0; i<6; ++i)
colours->push_back (osg::Vec4f (0.08f, 0.44f, 0.7f, 1.0f)); colours->push_back (osg::Vec4f (0.08f, 0.44f, 0.7f, 1.0f));

@ -8,7 +8,7 @@ namespace CSVRender
/// @note See the respective file in OpenMW (apps/openmw/mwrender/vismask.hpp) /// @note See the respective file in OpenMW (apps/openmw/mwrender/vismask.hpp)
/// for general usage hints about node masks. /// for general usage hints about node masks.
/// @copydoc MWRender::VisMask /// @copydoc MWRender::VisMask
enum Mask enum Mask : unsigned int
{ {
// elements that are part of the actual scene // elements that are part of the actual scene
Mask_Reference = 0x2, Mask_Reference = 0x2,

@ -121,7 +121,7 @@ void RenderWidget::flagAsModified()
mView->requestRedraw(); mView->requestRedraw();
} }
void RenderWidget::setVisibilityMask(int mask) void RenderWidget::setVisibilityMask(unsigned int mask)
{ {
mView->getCamera()->setCullMask(mask | Mask_ParticleSystem | Mask_Lighting); mView->getCamera()->setCullMask(mask | Mask_ParticleSystem | Mask_Lighting);
} }

@ -55,7 +55,7 @@ namespace CSVRender
/// Initiates a request to redraw the view /// Initiates a request to redraw the view
void flagAsModified(); void flagAsModified();
void setVisibilityMask(int mask); void setVisibilityMask(unsigned int mask);
osg::Camera *getCamera(); osg::Camera *getCamera();

@ -606,7 +606,7 @@ void CSVRender::TerrainTextureMode::createTexture(std::string textureFileName)
newId = CSMWorld::LandTexture::createUniqueRecordId(0, counter); newId = CSMWorld::LandTexture::createUniqueRecordId(0, counter);
if (ltexTable.getRecord(newId).isDeleted() == 0) counter = (counter + 1) % maxCounter; if (ltexTable.getRecord(newId).isDeleted() == 0) counter = (counter + 1) % maxCounter;
} }
catch (const std::exception& e) catch (const std::exception&)
{ {
newId = CSMWorld::LandTexture::createUniqueRecordId(0, counter); newId = CSMWorld::LandTexture::createUniqueRecordId(0, counter);
freeIndexFound = true; freeIndexFound = true;

@ -163,7 +163,7 @@ void CSVRender::WorldspaceWidget::selectDefaultNavigationMode()
void CSVRender::WorldspaceWidget::centerOrbitCameraOnSelection() void CSVRender::WorldspaceWidget::centerOrbitCameraOnSelection()
{ {
std::vector<osg::ref_ptr<TagBase> > selection = getSelection(~0); std::vector<osg::ref_ptr<TagBase> > selection = getSelection(~0u);
for (std::vector<osg::ref_ptr<TagBase> >::iterator it = selection.begin(); it!=selection.end(); ++it) for (std::vector<osg::ref_ptr<TagBase> >::iterator it = selection.begin(); it!=selection.end(); ++it)
{ {

@ -43,26 +43,6 @@ namespace Misc
class CallbackManager; class CallbackManager;
} }
namespace MWScript
{
class ScriptManager;
}
namespace MWSound
{
class SoundManager;
}
namespace MWWorld
{
class World;
}
namespace MWGui
{
class WindowManager;
}
namespace Files namespace Files
{ {
struct ConfigurationManager; struct ConfigurationManager;

@ -32,7 +32,6 @@ namespace MyGUI
namespace ESM namespace ESM
{ {
struct Class;
class ESMReader; class ESMReader;
class ESMWriter; class ESMWriter;
struct CellId; struct CellId;
@ -173,6 +172,8 @@ namespace MWBase
virtual void setDragDrop(bool dragDrop) = 0; virtual void setDragDrop(bool dragDrop) = 0;
virtual bool getWorldMouseOver() = 0; virtual bool getWorldMouseOver() = 0;
virtual float getScalingFactor() = 0;
virtual bool toggleFogOfWar() = 0; virtual bool toggleFogOfWar() = 0;
virtual bool toggleFullHelp() = 0; virtual bool toggleFullHelp() = 0;

@ -288,13 +288,13 @@ namespace MWBase
virtual void deleteObject (const MWWorld::Ptr& ptr) = 0; virtual void deleteObject (const MWWorld::Ptr& ptr) = 0;
virtual void undeleteObject (const MWWorld::Ptr& ptr) = 0; virtual void undeleteObject (const MWWorld::Ptr& ptr) = 0;
virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, float x, float y, float z, bool moveToActive=false) = 0; virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, float x, float y, float z, bool movePhysics=true, bool moveToActive=false) = 0;
///< @return an updated Ptr in case the Ptr's cell changes ///< @return an updated Ptr in case the Ptr's cell changes
virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z, bool movePhysics=true) = 0; virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z, bool movePhysics=true) = 0;
///< @return an updated Ptr ///< @return an updated Ptr
virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, osg::Vec3f vec) = 0; virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, osg::Vec3f vec, bool moveToActive) = 0;
///< @return an updated Ptr ///< @return an updated Ptr
virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0; virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0;
@ -456,6 +456,8 @@ namespace MWBase
virtual void applyDeferredPreviewRotationToPlayer(float dt) = 0; virtual void applyDeferredPreviewRotationToPlayer(float dt) = 0;
virtual void disableDeferredPreviewRotation() = 0; virtual void disableDeferredPreviewRotation() = 0;
virtual void saveLoaded() = 0;
virtual void setupPlayer() = 0; virtual void setupPlayer() = 0;
virtual void renderPlayer() = 0; virtual void renderPlayer() = 0;
@ -618,7 +620,7 @@ namespace MWBase
/// Return a vector aiming the actor's weapon towards a target. /// Return a vector aiming the actor's weapon towards a target.
/// @note The length of the vector is the distance between actor and target. /// @note The length of the vector is the distance between actor and target.
virtual osg::Vec3f aimToTarget(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) = 0; virtual osg::Vec3f aimToTarget(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target, bool isRangedCombat) = 0;
/// Return the distance between actor's weapon and target's collision box. /// Return the distance between actor's weapon and target's collision box.
virtual float getHitDistance(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) = 0; virtual float getHitDistance(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) = 0;

@ -46,11 +46,6 @@ namespace MWClass
mStore.readState(inventory); mStore.readState(inventory);
} }
MWWorld::CustomData *ContainerCustomData::clone() const
{
return new ContainerCustomData (*this);
}
ContainerCustomData& ContainerCustomData::asContainerCustomData() ContainerCustomData& ContainerCustomData::asContainerCustomData()
{ {
return *this; return *this;
@ -72,7 +67,7 @@ namespace MWClass
MWWorld::LiveCellRef<ESM::Container> *ref = ptr.get<ESM::Container>(); MWWorld::LiveCellRef<ESM::Container> *ref = ptr.get<ESM::Container>();
// store // store
ptr.getRefData().setCustomData (std::make_unique<ContainerCustomData>(*ref->mBase, ptr.getCell()).release()); ptr.getRefData().setCustomData (std::make_unique<ContainerCustomData>(*ref->mBase, ptr.getCell()));
MWBase::Environment::get().getWorld()->addContainerScripts(ptr, ptr.getCell()); MWBase::Environment::get().getWorld()->addContainerScripts(ptr, ptr.getCell());
} }
@ -317,7 +312,7 @@ namespace MWClass
return; return;
const ESM::ContainerState& containerState = state.asContainerState(); const ESM::ContainerState& containerState = state.asContainerState();
ptr.getRefData().setCustomData(std::make_unique<ContainerCustomData>(containerState.mInventory).release()); ptr.getRefData().setCustomData(std::make_unique<ContainerCustomData>(containerState.mInventory));
} }
void Container::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const void Container::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const

@ -13,15 +13,13 @@ namespace ESM
namespace MWClass namespace MWClass
{ {
class ContainerCustomData : public MWWorld::CustomData class ContainerCustomData : public MWWorld::TypedCustomData<ContainerCustomData>
{ {
MWWorld::ContainerStore mStore; MWWorld::ContainerStore mStore;
public: public:
ContainerCustomData(const ESM::Container& container, MWWorld::CellStore* cell); ContainerCustomData(const ESM::Container& container, MWWorld::CellStore* cell);
ContainerCustomData(const ESM::InventoryState& inventory); ContainerCustomData(const ESM::InventoryState& inventory);
MWWorld::CustomData *clone() const override;
ContainerCustomData& asContainerCustomData() override; ContainerCustomData& asContainerCustomData() override;
const ContainerCustomData& asContainerCustomData() const override; const ContainerCustomData& asContainerCustomData() const override;

@ -52,14 +52,16 @@ namespace
namespace MWClass namespace MWClass
{ {
class CreatureCustomData : public MWWorld::CustomData class CreatureCustomData : public MWWorld::TypedCustomData<CreatureCustomData>
{ {
public: public:
MWMechanics::CreatureStats mCreatureStats; MWMechanics::CreatureStats mCreatureStats;
MWWorld::ContainerStore* mContainerStore; // may be InventoryStore for some creatures std::unique_ptr<MWWorld::ContainerStore> mContainerStore; // may be InventoryStore for some creatures
MWMechanics::Movement mMovement; MWMechanics::Movement mMovement;
MWWorld::CustomData *clone() const override; CreatureCustomData() = default;
CreatureCustomData(const CreatureCustomData& other);
CreatureCustomData(CreatureCustomData&& other) = default;
CreatureCustomData& asCreatureCustomData() override CreatureCustomData& asCreatureCustomData() override
{ {
@ -69,16 +71,13 @@ namespace MWClass
{ {
return *this; return *this;
} }
CreatureCustomData() : mContainerStore(nullptr) {}
virtual ~CreatureCustomData() { delete mContainerStore; }
}; };
MWWorld::CustomData *CreatureCustomData::clone() const CreatureCustomData::CreatureCustomData(const CreatureCustomData& other)
: mCreatureStats(other.mCreatureStats),
mContainerStore(other.mContainerStore->clone()),
mMovement(other.mMovement)
{ {
CreatureCustomData* cloned = new CreatureCustomData (*this);
cloned->mContainerStore = mContainerStore->clone();
return cloned;
} }
const Creature::GMST& Creature::getGmst() const Creature::GMST& Creature::getGmst()
@ -149,16 +148,16 @@ namespace MWClass
// inventory // inventory
bool hasInventory = hasInventoryStore(ptr); bool hasInventory = hasInventoryStore(ptr);
if (hasInventory) if (hasInventory)
data->mContainerStore = new MWWorld::InventoryStore(); data->mContainerStore = std::make_unique<MWWorld::InventoryStore>();
else else
data->mContainerStore = new MWWorld::ContainerStore(); data->mContainerStore = std::make_unique<MWWorld::ContainerStore>();
data->mCreatureStats.setGoldPool(ref->mBase->mData.mGold); data->mCreatureStats.setGoldPool(ref->mBase->mData.mGold);
data->mCreatureStats.setNeedRecalcDynamicStats(false); data->mCreatureStats.setNeedRecalcDynamicStats(false);
// store // store
ptr.getRefData().setCustomData(data.release()); ptr.getRefData().setCustomData(std::move(data));
getContainerStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId()); getContainerStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId());
@ -772,11 +771,11 @@ namespace MWClass
std::unique_ptr<CreatureCustomData> data (new CreatureCustomData); std::unique_ptr<CreatureCustomData> data (new CreatureCustomData);
if (hasInventoryStore(ptr)) if (hasInventoryStore(ptr))
data->mContainerStore = new MWWorld::InventoryStore(); data->mContainerStore = std::make_unique<MWWorld::InventoryStore>();
else else
data->mContainerStore = new MWWorld::ContainerStore(); data->mContainerStore = std::make_unique<MWWorld::ContainerStore>();
ptr.getRefData().setCustomData (data.release()); ptr.getRefData().setCustomData (std::move(data));
} }
} }
else else

@ -10,15 +10,13 @@
namespace MWClass namespace MWClass
{ {
class CreatureLevListCustomData : public MWWorld::CustomData class CreatureLevListCustomData : public MWWorld::TypedCustomData<CreatureLevListCustomData>
{ {
public: public:
// actorId of the creature we spawned // actorId of the creature we spawned
int mSpawnActorId; int mSpawnActorId;
bool mSpawn; // Should a new creature be spawned? bool mSpawn; // Should a new creature be spawned?
MWWorld::CustomData *clone() const override;
CreatureLevListCustomData& asCreatureLevListCustomData() override CreatureLevListCustomData& asCreatureLevListCustomData() override
{ {
return *this; return *this;
@ -29,11 +27,6 @@ namespace MWClass
} }
}; };
MWWorld::CustomData *CreatureLevListCustomData::clone() const
{
return new CreatureLevListCustomData (*this);
}
std::string CreatureLevList::getName (const MWWorld::ConstPtr& ptr) const std::string CreatureLevList::getName (const MWWorld::ConstPtr& ptr) const
{ {
return ""; return "";
@ -138,11 +131,11 @@ namespace MWClass
{ {
if (!ptr.getRefData().getCustomData()) if (!ptr.getRefData().getCustomData())
{ {
std::unique_ptr<CreatureLevListCustomData> data (new CreatureLevListCustomData); std::unique_ptr<CreatureLevListCustomData> data = std::make_unique<CreatureLevListCustomData>();
data->mSpawnActorId = -1; data->mSpawnActorId = -1;
data->mSpawn = true; data->mSpawn = true;
ptr.getRefData().setCustomData(data.release()); ptr.getRefData().setCustomData(std::move(data));
} }
} }

@ -31,13 +31,11 @@
namespace MWClass namespace MWClass
{ {
class DoorCustomData : public MWWorld::CustomData class DoorCustomData : public MWWorld::TypedCustomData<DoorCustomData>
{ {
public: public:
MWWorld::DoorState mDoorState = MWWorld::DoorState::Idle; MWWorld::DoorState mDoorState = MWWorld::DoorState::Idle;
MWWorld::CustomData *clone() const override;
DoorCustomData& asDoorCustomData() override DoorCustomData& asDoorCustomData() override
{ {
return *this; return *this;
@ -48,11 +46,6 @@ namespace MWClass
} }
}; };
MWWorld::CustomData *DoorCustomData::clone() const
{
return new DoorCustomData (*this);
}
void Door::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const void Door::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const
{ {
if (!model.empty()) if (!model.empty())
@ -327,8 +320,7 @@ namespace MWClass
{ {
if (!ptr.getRefData().getCustomData()) if (!ptr.getRefData().getCustomData())
{ {
std::unique_ptr<DoorCustomData> data(new DoorCustomData); ptr.getRefData().setCustomData(std::make_unique<DoorCustomData>());
ptr.getRefData().setCustomData(data.release());
} }
} }

@ -247,15 +247,13 @@ namespace
namespace MWClass namespace MWClass
{ {
class NpcCustomData : public MWWorld::CustomData class NpcCustomData : public MWWorld::TypedCustomData<NpcCustomData>
{ {
public: public:
MWMechanics::NpcStats mNpcStats; MWMechanics::NpcStats mNpcStats;
MWMechanics::Movement mMovement; MWMechanics::Movement mMovement;
MWWorld::InventoryStore mInventoryStore; MWWorld::InventoryStore mInventoryStore;
MWWorld::CustomData *clone() const override;
NpcCustomData& asNpcCustomData() override NpcCustomData& asNpcCustomData() override
{ {
return *this; return *this;
@ -266,11 +264,6 @@ namespace MWClass
} }
}; };
MWWorld::CustomData *NpcCustomData::clone() const
{
return new NpcCustomData (*this);
}
const Npc::GMST& Npc::getGmst() const Npc::GMST& Npc::getGmst()
{ {
static GMST gmst; static GMST gmst;
@ -398,7 +391,7 @@ namespace MWClass
data->mNpcStats.setGoldPool(gold); data->mNpcStats.setGoldPool(gold);
// store // store
ptr.getRefData().setCustomData (data.release()); ptr.getRefData().setCustomData(std::move(data));
getInventoryStore(ptr).autoEquip(ptr); getInventoryStore(ptr).autoEquip(ptr);
} }
@ -1331,8 +1324,7 @@ namespace MWClass
if (!ptr.getRefData().getCustomData()) if (!ptr.getRefData().getCustomData())
{ {
// Create a CustomData, but don't fill it from ESM records (not needed) // Create a CustomData, but don't fill it from ESM records (not needed)
std::unique_ptr<NpcCustomData> data (new NpcCustomData); ptr.getRefData().setCustomData(std::make_unique<NpcCustomData>());
ptr.getRefData().setCustomData (data.release());
} }
} }
else else

@ -17,7 +17,7 @@ namespace MWDialogue
std::vector<Token> parseHyperText(const std::string & text) std::vector<Token> parseHyperText(const std::string & text)
{ {
std::vector<Token> result; std::vector<Token> result;
size_t pos_end, iteration_pos = 0; size_t pos_end = std::string::npos, iteration_pos = 0;
for(;;) for(;;)
{ {
size_t pos_begin = text.find('@', iteration_pos); size_t pos_begin = text.find('@', iteration_pos);

@ -1,5 +1,7 @@
#include "bookpage.hpp" #include "bookpage.hpp"
#include <optional>
#include "MyGUI_RenderItem.h" #include "MyGUI_RenderItem.h"
#include "MyGUI_RenderManager.h" #include "MyGUI_RenderManager.h"
#include "MyGUI_TextureUtility.h" #include "MyGUI_TextureUtility.h"
@ -894,6 +896,27 @@ protected:
return mIsPageReset || (mPage != page); return mIsPageReset || (mPage != page);
} }
std::optional<MyGUI::IntPoint> getAdjustedPos(int left, int top, bool move = false)
{
if (!mBook)
return {};
if (mPage >= mBook->mPages.size())
return {};
MyGUI::IntPoint pos (left, top);
#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
// work around inconsistency in MyGUI where the mouse press coordinates aren't
// transformed by the current Layer (even though mouse *move* events are).
if(!move)
pos = mNode->getLayer()->getPosition(left, top);
#endif
pos.left -= mCroppedParent->getAbsoluteLeft ();
pos.top -= mCroppedParent->getAbsoluteTop ();
pos.top += mViewTop;
return pos;
}
public: public:
typedef TypesetBookImpl::StyleImpl Style; typedef TypesetBookImpl::StyleImpl Style;
@ -952,16 +975,10 @@ public:
void onMouseMove (int left, int top) void onMouseMove (int left, int top)
{ {
if (!mBook) Style * hit = nullptr;
return; if(auto pos = getAdjustedPos(left, top, true))
if(pos->top <= mViewBottom)
if (mPage >= mBook->mPages.size()) hit = mBook->hitTestWithMargin (pos->left, pos->top);
return;
left -= mCroppedParent->getAbsoluteLeft ();
top -= mCroppedParent->getAbsoluteTop ();
Style * hit = mBook->hitTestWithMargin (left, mViewTop + top);
if (mLastDown == MyGUI::MouseButton::None) if (mLastDown == MyGUI::MouseButton::None)
{ {
@ -991,24 +1008,11 @@ public:
void onMouseButtonPressed (int left, int top, MyGUI::MouseButton id) void onMouseButtonPressed (int left, int top, MyGUI::MouseButton id)
{ {
if (!mBook) auto pos = getAdjustedPos(left, top);
return;
if (mPage >= mBook->mPages.size()) if (pos && mLastDown == MyGUI::MouseButton::None)
return;
// work around inconsistency in MyGUI where the mouse press coordinates aren't
// transformed by the current Layer (even though mouse *move* events are).
MyGUI::IntPoint pos (left, top);
#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
pos = mNode->getLayer()->getPosition(left, top);
#endif
pos.left -= mCroppedParent->getAbsoluteLeft ();
pos.top -= mCroppedParent->getAbsoluteTop ();
if (mLastDown == MyGUI::MouseButton::None)
{ {
mFocusItem = mBook->hitTestWithMargin (pos.left, mViewTop + pos.top); mFocusItem = pos->top <= mViewBottom ? mBook->hitTestWithMargin (pos->left, pos->top) : nullptr;
mItemActive = true; mItemActive = true;
dirtyFocusItem (); dirtyFocusItem ();
@ -1019,25 +1023,11 @@ public:
void onMouseButtonReleased(int left, int top, MyGUI::MouseButton id) void onMouseButtonReleased(int left, int top, MyGUI::MouseButton id)
{ {
if (!mBook) auto pos = getAdjustedPos(left, top);
return;
if (mPage >= mBook->mPages.size())
return;
// work around inconsistency in MyGUI where the mouse release coordinates aren't
// transformed by the current Layer (even though mouse *move* events are).
MyGUI::IntPoint pos (left, top);
#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
pos = mNode->getLayer()->getPosition(left, top);
#endif
pos.left -= mCroppedParent->getAbsoluteLeft ();
pos.top -= mCroppedParent->getAbsoluteTop ();
if (mLastDown == id) if (pos && mLastDown == id)
{ {
Style * item = mBook->hitTestWithMargin (pos.left, mViewTop + pos.top); Style * item = pos->top <= mViewBottom ? mBook->hitTestWithMargin (pos->left, pos->top) : nullptr;
bool clicked = mFocusItem == item; bool clicked = mFocusItem == item;

@ -6,11 +6,6 @@
#include "itemmodel.hpp" #include "itemmodel.hpp"
namespace MWWorld
{
class Environment;
}
namespace MyGUI namespace MyGUI
{ {
class Gui; class Gui;
@ -19,7 +14,6 @@ namespace MyGUI
namespace MWGui namespace MWGui
{ {
class WindowManager;
class ContainerWindow; class ContainerWindow;
class ItemView; class ItemView;
class SortFilterItemModel; class SortFilterItemModel;

@ -148,7 +148,7 @@ namespace MWGui
// We need this copy for when @# hyperlinks are replaced // We need this copy for when @# hyperlinks are replaced
std::string text = mText; std::string text = mText;
size_t pos_end; size_t pos_end = std::string::npos;
for(;;) for(;;)
{ {
size_t pos_begin = text.find('@'); size_t pos_begin = text.find('@');

@ -15,11 +15,6 @@ namespace Gui
class MWList; class MWList;
} }
namespace MWGui
{
class WindowManager;
}
namespace MWGui namespace MWGui
{ {
class ResponseCallback; class ResponseCallback;

@ -71,13 +71,8 @@ namespace MWGui
, mLastYSize(0) , mLastYSize(0)
, mPreview(new MWRender::InventoryPreview(parent, resourceSystem, MWMechanics::getPlayer())) , mPreview(new MWRender::InventoryPreview(parent, resourceSystem, MWMechanics::getPlayer()))
, mTrading(false) , mTrading(false)
, mScaleFactor(1.0f)
, mUpdateTimer(0.f) , mUpdateTimer(0.f)
{ {
float uiScale = Settings::Manager::getFloat("scaling factor", "GUI");
if (uiScale > 1.0)
mScaleFactor = uiScale;
mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture())); mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture()));
mPreview->rebuild(); mPreview->rebuild();
@ -473,10 +468,11 @@ namespace MWGui
MyGUI::IntSize size = mAvatarImage->getSize(); MyGUI::IntSize size = mAvatarImage->getSize();
int width = std::min(mPreview->getTextureWidth(), size.width); int width = std::min(mPreview->getTextureWidth(), size.width);
int height = std::min(mPreview->getTextureHeight(), size.height); int height = std::min(mPreview->getTextureHeight(), size.height);
mPreview->setViewport(int(width*mScaleFactor), int(height*mScaleFactor)); float scalingFactor = MWBase::Environment::get().getWindowManager()->getScalingFactor();
mPreview->setViewport(int(width*scalingFactor), int(height*scalingFactor));
mAvatarImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, mAvatarImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f,
width*mScaleFactor/float(mPreview->getTextureWidth()), height*mScaleFactor/float(mPreview->getTextureHeight()))); width*scalingFactor/float(mPreview->getTextureWidth()), height*scalingFactor/float(mPreview->getTextureHeight())));
} }
void InventoryWindow::onNameFilterChanged(MyGUI::EditBox* _sender) void InventoryWindow::onNameFilterChanged(MyGUI::EditBox* _sender)
@ -641,8 +637,9 @@ namespace MWGui
y = (mAvatarImage->getHeight()-1) - y; y = (mAvatarImage->getHeight()-1) - y;
// Scale coordinates // Scale coordinates
x = int(x*mScaleFactor); float scalingFactor = MWBase::Environment::get().getWindowManager()->getScalingFactor();
y = int(y*mScaleFactor); x = static_cast<int>(x*scalingFactor);
y = static_cast<int>(y*scalingFactor);
int slot = mPreview->getSlotSelected (x, y); int slot = mPreview->getSlotSelected (x, y);

@ -104,7 +104,6 @@ namespace MWGui
std::unique_ptr<MWRender::InventoryPreview> mPreview; std::unique_ptr<MWRender::InventoryPreview> mPreview;
bool mTrading; bool mTrading;
float mScaleFactor;
float mUpdateTimer; float mUpdateTimer;
void toggleMaximized(); void toggleMaximized();

@ -129,7 +129,7 @@ struct JournalViewModelImpl : JournalViewModel
utf8text.replace(pos_begin, pos_end+1-pos_begin, displayName); utf8text.replace(pos_begin, pos_end+1-pos_begin, displayName);
intptr_t value; intptr_t value = 0;
if (mModel->mKeywordSearch.containsKeyword(topicName, value)) if (mModel->mKeywordSearch.containsKeyword(topicName, value))
mHyperLinks[std::make_pair(pos_begin, pos_begin+displayName.size())] = value; mHyperLinks[std::make_pair(pos_begin, pos_begin+displayName.size())] = value;
} }

@ -7,11 +7,6 @@
#include <MyGUI_RenderManager.h> #include <MyGUI_RenderManager.h>
namespace MWGui
{
class WindowManager;
}
namespace MWRender namespace MWRender
{ {
class RaceSelectionPreview; class RaceSelectionPreview;

@ -11,11 +11,6 @@ namespace ESM
struct Spell; struct Spell;
} }
namespace MWGui
{
class WindowManager;
}
namespace MWGui namespace MWGui
{ {
class ReviewDialog : public WindowModal class ReviewDialog : public WindowModal

@ -11,12 +11,16 @@
#include <iomanip> #include <iomanip>
#include <numeric> #include <numeric>
#include <array>
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include <components/misc/stringops.hpp> #include <components/misc/stringops.hpp>
#include <components/misc/constants.hpp> #include <components/misc/constants.hpp>
#include <components/widgets/sharedstatebutton.hpp> #include <components/widgets/sharedstatebutton.hpp>
#include <components/settings/settings.hpp> #include <components/settings/settings.hpp>
#include <components/resource/resourcesystem.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/sceneutil/lightmanager.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
@ -69,6 +73,9 @@ namespace
std::string getAspect (int x, int y) std::string getAspect (int x, int y)
{ {
int gcd = std::gcd (x, y); int gcd = std::gcd (x, y);
if (gcd == 0)
return std::string();
int xaspect = x / gcd; int xaspect = x / gcd;
int yaspect = y / gcd; int yaspect = y / gcd;
// special case: 8 : 5 is usually referred to as 16:10 // special case: 8 : 5 is usually referred to as 16:10
@ -111,11 +118,24 @@ namespace
if (!widget->getUserString(settingMax).empty()) if (!widget->getUserString(settingMax).empty())
max = MyGUI::utility::parseFloat(widget->getUserString(settingMax)); max = MyGUI::utility::parseFloat(widget->getUserString(settingMax));
} }
void updateMaxLightsComboBox(MyGUI::ComboBox* box)
{
constexpr int min = 8;
constexpr int max = 32;
constexpr int increment = 8;
int maxLights = Settings::Manager::getInt("max lights", "Shaders");
// show increments of 8 in dropdown
if (maxLights >= min && maxLights <= max && !(maxLights % increment))
box->setIndexSelected((maxLights / increment)-1);
else
box->setIndexSelected(MyGUI::ITEM_NONE);
}
} }
namespace MWGui namespace MWGui
{ {
void SettingsWindow::configureWidgets(MyGUI::Widget* widget) void SettingsWindow::configureWidgets(MyGUI::Widget* widget, bool init)
{ {
MyGUI::EnumeratorWidgetPtr widgets = widget->getEnumerator(); MyGUI::EnumeratorWidgetPtr widgets = widget->getEnumerator();
while (widgets.next()) while (widgets.next())
@ -129,7 +149,8 @@ namespace MWGui
getSettingCategory(current)) getSettingCategory(current))
? "#{sOn}" : "#{sOff}"; ? "#{sOn}" : "#{sOff}";
current->castType<MyGUI::Button>()->setCaptionWithReplacing(initialValue); current->castType<MyGUI::Button>()->setCaptionWithReplacing(initialValue);
current->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); if (init)
current->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
} }
if (type == sliderType) if (type == sliderType)
{ {
@ -149,6 +170,12 @@ namespace MWGui
ss << std::fixed << std::setprecision(2) << value/Constants::CellSizeInUnits; ss << std::fixed << std::setprecision(2) << value/Constants::CellSizeInUnits;
valueStr = ss.str(); valueStr = ss.str();
} }
else if (valueType == "Float")
{
std::stringstream ss;
ss << std::fixed << std::setprecision(2) << value;
valueStr = ss.str();
}
else else
valueStr = MyGUI::utility::toString(int(value)); valueStr = MyGUI::utility::toString(int(value));
@ -163,12 +190,13 @@ namespace MWGui
valueStr = MyGUI::utility::toString(value); valueStr = MyGUI::utility::toString(value);
scroll->setScrollPosition(value); scroll->setScrollPosition(value);
} }
scroll->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); if (init)
scroll->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition);
if (scroll->getVisible()) if (scroll->getVisible())
updateSliderLabel(scroll, valueStr); updateSliderLabel(scroll, valueStr);
} }
configureWidgets(current); configureWidgets(current, init);
} }
} }
@ -199,7 +227,7 @@ namespace MWGui
getWidget(unusedSlider, widgetName); getWidget(unusedSlider, widgetName);
unusedSlider->setVisible(false); unusedSlider->setVisible(false);
configureWidgets(mMainWidget); configureWidgets(mMainWidget, true);
setTitle("#{sOptions}"); setTitle("#{sOptions}");
@ -216,6 +244,9 @@ namespace MWGui
getWidget(mControllerSwitch, "ControllerButton"); getWidget(mControllerSwitch, "ControllerButton");
getWidget(mWaterTextureSize, "WaterTextureSize"); getWidget(mWaterTextureSize, "WaterTextureSize");
getWidget(mWaterReflectionDetail, "WaterReflectionDetail"); getWidget(mWaterReflectionDetail, "WaterReflectionDetail");
getWidget(mLightingMethodButton, "LightingMethodButton");
getWidget(mLightsResetButton, "LightsResetButton");
getWidget(mMaxLights, "MaxLights");
if (MWBase::Environment::get().getVrMode()) if (MWBase::Environment::get().getVrMode())
{ {
@ -247,6 +278,10 @@ namespace MWGui
mWaterTextureSize->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterTextureSizeChanged); mWaterTextureSize->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterTextureSizeChanged);
mWaterReflectionDetail->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterReflectionDetailChanged); mWaterReflectionDetail->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterReflectionDetailChanged);
mLightingMethodButton->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onLightingMethodButtonChanged);
mLightsResetButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onLightsResetButtonClicked);
mMaxLights->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onMaxLightsChanged);
mKeyboardSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onKeyboardSwitchClicked); mKeyboardSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onKeyboardSwitchClicked);
mControllerSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onControllerSwitchClicked); mControllerSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onControllerSwitchClicked);
@ -273,8 +308,10 @@ namespace MWGui
std::sort(resolutions.begin(), resolutions.end(), sortResolutions); std::sort(resolutions.begin(), resolutions.end(), sortResolutions);
for (std::pair<int, int>& resolution : resolutions) for (std::pair<int, int>& resolution : resolutions)
{ {
std::string str = MyGUI::utility::toString(resolution.first) + " x " + MyGUI::utility::toString(resolution.second) std::string str = MyGUI::utility::toString(resolution.first) + " x " + MyGUI::utility::toString(resolution.second);
+ " (" + getAspect(resolution.first, resolution.second) + ")"; std::string aspect = getAspect(resolution.first, resolution.second);
if (!aspect.empty())
str = str + " (" + aspect + ")";
if (mResolutionList->findItemIndexWith(str) == MyGUI::ITEM_NONE) if (mResolutionList->findItemIndexWith(str) == MyGUI::ITEM_NONE)
mResolutionList->addItem(str); mResolutionList->addItem(str);
@ -309,6 +346,8 @@ namespace MWGui
waterReflectionDetail = std::min(5, std::max(0, waterReflectionDetail)); waterReflectionDetail = std::min(5, std::max(0, waterReflectionDetail));
mWaterReflectionDetail->setIndexSelected(waterReflectionDetail); mWaterReflectionDetail->setIndexSelected(waterReflectionDetail);
updateMaxLightsComboBox(mMaxLights);
mWindowBorderButton->setEnabled(!Settings::Manager::getBool("fullscreen", "Video")); mWindowBorderButton->setEnabled(!Settings::Manager::getBool("fullscreen", "Video"));
mKeyboardSwitch->setStateSelected(true); mKeyboardSwitch->setStateSelected(true);
@ -409,6 +448,54 @@ namespace MWGui
apply(); apply();
} }
void SettingsWindow::onLightingMethodButtonChanged(MyGUI::ComboBox* _sender, size_t pos)
{
if (pos == MyGUI::ITEM_NONE)
return;
std::string message = "This change requires a restart to take effect.";
MWBase::Environment::get().getWindowManager()->interactiveMessageBox(message, {"#{sOK}"}, true);
Settings::Manager::setString("lighting method", "Shaders", _sender->getItemNameAt(pos));
apply();
}
void SettingsWindow::onMaxLightsChanged(MyGUI::ComboBox* _sender, size_t pos)
{
int count = 8 * (pos + 1);
Settings::Manager::setInt("max lights", "Shaders", count);
apply();
configureWidgets(mMainWidget, false);
}
void SettingsWindow::onLightsResetButtonClicked(MyGUI::Widget* _sender)
{
std::vector<std::string> buttons = {"#{sYes}", "#{sNo}"};
std::string message = "Resets to default values, would you like to continue? Changes to lighting method will require a restart.";
MWBase::Environment::get().getWindowManager()->interactiveMessageBox(message, buttons, true);
int selectedButton = MWBase::Environment::get().getWindowManager()->readPressedButton();
if (selectedButton == 1 || selectedButton == -1)
return;
constexpr std::array<const char*, 6> settings = {
"light bounds multiplier",
"maximum light distance",
"light fade start",
"minimum interior brightness",
"max lights",
"lighting method",
};
for (const auto& setting : settings)
Settings::Manager::setString(setting, "Shaders", Settings::Manager::mDefaultSettings[{"Shaders", setting}]);
mLightingMethodButton->setIndexSelected(mLightingMethodButton->findItemIndexWith(Settings::Manager::mDefaultSettings[{"Shaders", "lighting method"}]));
updateMaxLightsComboBox(mMaxLights);
apply();
configureWidgets(mMainWidget, false);
}
void SettingsWindow::onButtonToggled(MyGUI::Widget* _sender) void SettingsWindow::onButtonToggled(MyGUI::Widget* _sender)
{ {
std::string on = MWBase::Environment::get().getWindowManager()->getGameSettingString("sOn", "On"); std::string on = MWBase::Environment::get().getWindowManager()->getGameSettingString("sOn", "On");
@ -511,6 +598,12 @@ namespace MWGui
ss << std::fixed << std::setprecision(2) << value/Constants::CellSizeInUnits; ss << std::fixed << std::setprecision(2) << value/Constants::CellSizeInUnits;
valueStr = ss.str(); valueStr = ss.str();
} }
else if (valueType == "Float")
{
std::stringstream ss;
ss << std::fixed << std::setprecision(2) << value;
valueStr = ss.str();
}
else else
valueStr = MyGUI::utility::toString(int(value)); valueStr = MyGUI::utility::toString(int(value));
} }
@ -609,6 +702,30 @@ namespace MWGui
layoutControlsBox(); layoutControlsBox();
} }
void SettingsWindow::updateLightSettings()
{
auto lightingMethod = MWBase::Environment::get().getResourceSystem()->getSceneManager()->getLightingMethod();
std::string lightingMethodStr = SceneUtil::LightManager::getLightingMethodString(lightingMethod);
mLightingMethodButton->removeAllItems();
std::array<SceneUtil::LightingMethod, 3> methods = {
SceneUtil::LightingMethod::FFP,
SceneUtil::LightingMethod::PerObjectUniform,
SceneUtil::LightingMethod::SingleUBO,
};
for (const auto& method : methods)
{
if (!MWBase::Environment::get().getResourceSystem()->getSceneManager()->isSupportedLightingMethod(method))
continue;
mLightingMethodButton->addItem(SceneUtil::LightManager::getLightingMethodString(method));
}
mLightingMethodButton->setIndexSelected(mLightingMethodButton->findItemIndexWith(lightingMethodStr));
}
void SettingsWindow::layoutControlsBox() void SettingsWindow::layoutControlsBox()
{ {
const int h = 18; const int h = 18;
@ -671,6 +788,7 @@ namespace MWGui
{ {
highlightCurrentResolution(); highlightCurrentResolution();
updateControlsBox(); updateControlsBox();
updateLightSettings();
resetScrollbars(); resetScrollbars();
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mOkButton); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mOkButton);
} }

@ -3,11 +3,6 @@
#include "windowbase.hpp" #include "windowbase.hpp"
namespace MWGui
{
class WindowManager;
}
namespace MWGui namespace MWGui
{ {
class SettingsWindow : public WindowBase class SettingsWindow : public WindowBase
@ -19,6 +14,8 @@ namespace MWGui
void updateControlsBox(); void updateControlsBox();
void updateLightSettings();
void onResChange(int, int) override { center(); } void onResChange(int, int) override { center(); }
protected: protected:
@ -39,6 +36,10 @@ namespace MWGui
MyGUI::ComboBox* mWaterTextureSize; MyGUI::ComboBox* mWaterTextureSize;
MyGUI::ComboBox* mWaterReflectionDetail; MyGUI::ComboBox* mWaterReflectionDetail;
MyGUI::ComboBox* mMaxLights;
MyGUI::ComboBox* mLightingMethodButton;
MyGUI::Button* mLightsResetButton;
// controls // controls
MyGUI::ScrollView* mControlsBox; MyGUI::ScrollView* mControlsBox;
MyGUI::Button* mResetControlsButton; MyGUI::Button* mResetControlsButton;
@ -62,6 +63,10 @@ namespace MWGui
void onWaterTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos); void onWaterTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos);
void onWaterReflectionDetailChanged(MyGUI::ComboBox* _sender, size_t pos); void onWaterReflectionDetailChanged(MyGUI::ComboBox* _sender, size_t pos);
void onLightingMethodButtonChanged(MyGUI::ComboBox* _sender, size_t pos);
void onLightsResetButtonClicked(MyGUI::Widget* _sender);
void onMaxLightsChanged(MyGUI::ComboBox* _sender, size_t pos);
void onRebindAction(MyGUI::Widget* _sender); void onRebindAction(MyGUI::Widget* _sender);
void onInputTabMouseWheel(MyGUI::Widget* _sender, int _rel); void onInputTabMouseWheel(MyGUI::Widget* _sender, int _rel);
void onResetDefaultBindings(MyGUI::Widget* _sender); void onResetDefaultBindings(MyGUI::Widget* _sender);
@ -73,7 +78,7 @@ namespace MWGui
void apply(); void apply();
void configureWidgets(MyGUI::Widget* widget); void configureWidgets(MyGUI::Widget* widget, bool init);
void updateSliderLabel(MyGUI::ScrollBar* scroller, const std::string& value); void updateSliderLabel(MyGUI::ScrollBar* scroller, const std::string& value);
void layoutControlsBox(); void layoutControlsBox();

@ -15,11 +15,6 @@ namespace MyGUI
class Widget; class Widget;
} }
namespace MWGui
{
class WindowManager;
}
namespace MWGui namespace MWGui
{ {
class SpellBuyingWindow : public ReferenceInterface, public WindowBase class SpellBuyingWindow : public ReferenceInterface, public WindowBase

@ -393,7 +393,8 @@ namespace MWGui
MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::Ptr player = MWMechanics::getPlayer();
int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId);
if (MyGUI::utility::parseInt(mPriceLabel->getCaption()) > playerGold) int price = MyGUI::utility::parseInt(mPriceLabel->getCaption());
if (price > playerGold)
{ {
MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage18}"); MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage18}");
return; return;
@ -401,8 +402,6 @@ namespace MWGui
mSpell.mName = mNameEdit->getCaption(); mSpell.mName = mNameEdit->getCaption();
int price = MyGUI::utility::parseInt(mPriceLabel->getCaption());
player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player);
// add gold to NPC trading gold pool // add gold to NPC trading gold pool

@ -97,7 +97,7 @@ namespace MWGui
int windowHeight = window->getSize().height; int windowHeight = window->getSize().height;
//initial values defined in openmw_stats_window.layout, if custom options are not present in .layout, a default is loaded //initial values defined in openmw_stats_window.layout, if custom options are not present in .layout, a default is loaded
float leftPaneRatio = 0.44; float leftPaneRatio = 0.44f;
if (mLeftPane->isUserString("LeftPaneRatio")) if (mLeftPane->isUserString("LeftPaneRatio"))
leftPaneRatio = MyGUI::utility::parseFloat(mLeftPane->getUserString("LeftPaneRatio")); leftPaneRatio = MyGUI::utility::parseFloat(mLeftPane->getUserString("LeftPaneRatio"));

@ -6,8 +6,6 @@
namespace MWGui namespace MWGui
{ {
class WindowManager;
class StatsWindow : public WindowPinnableBase, public NoDrop, public StatsListener class StatsWindow : public WindowPinnableBase, public NoDrop, public StatsListener
{ {
public: public:

@ -3,11 +3,6 @@
#include "windowbase.hpp" #include "windowbase.hpp"
namespace MWGui
{
class WindowManager;
}
namespace MWGui namespace MWGui
{ {
class TextInputDialog : public WindowModal class TextInputDialog : public WindowModal

@ -58,12 +58,12 @@ namespace MWGui
} }
} }
int TimeAdvancer::getHours() int TimeAdvancer::getHours() const
{ {
return mHours; return mHours;
} }
bool TimeAdvancer::isRunning() bool TimeAdvancer::isRunning() const
{ {
return mRunning; return mRunning;
} }

@ -14,8 +14,8 @@ namespace MWGui
void stop(); void stop();
void onFrame(float dt); void onFrame(float dt);
int getHours(); int getHours() const;
bool isRunning(); bool isRunning() const;
// signals // signals
typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void;

@ -11,12 +11,6 @@ namespace MyGUI
class Widget; class Widget;
} }
namespace MWGui
{
class WindowManager;
}
namespace MWGui namespace MWGui
{ {
class TravelWindow : public ReferenceInterface, public WindowBase class TravelWindow : public ReferenceInterface, public WindowBase

@ -3,11 +3,6 @@
#include "layout.hpp" #include "layout.hpp"
namespace MWBase
{
class WindowManager;
}
namespace MWWorld namespace MWWorld
{ {
class Ptr; class Ptr;
@ -15,7 +10,6 @@ namespace MWWorld
namespace MWGui namespace MWGui
{ {
class WindowManager;
class DragAndDrop; class DragAndDrop;
class WindowBase: public Layout class WindowBase: public Layout

@ -1,5 +1,6 @@
#include "windowmanagerimp.hpp" #include "windowmanagerimp.hpp"
#include <algorithm>
#include <cassert> #include <cassert>
#include <chrono> #include <chrono>
#include <thread> #include <thread>
@ -199,8 +200,8 @@ namespace MWGui
, mVersionDescription(versionDescription) , mVersionDescription(versionDescription)
, mWindowVisible(true) , mWindowVisible(true)
{ {
float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); mScalingFactor = std::clamp(Settings::Manager::getFloat("scaling factor", "GUI"), 0.5f, 8.f);
mGuiPlatform = new osgMyGUI::Platform(viewer, guiRoot, resourceSystem->getImageManager(), uiScale); mGuiPlatform = new osgMyGUI::Platform(viewer, guiRoot, resourceSystem->getImageManager(), mScalingFactor);
mGuiPlatform->initialise(resourcePath, (boost::filesystem::path(logpath) / "MyGUI.log").generic_string()); mGuiPlatform->initialise(resourcePath, (boost::filesystem::path(logpath) / "MyGUI.log").generic_string());
@ -216,7 +217,7 @@ namespace MWGui
MyGUI::LanguageManager::getInstance().eventRequestTag = MyGUI::newDelegate(this, &WindowManager::onRetrieveTag); MyGUI::LanguageManager::getInstance().eventRequestTag = MyGUI::newDelegate(this, &WindowManager::onRetrieveTag);
// Load fonts // Load fonts
mFontLoader.reset(new Gui::FontLoader(encoding, resourceSystem->getVFS(), userDataPath)); mFontLoader.reset(new Gui::FontLoader(encoding, resourceSystem->getVFS(), userDataPath, mScalingFactor));
mFontLoader->loadBitmapFonts(exportFonts); mFontLoader->loadBitmapFonts(exportFonts);
//Register own widgets with MyGUI //Register own widgets with MyGUI
@ -1372,6 +1373,11 @@ namespace MWGui
return mHud->getWorldMouseOver(); return mHud->getWorldMouseOver();
} }
float WindowManager::getScalingFactor()
{
return mScalingFactor;
}
void WindowManager::executeInConsole (const std::string& path) void WindowManager::executeInConsole (const std::string& path)
{ {
mConsole->executeFile (path); mConsole->executeFile (path);

@ -120,7 +120,6 @@ namespace MWGui
class TrainingWindow; class TrainingWindow;
class SpellIcons; class SpellIcons;
class MerchantRepair; class MerchantRepair;
class Repair;
class SoulgemDialog; class SoulgemDialog;
class Recharge; class Recharge;
class CompanionWindow; class CompanionWindow;
@ -216,6 +215,8 @@ namespace MWGui
void setDragDrop(bool dragDrop) override; void setDragDrop(bool dragDrop) override;
bool getWorldMouseOver() override; bool getWorldMouseOver() override;
float getScalingFactor() override;
bool toggleFogOfWar() override; bool toggleFogOfWar() override;
bool toggleFullHelp() override; ///< show extra info in item tooltips (owner, script) bool toggleFullHelp() override; ///< show extra info in item tooltips (owner, script)
bool getFullHelp() const override; bool getFullHelp() const override;
@ -532,6 +533,8 @@ namespace MWGui
SDLUtil::VideoWrapper* mVideoWrapper; SDLUtil::VideoWrapper* mVideoWrapper;
float mScalingFactor;
/** /**
* Called when MyGUI tries to retrieve a tag's value. Tags must be denoted in #{tag} notation and will be replaced upon setting a user visible text/property. * Called when MyGUI tries to retrieve a tag's value. Tags must be denoted in #{tag} notation and will be replaced upon setting a user visible text/property.
* Supported syntax: * Supported syntax:

@ -5,8 +5,6 @@
namespace MWGui namespace MWGui
{ {
class WindowManager;
class WindowPinnableBase: public WindowBase class WindowPinnableBase: public WindowBase
{ {
public: public:

@ -32,7 +32,6 @@ namespace MWInput
, mMouseManager(mouseManager) , mMouseManager(mouseManager)
, mJoystickEnabled (Settings::Manager::getBool("enable controller", "Input")) , mJoystickEnabled (Settings::Manager::getBool("enable controller", "Input"))
, mGamepadCursorSpeed(Settings::Manager::getFloat("gamepad cursor speed", "Input")) , mGamepadCursorSpeed(Settings::Manager::getFloat("gamepad cursor speed", "Input"))
, mInvUiScalingFactor(1.f)
, mSneakToggleShortcutTimer(0.f) , mSneakToggleShortcutTimer(0.f)
, mGamepadZoom(0) , mGamepadZoom(0)
, mGamepadGuiCursorEnabled(true) , mGamepadGuiCursorEnabled(true)
@ -69,10 +68,6 @@ namespace MWInput
} }
} }
float uiScale = Settings::Manager::getFloat("scaling factor", "GUI");
if (uiScale != 0.f)
mInvUiScalingFactor = 1.f / uiScale;
float deadZoneRadius = Settings::Manager::getFloat("joystick dead zone", "Input"); float deadZoneRadius = Settings::Manager::getFloat("joystick dead zone", "Input");
deadZoneRadius = std::min(std::max(deadZoneRadius, 0.0f), 0.5f); deadZoneRadius = std::min(std::max(deadZoneRadius, 0.0f), 0.5f);
mBindingsManager->setJoystickDeadZone(deadZoneRadius); mBindingsManager->setJoystickDeadZone(deadZoneRadius);
@ -102,8 +97,10 @@ namespace MWInput
// We keep track of our own mouse position, so that moving the mouse while in // We keep track of our own mouse position, so that moving the mouse while in
// game mode does not move the position of the GUI cursor // game mode does not move the position of the GUI cursor
float xMove = xAxis * dt * 1500.0f * mInvUiScalingFactor * mGamepadCursorSpeed; float uiScale = MWBase::Environment::get().getWindowManager()->getScalingFactor();
float yMove = yAxis * dt * 1500.0f * mInvUiScalingFactor * mGamepadCursorSpeed; float xMove = xAxis * dt * 1500.0f / uiScale;
float yMove = yAxis * dt * 1500.0f / uiScale;
float mouseWheelMove = -zAxis * dt * 1500.0f; float mouseWheelMove = -zAxis * dt * 1500.0f;
if (xMove != 0 || yMove != 0 || mouseWheelMove != 0) if (xMove != 0 || yMove != 0 || mouseWheelMove != 0)
{ {

@ -52,7 +52,6 @@ namespace MWInput
bool mJoystickEnabled; bool mJoystickEnabled;
float mGamepadCursorSpeed; float mGamepadCursorSpeed;
float mInvUiScalingFactor;
float mSneakToggleShortcutTimer; float mSneakToggleShortcutTimer;
float mGamepadZoom; float mGamepadZoom;
bool mGamepadGuiCursorEnabled; bool mGamepadGuiCursorEnabled;

@ -29,22 +29,18 @@ namespace MWInput
, mCameraYMultiplier(Settings::Manager::getFloat("camera y multiplier", "Input")) , mCameraYMultiplier(Settings::Manager::getFloat("camera y multiplier", "Input"))
, mBindingsManager(bindingsManager) , mBindingsManager(bindingsManager)
, mInputWrapper(inputWrapper) , mInputWrapper(inputWrapper)
, mInvUiScalingFactor(1.f)
, mGuiCursorX(0) , mGuiCursorX(0)
, mGuiCursorY(0) , mGuiCursorY(0)
, mMouseWheel(0) , mMouseWheel(0)
, mMouseLookEnabled(false) , mMouseLookEnabled(false)
, mGuiCursorEnabled(true) , mGuiCursorEnabled(true)
{ {
float uiScale = Settings::Manager::getFloat("scaling factor", "GUI");
if (uiScale != 0.f)
mInvUiScalingFactor = 1.f / uiScale;
int w,h; int w,h;
SDL_GetWindowSize(window, &w, &h); SDL_GetWindowSize(window, &w, &h);
mGuiCursorX = mInvUiScalingFactor * w / 2.f; float uiScale = MWBase::Environment::get().getWindowManager()->getScalingFactor();
mGuiCursorY = mInvUiScalingFactor * h / 2.f; mGuiCursorX = w / (2.f * uiScale);
mGuiCursorY = h / (2.f * uiScale);
} }
void MouseManager::processChangedSettings(const Settings::CategorySettingVector& changed) void MouseManager::processChangedSettings(const Settings::CategorySettingVector& changed)
@ -82,8 +78,9 @@ namespace MWInput
// We keep track of our own mouse position, so that moving the mouse while in // We keep track of our own mouse position, so that moving the mouse while in
// game mode does not move the position of the GUI cursor // game mode does not move the position of the GUI cursor
mGuiCursorX = static_cast<float>(arg.x) * mInvUiScalingFactor; float uiScale = MWBase::Environment::get().getWindowManager()->getScalingFactor();
mGuiCursorY = static_cast<float>(arg.y) * mInvUiScalingFactor; mGuiCursorX = static_cast<float>(arg.x) / uiScale;
mGuiCursorY = static_cast<float>(arg.y) / uiScale;
mMouseWheel = static_cast<int>(arg.z); mMouseWheel = static_cast<int>(arg.z);
@ -254,17 +251,14 @@ namespace MWInput
void MouseManager::warpMouse() void MouseManager::warpMouse()
{ {
mInputWrapper->warpMouse(static_cast<int>(mGuiCursorX / mInvUiScalingFactor), static_cast<int>(mGuiCursorY / mInvUiScalingFactor)); float uiScale = MWBase::Environment::get().getWindowManager()->getScalingFactor();
mInputWrapper->warpMouse(static_cast<int>(mGuiCursorX*uiScale), static_cast<int>(mGuiCursorY*uiScale));
} }
void MouseManager::setMousePosition(int x, int y) void MouseManager::setMousePosition(int x, int y)
{ {
mGuiCursorX = x * mInvUiScalingFactor; float uiScale = MWBase::Environment::get().getWindowManager()->getScalingFactor();
mGuiCursorY = y * mInvUiScalingFactor; mGuiCursorX = x / uiScale;
} mGuiCursorY = y / uiScale;
void MouseManager::setGUIScale(float scale)
{
mInvUiScalingFactor = 1.f / scale;
} }
} }

@ -40,7 +40,6 @@ namespace MWInput
// Used to override mouse position when using controllers not through SDL, such as OpenXR. // Used to override mouse position when using controllers not through SDL, such as OpenXR.
void setMousePosition(int x, int y); void setMousePosition(int x, int y);
void setGUIScale(float scale);
private: private:
bool mInvertX; bool mInvertX;
@ -51,7 +50,6 @@ namespace MWInput
BindingsManager* mBindingsManager; BindingsManager* mBindingsManager;
SDLUtil::InputWrapper* mInputWrapper; SDLUtil::InputWrapper* mInputWrapper;
float mInvUiScalingFactor;
float mGuiCursorX; float mGuiCursorX;
float mGuiCursorY; float mGuiCursorY;

@ -432,7 +432,8 @@ namespace MWMechanics
} }
void Actors::updateHeadTracking(const MWWorld::Ptr& actor, const MWWorld::Ptr& targetActor, void Actors::updateHeadTracking(const MWWorld::Ptr& actor, const MWWorld::Ptr& targetActor,
MWWorld::Ptr& headTrackTarget, float& sqrHeadTrackDistance) MWWorld::Ptr& headTrackTarget, float& sqrHeadTrackDistance,
bool inCombatOrPursue)
{ {
if (!actor.getRefData().getBaseNode()) if (!actor.getRefData().getBaseNode())
return; return;
@ -453,7 +454,7 @@ namespace MWMechanics
const osg::Vec3f actor2Pos(targetActor.getRefData().getPosition().asVec3()); const osg::Vec3f actor2Pos(targetActor.getRefData().getPosition().asVec3());
float sqrDist = (actor1Pos - actor2Pos).length2(); float sqrDist = (actor1Pos - actor2Pos).length2();
if (sqrDist > std::min(maxDistance * maxDistance, sqrHeadTrackDistance)) if (sqrDist > std::min(maxDistance * maxDistance, sqrHeadTrackDistance) && !inCombatOrPursue)
return; return;
// stop tracking when target is behind the actor // stop tracking when target is behind the actor
@ -461,7 +462,7 @@ namespace MWMechanics
osg::Vec3f targetDirection(actor2Pos - actor1Pos); osg::Vec3f targetDirection(actor2Pos - actor1Pos);
actorDirection.z() = 0; actorDirection.z() = 0;
targetDirection.z() = 0; targetDirection.z() = 0;
if (actorDirection * targetDirection > 0 if ((actorDirection * targetDirection > 0 || inCombatOrPursue)
&& MWBase::Environment::get().getWorld()->getLOS(actor, targetActor) // check LOS and awareness last as it's the most expensive function && MWBase::Environment::get().getWorld()->getLOS(actor, targetActor) // check LOS and awareness last as it's the most expensive function
&& MWBase::Environment::get().getMechanicsManager()->awarenessCheck(targetActor, actor)) && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(targetActor, actor))
{ {
@ -2012,28 +2013,25 @@ namespace MWMechanics
MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first); MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first);
bool firstPersonPlayer = isPlayer && world->isFirstPerson(); bool firstPersonPlayer = isPlayer && world->isFirstPerson();
bool inCombatOrPursue = stats.getAiSequence().isInCombat() || stats.getAiSequence().hasPackage(AiPackageTypeId::Pursue); bool inCombatOrPursue = stats.getAiSequence().isInCombat() || stats.getAiSequence().hasPackage(AiPackageTypeId::Pursue);
MWWorld::Ptr activePackageTarget;
// 1. Unconsious actor can not track target // 1. Unconsious actor can not track target
// 2. Actors in combat and pursue mode do not bother to headtrack // 2. Actors in combat and pursue mode do not bother to headtrack anyone except their target
// 3. Player character does not use headtracking in the 1st-person view // 3. Player character does not use headtracking in the 1st-person view
if (!stats.getKnockedDown() && !firstPersonPlayer && !inCombatOrPursue) if (!stats.getKnockedDown() && !firstPersonPlayer)
{ {
for(PtrActorMap::iterator it(mActors.begin()); it != mActors.end(); ++it) if (inCombatOrPursue)
activePackageTarget = stats.getAiSequence().getActivePackage().getTarget();
for (PtrActorMap::iterator it(mActors.begin()); it != mActors.end(); ++it)
{ {
if (it->first == iter->first) if (it->first == iter->first)
continue; continue;
updateHeadTracking(iter->first, it->first, headTrackTarget, sqrHeadTrackDistance);
}
}
if (!stats.getKnockedDown() && !isPlayer && inCombatOrPursue) if (inCombatOrPursue && it->first != activePackageTarget)
{ continue;
// Actors in combat and pursue mode always look at their target.
for (const auto& package : stats.getAiSequence()) updateHeadTracking(iter->first, it->first, headTrackTarget, sqrHeadTrackDistance, inCombatOrPursue);
{
headTrackTarget = package->getTarget();
if (!headTrackTarget.isEmpty())
break;
} }
} }

@ -131,7 +131,8 @@ namespace MWMechanics
void turnActorToFacePlayer(const MWWorld::Ptr& actor, Actor& actorState, const osg::Vec3f& dir); void turnActorToFacePlayer(const MWWorld::Ptr& actor, Actor& actorState, const osg::Vec3f& dir);
void updateHeadTracking(const MWWorld::Ptr& actor, const MWWorld::Ptr& targetActor, void updateHeadTracking(const MWWorld::Ptr& actor, const MWWorld::Ptr& targetActor,
MWWorld::Ptr& headTrackTarget, float& sqrHeadTrackDistance); MWWorld::Ptr& headTrackTarget, float& sqrHeadTrackDistance,
bool inCombatOrPursue);
void rest(double hours, bool sleep); void rest(double hours, bool sleep);
///< Update actors while the player is waiting or sleeping. ///< Update actors while the player is waiting or sleeping.

@ -23,4 +23,10 @@ namespace MWMechanics
MWBase::World* world = MWBase::Environment::get().getWorld(); MWBase::World* world = MWBase::Environment::get().getWorld();
return (actor.getClass().canSwim(actor) && world->isSwimming(actor)) || world->isFlying(actor); return (actor.getClass().canSwim(actor) && world->isSwimming(actor)) || world->isFlying(actor);
} }
bool hasWaterWalking(const MWWorld::Ptr& actor)
{
const MWMechanics::MagicEffects& effects = actor.getClass().getCreatureStats(actor).getMagicEffects();
return effects.get(ESM::MagicEffect::WaterWalking).getMagnitude() > 0;
}
} }

@ -31,6 +31,7 @@ namespace MWMechanics
MWWorld::Ptr getPlayer(); MWWorld::Ptr getPlayer();
bool isPlayerInCombat(); bool isPlayerInCombat();
bool canActorMoveByZAxis(const MWWorld::Ptr& actor); bool canActorMoveByZAxis(const MWWorld::Ptr& actor);
bool hasWaterWalking(const MWWorld::Ptr& actor);
template<class T> template<class T>
void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value)

@ -45,13 +45,13 @@ bool MWMechanics::AiAvoidDoor::execute (const MWWorld::Ptr& actor, CharacterCont
return true; //Door is no longer opening return true; //Door is no longer opening
ESM::Position tPos = mDoorPtr.getRefData().getPosition(); //Position of the door ESM::Position tPos = mDoorPtr.getRefData().getPosition(); //Position of the door
float x = pos.pos[0] - tPos.pos[0]; float x = pos.pos[1] - tPos.pos[1];
float y = pos.pos[1] - tPos.pos[1]; float y = pos.pos[0] - tPos.pos[0];
actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
// Turn away from the door and move when turn completed // Turn away from the door and move when turn completed
if (zTurn(actor, std::atan2(x,y) + getAdjustedAngle(), osg::DegreesToRadians(5.f))) if (zTurn(actor, std::atan2(y,x) + getAdjustedAngle(), osg::DegreesToRadians(5.f)))
actor.getClass().getMovementSettings(actor).mPosition[1] = 1; actor.getClass().getMovementSettings(actor).mPosition[1] = 1;
else else
actor.getClass().getMovementSettings(actor).mPosition[1] = 0; actor.getClass().getMovementSettings(actor).mPosition[1] = 0;

@ -23,7 +23,7 @@ bool MWMechanics::AiBreathe::execute (const MWWorld::Ptr& actor, CharacterContro
actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
actorClass.getMovementSettings(actor).mPosition[1] = 1; actorClass.getMovementSettings(actor).mPosition[1] = 1;
smoothTurn(actor, -osg::PI / 2, 0); smoothTurn(actor, static_cast<float>(-osg::PI_2), 0);
return false; return false;
} }

@ -228,7 +228,6 @@ namespace MWMechanics
const osg::Vec3f vActorPos(pos.asVec3()); const osg::Vec3f vActorPos(pos.asVec3());
const osg::Vec3f vTargetPos(target.getRefData().getPosition().asVec3()); const osg::Vec3f vTargetPos(target.getRefData().getPosition().asVec3());
osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target);
float distToTarget = MWBase::Environment::get().getWorld()->getHitDistance(actor, target); float distToTarget = MWBase::Environment::get().getWorld()->getHitDistance(actor, target);
storage.mReadyToAttack = (currentAction->isAttackingOrSpell() && distToTarget <= rangeAttack && storage.mLOS); storage.mReadyToAttack = (currentAction->isAttackingOrSpell() && distToTarget <= rangeAttack && storage.mLOS);
@ -236,13 +235,14 @@ namespace MWMechanics
if (isRangedCombat) if (isRangedCombat)
{ {
// rotate actor taking into account target movement direction and projectile speed // rotate actor taking into account target movement direction and projectile speed
vAimDir = AimDirToMovingTarget(actor, target, storage.mLastTargetPos, AI_REACTION_TIME, (weapon ? weapon->mData.mType : 0), storage.mStrength); osg::Vec3f vAimDir = AimDirToMovingTarget(actor, target, storage.mLastTargetPos, AI_REACTION_TIME, (weapon ? weapon->mData.mType : 0), storage.mStrength);
storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir); storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir);
storage.mMovement.mRotation[2] = getZAngleToDir(vAimDir); storage.mMovement.mRotation[2] = getZAngleToDir(vAimDir);
} }
else else
{ {
osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target, false);
storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir); storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir);
storage.mMovement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated storage.mMovement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated
} }
@ -263,8 +263,9 @@ namespace MWMechanics
&& ((!storage.mReadyToAttack && !mPathFinder.isPathConstructed()) && ((!storage.mReadyToAttack && !mPathFinder.isPathConstructed())
|| (storage.mUseCustomDestination && (storage.mCustomDestination - vTargetPos).length() > rangeAttack))) || (storage.mUseCustomDestination && (storage.mCustomDestination - vTargetPos).length() > rangeAttack)))
{ {
const MWBase::World* world = MWBase::Environment::get().getWorld();
// Try to build path to the target. // Try to build path to the target.
const auto halfExtents = MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(actor); const auto halfExtents = world->getPathfindingHalfExtents(actor);
const auto navigatorFlags = getNavigatorFlags(actor); const auto navigatorFlags = getNavigatorFlags(actor);
const auto areaCosts = getAreaCosts(actor); const auto areaCosts = getAreaCosts(actor);
const auto pathGridGraph = getPathGridGraph(actor.getCell()); const auto pathGridGraph = getPathGridGraph(actor.getCell());
@ -274,11 +275,7 @@ namespace MWMechanics
{ {
// If there is no path, try to find a point on a line from the actor position to target projected // If there is no path, try to find a point on a line from the actor position to target projected
// on navmesh to attack the target from there. // on navmesh to attack the target from there.
const MWBase::World* world = MWBase::Environment::get().getWorld();
const auto halfExtents = world->getPathfindingHalfExtents(actor);
const auto navigator = world->getNavigator(); const auto navigator = world->getNavigator();
const auto navigatorFlags = getNavigatorFlags(actor);
const auto areaCosts = getAreaCosts(actor);
const auto hit = navigator->raycast(halfExtents, vActorPos, vTargetPos, navigatorFlags); const auto hit = navigator->raycast(halfExtents, vActorPos, vTargetPos, navigatorFlags);
if (hit.has_value() && (*hit - vTargetPos).length() <= rangeAttack) if (hit.has_value() && (*hit - vTargetPos).length() <= rangeAttack)
@ -534,7 +531,7 @@ namespace MWMechanics
// Otherwise apply a random side step (kind of dodging) with some probability // Otherwise apply a random side step (kind of dodging) with some probability
// if actor is within range of target's weapon. // if actor is within range of target's weapon.
if (std::abs(angleToTarget) > osg::PI / 4) if (std::abs(angleToTarget) > osg::PI / 4)
moveDuration = 0.2; moveDuration = 0.2f;
else if (distToTarget <= rangeAttackOfTarget && Misc::Rng::rollClosedProbability() < 0.25) else if (distToTarget <= rangeAttackOfTarget && Misc::Rng::rollClosedProbability() < 0.25)
moveDuration = 0.1f + 0.1f * Misc::Rng::rollClosedProbability(); moveDuration = 0.1f + 0.1f * Misc::Rng::rollClosedProbability();
if (moveDuration > 0) if (moveDuration > 0)
@ -698,7 +695,7 @@ osg::Vec3f AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& t
// idea: perpendicular to dir to target speed components of target move vector and projectile vector should be the same // idea: perpendicular to dir to target speed components of target move vector and projectile vector should be the same
osg::Vec3f vTargetPos = target.getRefData().getPosition().asVec3(); osg::Vec3f vTargetPos = target.getRefData().getPosition().asVec3();
osg::Vec3f vDirToTarget = MWBase::Environment::get().getWorld()->aimToTarget(actor, target); osg::Vec3f vDirToTarget = MWBase::Environment::get().getWorld()->aimToTarget(actor, target, true);
float distToTarget = vDirToTarget.length(); float distToTarget = vDirToTarget.length();
osg::Vec3f vTargetMoveDir = vTargetPos - vLastTargetPos; osg::Vec3f vTargetMoveDir = vTargetPos - vLastTargetPos;

@ -25,6 +25,14 @@
#include <osg/Quat> #include <osg/Quat>
namespace
{
float divOrMax(float dividend, float divisor)
{
return divisor == 0 ? std::numeric_limits<float>::max() * std::numeric_limits<float>::epsilon() : dividend / divisor;
}
}
MWMechanics::AiPackage::AiPackage(AiPackageTypeId typeId, const Options& options) : MWMechanics::AiPackage::AiPackage(AiPackageTypeId typeId, const Options& options) :
mTypeId(typeId), mTypeId(typeId),
mOptions(options), mOptions(options),
@ -411,13 +419,20 @@ bool MWMechanics::AiPackage::isReachableRotatingOnTheRun(const MWWorld::Ptr& act
DetourNavigator::Flags MWMechanics::AiPackage::getNavigatorFlags(const MWWorld::Ptr& actor) const DetourNavigator::Flags MWMechanics::AiPackage::getNavigatorFlags(const MWWorld::Ptr& actor) const
{ {
static const bool allowToFollowOverWaterSurface = Settings::Manager::getBool("allow actors to follow over water surface", "Game");
const MWWorld::Class& actorClass = actor.getClass(); const MWWorld::Class& actorClass = actor.getClass();
DetourNavigator::Flags result = DetourNavigator::Flag_none; DetourNavigator::Flags result = DetourNavigator::Flag_none;
if (actorClass.isPureWaterCreature(actor) || (getTypeId() != AiPackageTypeId::Wander && actorClass.canSwim(actor))) if ((actorClass.isPureWaterCreature(actor)
|| (getTypeId() != AiPackageTypeId::Wander
&& ((allowToFollowOverWaterSurface && getTypeId() == AiPackageTypeId::Follow)
|| actorClass.canSwim(actor)
|| hasWaterWalking(actor)))
) && actorClass.getSwimSpeed(actor) > 0)
result |= DetourNavigator::Flag_swim; result |= DetourNavigator::Flag_swim;
if (actorClass.canWalk(actor)) if (actorClass.canWalk(actor) && actor.getClass().getWalkSpeed(actor) > 0)
result |= DetourNavigator::Flag_walk; result |= DetourNavigator::Flag_walk;
if (actorClass.isBipedal(actor) && getTypeId() != AiPackageTypeId::Wander) if (actorClass.isBipedal(actor) && getTypeId() != AiPackageTypeId::Wander)
@ -433,15 +448,15 @@ DetourNavigator::AreaCosts MWMechanics::AiPackage::getAreaCosts(const MWWorld::P
const MWWorld::Class& actorClass = actor.getClass(); const MWWorld::Class& actorClass = actor.getClass();
if (flags & DetourNavigator::Flag_swim) if (flags & DetourNavigator::Flag_swim)
costs.mWater = costs.mWater / actorClass.getSwimSpeed(actor); costs.mWater = divOrMax(costs.mWater, actorClass.getSwimSpeed(actor));
if (flags & DetourNavigator::Flag_walk) if (flags & DetourNavigator::Flag_walk)
{ {
float walkCost; float walkCost;
if (getTypeId() == AiPackageTypeId::Wander) if (getTypeId() == AiPackageTypeId::Wander)
walkCost = 1.0 / actorClass.getWalkSpeed(actor); walkCost = divOrMax(1.0, actorClass.getWalkSpeed(actor));
else else
walkCost = 1.0 / actorClass.getRunSpeed(actor); walkCost = divOrMax(1.0, actorClass.getRunSpeed(actor));
costs.mDoor = costs.mDoor * walkCost; costs.mDoor = costs.mDoor * walkCost;
costs.mPathgrid = costs.mPathgrid * walkCost; costs.mPathgrid = costs.mPathgrid * walkCost;
costs.mGround = costs.mGround * walkCost; costs.mGround = costs.mGround * walkCost;

@ -217,38 +217,48 @@ void CharacterController::refreshHitRecoilAnims(CharacterState& idle)
if(mHitState == CharState_None) if(mHitState == CharState_None)
{ {
if ((mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() < 0 if ((mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() < 0
|| mPtr.getClass().getCreatureStats(mPtr).getFatigue().getBase() == 0) || mPtr.getClass().getCreatureStats(mPtr).getFatigue().getBase() == 0))
&& mAnimation->hasAnimation("knockout"))
{ {
mTimeUntilWake = Misc::Rng::rollClosedProbability() * 2 + 1; // Wake up after 1 to 3 seconds mTimeUntilWake = Misc::Rng::rollClosedProbability() * 2 + 1; // Wake up after 1 to 3 seconds
if (isSwimming && mAnimation->hasAnimation("swimknockout")) if (isSwimming && mAnimation->hasAnimation("swimknockout"))
{ {
mHitState = CharState_SwimKnockOut; mHitState = CharState_SwimKnockOut;
mCurrentHit = "swimknockout"; mCurrentHit = "swimknockout";
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul);
} }
else else if (!isSwimming && mAnimation->hasAnimation("knockout"))
{ {
mHitState = CharState_KnockOut; mHitState = CharState_KnockOut;
mCurrentHit = "knockout"; mCurrentHit = "knockout";
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul);
}
else
{
// Knockout animations are missing. Fall back to idle animation, so target actor still can be killed via HtH.
mCurrentHit.erase();
} }
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul);
mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(true); mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(true);
} }
else if(knockdown && mAnimation->hasAnimation("knockdown")) else if (knockdown)
{ {
if (isSwimming && mAnimation->hasAnimation("swimknockdown")) if (isSwimming && mAnimation->hasAnimation("swimknockdown"))
{ {
mHitState = CharState_SwimKnockDown; mHitState = CharState_SwimKnockDown;
mCurrentHit = "swimknockdown"; mCurrentHit = "swimknockdown";
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0);
} }
else else if (!isSwimming && mAnimation->hasAnimation("knockdown"))
{ {
mHitState = CharState_KnockDown; mHitState = CharState_KnockDown;
mCurrentHit = "knockdown"; mCurrentHit = "knockdown";
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0);
}
else
{
// Knockdown animation is missing. Cancel knockdown state.
mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(false);
} }
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0);
} }
else if (recovery) else if (recovery)
{ {
@ -2968,7 +2978,7 @@ void CharacterController::updateHeadTracking(float duration)
} }
else else
// no head node to look at, fall back to look at center of collision box // no head node to look at, fall back to look at center of collision box
direction = MWBase::Environment::get().getWorld()->aimToTarget(mPtr, mHeadTrackTarget); direction = MWBase::Environment::get().getWorld()->aimToTarget(mPtr, mHeadTrackTarget, false);
} }
direction.normalize(); direction.normalize();

@ -681,7 +681,7 @@ namespace MWMechanics
// Deviating from Morrowind here: it doesn't increase disposition on marginal wins, // Deviating from Morrowind here: it doesn't increase disposition on marginal wins,
// which seems to be a bug (MCP fixes it too). // which seems to be a bug (MCP fixes it too).
// Original logic: x = 0, y = -iPerMinChange // Original logic: x = 0, y = -iPerMinChange
x = -iPerMinChange; x = iPerMinChange;
y = x; // This goes unused. y = x; // This goes unused.
} }
else else

@ -153,23 +153,23 @@ namespace MWMechanics
void SpellList::addListener(Spells* spells) void SpellList::addListener(Spells* spells)
{ {
for(const auto ptr : mListeners) if (std::find(mListeners.begin(), mListeners.end(), spells) != mListeners.end())
{ return;
if(ptr == spells)
return;
}
mListeners.push_back(spells); mListeners.push_back(spells);
} }
void SpellList::removeListener(Spells* spells) void SpellList::removeListener(Spells* spells)
{ {
for(auto it = mListeners.begin(); it != mListeners.end(); it++) const auto it = std::find(mListeners.begin(), mListeners.end(), spells);
{ if (it != mListeners.end())
if(*it == spells) mListeners.erase(it);
{ }
mListeners.erase(it);
break; void SpellList::updateListener(Spells* before, Spells* after)
} {
} const auto it = std::find(mListeners.begin(), mListeners.end(), before);
if (it == mListeners.end())
return mListeners.push_back(after);
*it = after;
} }
} }

@ -61,6 +61,8 @@ namespace MWMechanics
void removeListener(Spells* spells); void removeListener(Spells* spells);
void updateListener(Spells* before, Spells* after);
const std::vector<std::string> getSpells() const; const std::vector<std::string> getSpells() const;
}; };
} }

@ -32,6 +32,15 @@ namespace MWMechanics
mSpellList->addListener(this); mSpellList->addListener(this);
} }
Spells::Spells(Spells&& spells) : mSpellList(std::move(spells.mSpellList)), mSpells(std::move(spells.mSpells)),
mSelectedSpell(std::move(spells.mSelectedSpell)), mUsedPowers(std::move(spells.mUsedPowers)),
mSpellsChanged(std::move(spells.mSpellsChanged)), mEffects(std::move(spells.mEffects)),
mSourcedEffects(std::move(spells.mSourcedEffects))
{
if (mSpellList)
mSpellList->updateListener(&spells, this);
}
std::map<const ESM::Spell*, SpellParams>::const_iterator Spells::begin() const std::map<const ESM::Spell*, SpellParams>::const_iterator Spells::begin() const
{ {
return mSpells.begin(); return mSpells.begin();

@ -59,7 +59,7 @@ namespace MWMechanics
Spells(const Spells&); Spells(const Spells&);
Spells(const Spells&&) = delete; Spells(Spells&& spells);
~Spells(); ~Spells();

@ -121,6 +121,7 @@ void Actor::updatePosition()
mPreviousPosition = mWorldPosition; mPreviousPosition = mWorldPosition;
mPosition = mWorldPosition; mPosition = mWorldPosition;
mSimulationPosition = mWorldPosition; mSimulationPosition = mWorldPosition;
mPositionOffset = osg::Vec3f();
mStandingOnPtr = nullptr; mStandingOnPtr = nullptr;
mSkipSimulation = true; mSkipSimulation = true;
} }
@ -179,9 +180,10 @@ bool Actor::setPosition(const osg::Vec3f& position)
if (mSkipSimulation) if (mSkipSimulation)
return false; return false;
bool hasChanged = mPosition != position || mPositionOffset.length() != 0 || mWorldPositionChanged; bool hasChanged = mPosition != position || mPositionOffset.length() != 0 || mWorldPositionChanged;
mPreviousPosition = mPosition + mPositionOffset; updateWorldPosition();
mPosition = position + mPositionOffset; applyOffsetChange();
mPositionOffset = osg::Vec3f(); mPreviousPosition = mPosition;
mPosition = position;
return hasChanged; return hasChanged;
} }
@ -195,9 +197,9 @@ void Actor::applyOffsetChange()
{ {
if (mPositionOffset.length() == 0) if (mPositionOffset.length() == 0)
return; return;
mWorldPosition += mPositionOffset;
mPosition += mPositionOffset; mPosition += mPositionOffset;
mPreviousPosition += mPositionOffset; mPreviousPosition += mPositionOffset;
mSimulationPosition += mPositionOffset;
mPositionOffset = osg::Vec3f(); mPositionOffset = osg::Vec3f();
mWorldPositionChanged = true; mWorldPositionChanged = true;
} }

@ -17,10 +17,10 @@ namespace MWPhysics
// Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared. // Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared.
static constexpr int sMaxIterations = 8; static constexpr int sMaxIterations = 8;
// Allows for more precise movement solving without getting stuck or snagging too easily. // Allows for more precise movement solving without getting stuck or snagging too easily.
static constexpr float sCollisionMargin = 0.1; static constexpr float sCollisionMargin = 0.1f;
// Allow for a small amount of penetration to prevent numerical precision issues from causing the "unstuck"ing code to run unnecessarily // Allow for a small amount of penetration to prevent numerical precision issues from causing the "unstuck"ing code to run unnecessarily
// Currently set to 0 because having the "unstuck"ing code run whenever possible prevents some glitchy snagging issues // Currently set to 0 because having the "unstuck"ing code run whenever possible prevents some glitchy snagging issues
static constexpr float sAllowedPenetration = 0.0; static constexpr float sAllowedPenetration = 0.0f;
} }
#endif #endif

@ -593,8 +593,7 @@ namespace MWPhysics
const auto verticalHalfExtent = osg::Vec3f(0.0, 0.0, physicActor->getHalfExtents().z()); const auto verticalHalfExtent = osg::Vec3f(0.0, 0.0, physicActor->getHalfExtents().z());
// use a 3d approximation of the movement vector to better judge player intent // use a 3d approximation of the movement vector to better judge player intent
const ESM::Position& refpos = ptr.getRefData().getPosition(); auto velocity = (osg::Quat(actor.mRefpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(actor.mRefpos.rot[2], osg::Vec3f(0, 0, -1))) * actor.mMovement;
auto velocity = (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * actor.mMovement;
// try to pop outside of the world before doing anything else if we're inside of it // try to pop outside of the world before doing anything else if we're inside of it
if (!physicActor->getOnGround() || physicActor->getOnSlope()) if (!physicActor->getOnGround() || physicActor->getOnSlope())
velocity += physicActor->getInertialForce(); velocity += physicActor->getInertialForce();

@ -9,6 +9,7 @@
#include "components/settings/settings.hpp" #include "components/settings/settings.hpp"
#include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/actorutil.hpp"
#include "../mwmechanics/movement.hpp" #include "../mwmechanics/movement.hpp"
#include "../mwrender/bulletdebugdraw.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/player.hpp" #include "../mwworld/player.hpp"
@ -137,11 +138,12 @@ namespace
namespace MWPhysics namespace MWPhysics
{ {
PhysicsTaskScheduler::PhysicsTaskScheduler(float physicsDt, std::shared_ptr<btCollisionWorld> collisionWorld) PhysicsTaskScheduler::PhysicsTaskScheduler(float physicsDt, btCollisionWorld *collisionWorld, MWRender::DebugDrawer* debugDrawer)
: mDefaultPhysicsDt(physicsDt) : mDefaultPhysicsDt(physicsDt)
, mPhysicsDt(physicsDt) , mPhysicsDt(physicsDt)
, mTimeAccum(0.f) , mTimeAccum(0.f)
, mCollisionWorld(std::move(collisionWorld)) , mCollisionWorld(collisionWorld)
, mDebugDrawer(debugDrawer)
, mNumJobs(0) , mNumJobs(0)
, mRemainingSteps(0) , mRemainingSteps(0)
, mLOSCacheExpiry(Settings::Manager::getInt("lineofsight keep inactive cache", "Physics")) , mLOSCacheExpiry(Settings::Manager::getInt("lineofsight keep inactive cache", "Physics"))
@ -185,7 +187,7 @@ namespace MWPhysics
if (data.mActor.lock()) if (data.mActor.lock())
{ {
std::unique_lock lock(mCollisionWorldMutex); std::unique_lock lock(mCollisionWorldMutex);
MovementSolver::unstuck(data, mCollisionWorld.get()); MovementSolver::unstuck(data, mCollisionWorld);
} }
}); });
@ -315,7 +317,7 @@ namespace MWPhysics
// init // init
for (auto& data : actorsData) for (auto& data : actorsData)
data.updatePosition(); data.updatePosition(mCollisionWorld);
mPrevStepCount = numSteps; mPrevStepCount = numSteps;
mRemainingSteps = numSteps; mRemainingSteps = numSteps;
mTimeAccum = timeAccum; mTimeAccum = timeAccum;
@ -381,7 +383,7 @@ namespace MWPhysics
void PhysicsTaskScheduler::contactTest(btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback) void PhysicsTaskScheduler::contactTest(btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback)
{ {
std::shared_lock lock(mCollisionWorldMutex); std::shared_lock lock(mCollisionWorldMutex);
ContactTestWrapper::contactTest(mCollisionWorld.get(), colObj, resultCallback); ContactTestWrapper::contactTest(mCollisionWorld, colObj, resultCallback);
} }
std::optional<btVector3> PhysicsTaskScheduler::getHitPoint(const btTransform& from, btCollisionObject* target) std::optional<btVector3> PhysicsTaskScheduler::getHitPoint(const btTransform& from, btCollisionObject* target)
@ -532,7 +534,7 @@ namespace MWPhysics
if(const auto actor = mActorsFrameData[job].mActor.lock()) if(const auto actor = mActorsFrameData[job].mActor.lock())
{ {
MaybeSharedLock lockColWorld(mCollisionWorldMutex, mThreadSafeBullet); MaybeSharedLock lockColWorld(mCollisionWorldMutex, mThreadSafeBullet);
MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld.get(), *mWorldFrameData); MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld, *mWorldFrameData);
} }
} }
@ -594,8 +596,8 @@ namespace MWPhysics
{ {
for (auto& actorData : mActorsFrameData) for (auto& actorData : mActorsFrameData)
{ {
MovementSolver::unstuck(actorData, mCollisionWorld.get()); MovementSolver::unstuck(actorData, mCollisionWorld);
MovementSolver::move(actorData, mPhysicsDt, mCollisionWorld.get(), *mWorldFrameData); MovementSolver::move(actorData, mPhysicsDt, mCollisionWorld, *mWorldFrameData);
} }
updateActorsPositions(); updateActorsPositions();
@ -626,4 +628,10 @@ namespace MWPhysics
mTimeBegin = mTimer->tick(); mTimeBegin = mTimer->tick();
mFrameNumber = frameNumber; mFrameNumber = frameNumber;
} }
void PhysicsTaskScheduler::debugDraw()
{
std::shared_lock lock(mCollisionWorldMutex);
mDebugDrawer->step();
}
} }

@ -20,12 +20,17 @@ namespace Misc
class Barrier; class Barrier;
} }
namespace MWRender
{
class DebugDrawer;
}
namespace MWPhysics namespace MWPhysics
{ {
class PhysicsTaskScheduler class PhysicsTaskScheduler
{ {
public: public:
PhysicsTaskScheduler(float physicsDt, std::shared_ptr<btCollisionWorld> collisionWorld); PhysicsTaskScheduler(float physicsDt, btCollisionWorld* collisionWorld, MWRender::DebugDrawer* debugDrawer);
~PhysicsTaskScheduler(); ~PhysicsTaskScheduler();
/// @brief move actors taking into account desired movements and collisions /// @brief move actors taking into account desired movements and collisions
@ -49,6 +54,7 @@ namespace MWPhysics
void removeCollisionObject(btCollisionObject* collisionObject); void removeCollisionObject(btCollisionObject* collisionObject);
void updateSingleAabb(std::weak_ptr<PtrHolder> ptr, bool immediate=false); void updateSingleAabb(std::weak_ptr<PtrHolder> ptr, bool immediate=false);
bool getLineOfSight(const std::weak_ptr<Actor>& actor1, const std::weak_ptr<Actor>& actor2); bool getLineOfSight(const std::weak_ptr<Actor>& actor1, const std::weak_ptr<Actor>& actor2);
void debugDraw();
private: private:
void syncComputation(); void syncComputation();
@ -67,7 +73,8 @@ namespace MWPhysics
float mDefaultPhysicsDt; float mDefaultPhysicsDt;
float mPhysicsDt; float mPhysicsDt;
float mTimeAccum; float mTimeAccum;
std::shared_ptr<btCollisionWorld> mCollisionWorld; btCollisionWorld* mCollisionWorld;
MWRender::DebugDrawer* mDebugDrawer;
std::vector<LOSRequest> mLOSCache; std::vector<LOSRequest> mLOSCache;
std::set<std::weak_ptr<PtrHolder>, std::owner_less<std::weak_ptr<PtrHolder>>> mUpdateAabb; std::set<std::weak_ptr<PtrHolder>, std::owner_less<std::weak_ptr<PtrHolder>>> mUpdateAabb;

@ -60,6 +60,22 @@
#include "movementsolver.hpp" #include "movementsolver.hpp"
#include "mtphysics.hpp" #include "mtphysics.hpp"
namespace
{
bool canMoveToWaterSurface(const MWPhysics::Actor* physicActor, const float waterlevel, btCollisionWorld* world)
{
if (!physicActor)
return false;
const float halfZ = physicActor->getHalfExtents().z();
const osg::Vec3f actorPosition = physicActor->getPosition();
const osg::Vec3f startingPosition(actorPosition.x(), actorPosition.y(), actorPosition.z() + halfZ);
const osg::Vec3f destinationPosition(actorPosition.x(), actorPosition.y(), waterlevel + halfZ);
MWPhysics::ActorTracer tracer;
tracer.doTrace(physicActor->getCollisionObject(), startingPosition, destinationPosition, world);
return (tracer.mFraction >= 1.0f);
}
}
namespace MWPhysics namespace MWPhysics
{ {
PhysicsSystem::PhysicsSystem(Resource::ResourceSystem* resourceSystem, osg::ref_ptr<osg::Group> parentNode) PhysicsSystem::PhysicsSystem(Resource::ResourceSystem* resourceSystem, osg::ref_ptr<osg::Group> parentNode)
@ -79,7 +95,7 @@ namespace MWPhysics
mDispatcher = std::make_unique<btCollisionDispatcher>(mCollisionConfiguration.get()); mDispatcher = std::make_unique<btCollisionDispatcher>(mCollisionConfiguration.get());
mBroadphase = std::make_unique<btDbvtBroadphase>(); mBroadphase = std::make_unique<btDbvtBroadphase>();
mCollisionWorld = std::make_shared<btCollisionWorld>(mDispatcher.get(), mBroadphase.get(), mCollisionConfiguration.get()); mCollisionWorld = std::make_unique<btCollisionWorld>(mDispatcher.get(), mBroadphase.get(), mCollisionConfiguration.get());
// Don't update AABBs of all objects every frame. Most objects in MW are static, so we don't need this. // Don't update AABBs of all objects every frame. Most objects in MW are static, so we don't need this.
// Should a "static" object ever be moved, we have to update its AABB manually using DynamicsWorld::updateSingleAabb. // Should a "static" object ever be moved, we have to update its AABB manually using DynamicsWorld::updateSingleAabb.
@ -97,8 +113,8 @@ namespace MWPhysics
} }
} }
mTaskScheduler = std::make_unique<PhysicsTaskScheduler>(mPhysicsDt, mCollisionWorld);
mDebugDrawer = std::make_unique<MWRender::DebugDrawer>(mParentNode, mCollisionWorld.get(), mDebugDrawEnabled); mDebugDrawer = std::make_unique<MWRender::DebugDrawer>(mParentNode, mCollisionWorld.get(), mDebugDrawEnabled);
mTaskScheduler = std::make_unique<PhysicsTaskScheduler>(mPhysicsDt, mCollisionWorld.get(), mDebugDrawer.get());
} }
PhysicsSystem::~PhysicsSystem() PhysicsSystem::~PhysicsSystem()
@ -347,16 +363,7 @@ namespace MWPhysics
bool PhysicsSystem::canMoveToWaterSurface(const MWWorld::ConstPtr &actor, const float waterlevel) bool PhysicsSystem::canMoveToWaterSurface(const MWWorld::ConstPtr &actor, const float waterlevel)
{ {
const Actor* physicActor = getActor(actor); return ::canMoveToWaterSurface(getActor(actor), waterlevel, mCollisionWorld.get());
if (!physicActor)
return false;
const float halfZ = physicActor->getHalfExtents().z();
const osg::Vec3f actorPosition = physicActor->getPosition();
const osg::Vec3f startingPosition(actorPosition.x(), actorPosition.y(), actorPosition.z() + halfZ);
const osg::Vec3f destinationPosition(actorPosition.x(), actorPosition.y(), waterlevel + halfZ);
ActorTracer tracer;
tracer.doTrace(physicActor->getCollisionObject(), startingPosition, destinationPosition, mCollisionWorld.get());
return (tracer.mFraction >= 1.0f);
} }
osg::Vec3f PhysicsSystem::getHalfExtents(const MWWorld::ConstPtr &actor) const osg::Vec3f PhysicsSystem::getHalfExtents(const MWWorld::ConstPtr &actor) const
@ -605,9 +612,6 @@ namespace MWPhysics
return nullptr; return nullptr;
}(); }();
if (caster == nullptr)
Log(Debug::Warning) << "No caster for projectile " << projectileId;
ProjectileConvexCallback resultCallback(caster, btFrom, btTo, projectile); ProjectileConvexCallback resultCallback(caster, btFrom, btTo, projectile);
resultCallback.m_collisionFilterMask = 0xff; resultCallback.m_collisionFilterMask = 0xff;
resultCallback.m_collisionFilterGroup = CollisionType_Projectile; resultCallback.m_collisionFilterGroup = CollisionType_Projectile;
@ -772,16 +776,10 @@ namespace MWPhysics
const MWMechanics::MagicEffects& effects = character.getClass().getCreatureStats(character).getMagicEffects(); const MWMechanics::MagicEffects& effects = character.getClass().getCreatureStats(character).getMagicEffects();
bool waterCollision = false; bool waterCollision = false;
bool moveToWaterSurface = false;
if (cell->getCell()->hasWater() && effects.get(ESM::MagicEffect::WaterWalking).getMagnitude()) if (cell->getCell()->hasWater() && effects.get(ESM::MagicEffect::WaterWalking).getMagnitude())
{ {
if (!world->isUnderwater(character.getCell(), osg::Vec3f(character.getRefData().getPosition().asVec3()))) if (physicActor->getCollisionMode() || !world->isUnderwater(character.getCell(), osg::Vec3f(character.getRefData().getPosition().asVec3())))
waterCollision = true;
else if (physicActor->getCollisionMode() && canMoveToWaterSurface(character, waterlevel))
{
moveToWaterSurface = true;
waterCollision = true; waterCollision = true;
}
} }
physicActor->setCanWaterWalk(waterCollision); physicActor->setCanWaterWalk(waterCollision);
@ -794,7 +792,7 @@ namespace MWPhysics
if (!willSimulate) if (!willSimulate)
standingOn = physicActor->getStandingOnPtr(); standingOn = physicActor->getStandingOnPtr();
actorsFrameData.emplace_back(std::move(physicActor), standingOn, moveToWaterSurface, movement, slowFall, waterlevel); actorsFrameData.emplace_back(std::move(physicActor), standingOn, waterCollision, movement, slowFall, waterlevel);
} }
mMovementQueue.clear(); mMovementQueue.clear();
return actorsFrameData; return actorsFrameData;
@ -827,7 +825,7 @@ namespace MWPhysics
void PhysicsSystem::debugDraw() void PhysicsSystem::debugDraw()
{ {
if (mDebugDrawEnabled) if (mDebugDrawEnabled)
mDebugDrawer->step(); mTaskScheduler->debugDraw();
} }
bool PhysicsSystem::isActorStandingOn(const MWWorld::Ptr &actor, const MWWorld::ConstPtr &object) const bool PhysicsSystem::isActorStandingOn(const MWWorld::Ptr &actor, const MWWorld::ConstPtr &object) const
@ -937,9 +935,9 @@ namespace MWPhysics
} }
ActorFrameData::ActorFrameData(const std::shared_ptr<Actor>& actor, const MWWorld::Ptr standingOn, ActorFrameData::ActorFrameData(const std::shared_ptr<Actor>& actor, const MWWorld::Ptr standingOn,
bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel) bool waterCollision, osg::Vec3f movement, float slowFall, float waterlevel)
: mActor(actor), mActorRaw(actor.get()), mStandingOn(standingOn), : mActor(actor), mActorRaw(actor.get()), mStandingOn(standingOn),
mDidJump(false), mNeedLand(false), mMoveToWaterSurface(moveToWaterSurface), mDidJump(false), mNeedLand(false), mWaterCollision(waterCollision),
mWaterlevel(waterlevel), mSlowFall(slowFall), mOldHeight(0), mFallHeight(0), mMovement(movement), mPosition(), mRefpos() mWaterlevel(waterlevel), mSlowFall(slowFall), mOldHeight(0), mFallHeight(0), mMovement(movement), mPosition(), mRefpos()
{ {
const MWBase::World *world = MWBase::Environment::get().getWorld(); const MWBase::World *world = MWBase::Environment::get().getWorld();
@ -953,7 +951,7 @@ namespace MWPhysics
mWasOnGround = actor->getOnGround(); mWasOnGround = actor->getOnGround();
} }
void ActorFrameData::updatePosition() void ActorFrameData::updatePosition(btCollisionWorld* world)
{ {
mActorRaw->updateWorldPosition(); mActorRaw->updateWorldPosition();
// If physics runs "fast enough", position are interpolated without simulation // If physics runs "fast enough", position are interpolated without simulation
@ -961,10 +959,10 @@ namespace MWPhysics
// regardless of simulation speed. // regardless of simulation speed.
mActorRaw->applyOffsetChange(); mActorRaw->applyOffsetChange();
mPosition = mActorRaw->getPosition(); mPosition = mActorRaw->getPosition();
if (mMoveToWaterSurface) if (mWaterCollision && mPosition.z() < mWaterlevel && canMoveToWaterSurface(mActorRaw, mWaterlevel, world))
{ {
mPosition.z() = mWaterlevel; mPosition.z() = mWaterlevel;
MWBase::Environment::get().getWorld()->moveObject(mActorRaw->getPtr(), mPosition.x(), mPosition.y(), mPosition.z()); MWBase::Environment::get().getWorld()->moveObject(mActorRaw->getPtr(), mPosition.x(), mPosition.y(), mPosition.z(), false);
} }
mOldHeight = mPosition.z(); mOldHeight = mPosition.z();
mRefpos = mActorRaw->getPtr().getRefData().getPosition(); mRefpos = mActorRaw->getPtr().getRefData().getPosition();

@ -79,7 +79,7 @@ namespace MWPhysics
struct ActorFrameData struct ActorFrameData
{ {
ActorFrameData(const std::shared_ptr<Actor>& actor, const MWWorld::Ptr standingOn, bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel); ActorFrameData(const std::shared_ptr<Actor>& actor, const MWWorld::Ptr standingOn, bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel);
void updatePosition(); void updatePosition(btCollisionWorld* world);
std::weak_ptr<Actor> mActor; std::weak_ptr<Actor> mActor;
Actor* mActorRaw; Actor* mActorRaw;
MWWorld::Ptr mStandingOn; MWWorld::Ptr mStandingOn;
@ -90,7 +90,7 @@ namespace MWPhysics
bool mDidJump; bool mDidJump;
bool mFloatToSurface; bool mFloatToSurface;
bool mNeedLand; bool mNeedLand;
bool mMoveToWaterSurface; bool mWaterCollision;
float mWaterlevel; float mWaterlevel;
float mSlowFall; float mSlowFall;
float mOldHeight; float mOldHeight;
@ -259,7 +259,7 @@ namespace MWPhysics
std::unique_ptr<btBroadphaseInterface> mBroadphase; std::unique_ptr<btBroadphaseInterface> mBroadphase;
std::unique_ptr<btDefaultCollisionConfiguration> mCollisionConfiguration; std::unique_ptr<btDefaultCollisionConfiguration> mCollisionConfiguration;
std::unique_ptr<btCollisionDispatcher> mDispatcher; std::unique_ptr<btCollisionDispatcher> mDispatcher;
std::shared_ptr<btCollisionWorld> mCollisionWorld; std::unique_ptr<btCollisionWorld> mCollisionWorld;
std::unique_ptr<PhysicsTaskScheduler> mTaskScheduler; std::unique_ptr<PhysicsTaskScheduler> mTaskScheduler;
std::unique_ptr<Resource::BulletShapeManager> mShapeManager; std::unique_ptr<Resource::BulletShapeManager> mShapeManager;

@ -60,14 +60,14 @@ namespace MWPhysics
// attempt 3: further, less tall fixed distance movement, same as above // attempt 3: further, less tall fixed distance movement, same as above
// If you're making a full conversion you should purge the logic for attempts 2 and 3. Attempts 2 and 3 just try to work around problems with vanilla Morrowind assets. // If you're making a full conversion you should purge the logic for attempts 2 and 3. Attempts 2 and 3 just try to work around problems with vanilla Morrowind assets.
int attempt = 0; int attempt = 0;
float downStepSize; float downStepSize = 0;
while(attempt < 3) while(attempt < 3)
{ {
attempt++; attempt++;
if(attempt == 1) if(attempt == 1)
tracerDest = tracerPos + toMove; tracerDest = tracerPos + toMove;
else if (!firstIteration || !sDoExtraStairHacks) // first attempt failed and not on first movement solver iteration, can't retry -- or we have extra hacks disabled else if (!sDoExtraStairHacks) // early out if we have extra hacks disabled
{ {
return false; return false;
} }

@ -358,6 +358,8 @@ void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons)
} }
mScabbard = attachMesh(scabbardName, boneName); mScabbard = attachMesh(scabbardName, boneName);
if (mScabbard)
resetControllers(mScabbard->getNode());
osg::Group* weaponNode = getBoneByName("Bip01 Weapon"); osg::Group* weaponNode = getBoneByName("Bip01 Weapon");
if (!weaponNode) if (!weaponNode)

@ -500,6 +500,11 @@ namespace MWRender
mAlpha = alpha; mAlpha = alpha;
} }
void setLightSource(const osg::ref_ptr<SceneUtil::LightSource>& lightSource)
{
mLightSource = lightSource;
}
protected: protected:
void setDefaults(osg::StateSet* stateset) override void setDefaults(osg::StateSet* stateset) override
{ {
@ -522,10 +527,13 @@ namespace MWRender
{ {
osg::Material* material = static_cast<osg::Material*>(stateset->getAttribute(osg::StateAttribute::MATERIAL)); osg::Material* material = static_cast<osg::Material*>(stateset->getAttribute(osg::StateAttribute::MATERIAL));
material->setAlpha(osg::Material::FRONT_AND_BACK, mAlpha); material->setAlpha(osg::Material::FRONT_AND_BACK, mAlpha);
if (mLightSource)
mLightSource->setActorFade(mAlpha);
} }
private: private:
float mAlpha; float mAlpha;
osg::ref_ptr<SceneUtil::LightSource> mLightSource;
}; };
struct Animation::AnimSource struct Animation::AnimSource
@ -1667,7 +1675,7 @@ namespace MWRender
{ {
bool exterior = mPtr.isInCell() && mPtr.getCell()->getCell()->isExterior(); bool exterior = mPtr.isInCell() && mPtr.getCell()->getCell()->isExterior();
SceneUtil::addLight(parent, esmLight, Mask_ParticleSystem, Mask_Lighting, exterior); mExtraLightSource = SceneUtil::addLight(parent, esmLight, Mask_ParticleSystem, Mask_Lighting, exterior);
} }
void Animation::addEffect (const std::string& model, int effectId, bool loop, const std::string& bonename, const std::string& texture) void Animation::addEffect (const std::string& model, int effectId, bool loop, const std::string& bonename, const std::string& texture)
@ -1817,6 +1825,7 @@ namespace MWRender
if (mTransparencyUpdater == nullptr) if (mTransparencyUpdater == nullptr)
{ {
mTransparencyUpdater = new TransparencyUpdater(alpha); mTransparencyUpdater = new TransparencyUpdater(alpha);
mTransparencyUpdater->setLightSource(mExtraLightSource);
mObjectRoot->addCullCallback(mTransparencyUpdater); mObjectRoot->addCullCallback(mTransparencyUpdater);
} }
else else

@ -278,6 +278,7 @@ protected:
osg::ref_ptr<SceneUtil::LightSource> mGlowLight; osg::ref_ptr<SceneUtil::LightSource> mGlowLight;
osg::ref_ptr<SceneUtil::GlowUpdater> mGlowUpdater; osg::ref_ptr<SceneUtil::GlowUpdater> mGlowUpdater;
osg::ref_ptr<TransparencyUpdater> mTransparencyUpdater; osg::ref_ptr<TransparencyUpdater> mTransparencyUpdater;
osg::ref_ptr<SceneUtil::LightSource> mExtraLightSource;
float mAlpha; float mAlpha;

@ -453,7 +453,7 @@ namespace MWRender
void Camera::setPitch(float angle) void Camera::setPitch(float angle)
{ {
const float epsilon = 0.000001f; const float epsilon = 0.000001f;
float limit = osg::PI_2 - epsilon; float limit = static_cast<float>(osg::PI_2) - epsilon;
mPitch = osg::clampBetween(angle, -limit, limit); mPitch = osg::clampBetween(angle, -limit, limit);
} }

@ -174,7 +174,9 @@ namespace MWRender
mCamera->setNodeMask(Mask_RenderToTexture); mCamera->setNodeMask(Mask_RenderToTexture);
osg::ref_ptr<SceneUtil::LightManager> lightManager = new SceneUtil::LightManager; bool ffp = mResourceSystem->getSceneManager()->getLightingMethod() == SceneUtil::LightingMethod::FFP;
osg::ref_ptr<SceneUtil::LightManager> lightManager = new SceneUtil::LightManager(ffp);
lightManager->setStartLight(1); lightManager->setStartLight(1);
osg::ref_ptr<osg::StateSet> stateset = lightManager->getOrCreateStateSet(); osg::ref_ptr<osg::StateSet> stateset = lightManager->getOrCreateStateSet();
stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON); stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON);
@ -231,12 +233,22 @@ namespace MWRender
float positionZ = std::cos(altitude); float positionZ = std::cos(altitude);
light->setPosition(osg::Vec4(positionX,positionY,positionZ, 0.0)); light->setPosition(osg::Vec4(positionX,positionY,positionZ, 0.0));
light->setDiffuse(osg::Vec4(diffuseR,diffuseG,diffuseB,1)); light->setDiffuse(osg::Vec4(diffuseR,diffuseG,diffuseB,1));
light->setAmbient(osg::Vec4(ambientR,ambientG,ambientB,1)); osg::Vec4 ambientRGBA = osg::Vec4(ambientR,ambientG,ambientB,1);
if (mResourceSystem->getSceneManager()->getForceShaders())
{
// When using shaders, we now skip the ambient sun calculation as this is the only place it's used.
// Using the scene ambient will give identical results.
lightmodel->setAmbientIntensity(ambientRGBA);
light->setAmbient(osg::Vec4(0,0,0,1));
}
else
light->setAmbient(ambientRGBA);
light->setSpecular(osg::Vec4(0,0,0,0)); light->setSpecular(osg::Vec4(0,0,0,0));
light->setLightNum(0); light->setLightNum(0);
light->setConstantAttenuation(1.f); light->setConstantAttenuation(1.f);
light->setLinearAttenuation(0.f); light->setLinearAttenuation(0.f);
light->setQuadraticAttenuation(0.f); light->setQuadraticAttenuation(0.f);
lightManager->setSunlight(light);
osg::ref_ptr<osg::LightSource> lightSource = new osg::LightSource; osg::ref_ptr<osg::LightSource> lightSource = new osg::LightSource;
lightSource->setLight(light); lightSource->setLight(light);
@ -414,7 +426,7 @@ namespace MWRender
visitor.setTraversalNumber(mDrawOnceCallback->getLastRenderedFrame()); visitor.setTraversalNumber(mDrawOnceCallback->getLastRenderedFrame());
osg::Node::NodeMask nodeMask = mCamera->getNodeMask(); osg::Node::NodeMask nodeMask = mCamera->getNodeMask();
mCamera->setNodeMask(~0); mCamera->setNodeMask(~0u);
mCamera->accept(visitor); mCamera->accept(visitor);
mCamera->setNodeMask(nodeMask); mCamera->setNodeMask(nodeMask);

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

Loading…
Cancel
Save