1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-04-13 07:06:43 +00:00

Merge branch 'upstream' into movement_tweaks

This commit is contained in:
wareya 2022-01-16 17:19:20 -05:00
commit 784b1888a9
586 changed files with 20175 additions and 6675 deletions

View file

@ -3,17 +3,25 @@
stages: stages:
- build - build
.Debian_Image: # https://blog.nimbleways.com/let-s-make-faster-gitlab-ci-cd-pipelines/
variables:
FF_USE_NEW_SHELL_ESCAPE: "true"
FF_USE_FASTZIP: "true"
# These can be specified per job or per pipeline
ARTIFACT_COMPRESSION_LEVEL: "fast"
CACHE_COMPRESSION_LEVEL: "fast"
.Ubuntu_Image:
tags: tags:
- docker - docker
- linux - linux
image: debian:bullseye image: ubuntu:focal
rules: rules:
- if: $CI_PIPELINE_SOURCE == "push" - if: $CI_PIPELINE_SOURCE == "push"
.Debian: .Ubuntu:
extends: .Debian_Image extends: .Ubuntu_Image
cache: cache:
paths: paths:
- apt-cache/ - apt-cache/
@ -35,7 +43,7 @@ stages:
- build/install/ - build/install/
Clang_Tidy: Clang_Tidy:
extends: .Debian_Image extends: .Ubuntu_Image
stage: build stage: build
rules: rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"' - if: '$CI_PIPELINE_SOURCE == "schedule"'
@ -50,34 +58,41 @@ Clang_Tidy:
CXX: clang++ CXX: clang++
CI_CLANG_TIDY: 1 CI_CLANG_TIDY: 1
timeout: 8h timeout: 8h
artifacts:
paths: []
expire_in: 1 minute
Coverity: Coverity:
extends: .Debian_Image extends: .Ubuntu_Image
stage: build stage: build
rules: rules:
- if: $CI_PIPELINE_SOURCE == "schedule" - if: $CI_PIPELINE_SOURCE == "schedule"
before_script: before_script:
- CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic coverity - CI/install_debian_deps.sh clang 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 - 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 - tar xfz /tmp/cov-analysis-linux64.tgz
script: script:
- CI/before_script.linux.sh - CI/before_script.linux.sh
# Remove the specific targets and build everything once we can do it under 3h # Remove the specific targets and build everything once we can do it under 3h
- cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build build -- -j $(nproc) openmw esmtool bsatool niftest openmw-wizard openmw-launcher openmw-iniimporter openmw-essimporter - cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build build -- -j $(nproc) openmw esmtool bsatool niftest openmw-wizard openmw-launcher openmw-iniimporter openmw-essimporter openmw-navmeshtool openmw-cs
after_script: after_script:
- tar cfz cov-int.tar.gz cov-int - tar cfz cov-int.tar.gz cov-int
- curl https://scan.coverity.com/builds?project=$COVERITY_SCAN_PROJECT_NAME - curl https://scan.coverity.com/builds?project=$COVERITY_SCAN_PROJECT_NAME
--form token=$COVERITY_SCAN_TOKEN --form email=$GITLAB_USER_EMAIL --form token=$COVERITY_SCAN_TOKEN --form email=$GITLAB_USER_EMAIL
--form file=@cov-int.tar.gz --form version="`git describe --tags`" --form file=@cov-int.tar.gz --form version="$CI_COMMIT_REF_NAME:$CI_COMMIT_SHORT_SHA"
--form description="`git describe --tags` / $CI_COMMIT_TITLE / $CI_COMMIT_REF_NAME:$CI_PIPELINE_ID" --form description="CI_COMMIT_SHORT_SHA / $CI_COMMIT_TITLE / $CI_COMMIT_REF_NAME:$CI_PIPELINE_ID"
variables: variables:
CC: gcc CC: clang
CXX: g++ CXX: clang++
CXXFLAGS: -O0
artifacts:
paths:
- /builds/OpenMW/openmw/cov-int/build-log.txt
Debian_GCC: Ubuntu_GCC:
extends: .Debian extends: .Ubuntu
cache: cache:
key: Debian_GCC.v2 key: Ubuntu_GCC.v2
before_script: before_script:
- CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic - CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic
variables: variables:
@ -87,27 +102,41 @@ Debian_GCC:
# When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks.
timeout: 2h timeout: 2h
Debian_GCC_tests: Ubuntu_GCC_tests:
extends: Debian_GCC extends: Ubuntu_GCC
cache: cache:
key: Debian_GCC_tests.v2 key: Ubuntu_GCC_tests.v2
variables: variables:
CCACHE_SIZE: 1G CCACHE_SIZE: 1G
BUILD_TESTS_ONLY: 1 BUILD_TESTS_ONLY: 1
artifacts:
paths: []
expire_in: 1 minute
Debian_GCC_tests_Debug: Ubuntu_GCC_tests_Debug:
extends: Debian_GCC extends: Ubuntu_GCC
cache: cache:
key: Debian_GCC_tests_Debug.v1 key: Ubuntu_GCC_tests_Debug.v1
variables: variables:
CCACHE_SIZE: 1G CCACHE_SIZE: 1G
BUILD_TESTS_ONLY: 1 BUILD_TESTS_ONLY: 1
CMAKE_BUILD_TYPE: Debug CMAKE_BUILD_TYPE: Debug
artifacts:
paths: []
expire_in: 1 minute
Debian_GCC_Static_Deps: Ubuntu_GCC_Static_Deps:
extends: Debian_GCC extends: Ubuntu_GCC
rules:
- if: $CI_PIPELINE_SOURCE == "push"
changes:
- "**/CMakeLists.txt"
- "cmake/**/*"
- "CI/**/*"
- ".gitlab-ci.yml"
allow_failure: true
cache: cache:
key: Debian_GCC_Static_Deps key: Ubuntu_GCC_Static_Deps
paths: paths:
- apt-cache/ - apt-cache/
- ccache/ - ccache/
@ -118,20 +147,23 @@ Debian_GCC_Static_Deps:
CI_OPENMW_USE_STATIC_DEPS: 1 CI_OPENMW_USE_STATIC_DEPS: 1
timeout: 3h timeout: 3h
Debian_GCC_Static_Deps_tests: Ubuntu_GCC_Static_Deps_tests:
extends: Debian_GCC_Static_Deps extends: Ubuntu_GCC_Static_Deps
cache: cache:
key: Debian_GCC_Static_Deps_tests key: Ubuntu_GCC_Static_Deps_tests
variables: variables:
CCACHE_SIZE: 1G CCACHE_SIZE: 1G
BUILD_TESTS_ONLY: 1 BUILD_TESTS_ONLY: 1
artifacts:
paths: []
expire_in: 1 minute
Debian_Clang: Ubuntu_Clang:
extends: .Debian extends: .Ubuntu
before_script: before_script:
- CI/install_debian_deps.sh clang openmw-deps openmw-deps-dynamic - CI/install_debian_deps.sh clang openmw-deps openmw-deps-dynamic
cache: cache:
key: Debian_Clang.v2 key: Ubuntu_Clang.v2
variables: variables:
CC: clang CC: clang
CXX: clang++ CXX: clang++
@ -139,22 +171,28 @@ Debian_Clang:
# When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks.
timeout: 2h timeout: 2h
Debian_Clang_tests: Ubuntu_Clang_tests:
extends: Debian_Clang extends: Ubuntu_Clang
cache: cache:
key: Debian_Clang_tests.v2 key: Ubuntu_Clang_tests.v2
variables: variables:
CCACHE_SIZE: 1G CCACHE_SIZE: 1G
BUILD_TESTS_ONLY: 1 BUILD_TESTS_ONLY: 1
artifacts:
paths: []
expire_in: 1 minute
Debian_Clang_tests_Debug: Ubuntu_Clang_tests_Debug:
extends: Debian_Clang extends: Ubuntu_Clang
cache: cache:
key: Debian_Clang_tests_Debug.v1 key: Ubuntu_Clang_tests_Debug.v1
variables: variables:
CCACHE_SIZE: 1G CCACHE_SIZE: 1G
BUILD_TESTS_ONLY: 1 BUILD_TESTS_ONLY: 1
CMAKE_BUILD_TYPE: Debug CMAKE_BUILD_TYPE: Debug
artifacts:
paths: []
expire_in: 1 minute
.MacOS: .MacOS:
image: macos-11-xcode-12 image: macos-11-xcode-12
@ -163,8 +201,7 @@ Debian_Clang_tests_Debug:
stage: build stage: build
only: only:
variables: variables:
- $CI_PROJECT_ID == "7107382" - $CI_PROJECT_ID == "7107382" && $CI_PIPELINE_SOURCE == "push"
- $CI_PIPELINE_SOURCE == "push"
cache: cache:
paths: paths:
- ccache/ - ccache/
@ -177,7 +214,7 @@ Debian_Clang_tests_Debug:
- ccache -z -M "${CCACHE_SIZE}" - ccache -z -M "${CCACHE_SIZE}"
- CI/before_script.osx.sh - CI/before_script.osx.sh
- cd build; make -j $(sysctl -n hw.logicalcpu) 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 - ccache -s
artifacts: artifacts:
paths: paths:
@ -192,21 +229,20 @@ macOS11_Xcode12:
variables: variables:
CCACHE_SIZE: 3G CCACHE_SIZE: 3G
macOS10.15_Xcode11: macOS12_Xcode13:
extends: .MacOS extends: .MacOS
image: macos-10.15-xcode-11 image: macos-12-xcode-13
allow_failure: true
cache: cache:
key: macOS10.15_Xcode11.v1 key: macOS12_Xcode13.v1
variables: variables:
CCACHE_SIZE: 3G CCACHE_SIZE: 3G
variables: &engine-targets variables: &engine-targets
targets: "openmw,openmw-essimporter,openmw-iniimporter,openmw-launcher,openmw-wizard" targets: "openmw,openmw-iniimporter,openmw-launcher,openmw-wizard,openmw-navmeshtool"
package: "Engine" package: "Engine"
variables: &cs-targets variables: &cs-targets
targets: "openmw-cs,bsatool,esmtool,niftest" targets: "openmw-cs,bsatool,esmtool,niftest,openmw-essimporter"
package: "CS" package: "CS"
variables: &tests-targets variables: &tests-targets
@ -330,6 +366,9 @@ Windows_Ninja_Tests_RelWithDebInfo:
config: "RelWithDebInfo" config: "RelWithDebInfo"
# Gitlab can't successfully execute following binaries due to unknown reason # Gitlab can't successfully execute following binaries due to unknown reason
# executables: "openmw_test_suite.exe,openmw_detournavigator_navmeshtilescache_benchmark.exe" # executables: "openmw_test_suite.exe,openmw_detournavigator_navmeshtilescache_benchmark.exe"
artifacts:
paths: []
expire_in: 1 minute
.Windows_MSBuild_Base: .Windows_MSBuild_Base:
tags: tags:
@ -396,21 +435,16 @@ Windows_Ninja_Tests_RelWithDebInfo:
- MSVC2019_64/*/*/*/*/*/*/*.log - MSVC2019_64/*/*/*/*/*/*/*.log
- MSVC2019_64/*/*/*/*/*/*/*/*.log - MSVC2019_64/*/*/*/*/*/*/*/*.log
Daily_Windows_MSBuild_Engine_Release:on-schedule:
extends:
- .Windows_MSBuild_Base
variables:
<<: *engine-targets
config: "Release"
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
Windows_MSBuild_Engine_Release: Windows_MSBuild_Engine_Release:
extends: extends:
- .Windows_MSBuild_Base - .Windows_MSBuild_Base
variables: variables:
<<: *engine-targets <<: *engine-targets
config: "Release" config: "Release"
rules:
# run this for both pushes and schedules so 'latest successful pipeline for branch' always includes it
- if: $CI_PIPELINE_SOURCE == "push"
- if: $CI_PIPELINE_SOURCE == "schedule"
Windows_MSBuild_Engine_Debug: Windows_MSBuild_Engine_Debug:
extends: extends:
@ -432,6 +466,10 @@ Windows_MSBuild_CS_Release:
variables: variables:
<<: *cs-targets <<: *cs-targets
config: "Release" config: "Release"
rules:
# run this for both pushes and schedules so 'latest successful pipeline for branch' always includes it
- if: $CI_PIPELINE_SOURCE == "push"
- if: $CI_PIPELINE_SOURCE == "schedule"
Windows_MSBuild_CS_Debug: Windows_MSBuild_CS_Debug:
extends: extends:
@ -455,27 +493,28 @@ Windows_MSBuild_Tests_RelWithDebInfo:
config: "RelWithDebInfo" config: "RelWithDebInfo"
# Gitlab can't successfully execute following binaries due to unknown reason # Gitlab can't successfully execute following binaries due to unknown reason
# executables: "openmw_test_suite.exe,openmw_detournavigator_navmeshtilescache_benchmark.exe" # executables: "openmw_test_suite.exe,openmw_detournavigator_navmeshtilescache_benchmark.exe"
artifacts:
paths: []
expire_in: 1 minute
Debian_AndroidNDK_arm64-v8a: Ubuntu_AndroidNDK_arm64-v8a:
tags: tags:
- linux - linux
image: debian:bullseye image: psi29a/android-ndk:focal-ndk22
rules: rules:
- if: $CI_PIPELINE_SOURCE == "push" - if: $CI_PIPELINE_SOURCE == "push"
variables: variables:
CCACHE_SIZE: 3G CCACHE_SIZE: 3G
cache: cache:
key: Debian_AndroidNDK_arm64-v8a.v3 key: Ubuntu__Focal_AndroidNDK_r22b_arm64-v8a.v1
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 "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
stage: build stage: build
script: script:
- export CCACHE_BASEDIR="`pwd`" - export CCACHE_BASEDIR="`pwd`"
@ -493,3 +532,18 @@ Debian_AndroidNDK_arm64-v8a:
# 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
FindMissingMergeRequests:
image: python:latest
stage: build
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
cache:
key: FindMissingMergeRequests.v1
paths:
- .cache/pip
before_script:
- pip3 install --user requests click discord_webhook
script:
- scripts/find_missing_merge_requests.py --project_id=$CI_PROJECT_ID --ignored_mrs_path=$CI_PROJECT_DIR/.resubmitted_merge_requests.txt

View file

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

View file

@ -1,80 +0,0 @@
language: cpp
branches:
only:
- master
- /openmw-.*$/
cache: ccache
addons:
apt:
sources:
- sourceline: 'ppa:openmw/openmw'
packages: [
# Dev
build-essential, cmake, clang-tools-9, ccache,
# Boost
libboost-filesystem-dev, libboost-iostreams-dev, libboost-program-options-dev, libboost-system-dev,
# FFmpeg
libavcodec-dev, libavformat-dev, libavutil-dev, libswresample-dev, libswscale-dev,
# Audio, Video and Misc. deps
libsdl2-dev, libqt5opengl5-dev, libopenal-dev, libunshield-dev, libtinyxml-dev, liblz4-dev,
# The other ones from OpenMW ppa
libbullet-dev, libopenscenegraph-dev, libmygui-dev
]
matrix:
include:
- name: OpenMW (all) on MacOS 10.15 with Xcode 11.6
os: osx
osx_image: xcode11.6
- name: OpenMW (all) on Ubuntu Focal with GCC
os: linux
dist: focal
- name: OpenMW (tests only) on Ubuntu Focal with GCC
os: linux
dist: focal
env:
- BUILD_TESTS_ONLY: 1
- name: OpenMW (openmw) on Ubuntu Focal with Clang's Static Analysis
os: linux
dist: focal
env:
- MATRIX_EVAL="CC=clang-9 && CXX=clang++-9"
- ANALYZE="scan-build-9 --force-analyze-debug-code --use-cc clang-9 --use-c++ clang++-9"
compiler: clang
before_install:
- if [ "${TRAVIS_OS_NAME}" = "linux" ]; then eval "${MATRIX_EVAL}"; fi
- ./CI/before_install.${TRAVIS_OS_NAME}.sh
before_script:
- ccache -z
- ./CI/before_script.${TRAVIS_OS_NAME}.sh
script:
- cd ./build
- ${ANALYZE} make -j3;
- if [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi
- if [ "${TRAVIS_OS_NAME}" = "osx" ]; then ../CI/check_package.osx.sh; fi
- if [ "${TRAVIS_OS_NAME}" = "linux" ] && [ "${BUILD_TESTS_ONLY}" ]; then ./openmw_test_suite; fi
- if [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi
- cd "${TRAVIS_BUILD_DIR}"
- ccache -s
deploy:
provider: script
script: ./CI/deploy.osx.sh
skip_cleanup: true
on:
branch: master
condition: "$TRAVIS_EVENT_TYPE = cron && $TRAVIS_OS_NAME = osx"
repo: OpenMW/openmw
notifications:
email:
if: repository_slug = OpenMW/openmw AND branch = master
recipients:
- corrmage+travis-ci@gmail.com
on_success: change
on_failure: always
irc:
if: repository_slug = OpenMW/openmw AND branch = master
channels:
- "irc.libera.chat#openmw"
on_success: change
on_failure: always
use_notice: true

View file

@ -31,6 +31,7 @@ Programmers
Allofich Allofich
Andreas Stöckel Andreas Stöckel
Andrei Kortunov (akortunov) Andrei Kortunov (akortunov)
Andrew Appuhamy (andrew-app)
AnyOldName3 AnyOldName3
Ardekantur Ardekantur
Armin Preiml Armin Preiml
@ -92,6 +93,7 @@ Programmers
Haoda Wang (h313) Haoda Wang (h313)
hristoast hristoast
Internecine Internecine
Ivan Beloborodov (myrix)
Jackerty Jackerty
Jacob Essex (Yacoby) Jacob Essex (Yacoby)
Jacob Turnbull (Tankinfrank) Jacob Turnbull (Tankinfrank)
@ -113,6 +115,7 @@ Programmers
John Blomberg (fstp) John Blomberg (fstp)
Jordan Ayers Jordan Ayers
Jordan Milne Jordan Milne
Josquin Frei
Josua Grawitter Josua Grawitter
Jules Blok (Armada651) Jules Blok (Armada651)
julianko julianko
@ -215,6 +218,7 @@ Programmers
tlmullis tlmullis
tri4ng1e tri4ng1e
Thoronador Thoronador
Tom Lowe (Vulpen)
Tom Mason (wheybags) Tom Mason (wheybags)
Torben Leif Carrington (TorbenC) Torben Leif Carrington (TorbenC)
unelsson unelsson
@ -230,7 +234,7 @@ Programmers
Yuri Krupenin Yuri Krupenin
zelurker zelurker
Noah Gooder Noah Gooder
Andrew Appuhamy (andrew-app)
Documentation Documentation
------------- -------------

View file

@ -2,21 +2,31 @@
------ ------
Bug #1751: Birthsign abilities increase modified attribute values instead of base ones Bug #1751: Birthsign abilities increase modified attribute values instead of base ones
Bug #1930: Followers are still fighting if a target stops combat with a leader
Bug #2036: SetStat and ModStat instructions aren't implemented the same way as in Morrowind
Bug #3246: ESSImporter: Most NPCs are dead on save load Bug #3246: ESSImporter: Most NPCs are dead on save load
Bug #3488: AI combat aiming is too slow
Bug #3514: Editing a reference's position after loading an esp file makes the reference disappear Bug #3514: Editing a reference's position after loading an esp file makes the reference disappear
Bug #3737: Scripts from The Underground 2 .esp do not play (all patched versions) Bug #3737: Scripts from The Underground 2 .esp do not play (all patched versions)
Bug #3792: 1 frame late magicka recalc breaks early scripted magicka reactions to Intelligence change Bug #3792: 1 frame late magicka recalc breaks early scripted magicka reactions to Intelligence change
Bug #3846: Strings starting with "-" fail to compile if not enclosed in quotes Bug #3846: Strings starting with "-" fail to compile if not enclosed in quotes
Bug #3855: AI sometimes spams defensive spells
Bug #3905: Great House Dagoth issues Bug #3905: Great House Dagoth issues
Bug #4203: Resurrecting an actor should close the loot GUI Bug #4203: Resurrecting an actor should close the loot GUI
Bug #4376: Moved actors don't respawn in their original cells
Bug #4389: NPC's lips do not move if his head model has the NiBSAnimationNode root node
Bug #4602: Robert's Bodies: crash inside createInstance() Bug #4602: Robert's Bodies: crash inside createInstance()
Bug #4700: Editor: Incorrect command implementation Bug #4700: Editor: Incorrect command implementation
Bug #4744: Invisible particles must still be processed Bug #4744: Invisible particles must still be processed
Bug #4949: Incorrect particle lighting
Bug #5088: Sky abruptly changes direction during certain weather transitions Bug #5088: Sky abruptly changes direction during certain weather transitions
Bug #5100: Persuasion doesn't always clamp the resulting disposition Bug #5100: Persuasion doesn't always clamp the resulting disposition
Bug #5120: Scripted object spawning updates physics system Bug #5120: Scripted object spawning updates physics system
Bug #5207: Loose summons can be present in scene Bug #5207: Loose summons can be present in scene
Bug #5377: console does not appear after using menutest in inventory
Bug #5379: Wandering NPCs falling through cantons Bug #5379: Wandering NPCs falling through cantons
Bug #5394: Windows snapping no longer works
Bug #5434: Pinned windows shouldn't cover breath progress bar
Bug #5453: Magic effect VFX are offset for creatures Bug #5453: Magic effect VFX are offset for creatures
Bug #5483: AutoCalc flag is not used to calculate spells cost Bug #5483: AutoCalc flag is not used to calculate spells cost
Bug #5508: Engine binary links to Qt without using it Bug #5508: Engine binary links to Qt without using it
@ -28,6 +38,7 @@
Bug #5842: GetDisposition adds temporary disposition change from different actors Bug #5842: GetDisposition adds temporary disposition change from different actors
Bug #5863: GetEffect should return true after the player has teleported Bug #5863: GetEffect should return true after the player has teleported
Bug #5913: Failed assertion during Ritual of Trees quest Bug #5913: Failed assertion during Ritual of Trees quest
Bug #5928: Glow in the Dahrk functionality used without mod installed
Bug #5937: Lights always need to be rotated by 90 degrees Bug #5937: Lights always need to be rotated by 90 degrees
Bug #6037: Morrowind Content Language Cannot be Set to English in OpenMW Launcher Bug #6037: Morrowind Content Language Cannot be Set to English in OpenMW Launcher
Bug #6051: NaN water height in ESM file is not handled gracefully Bug #6051: NaN water height in ESM file is not handled gracefully
@ -47,7 +58,9 @@
Bug #6168: Weather particles flicker for a frame at start of storms Bug #6168: Weather particles flicker for a frame at start of storms
Bug #6172: Some creatures can't open doors Bug #6172: Some creatures can't open doors
Bug #6174: Spellmaking and Enchanting sliders differences from vanilla Bug #6174: Spellmaking and Enchanting sliders differences from vanilla
Bug #6177: Followers of player follower stop following after waiting for a day
Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla
Bug #6191: Encumbrance messagebox timer works incorrectly
Bug #6197: Infinite Casting Loop Bug #6197: Infinite Casting Loop
Bug #6253: Multiple instances of Reflect stack additively Bug #6253: Multiple instances of Reflect stack additively
Bug #6255: Reflect is different from vanilla Bug #6255: Reflect is different from vanilla
@ -59,15 +72,36 @@
Bug #6289: Keyword search in dialogues expected the text to be all ASCII characters Bug #6289: Keyword search in dialogues expected the text to be all ASCII characters
Bug #6291: Can't pickup the dead mage's journal from the mysterious hunter mod Bug #6291: Can't pickup the dead mage's journal from the mysterious hunter mod
Bug #6302: Teleporting disabled actor breaks its disabled state Bug #6302: Teleporting disabled actor breaks its disabled state
Bug #6303: After "go to jail" weapon can stuck in the ready to attack state
Bug #6307: Pathfinding in Infidelities quest from Tribunal addon is broken Bug #6307: Pathfinding in Infidelities quest from Tribunal addon is broken
Bug #6321: Arrow enchantments should always be applied to the target Bug #6321: Arrow enchantments should always be applied to the target
Bug #6322: Total sold/cost should reset to 0 when there are no items offered Bug #6322: Total sold/cost should reset to 0 when there are no items offered
Bug #6323: Wyrmhaven: Alboin doesn't follower the player character out of his house Bug #6323: Wyrmhaven: Alboin doesn't follower the player character out of his house
Bug #6324: Special Slave Companions: Can't buy the slave companions
Bug #6326: Detect Enchantment/Key should detect items in unresolved containers Bug #6326: Detect Enchantment/Key should detect items in unresolved containers
Bug #6327: Blocking roots the character in place
Bug #6333: Werewolf stat changes should be implemented as damage/fortifications
Bug #6343: Magic projectile speed doesn't take race weight into account
Bug #6347: PlaceItem/PlaceItemCell/PlaceAt should work with levelled creatures Bug #6347: PlaceItem/PlaceItemCell/PlaceAt should work with levelled creatures
Bug #6354: SFX abruptly cut off after crossing max distance; implement soft fading of sound effects
Bug #6358: Changeweather command does not report an error when entering non-existent region
Bug #6363: Some scripts in Morrowland fail to work Bug #6363: Some scripts in Morrowland fail to work
Bug #6376: Creatures should be able to use torches Bug #6376: Creatures should be able to use torches
Bug #6386: Artifacts in water reflection due to imprecise screen-space coordinate computation
Bug #6396: Inputting certain Unicode characters triggers an assertion
Bug #6416: Morphs are applied to the wrong target
Bug #6417: OpenMW doesn't always use the right node to accumulate movement
Bug #6429: Wyrmhaven: Can't add AI packages to player
Bug #6433: Items bound to Quick Keys sometimes do not appear until the Quick Key menu is opened
Bug #6451: Weapon summoned from Cast When Used item will have the name "None"
Bug #6473: Strings from NIF should be parsed only to first null terminator
Bug #6493: Unlocking owned but not locked or unlocked containers is considered a crime
Bug #6517: Rotations for KeyframeData in NIFs should be optional
Bug #6519: Effects tooltips for ingredients work incorrectly
Bug #6523: Disintegrate Weapon is resisted by Resist Magicka instead of Sanctuary
Bug #6544: Far from world origin objects jitter when camera is still
Feature #890: OpenMW-CS: Column filtering Feature #890: OpenMW-CS: Column filtering
Feature #1465: "Reset" argument for AI functions
Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record
Feature #2780: A way to see current OpenMW version in the console Feature #2780: A way to see current OpenMW version in the console
Feature #3616: Allow Zoom levels on the World Map Feature #3616: Allow Zoom levels on the World Map
@ -82,12 +116,18 @@
Feature #6017: Separate persistent and temporary cell references when saving Feature #6017: Separate persistent and temporary cell references when saving
Feature #6032: Reverse-z depth buffer Feature #6032: Reverse-z depth buffer
Feature #6078: First person should not clear depth buffer Feature #6078: First person should not clear depth buffer
Feature #6128: Soft Particles
Feature #6161: Refactor Sky to use shaders and GLES/GL3 friendly Feature #6161: Refactor Sky to use shaders and GLES/GL3 friendly
Feature #6162: Refactor GUI to use shaders and to be GLES and GL3+ friendly Feature #6162: Refactor GUI to use shaders and to be GLES and GL3+ friendly
Feature #6189: Navigation mesh disk cache
Feature #6199: Support FBO Rendering Feature #6199: Support FBO Rendering
Feature #6248: Embedded error marker mesh
Feature #6249: Alpha testing support for Collada Feature #6249: Alpha testing support for Collada
Feature #6251: OpenMW-CS: Set instance movement based on camera zoom Feature #6251: OpenMW-CS: Set instance movement based on camera zoom
Feature #6288: Preserve the "blocked" record flag for referenceable objects. Feature #6288: Preserve the "blocked" record flag for referenceable objects.
Feature #6380: Commas are treated as whitespace in vanilla
Feature #6419: Topics shouldn't be greyed out if they can produce another topic reference
Feature #6534: Shader-based object texture blending
Task #6201: Remove the "Note: No relevant classes found. No output generated" warnings Task #6201: Remove the "Note: No relevant classes found. No output generated" warnings
Task #6264: Remove the old classes in animation.cpp Task #6264: Remove the old classes in animation.cpp
@ -224,6 +264,8 @@
Bug #6043: Actor can have torch missing when torch animation is played Bug #6043: Actor can have torch missing when torch animation is played
Bug #6047: Mouse bindings can be triggered during save loading Bug #6047: Mouse bindings can be triggered during save loading
Bug #6136: Game freezes when NPCs try to open doors that are about to be closed Bug #6136: Game freezes when NPCs try to open doors that are about to be closed
Bug #6142: Groundcover plugins change cells flags
Bug #6276: Deleted groundcover instances are not deleted in game
Bug #6294: Game crashes with empty pathgrid Bug #6294: Game crashes with empty pathgrid
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

View file

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

View file

@ -7,9 +7,10 @@ mkdir -p build
cd build cd build
cmake \ cmake \
-DCMAKE_TOOLCHAIN_FILE=/usr/lib/android-sdk/ndk-bundle/build/cmake/android.toolchain.cmake \ -DCMAKE_TOOLCHAIN_FILE=/android-ndk-r22/build/cmake/android.toolchain.cmake \
-DANDROID_ABI=arm64-v8a \ -DANDROID_ABI=arm64-v8a \
-DANDROID_PLATFORM=android-21 \ -DANDROID_PLATFORM=android-21 \
-DANDROID_LD=deprecated \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \ -DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DCMAKE_INSTALL_PREFIX=install \ -DCMAKE_INSTALL_PREFIX=install \
@ -21,8 +22,7 @@ cmake \
-DBUILD_ESSIMPORTER=0 \ -DBUILD_ESSIMPORTER=0 \
-DBUILD_OPENCS=0 \ -DBUILD_OPENCS=0 \
-DBUILD_WIZARD=0 \ -DBUILD_WIZARD=0 \
-DBUILD_NAVMESHTOOL=OFF \
-DOPENMW_USE_SYSTEM_MYGUI=OFF \ -DOPENMW_USE_SYSTEM_MYGUI=OFF \
-DOPENMW_USE_SYSTEM_OSG=OFF \
-DOPENMW_USE_SYSTEM_BULLET=OFF \
-DOPENMW_USE_SYSTEM_SQLITE3=OFF \ -DOPENMW_USE_SYSTEM_SQLITE3=OFF \
.. ..

View file

@ -46,7 +46,7 @@ fi
if [[ $CI_CLANG_TIDY ]]; then if [[ $CI_CLANG_TIDY ]]; then
CMAKE_CONF_OPTS+=( CMAKE_CONF_OPTS+=(
-DCMAKE_CXX_CLANG_TIDY='clang-tidy;-checks=-*,boost-*,clang-analyzer-*,concurrency-*,performance-*,-header-filter=.*,bugprone-*,misc-definitions-in-headers,misc-misplaced-const,misc-redundant-expression' -DCMAKE_CXX_CLANG_TIDY='clang-tidy;-checks=-*,boost-*,clang-analyzer-*,concurrency-*,performance-*,-header-filter=.*,bugprone-*,misc-definitions-in-headers,misc-misplaced-const,misc-redundant-expression,-bugprone-narrowing-conversions'
) )
fi fi
@ -71,6 +71,7 @@ if [[ "${BUILD_TESTS_ONLY}" ]]; then
-DBUILD_ESSIMPORTER=OFF \ -DBUILD_ESSIMPORTER=OFF \
-DBUILD_OPENCS=OFF \ -DBUILD_OPENCS=OFF \
-DBUILD_WIZARD=OFF \ -DBUILD_WIZARD=OFF \
-DBUILD_NAVMESHTOOL=OFF \
-DBUILD_UNITTESTS=${BUILD_UNITTESTS} \ -DBUILD_UNITTESTS=${BUILD_UNITTESTS} \
-DBUILD_BENCHMARKS=${BUILD_BENCHMARKS} \ -DBUILD_BENCHMARKS=${BUILD_BENCHMARKS} \
-DGTEST_ROOT="${GOOGLETEST_DIR}" \ -DGTEST_ROOT="${GOOGLETEST_DIR}" \

View file

@ -566,9 +566,9 @@ if [ -z $SKIP_DOWNLOAD ]; then
fi fi
# SDL2 # SDL2
download "SDL 2.0.12" \ download "SDL 2.0.18" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/SDL2-2.0.12.zip" \ "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/SDL2-2.0.18.zip" \
"SDL2-2.0.12.zip" "SDL2-2.0.18.zip"
# LZ4 # LZ4
download "LZ4 1.9.2" \ download "LZ4 1.9.2" \
@ -898,17 +898,17 @@ fi
cd $DEPS cd $DEPS
echo echo
# SDL2 # SDL2
printf "SDL 2.0.12... " printf "SDL 2.0.18... "
{ {
if [ -d SDL2-2.0.12 ]; then if [ -d SDL2-2.0.18 ]; then
printf "Exists. " printf "Exists. "
elif [ -z $SKIP_EXTRACT ]; then elif [ -z $SKIP_EXTRACT ]; then
rm -rf SDL2-2.0.12 rm -rf SDL2-2.0.18
eval 7z x -y SDL2-2.0.12.zip $STRIP eval 7z x -y SDL2-2.0.18.zip $STRIP
fi fi
export SDL2DIR="$(real_pwd)/SDL2-2.0.12" export SDL2DIR="$(real_pwd)/SDL2-2.0.18"
for config in ${CONFIGURATIONS[@]}; do for config in ${CONFIGURATIONS[@]}; do
add_runtime_dlls $config "$(pwd)/SDL2-2.0.12/lib/x${ARCHSUFFIX}/SDL2.dll" add_runtime_dlls $config "$(pwd)/SDL2-2.0.18/lib/x${ARCHSUFFIX}/SDL2.dll"
done done
echo Done. echo Done.
} }

View file

@ -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.14" \ -D CMAKE_OSX_DEPLOYMENT_TARGET="10.15" \
-D CMAKE_BUILD_TYPE=RELEASE \ -D CMAKE_BUILD_TYPE=RELEASE \
-D OPENMW_OSX_DEPLOYMENT=TRUE \ -D OPENMW_OSX_DEPLOYMENT=TRUE \
-D OPENMW_USE_SYSTEM_SQLITE3=OFF \ -D OPENMW_USE_SYSTEM_SQLITE3=OFF \

View file

@ -22,9 +22,8 @@ declare -rA GROUPED_DEPS=(
libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev
libsdl2-dev libqt5opengl5-dev libopenal-dev libunshield-dev libtinyxml-dev libsdl2-dev libqt5opengl5-dev libopenal-dev libunshield-dev libtinyxml-dev
libbullet-dev liblz4-dev libpng-dev libjpeg-dev libluajit-5.1-dev libbullet-dev liblz4-dev libpng-dev libjpeg-dev libluajit-5.1-dev
ca-certificates librecast-dev libsqlite3-dev ca-certificates
" "
# TODO: add librecastnavigation-dev when debian is ready
# 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 libsqlite3-dev" [openmw-deps-dynamic]="libmygui-dev libopenscenegraph-dev libsqlite3-dev"
@ -64,5 +63,7 @@ done
export APT_CACHE_DIR="${PWD}/apt-cache" export APT_CACHE_DIR="${PWD}/apt-cache"
set -x set -x
mkdir -pv "$APT_CACHE_DIR" mkdir -pv "$APT_CACHE_DIR"
apt-get update -yq apt-get update -yqq
apt-get -q -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends "${deps[@]}" apt-get -qq -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends software-properties-common >/dev/null
add-apt-repository -y ppa:openmw/openmw
apt-get -qq -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends "${deps[@]}" >/dev/null

View file

@ -37,6 +37,7 @@ option(BUILD_DOCS "Build documentation." OFF )
option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF) option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF)
option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF) option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF)
option(BUILD_BENCHMARKS "Build benchmarks with Google Benchmark" OFF) option(BUILD_BENCHMARKS "Build benchmarks with Google Benchmark" OFF)
option(BUILD_NAVMESHTOOL "Build navmesh tool" ON)
set(OpenGL_GL_PREFERENCE LEGACY) # Use LEGACY as we use GL2; GLNVD is for GL3 and up. set(OpenGL_GL_PREFERENCE LEGACY) # Use LEGACY as we use GL2; GLNVD is for GL3 and up.
@ -398,7 +399,7 @@ set(Boost_NO_BOOST_CMAKE ON)
find_package(Boost 1.6.2 REQUIRED COMPONENTS ${BOOST_COMPONENTS} OPTIONAL_COMPONENTS ${BOOST_OPTIONAL_COMPONENTS}) find_package(Boost 1.6.2 REQUIRED COMPONENTS ${BOOST_COMPONENTS} OPTIONAL_COMPONENTS ${BOOST_OPTIONAL_COMPONENTS})
if(OPENMW_USE_SYSTEM_MYGUI) if(OPENMW_USE_SYSTEM_MYGUI)
find_package(MyGUI 3.2.2 REQUIRED) find_package(MyGUI 3.4.1 REQUIRED)
endif() endif()
find_package(SDL2 2.0.9 REQUIRED) find_package(SDL2 2.0.9 REQUIRED)
find_package(OpenAL REQUIRED) find_package(OpenAL REQUIRED)
@ -414,7 +415,8 @@ else(USE_LUAJIT)
endif(USE_LUAJIT) endif(USE_LUAJIT)
# C++ library binding to Lua # C++ library binding to Lua
set(SOL_INCLUDE_DIRS ${OpenMW_SOURCE_DIR}/extern/sol3.2.2 ${OpenMW_SOURCE_DIR}/extern/sol_config) set(SOL_INCLUDE_DIR ${OpenMW_SOURCE_DIR}/extern/sol3.2.2)
set(SOL_CONFIG_DIR ${OpenMW_SOURCE_DIR}/extern/sol_config)
include_directories( include_directories(
BEFORE SYSTEM BEFORE SYSTEM
@ -426,7 +428,8 @@ include_directories(
${OPENGL_INCLUDE_DIR} ${OPENGL_INCLUDE_DIR}
${BULLET_INCLUDE_DIRS} ${BULLET_INCLUDE_DIRS}
${LUA_INCLUDE_DIR} ${LUA_INCLUDE_DIR}
${SOL_INCLUDE_DIRS} ${SOL_INCLUDE_DIR}
${SOL_CONFIG_DIR}
) )
link_directories(${SDL2_LIBRARY_DIRS} ${Boost_LIBRARY_DIRS}) link_directories(${SDL2_LIBRARY_DIRS} ${Boost_LIBRARY_DIRS})
@ -443,9 +446,8 @@ if (APPLE)
"${APP_BUNDLE_DIR}/Contents/Resources/OpenMW.icns" COPYONLY) "${APP_BUNDLE_DIR}/Contents/Resources/OpenMW.icns" COPYONLY)
endif (APPLE) endif (APPLE)
if (NOT APPLE) if (NOT APPLE) # this is modified for macOS use later in "apps/open[mw|cs]/CMakeLists.txt"
set(OPENMW_MYGUI_FILES_ROOT ${OpenMW_BINARY_DIR}) set(OPENMW_RESOURCES_ROOT ${OpenMW_BINARY_DIR})
set(OPENMW_SHADERS_ROOT ${OpenMW_BINARY_DIR})
endif () endif ()
add_subdirectory(files/) add_subdirectory(files/)
@ -602,6 +604,10 @@ if (BUILD_BENCHMARKS)
add_subdirectory(apps/benchmarks) add_subdirectory(apps/benchmarks)
endif() endif()
if (BUILD_NAVMESHTOOL)
add_subdirectory(apps/navmeshtool)
endif()
if (WIN32) if (WIN32)
if (MSVC) if (MSVC)
if (OPENMW_MP_BUILD) if (OPENMW_MP_BUILD)
@ -701,6 +707,10 @@ if (WIN32)
if (BUILD_BENCHMARKS) if (BUILD_BENCHMARKS)
set_target_properties(openmw_detournavigator_navmeshtilescache_benchmark PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") set_target_properties(openmw_detournavigator_navmeshtilescache_benchmark PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
endif() endif()
if (BUILD_NAVMESHTOOL)
set_target_properties(openmw-navmeshtool PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
endif()
endif(MSVC) endif(MSVC)
# TODO: At some point release builds should not use the console but rather write to a log file # TODO: At some point release builds should not use the console but rather write to a log file
@ -709,8 +719,10 @@ if (WIN32)
endif() endif()
if (BUILD_OPENMW AND APPLE) if (BUILD_OPENMW AND APPLE)
if (USE_LUAJIT)
# Without these flags LuaJit crashes on startup on OSX # Without these flags LuaJit crashes on startup on OSX
set_target_properties(openmw PROPERTIES LINK_FLAGS "-pagezero_size 10000 -image_base 100000000") set_target_properties(openmw PROPERTIES LINK_FLAGS "-pagezero_size 10000 -image_base 100000000")
endif(USE_LUAJIT)
target_compile_definitions(components PRIVATE GL_SILENCE_DEPRECATION=1) target_compile_definitions(components PRIVATE GL_SILENCE_DEPRECATION=1)
target_compile_definitions(openmw PRIVATE GL_SILENCE_DEPRECATION=1) target_compile_definitions(openmw PRIVATE GL_SILENCE_DEPRECATION=1)
endif() endif()
@ -941,6 +953,9 @@ elseif(NOT APPLE)
IF(BUILD_WIZARD) IF(BUILD_WIZARD)
INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-wizard" DESTINATION "${BINDIR}" ) INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-wizard" DESTINATION "${BINDIR}" )
ENDIF(BUILD_WIZARD) ENDIF(BUILD_WIZARD)
if(BUILD_NAVMESHTOOL)
install(PROGRAMS "${INSTALL_SOURCE}/openmw-navmeshtool" DESTINATION "${BINDIR}" )
endif()
# Install licenses # Install licenses
INSTALL(FILES "files/mygui/DejaVuFontLicense.txt" DESTINATION "${LICDIR}" ) INSTALL(FILES "files/mygui/DejaVuFontLicense.txt" DESTINATION "${LICDIR}" )

View file

@ -1,8 +1,6 @@
OpenMW OpenMW
====== ======
[![Coverity Scan Build Status](https://scan.coverity.com/projects/3740/badge.svg)](https://scan.coverity.com/projects/3740) [![pipeline status](https://gitlab.com/OpenMW/openmw/badges/master/pipeline.svg)](https://gitlab.com/OpenMW/openmw/commits/master)
OpenMW is an open-source game engine that supports playing Morrowind by Bethesda Softworks. You need to own the game for OpenMW to play Morrowind. OpenMW is an open-source game engine that supports playing Morrowind by Bethesda Softworks. You need to own the game for OpenMW to play Morrowind.
OpenMW also comes with OpenMW-CS, a replacement for Bethesda's Construction Set. OpenMW also comes with OpenMW-CS, a replacement for Bethesda's Construction Set.

View file

@ -25,18 +25,10 @@ namespace
}; };
template <typename Random> template <typename Random>
TilePosition generateTilePosition(int max, Random& random) osg::Vec2i generateVec2i(int max, Random& random)
{ {
std::uniform_int_distribution<int> distribution(0, max); std::uniform_int_distribution<int> distribution(0, max);
return TilePosition(distribution(random), distribution(random)); return osg::Vec2i(distribution(random), distribution(random));
}
template <typename Random>
TileBounds generateTileBounds(Random& random)
{
std::uniform_real_distribution<float> distribution(0.0, 1.0);
const osg::Vec2f min(distribution(random), distribution(random));
return TileBounds {min, min + osg::Vec2f(1.0, 1.0)};
} }
template <typename Random> template <typename Random>
@ -91,8 +83,7 @@ namespace
{ {
std::uniform_real_distribution<float> distribution(0.0, 1.0); std::uniform_real_distribution<float> distribution(0.0, 1.0);
std::generate_n(out, count, [&] { std::generate_n(out, count, [&] {
const osg::Vec3f shift(distribution(random), distribution(random), distribution(random)); return CellWater {generateVec2i(1000, random), Water {ESM::Land::REAL_SIZE, distribution(random)}};
return Cell {1, shift};
}); });
} }
@ -117,16 +108,18 @@ namespace
{ {
std::uniform_real_distribution<float> distribution(0.0, 1.0); std::uniform_real_distribution<float> distribution(0.0, 1.0);
Heightfield result; Heightfield result;
result.mBounds = generateTileBounds(random); result.mCellPosition = generateVec2i(1000, random);
result.mCellSize = ESM::Land::REAL_SIZE;
result.mMinHeight = distribution(random); result.mMinHeight = distribution(random);
result.mMaxHeight = result.mMinHeight + 1.0; result.mMaxHeight = result.mMinHeight + 1.0;
result.mShift = osg::Vec3f(distribution(random), distribution(random), distribution(random));
result.mScale = distribution(random);
result.mLength = static_cast<std::uint8_t>(ESM::Land::LAND_SIZE); result.mLength = static_cast<std::uint8_t>(ESM::Land::LAND_SIZE);
std::generate_n(std::back_inserter(result.mHeights), ESM::Land::LAND_NUM_VERTS, [&] std::generate_n(std::back_inserter(result.mHeights), ESM::Land::LAND_NUM_VERTS, [&]
{ {
return distribution(random); return distribution(random);
}); });
result.mOriginalSize = ESM::Land::LAND_SIZE;
result.mMinX = 0;
result.mMinY = 0;
return result; return result;
} }
@ -135,7 +128,8 @@ namespace
{ {
std::uniform_real_distribution<float> distribution(0.0, 1.0); std::uniform_real_distribution<float> distribution(0.0, 1.0);
FlatHeightfield result; FlatHeightfield result;
result.mBounds = generateTileBounds(random); result.mCellPosition = generateVec2i(1000, random);
result.mCellSize = ESM::Land::REAL_SIZE;
result.mHeight = distribution(random); result.mHeight = distribution(random);
return result; return result;
} }
@ -144,14 +138,14 @@ namespace
Key generateKey(std::size_t triangles, Random& random) Key generateKey(std::size_t triangles, Random& random)
{ {
const osg::Vec3f agentHalfExtents = generateAgentHalfExtents(0.5, 1.5, random); const osg::Vec3f agentHalfExtents = generateAgentHalfExtents(0.5, 1.5, random);
const TilePosition tilePosition = generateTilePosition(10000, random); const TilePosition tilePosition = generateVec2i(10000, random);
const std::size_t generation = std::uniform_int_distribution<std::size_t>(0, 100)(random); const std::size_t generation = std::uniform_int_distribution<std::size_t>(0, 100)(random);
const std::size_t revision = std::uniform_int_distribution<std::size_t>(0, 10000)(random); const std::size_t revision = std::uniform_int_distribution<std::size_t>(0, 10000)(random);
Mesh mesh = generateMesh(triangles, random); Mesh mesh = generateMesh(triangles, random);
std::vector<Cell> water; std::vector<CellWater> water;
generateWater(std::back_inserter(water), 1, random); generateWater(std::back_inserter(water), 1, random);
RecastMesh recastMesh(generation, revision, std::move(mesh), std::move(water), RecastMesh recastMesh(generation, revision, std::move(mesh), std::move(water),
{generateHeightfield(random)}, {generateFlatHeightfield(random)}); {generateHeightfield(random)}, {generateFlatHeightfield(random)}, {});
return Key {agentHalfExtents, tilePosition, std::move(recastMesh)}; return Key {agentHalfExtents, tilePosition, std::move(recastMesh)};
} }

View file

@ -30,7 +30,7 @@ void printAIPackage(const ESM::AIPackage& p)
{ {
std::cout << " Travel Coordinates: (" << p.mTravel.mX << "," std::cout << " Travel Coordinates: (" << p.mTravel.mX << ","
<< p.mTravel.mY << "," << p.mTravel.mZ << ")" << std::endl; << p.mTravel.mY << "," << p.mTravel.mZ << ")" << std::endl;
std::cout << " Travel Unknown: " << p.mTravel.mUnk << std::endl; std::cout << " Should repeat: " << p.mTravel.mShouldRepeat << std::endl;
} }
else if (p.mType == ESM::AI_Follow || p.mType == ESM::AI_Escort) else if (p.mType == ESM::AI_Follow || p.mType == ESM::AI_Escort)
{ {
@ -38,12 +38,12 @@ void printAIPackage(const ESM::AIPackage& p)
<< p.mTarget.mY << "," << p.mTarget.mZ << ")" << std::endl; << p.mTarget.mY << "," << p.mTarget.mZ << ")" << std::endl;
std::cout << " Duration: " << p.mTarget.mDuration << std::endl; std::cout << " Duration: " << p.mTarget.mDuration << std::endl;
std::cout << " Target ID: " << p.mTarget.mId.toString() << std::endl; std::cout << " Target ID: " << p.mTarget.mId.toString() << std::endl;
std::cout << " Unknown: " << p.mTarget.mUnk << std::endl; std::cout << " Should repeat: " << p.mTarget.mShouldRepeat << std::endl;
} }
else if (p.mType == ESM::AI_Activate) else if (p.mType == ESM::AI_Activate)
{ {
std::cout << " Name: " << p.mActivate.mName.toString() << std::endl; std::cout << " Name: " << p.mActivate.mName.toString() << std::endl;
std::cout << " Activate Unknown: " << p.mActivate.mUnk << std::endl; std::cout << " Should repeat: " << p.mActivate.mShouldRepeat << std::endl;
} }
else { else {
std::cout << " BadPackage: " << Misc::StringUtils::format("0x%08X", p.mType) << std::endl; std::cout << " BadPackage: " << Misc::StringUtils::format("0x%08X", p.mType) << std::endl;

View file

@ -2,7 +2,6 @@
#include <stdexcept> #include <stdexcept>
#include <algorithm> #include <algorithm>
#include <climits> // INT_MIN
#include <osgDB/WriteFile> #include <osgDB/WriteFile>
@ -371,7 +370,7 @@ namespace ESSImport
if (cellref.mHasACDT) if (cellref.mHasACDT)
convertACDT(cellref.mACDT, objstate.mCreatureStats); convertACDT(cellref.mACDT, objstate.mCreatureStats);
else else
objstate.mCreatureStats.mGoldPool = INT_MIN; // HACK: indicates no ACDT objstate.mCreatureStats.mMissingACDT = true;
if (cellref.mHasACSC) if (cellref.mHasACSC)
convertACSC(cellref.mACSC, objstate.mCreatureStats); convertACSC(cellref.mACSC, objstate.mCreatureStats);
convertNpcData(cellref, objstate.mNpcStats); convertNpcData(cellref, objstate.mNpcStats);
@ -414,7 +413,7 @@ namespace ESSImport
if (cellref.mHasACDT) if (cellref.mHasACDT)
convertACDT(cellref.mACDT, objstate.mCreatureStats); convertACDT(cellref.mACDT, objstate.mCreatureStats);
else else
objstate.mCreatureStats.mGoldPool = INT_MIN; // HACK: indicates no ACDT objstate.mCreatureStats.mMissingACDT = true;
if (cellref.mHasACSC) if (cellref.mHasACSC)
convertACSC(cellref.mACSC, objstate.mCreatureStats); convertACSC(cellref.mACSC, objstate.mCreatureStats);
convertCREC(crecIt->second, objstate); convertCREC(crecIt->second, objstate);

View file

@ -1,6 +1,7 @@
#include "advancedpage.hpp" #include "advancedpage.hpp"
#include <array> #include <array>
#include <string>
#include <components/config/gamesettings.hpp> #include <components/config/gamesettings.hpp>
#include <QFileDialog> #include <QFileDialog>
@ -20,13 +21,13 @@ Launcher::AdvancedPage::AdvancedPage(Config::GameSettings &gameSettings, QWidget
setObjectName ("AdvancedPage"); setObjectName ("AdvancedPage");
setupUi(this); setupUi(this);
for(const char * name : Launcher::enumerateOpenALDevices()) for(const std::string& name : Launcher::enumerateOpenALDevices())
{ {
audioDeviceSelectorComboBox->addItem(QString::fromUtf8(name), QString::fromUtf8(name)); audioDeviceSelectorComboBox->addItem(QString::fromStdString(name), QString::fromStdString(name));
} }
for(const char * name : Launcher::enumerateOpenALDevicesHrtf()) for(const std::string& name : Launcher::enumerateOpenALDevicesHrtf())
{ {
hrtfProfileSelectorComboBox->addItem(QString::fromUtf8(name), QString::fromUtf8(name)); hrtfProfileSelectorComboBox->addItem(QString::fromStdString(name), QString::fromStdString(name));
} }
loadSettings(); loadSettings();
@ -117,6 +118,11 @@ bool Launcher::AdvancedPage::loadSettings()
loadSettingBool(autoUseTerrainSpecularMapsCheckBox, "auto use terrain specular maps", "Shaders"); loadSettingBool(autoUseTerrainSpecularMapsCheckBox, "auto use terrain specular maps", "Shaders");
loadSettingBool(bumpMapLocalLightingCheckBox, "apply lighting to environment maps", "Shaders"); loadSettingBool(bumpMapLocalLightingCheckBox, "apply lighting to environment maps", "Shaders");
loadSettingBool(radialFogCheckBox, "radial fog", "Shaders"); loadSettingBool(radialFogCheckBox, "radial fog", "Shaders");
loadSettingBool(softParticlesCheckBox, "soft particles", "Shaders");
loadSettingBool(antialiasAlphaTestCheckBox, "antialias alpha test", "Shaders");
if (Settings::Manager::getInt("antialiasing", "Video") == 0) {
antialiasAlphaTestCheckBox->setCheckState(Qt::Unchecked);
}
loadSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game"); loadSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game");
connect(animSourcesCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotAnimSourcesToggled(bool))); connect(animSourcesCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotAnimSourcesToggled(bool)));
loadSettingBool(animSourcesCheckBox, "use additional anim sources", "Game"); loadSettingBool(animSourcesCheckBox, "use additional anim sources", "Game");
@ -137,6 +143,8 @@ bool Launcher::AdvancedPage::loadSettings()
loadSettingBool(activeGridObjectPagingCheckBox, "object paging active grid", "Terrain"); loadSettingBool(activeGridObjectPagingCheckBox, "object paging active grid", "Terrain");
viewingDistanceComboBox->setValue(convertToCells(Settings::Manager::getInt("viewing distance", "Camera"))); viewingDistanceComboBox->setValue(convertToCells(Settings::Manager::getInt("viewing distance", "Camera")));
objectPagingMinSizeComboBox->setValue(Settings::Manager::getDouble("object paging min size", "Terrain")); objectPagingMinSizeComboBox->setValue(Settings::Manager::getDouble("object paging min size", "Terrain"));
loadSettingBool(nightDaySwitchesCheckBox, "day night switches", "Game");
} }
// Audio // Audio
@ -207,7 +215,7 @@ bool Launcher::AdvancedPage::loadSettings()
{ {
// Saves // Saves
loadSettingBool(timePlayedCheckbox, "timeplayed", "Saves"); loadSettingBool(timePlayedCheckbox, "timeplayed", "Saves");
maximumQuicksavesComboBox->setValue(Settings::Manager::getInt("max quicksaves", "Saves")); loadSettingInt(maximumQuicksavesComboBox,"max quicksaves", "Saves");
// Other Settings // Other Settings
QString screenshotFormatString = QString::fromStdString(Settings::Manager::getString("screenshot format", "General")).toUpper(); QString screenshotFormatString = QString::fromStdString(Settings::Manager::getString("screenshot format", "General")).toUpper();
@ -252,14 +260,10 @@ void Launcher::AdvancedPage::saveSettings()
saveSettingBool(normaliseRaceSpeedCheckBox, "normalise race speed", "Game"); saveSettingBool(normaliseRaceSpeedCheckBox, "normalise race speed", "Game");
saveSettingBool(swimUpwardCorrectionCheckBox, "swim upward correction", "Game"); saveSettingBool(swimUpwardCorrectionCheckBox, "swim upward correction", "Game");
saveSettingBool(avoidCollisionsCheckBox, "NPCs avoid collisions", "Game"); saveSettingBool(avoidCollisionsCheckBox, "NPCs avoid collisions", "Game");
int unarmedFactorsStrengthIndex = unarmedFactorsStrengthComboBox->currentIndex(); saveSettingInt(unarmedFactorsStrengthComboBox, "strength influences hand to hand", "Game");
if (unarmedFactorsStrengthIndex != Settings::Manager::getInt("strength influences hand to hand", "Game"))
Settings::Manager::setInt("strength influences hand to hand", "Game", unarmedFactorsStrengthIndex);
saveSettingBool(stealingFromKnockedOutCheckBox, "always allow stealing from knocked out actors", "Game"); saveSettingBool(stealingFromKnockedOutCheckBox, "always allow stealing from knocked out actors", "Game");
saveSettingBool(enableNavigatorCheckBox, "enable", "Navigator"); saveSettingBool(enableNavigatorCheckBox, "enable", "Navigator");
int numPhysicsThreads = physicsThreadsSpinBox->value(); saveSettingInt(physicsThreadsSpinBox, "async num threads", "Physics");
if (numPhysicsThreads != Settings::Manager::getInt("async num threads", "Physics"))
Settings::Manager::setInt("async num threads", "Physics", numPhysicsThreads);
} }
// Visuals // Visuals
@ -270,6 +274,8 @@ void Launcher::AdvancedPage::saveSettings()
saveSettingBool(autoUseTerrainSpecularMapsCheckBox, "auto use terrain specular maps", "Shaders"); saveSettingBool(autoUseTerrainSpecularMapsCheckBox, "auto use terrain specular maps", "Shaders");
saveSettingBool(bumpMapLocalLightingCheckBox, "apply lighting to environment maps", "Shaders"); saveSettingBool(bumpMapLocalLightingCheckBox, "apply lighting to environment maps", "Shaders");
saveSettingBool(radialFogCheckBox, "radial fog", "Shaders"); saveSettingBool(radialFogCheckBox, "radial fog", "Shaders");
saveSettingBool(softParticlesCheckBox, "soft particles", "Shaders");
saveSettingBool(antialiasAlphaTestCheckBox, "antialias alpha test", "Shaders");
saveSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game"); saveSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game");
saveSettingBool(animSourcesCheckBox, "use additional anim sources", "Game"); saveSettingBool(animSourcesCheckBox, "use additional anim sources", "Game");
saveSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game"); saveSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game");
@ -294,6 +300,8 @@ void Launcher::AdvancedPage::saveSettings()
double objectPagingMinSize = objectPagingMinSizeComboBox->value(); double objectPagingMinSize = objectPagingMinSizeComboBox->value();
if (objectPagingMinSize != Settings::Manager::getDouble("object paging min size", "Terrain")) if (objectPagingMinSize != Settings::Manager::getDouble("object paging min size", "Terrain"))
Settings::Manager::setDouble("object paging min size", "Terrain", objectPagingMinSize); Settings::Manager::setDouble("object paging min size", "Terrain", objectPagingMinSize);
saveSettingBool(nightDaySwitchesCheckBox, "day night switches", "Game");
} }
// Audio // Audio
@ -349,9 +357,7 @@ void Launcher::AdvancedPage::saveSettings()
saveSettingBool(showMeleeInfoCheckBox, "show melee info", "Game"); saveSettingBool(showMeleeInfoCheckBox, "show melee info", "Game");
saveSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game"); saveSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game");
saveSettingBool(changeDialogTopicsCheckBox, "color topic enable", "GUI"); saveSettingBool(changeDialogTopicsCheckBox, "color topic enable", "GUI");
int showOwnedCurrentIndex = showOwnedComboBox->currentIndex(); saveSettingInt(showOwnedComboBox,"show owned", "Game");
if (showOwnedCurrentIndex != Settings::Manager::getInt("show owned", "Game"))
Settings::Manager::setInt("show owned", "Game", showOwnedCurrentIndex);
saveSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI"); saveSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI");
saveSettingBool(useZoomOnMapCheckBox, "allow zooming", "Map"); saveSettingBool(useZoomOnMapCheckBox, "allow zooming", "Map");
saveSettingBool(graphicHerbalismCheckBox, "graphic herbalism", "Game"); saveSettingBool(graphicHerbalismCheckBox, "graphic herbalism", "Game");
@ -370,11 +376,7 @@ void Launcher::AdvancedPage::saveSettings()
{ {
// Saves Settings // Saves Settings
saveSettingBool(timePlayedCheckbox, "timeplayed", "Saves"); saveSettingBool(timePlayedCheckbox, "timeplayed", "Saves");
int maximumQuicksaves = maximumQuicksavesComboBox->value(); saveSettingInt(maximumQuicksavesComboBox, "max quicksaves", "Saves");
if (maximumQuicksaves != Settings::Manager::getInt("max quicksaves", "Saves"))
{
Settings::Manager::setInt("max quicksaves", "Saves", maximumQuicksaves);
}
// Other Settings // Other Settings
std::string screenshotFormatString = screenshotFormatComboBox->currentText().toLower().toStdString(); std::string screenshotFormatString = screenshotFormatComboBox->currentText().toLower().toStdString();
@ -416,6 +418,32 @@ void Launcher::AdvancedPage::saveSettingBool(QCheckBox *checkbox, const std::str
Settings::Manager::setBool(setting, group, cValue); Settings::Manager::setBool(setting, group, cValue);
} }
void Launcher::AdvancedPage::loadSettingInt(QComboBox *comboBox, const std::string &setting, const std::string &group)
{
int currentIndex = Settings::Manager::getInt(setting, group);
comboBox->setCurrentIndex(currentIndex);
}
void Launcher::AdvancedPage::saveSettingInt(QComboBox *comboBox, const std::string &setting, const std::string &group)
{
int currentIndex = comboBox->currentIndex();
if (currentIndex != Settings::Manager::getInt(setting, group))
Settings::Manager::setInt(setting, group, currentIndex);
}
void Launcher::AdvancedPage::loadSettingInt(QSpinBox *spinBox, const std::string &setting, const std::string &group)
{
int value = Settings::Manager::getInt(setting, group);
spinBox->setValue(value);
}
void Launcher::AdvancedPage::saveSettingInt(QSpinBox *spinBox, const std::string &setting, const std::string &group)
{
int value = spinBox->value();
if (value != Settings::Manager::getInt(setting, group))
Settings::Manager::setInt(setting, group, value);
}
void Launcher::AdvancedPage::slotLoadedCellsChanged(QStringList cellNames) void Launcher::AdvancedPage::slotLoadedCellsChanged(QStringList cellNames)
{ {
loadCellsForAutocomplete(cellNames); loadCellsForAutocomplete(cellNames);

View file

@ -41,8 +41,12 @@ namespace Launcher
* @param filePaths the file paths of the content files to be examined * @param filePaths the file paths of the content files to be examined
*/ */
void loadCellsForAutocomplete(QStringList filePaths); void loadCellsForAutocomplete(QStringList filePaths);
void loadSettingBool(QCheckBox *checkbox, const std::string& setting, const std::string& group); static void loadSettingBool(QCheckBox *checkbox, const std::string& setting, const std::string& group);
void saveSettingBool(QCheckBox *checkbox, const std::string& setting, const std::string& group); static void saveSettingBool(QCheckBox *checkbox, const std::string& setting, const std::string& group);
static void loadSettingInt(QComboBox *comboBox, const std::string& setting, const std::string& group);
static void saveSettingInt(QComboBox *comboBox, const std::string& setting, const std::string& group);
static void loadSettingInt(QSpinBox *spinBox, const std::string& setting, const std::string& group);
static void saveSettingInt(QSpinBox *spinBox, const std::string& setting, const std::string& group);
}; };
} }
#endif #endif

View file

@ -1,4 +1,5 @@
#include "datafilespage.hpp" #include "datafilespage.hpp"
#include "maindialog.hpp"
#include <QDebug> #include <QDebug>
@ -8,6 +9,7 @@
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
#include <thread> #include <thread>
#include <mutex> #include <mutex>
#include <algorithm>
#include <apps/launcher/utils/cellnameloader.hpp> #include <apps/launcher/utils/cellnameloader.hpp>
#include <components/files/configurationmanager.hpp> #include <components/files/configurationmanager.hpp>
@ -24,11 +26,14 @@
const char *Launcher::DataFilesPage::mDefaultContentListName = "Default"; const char *Launcher::DataFilesPage::mDefaultContentListName = "Default";
Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, Config::LauncherSettings &launcherSettings, QWidget *parent) Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings,
Config::LauncherSettings &launcherSettings, MainDialog *parent)
: QWidget(parent) : QWidget(parent)
, mMainDialog(parent)
, mCfgMgr(cfg) , mCfgMgr(cfg)
, mGameSettings(gameSettings) , mGameSettings(gameSettings)
, mLauncherSettings(launcherSettings) , mLauncherSettings(launcherSettings)
, mNavMeshToolInvoker(new Process::ProcessInvoker(this))
{ {
ui.setupUi (this); ui.setupUi (this);
setObjectName ("DataFilesPage"); setObjectName ("DataFilesPage");
@ -57,8 +62,6 @@ Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config:
void Launcher::DataFilesPage::buildView() void Launcher::DataFilesPage::buildView()
{ {
ui.verticalLayout->insertWidget (0, mSelector->uiWidget());
QToolButton * refreshButton = mSelector->refreshButton(); QToolButton * refreshButton = mSelector->refreshButton();
//tool buttons //tool buttons
@ -89,6 +92,13 @@ void Launcher::DataFilesPage::buildView()
this, SLOT (slotProfileChangedByUser(QString, QString))); this, SLOT (slotProfileChangedByUser(QString, QString)));
connect(ui.refreshDataFilesAction, SIGNAL(triggered()),this, SLOT(slotRefreshButtonClicked())); connect(ui.refreshDataFilesAction, SIGNAL(triggered()),this, SLOT(slotRefreshButtonClicked()));
connect(ui.updateNavMeshButton, SIGNAL(clicked()), this, SLOT(startNavMeshTool()));
connect(ui.cancelNavMeshButton, SIGNAL(clicked()), this, SLOT(killNavMeshTool()));
connect(mNavMeshToolInvoker->getProcess(), SIGNAL(readyReadStandardOutput()), this, SLOT(updateNavMeshProgress()));
connect(mNavMeshToolInvoker->getProcess(), SIGNAL(readyReadStandardError()), this, SLOT(updateNavMeshProgress()));
connect(mNavMeshToolInvoker->getProcess(), SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(navMeshToolFinished(int, QProcess::ExitStatus)));
} }
bool Launcher::DataFilesPage::loadSettings() bool Launcher::DataFilesPage::loadSettings()
@ -121,6 +131,7 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
for (const QString &path : paths) for (const QString &path : paths)
mSelector->addFiles(path); mSelector->addFiles(path);
mSelector->sortFiles();
PathIterator pathIterator(paths); PathIterator pathIterator(paths);
@ -410,3 +421,62 @@ void Launcher::DataFilesPage::reloadCells(QStringList selectedFiles)
std::sort(cellNamesList.begin(), cellNamesList.end()); std::sort(cellNamesList.begin(), cellNamesList.end());
emit signalLoadedCellsChanged(cellNamesList); emit signalLoadedCellsChanged(cellNamesList);
} }
void Launcher::DataFilesPage::startNavMeshTool()
{
mMainDialog->writeSettings();
ui.navMeshLogPlainTextEdit->clear();
ui.navMeshProgressBar->setValue(0);
ui.navMeshProgressBar->setMaximum(1);
if (!mNavMeshToolInvoker->startProcess(QLatin1String("openmw-navmeshtool")))
return;
ui.cancelNavMeshButton->setEnabled(true);
ui.navMeshProgressBar->setEnabled(true);
}
void Launcher::DataFilesPage::killNavMeshTool()
{
mNavMeshToolInvoker->killProcess();
}
void Launcher::DataFilesPage::updateNavMeshProgress()
{
QProcess& process = *mNavMeshToolInvoker->getProcess();
QString text;
while (process.canReadLine())
{
const QByteArray line = process.readLine();
const auto end = std::find_if(line.rbegin(), line.rend(), [] (auto v) { return v != '\n' && v != '\r'; });
text = QString::fromUtf8(line.mid(0, line.size() - (end - line.rbegin())));
ui.navMeshLogPlainTextEdit->appendPlainText(text);
}
const QRegularExpression pattern(R"([\( ](\d+)/(\d+)[\) ])");
QRegularExpressionMatch match = pattern.match(text);
if (!match.hasMatch())
return;
int value = match.captured(1).toInt();
const int maximum = match.captured(2).toInt();
if (text.contains("cell"))
ui.navMeshProgressBar->setMaximum(maximum * 100);
else if (maximum > ui.navMeshProgressBar->maximum())
ui.navMeshProgressBar->setMaximum(maximum);
else
value += static_cast<int>(std::round(
(ui.navMeshProgressBar->maximum() - maximum)
* (static_cast<float>(value) / static_cast<float>(maximum))
));
ui.navMeshProgressBar->setValue(value);
}
void Launcher::DataFilesPage::navMeshToolFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
updateNavMeshProgress();
ui.navMeshLogPlainTextEdit->appendPlainText(QString::fromUtf8(mNavMeshToolInvoker->getProcess()->readAll()));
if (exitCode == 0 && exitStatus == QProcess::ExitStatus::NormalExit)
ui.navMeshProgressBar->setValue(ui.navMeshProgressBar->maximum());
ui.cancelNavMeshButton->setEnabled(false);
ui.navMeshProgressBar->setEnabled(false);
}

View file

@ -2,6 +2,9 @@
#define DATAFILESPAGE_H #define DATAFILESPAGE_H
#include "ui_datafilespage.h" #include "ui_datafilespage.h"
#include <components/process/processinvoker.hpp>
#include <QWidget> #include <QWidget>
@ -19,6 +22,7 @@ namespace Config { class GameSettings;
namespace Launcher namespace Launcher
{ {
class MainDialog;
class TextInputDialog; class TextInputDialog;
class ProfilesComboBox; class ProfilesComboBox;
@ -31,7 +35,7 @@ namespace Launcher
public: public:
explicit DataFilesPage (Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, explicit DataFilesPage (Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings,
Config::LauncherSettings &launcherSettings, QWidget *parent = nullptr); Config::LauncherSettings &launcherSettings, MainDialog *parent = nullptr);
QAbstractItemModel* profilesModel() const; QAbstractItemModel* profilesModel() const;
@ -69,12 +73,18 @@ namespace Launcher
void on_cloneProfileAction_triggered(); void on_cloneProfileAction_triggered();
void on_deleteProfileAction_triggered(); void on_deleteProfileAction_triggered();
void startNavMeshTool();
void killNavMeshTool();
void updateNavMeshProgress();
void navMeshToolFinished(int exitCode, QProcess::ExitStatus exitStatus);
public: public:
/// Content List that is always present /// Content List that is always present
const static char *mDefaultContentListName; const static char *mDefaultContentListName;
private: private:
MainDialog *mMainDialog;
TextInputDialog *mNewProfileDialog; TextInputDialog *mNewProfileDialog;
TextInputDialog *mCloneProfileDialog; TextInputDialog *mCloneProfileDialog;
@ -87,6 +97,8 @@ namespace Launcher
QStringList previousSelectedFiles; QStringList previousSelectedFiles;
QString mDataLocal; QString mDataLocal;
Process::ProcessInvoker* mNavMeshToolInvoker;
void buildView(); void buildView();
void setProfile (int index, bool savePrevious); void setProfile (int index, bool savePrevious);
void setProfile (const QString &previous, const QString &current, bool savePrevious); void setProfile (const QString &previous, const QString &current, bool savePrevious);

View file

@ -47,7 +47,6 @@ Launcher::GraphicsPage::GraphicsPage(QWidget *parent)
connect(screenComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(screenChanged(int))); connect(screenComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(screenChanged(int)));
connect(framerateLimitCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotFramerateLimitToggled(bool))); connect(framerateLimitCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotFramerateLimitToggled(bool)));
connect(shadowDistanceCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotShadowDistLimitToggled(bool))); connect(shadowDistanceCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotShadowDistLimitToggled(bool)));
} }
bool Launcher::GraphicsPage::setupSDL() bool Launcher::GraphicsPage::setupSDL()

View file

@ -146,7 +146,6 @@ void Launcher::MainDialog::createPages()
connect(mDataFilesPage, SIGNAL(signalProfileChanged(int)), mPlayPage, SLOT(setProfilesIndex(int))); connect(mDataFilesPage, SIGNAL(signalProfileChanged(int)), mPlayPage, SLOT(setProfilesIndex(int)));
// Using Qt::QueuedConnection because signal is emitted in a subthread and slot is in the main thread // Using Qt::QueuedConnection because signal is emitted in a subthread and slot is in the main thread
connect(mDataFilesPage, SIGNAL(signalLoadedCellsChanged(QStringList)), mAdvancedPage, SLOT(slotLoadedCellsChanged(QStringList)), Qt::QueuedConnection); connect(mDataFilesPage, SIGNAL(signalLoadedCellsChanged(QStringList)), mAdvancedPage, SLOT(slotLoadedCellsChanged(QStringList)), Qt::QueuedConnection);
} }
Launcher::FirstRunDialogResult Launcher::MainDialog::showFirstRunDialog() Launcher::FirstRunDialogResult Launcher::MainDialog::showFirstRunDialog()

View file

@ -9,9 +9,9 @@
#define ALC_ALL_DEVICES_SPECIFIER 0x1013 #define ALC_ALL_DEVICES_SPECIFIER 0x1013
#endif #endif
std::vector<const char *> Launcher::enumerateOpenALDevices() std::vector<std::string> Launcher::enumerateOpenALDevices()
{ {
std::vector<const char *> devlist; std::vector<std::string> devlist;
const ALCchar *devnames; const ALCchar *devnames;
if(alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT")) if(alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT"))
@ -31,9 +31,9 @@ std::vector<const char *> Launcher::enumerateOpenALDevices()
return devlist; return devlist;
} }
std::vector<const char *> Launcher::enumerateOpenALDevicesHrtf() std::vector<std::string> Launcher::enumerateOpenALDevicesHrtf()
{ {
std::vector<const char *> ret; std::vector<std::string> ret;
ALCdevice *device = alcOpenDevice(nullptr); ALCdevice *device = alcOpenDevice(nullptr);
if(device) if(device)

View file

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

View file

@ -0,0 +1,22 @@
set(NAVMESHTOOL
worldspacedata.cpp
navmesh.cpp
main.cpp
)
source_group(apps\\navmeshtool FILES ${NAVMESHTOOL})
openmw_add_executable(openmw-navmeshtool ${NAVMESHTOOL})
target_link_libraries(openmw-navmeshtool
${Boost_PROGRAM_OPTIONS_LIBRARY}
components
)
if (BUILD_WITH_CODE_COVERAGE)
add_definitions(--coverage)
target_link_libraries(openmw-navmeshtool gcov)
endif()
if (WIN32)
install(TARGETS openmw-navmeshtool RUNTIME DESTINATION ".")
endif()

209
apps/navmeshtool/main.cpp Normal file
View file

@ -0,0 +1,209 @@
#include "worldspacedata.hpp"
#include "navmesh.hpp"
#include <components/debug/debugging.hpp>
#include <components/detournavigator/navmeshdb.hpp>
#include <components/detournavigator/recastglobalallocator.hpp>
#include <components/esm/esmreader.hpp>
#include <components/esm/variant.hpp>
#include <components/esmloader/esmdata.hpp>
#include <components/esmloader/load.hpp>
#include <components/fallback/fallback.hpp>
#include <components/fallback/validate.hpp>
#include <components/files/configurationmanager.hpp>
#include <components/resource/bulletshapemanager.hpp>
#include <components/resource/imagemanager.hpp>
#include <components/resource/niffilemanager.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/settings/settings.hpp>
#include <components/to_utf8/to_utf8.hpp>
#include <components/version/version.hpp>
#include <components/vfs/manager.hpp>
#include <components/vfs/registerarchives.hpp>
#include <osg/Vec3f>
#include <boost/filesystem.hpp>
#include <boost/program_options.hpp>
#include <cstddef>
#include <stdexcept>
#include <string>
#include <thread>
#include <vector>
namespace NavMeshTool
{
namespace
{
namespace bpo = boost::program_options;
using StringsVector = std::vector<std::string>;
bpo::options_description makeOptionsDescription()
{
using Fallback::FallbackMap;
bpo::options_description result;
result.add_options()
("help", "print help message")
("version", "print version information and quit")
("data", bpo::value<Files::MaybeQuotedPathContainer>()->default_value(Files::MaybeQuotedPathContainer(), "data")
->multitoken()->composing(), "set data directories (later directories have higher priority)")
("data-local", bpo::value<Files::MaybeQuotedPathContainer::value_type>()->default_value(Files::MaybeQuotedPathContainer::value_type(), ""),
"set local data directory (highest priority)")
("fallback-archive", bpo::value<StringsVector>()->default_value(StringsVector(), "fallback-archive")
->multitoken()->composing(), "set fallback BSA archives (later archives have higher priority)")
("resources", bpo::value<Files::MaybeQuotedPath>()->default_value(Files::MaybeQuotedPath(), "resources"),
"set resources directory")
("content", bpo::value<StringsVector>()->default_value(StringsVector(), "")
->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon/omwscripts")
("fs-strict", bpo::value<bool>()->implicit_value(true)
->default_value(false), "strict file system handling (no case folding)")
("encoding", bpo::value<std::string>()->
default_value("win1252"),
"Character encoding used in OpenMW game messages:\n"
"\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n"
"\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n"
"\n\twin1252 - Western European (Latin) alphabet, used by default")
("fallback", bpo::value<Fallback::FallbackMap>()->default_value(Fallback::FallbackMap(), "")
->multitoken()->composing(), "fallback values")
("threads", bpo::value<std::size_t>()->default_value(std::max<std::size_t>(std::thread::hardware_concurrency() - 1, 1)),
"number of threads for parallel processing")
("process-interior-cells", bpo::value<bool>()->implicit_value(true)
->default_value(false), "build navmesh for interior cells")
;
return result;
}
void loadSettings(const Files::ConfigurationManager& config, Settings::Manager& settings)
{
const std::string localDefault = (config.getLocalPath() / "defaults.bin").string();
const std::string globalDefault = (config.getGlobalPath() / "defaults.bin").string();
if (boost::filesystem::exists(localDefault))
settings.loadDefault(localDefault);
else if (boost::filesystem::exists(globalDefault))
settings.loadDefault(globalDefault);
else
throw std::runtime_error("No default settings file found! Make sure the file \"defaults.bin\" was properly installed.");
const std::string settingsPath = (config.getUserConfigPath() / "settings.cfg").string();
if (boost::filesystem::exists(settingsPath))
settings.loadUser(settingsPath);
}
int runNavMeshTool(int argc, char *argv[])
{
bpo::options_description desc = makeOptionsDescription();
bpo::parsed_options options = bpo::command_line_parser(argc, argv)
.options(desc).allow_unregistered().run();
bpo::variables_map variables;
bpo::store(options, variables);
bpo::notify(variables);
if (variables.find("help") != variables.end())
{
getRawStdout() << desc << std::endl;
return 0;
}
Files::ConfigurationManager config;
bpo::variables_map composingVariables = Files::separateComposingVariables(variables, desc);
config.readConfiguration(variables, desc);
Files::mergeComposingVariables(variables, composingVariables, desc);
const std::string encoding(variables["encoding"].as<std::string>());
Log(Debug::Info) << ToUTF8::encodingUsingMessage(encoding);
ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(encoding));
Files::PathContainer dataDirs(asPathContainer(variables["data"].as<Files::MaybeQuotedPathContainer>()));
auto local = variables["data-local"].as<Files::MaybeQuotedPathContainer::value_type>();
if (!local.empty())
dataDirs.push_back(std::move(local));
config.processPaths(dataDirs);
const auto fsStrict = variables["fs-strict"].as<bool>();
const auto resDir = variables["resources"].as<Files::MaybeQuotedPath>();
Version::Version v = Version::getOpenmwVersion(resDir.string());
Log(Debug::Info) << v.describe();
dataDirs.insert(dataDirs.begin(), resDir / "vfs");
const auto fileCollections = Files::Collections(dataDirs, !fsStrict);
const auto archives = variables["fallback-archive"].as<StringsVector>();
const auto contentFiles = variables["content"].as<StringsVector>();
const std::size_t threadsNumber = variables["threads"].as<std::size_t>();
if (threadsNumber < 1)
{
std::cerr << "Invalid threads number: " << threadsNumber << ", expected >= 1";
return -1;
}
const bool processInteriorCells = variables["process-interior-cells"].as<bool>();
Fallback::Map::init(variables["fallback"].as<Fallback::FallbackMap>().mMap);
VFS::Manager vfs(fsStrict);
VFS::registerArchives(&vfs, fileCollections, archives, true);
Settings::Manager settings;
loadSettings(config, settings);
const osg::Vec3f agentHalfExtents = Settings::Manager::getVector3("default actor pathfind half extents", "Game");
DetourNavigator::NavMeshDb db((config.getUserDataPath() / "navmesh.db").string());
std::vector<ESM::ESMReader> readers(contentFiles.size());
EsmLoader::Query query;
query.mLoadActivators = true;
query.mLoadCells = true;
query.mLoadContainers = true;
query.mLoadDoors = true;
query.mLoadGameSettings = true;
query.mLoadLands = true;
query.mLoadStatics = true;
const EsmLoader::EsmData esmData = EsmLoader::loadEsmData(query, contentFiles, fileCollections, readers, &encoder);
Resource::ImageManager imageManager(&vfs);
Resource::NifFileManager nifFileManager(&vfs);
Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager);
Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager);
DetourNavigator::RecastGlobalAllocator::init();
DetourNavigator::Settings navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager();
navigatorSettings.mRecast.mSwimHeightScale = EsmLoader::getGameSetting(esmData.mGameSettings, "fSwimHeightScale").getFloat();
WorldspaceData cellsData = gatherWorldspaceData(navigatorSettings, readers, vfs, bulletShapeManager,
esmData, processInteriorCells);
generateAllNavMeshTiles(agentHalfExtents, navigatorSettings, threadsNumber, cellsData, std::move(db));
Log(Debug::Info) << "Done";
return 0;
}
}
}
int main(int argc, char *argv[])
{
return wrapApplication(NavMeshTool::runNavMeshTool, argc, argv, "NavMeshTool");
}

View file

@ -0,0 +1,212 @@
#include "navmesh.hpp"
#include "worldspacedata.hpp"
#include <components/bullethelpers/aabb.hpp>
#include <components/debug/debuglog.hpp>
#include <components/detournavigator/generatenavmeshtile.hpp>
#include <components/detournavigator/gettilespositions.hpp>
#include <components/detournavigator/navmeshdb.hpp>
#include <components/detournavigator/navmeshdbutils.hpp>
#include <components/detournavigator/offmeshconnection.hpp>
#include <components/detournavigator/offmeshconnectionsmanager.hpp>
#include <components/detournavigator/preparednavmeshdata.hpp>
#include <components/detournavigator/recastmesh.hpp>
#include <components/detournavigator/recastmeshprovider.hpp>
#include <components/detournavigator/serialization.hpp>
#include <components/detournavigator/tilecachedrecastmeshmanager.hpp>
#include <components/detournavigator/tileposition.hpp>
#include <components/esm/loadcell.hpp>
#include <components/misc/guarded.hpp>
#include <components/misc/progressreporter.hpp>
#include <components/sceneutil/workqueue.hpp>
#include <components/sqlite3/transaction.hpp>
#include <DetourNavMesh.h>
#include <osg/Vec3f>
#include <algorithm>
#include <atomic>
#include <chrono>
#include <cstddef>
#include <stdexcept>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace NavMeshTool
{
namespace
{
using DetourNavigator::GenerateNavMeshTile;
using DetourNavigator::NavMeshDb;
using DetourNavigator::NavMeshTileInfo;
using DetourNavigator::PreparedNavMeshData;
using DetourNavigator::RecastMeshProvider;
using DetourNavigator::MeshSource;
using DetourNavigator::Settings;
using DetourNavigator::ShapeId;
using DetourNavigator::TileId;
using DetourNavigator::TilePosition;
using DetourNavigator::TileVersion;
using Sqlite3::Transaction;
void logGeneratedTiles(std::size_t provided, std::size_t expected)
{
Log(Debug::Info) << provided << "/" << expected << " ("
<< (static_cast<double>(provided) / static_cast<double>(expected) * 100)
<< "%) navmesh tiles are generated";
}
struct LogGeneratedTiles
{
void operator()(std::size_t provided, std::size_t expected) const
{
logGeneratedTiles(provided, expected);
}
};
class NavMeshTileConsumer final : public DetourNavigator::NavMeshTileConsumer
{
public:
std::atomic_size_t mExpected {0};
explicit NavMeshTileConsumer(NavMeshDb&& db)
: mDb(std::move(db))
, mTransaction(mDb.startTransaction())
, mNextTileId(mDb.getMaxTileId() + 1)
, mNextShapeId(mDb.getMaxShapeId() + 1)
{}
std::size_t getProvided() const { return mProvided.load(); }
std::size_t getInserted() const { return mInserted.load(); }
std::size_t getUpdated() const { return mUpdated.load(); }
std::int64_t resolveMeshSource(const MeshSource& source) override
{
const std::lock_guard lock(mMutex);
return DetourNavigator::resolveMeshSource(mDb, source, mNextShapeId);
}
std::optional<NavMeshTileInfo> find(const std::string& worldspace, const TilePosition &tilePosition,
const std::vector<std::byte> &input) override
{
std::optional<NavMeshTileInfo> result;
std::lock_guard lock(mMutex);
if (const auto tile = mDb.findTile(worldspace, tilePosition, input))
{
NavMeshTileInfo info;
info.mTileId = tile->mTileId;
info.mVersion = tile->mVersion;
result.emplace(info);
}
return result;
}
void ignore() override { report(); }
void insert(const std::string& worldspace, const TilePosition& tilePosition, std::int64_t version,
const std::vector<std::byte>& input, PreparedNavMeshData& data) override
{
data.mUserId = static_cast<unsigned>(mNextTileId);
{
std::lock_guard lock(mMutex);
mDb.insertTile(mNextTileId, worldspace, tilePosition, TileVersion {version}, input, serialize(data));
++mNextTileId.t;
}
++mInserted;
report();
}
void update(std::int64_t tileId, std::int64_t version, PreparedNavMeshData& data) override
{
data.mUserId = static_cast<unsigned>(tileId);
{
std::lock_guard lock(mMutex);
mDb.updateTile(TileId {tileId}, TileVersion {version}, serialize(data));
}
++mUpdated;
report();
}
void wait()
{
constexpr std::size_t tilesPerTransaction = 3000;
std::unique_lock lock(mMutex);
while (mProvided < mExpected)
{
mHasTile.wait(lock);
if (mProvided % tilesPerTransaction == 0)
{
mTransaction.commit();
mTransaction = mDb.startTransaction();
}
}
logGeneratedTiles(mProvided, mExpected);
}
void commit() { mTransaction.commit(); }
private:
std::atomic_size_t mProvided {0};
std::atomic_size_t mInserted {0};
std::atomic_size_t mUpdated {0};
std::mutex mMutex;
NavMeshDb mDb;
Transaction mTransaction;
TileId mNextTileId;
std::condition_variable mHasTile;
Misc::ProgressReporter<LogGeneratedTiles> mReporter;
ShapeId mNextShapeId;
void report()
{
mReporter(mProvided + 1, mExpected);
++mProvided;
mHasTile.notify_one();
}
};
}
void generateAllNavMeshTiles(const osg::Vec3f& agentHalfExtents, const Settings& settings,
const std::size_t threadsNumber, WorldspaceData& data, NavMeshDb&& db)
{
Log(Debug::Info) << "Generating navmesh tiles by " << threadsNumber << " parallel workers...";
SceneUtil::WorkQueue workQueue(threadsNumber);
auto navMeshTileConsumer = std::make_shared<NavMeshTileConsumer>(std::move(db));
std::size_t tiles = 0;
for (const std::unique_ptr<WorldspaceNavMeshInput>& input : data.mNavMeshInputs)
{
DetourNavigator::getTilesPositions(
Misc::Convert::toOsg(input->mAabb.m_min), Misc::Convert::toOsg(input->mAabb.m_max), settings.mRecast,
[&] (const TilePosition& tilePosition)
{
workQueue.addWorkItem(new GenerateNavMeshTile(
input->mWorldspace,
tilePosition,
RecastMeshProvider(input->mTileCachedRecastMeshManager),
agentHalfExtents,
settings,
navMeshTileConsumer
));
++tiles;
});
navMeshTileConsumer->mExpected = tiles;
}
navMeshTileConsumer->wait();
navMeshTileConsumer->commit();
Log(Debug::Info) << "Generated navmesh for " << navMeshTileConsumer->getProvided() << " tiles, "
<< navMeshTileConsumer->getInserted() << " are inserted and "
<< navMeshTileConsumer->getUpdated() << " updated";
}
}

View file

@ -0,0 +1,23 @@
#ifndef OPENMW_NAVMESHTOOL_NAVMESH_H
#define OPENMW_NAVMESHTOOL_NAVMESH_H
#include <osg/Vec3f>
#include <cstddef>
#include <string_view>
namespace DetourNavigator
{
class NavMeshDb;
struct Settings;
}
namespace NavMeshTool
{
struct WorldspaceData;
void generateAllNavMeshTiles(const osg::Vec3f& agentHalfExtents, const DetourNavigator::Settings& settings,
const std::size_t threadsNumber, WorldspaceData& cellsData, DetourNavigator::NavMeshDb&& db);
}
#endif

View file

@ -0,0 +1,330 @@
#include "worldspacedata.hpp"
#include <components/bullethelpers/aabb.hpp>
#include <components/bullethelpers/heightfield.hpp>
#include <components/debug/debuglog.hpp>
#include <components/detournavigator/gettilespositions.hpp>
#include <components/detournavigator/objectid.hpp>
#include <components/detournavigator/recastmesh.hpp>
#include <components/detournavigator/tilecachedrecastmeshmanager.hpp>
#include <components/esm/cellref.hpp>
#include <components/esm/esmreader.hpp>
#include <components/esm/loadcell.hpp>
#include <components/esm/loadland.hpp>
#include <components/esmloader/esmdata.hpp>
#include <components/esmloader/lessbyid.hpp>
#include <components/esmloader/record.hpp>
#include <components/misc/coordinateconverter.hpp>
#include <components/misc/resourcehelpers.hpp>
#include <components/misc/stringops.hpp>
#include <components/resource/bulletshapemanager.hpp>
#include <components/settings/settings.hpp>
#include <components/vfs/manager.hpp>
#include <LinearMath/btVector3.h>
#include <osg/Vec2i>
#include <osg/Vec3f>
#include <osg/ref_ptr>
#include <algorithm>
#include <memory>
#include <stdexcept>
#include <string>
#include <string_view>
#include <thread>
#include <tuple>
#include <utility>
#include <vector>
namespace NavMeshTool
{
namespace
{
using DetourNavigator::CollisionShape;
using DetourNavigator::HeightfieldPlane;
using DetourNavigator::HeightfieldShape;
using DetourNavigator::HeightfieldSurface;
using DetourNavigator::ObjectId;
using DetourNavigator::ObjectTransform;
struct CellRef
{
ESM::RecNameInts mType;
ESM::RefNum mRefNum;
std::string mRefId;
float mScale;
ESM::Position mPos;
CellRef(ESM::RecNameInts type, ESM::RefNum refNum, std::string&& refId, float scale, const ESM::Position& pos)
: mType(type), mRefNum(refNum), mRefId(std::move(refId)), mScale(scale), mPos(pos) {}
};
ESM::RecNameInts getType(const EsmLoader::EsmData& esmData, std::string_view refId)
{
const auto it = std::lower_bound(esmData.mRefIdTypes.begin(), esmData.mRefIdTypes.end(),
refId, EsmLoader::LessById {});
if (it == esmData.mRefIdTypes.end() || it->mId != refId)
return {};
return it->mType;
}
std::vector<CellRef> loadCellRefs(const ESM::Cell& cell, const EsmLoader::EsmData& esmData,
std::vector<ESM::ESMReader>& readers)
{
std::vector<EsmLoader::Record<CellRef>> cellRefs;
for (std::size_t i = 0; i < cell.mContextList.size(); i++)
{
ESM::ESMReader& reader = readers[static_cast<std::size_t>(cell.mContextList[i].index)];
cell.restore(reader, static_cast<int>(i));
ESM::CellRef cellRef;
bool deleted = false;
while (ESM::Cell::getNextRef(reader, cellRef, deleted))
{
Misc::StringUtils::lowerCaseInPlace(cellRef.mRefID);
const ESM::RecNameInts type = getType(esmData, cellRef.mRefID);
if (type == ESM::RecNameInts {})
continue;
cellRefs.emplace_back(deleted, type, cellRef.mRefNum, std::move(cellRef.mRefID),
cellRef.mScale, cellRef.mPos);
}
}
Log(Debug::Debug) << "Loaded " << cellRefs.size() << " cell refs";
const auto getKey = [] (const EsmLoader::Record<CellRef>& v) -> const ESM::RefNum& { return v.mValue.mRefNum; };
std::vector<CellRef> result = prepareRecords(cellRefs, getKey);
Log(Debug::Debug) << "Prepared " << result.size() << " unique cell refs";
return result;
}
template <class F>
void forEachObject(const ESM::Cell& cell, const EsmLoader::EsmData& esmData, const VFS::Manager& vfs,
Resource::BulletShapeManager& bulletShapeManager, std::vector<ESM::ESMReader>& readers,
F&& f)
{
std::vector<CellRef> cellRefs = loadCellRefs(cell, esmData, readers);
Log(Debug::Debug) << "Prepared " << cellRefs.size() << " unique cell refs";
for (CellRef& cellRef : cellRefs)
{
std::string model(getModel(esmData, cellRef.mRefId, cellRef.mType));
if (model.empty())
continue;
if (cellRef.mType != ESM::REC_STAT)
model = Misc::ResourceHelpers::correctActorModelPath(model, &vfs);
osg::ref_ptr<const Resource::BulletShape> shape = [&]
{
try
{
return bulletShapeManager.getShape("meshes/" + model);
}
catch (const std::exception& e)
{
Log(Debug::Warning) << "Failed to load cell ref \"" << cellRef.mRefId << "\" model \"" << model << "\": " << e.what();
return osg::ref_ptr<const Resource::BulletShape>();
}
} ();
if (shape == nullptr || shape->mCollisionShape == nullptr)
continue;
osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance(new Resource::BulletShapeInstance(std::move(shape)));
switch (cellRef.mType)
{
case ESM::REC_ACTI:
case ESM::REC_CONT:
case ESM::REC_DOOR:
case ESM::REC_STAT:
f(BulletObject(std::move(shapeInstance), cellRef.mPos, cellRef.mScale));
break;
default:
break;
}
}
}
struct GetXY
{
osg::Vec2i operator()(const ESM::Land& value) const { return osg::Vec2i(value.mX, value.mY); }
};
struct LessByXY
{
bool operator ()(const ESM::Land& lhs, const ESM::Land& rhs) const
{
return GetXY {}(lhs) < GetXY {}(rhs);
}
bool operator ()(const ESM::Land& lhs, const osg::Vec2i& rhs) const
{
return GetXY {}(lhs) < rhs;
}
bool operator ()(const osg::Vec2i& lhs, const ESM::Land& rhs) const
{
return lhs < GetXY {}(rhs);
}
};
btAABB getAabb(const osg::Vec2i& cellPosition, btScalar minHeight, btScalar maxHeight)
{
btAABB aabb;
aabb.m_min = btVector3(
static_cast<btScalar>(cellPosition.x() * ESM::Land::REAL_SIZE),
static_cast<btScalar>(cellPosition.y() * ESM::Land::REAL_SIZE),
minHeight
);
aabb.m_min = btVector3(
static_cast<btScalar>((cellPosition.x() + 1) * ESM::Land::REAL_SIZE),
static_cast<btScalar>((cellPosition.y() + 1) * ESM::Land::REAL_SIZE),
maxHeight
);
return aabb;
}
void mergeOrAssign(const btAABB& aabb, btAABB& target, bool& initialized)
{
if (initialized)
return target.merge(aabb);
target.m_min = aabb.m_min;
target.m_max = aabb.m_max;
initialized = true;
}
std::tuple<HeightfieldShape, float, float> makeHeightfieldShape(const std::optional<ESM::Land>& land,
const osg::Vec2i& cellPosition, std::vector<std::vector<float>>& heightfields,
std::vector<std::unique_ptr<ESM::Land::LandData>>& landDatas)
{
if (!land.has_value() || osg::Vec2i(land->mX, land->mY) != cellPosition
|| (land->mDataTypes & ESM::Land::DATA_VHGT) == 0)
return {HeightfieldPlane {ESM::Land::DEFAULT_HEIGHT}, ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT};
ESM::Land::LandData& landData = *landDatas.emplace_back(std::make_unique<ESM::Land::LandData>());
land->loadData(ESM::Land::DATA_VHGT, &landData);
heightfields.push_back(std::vector<float>(std::begin(landData.mHeights), std::end(landData.mHeights)));
HeightfieldSurface surface;
surface.mHeights = heightfields.back().data();
surface.mMinHeight = landData.mMinHeight;
surface.mMaxHeight = landData.mMaxHeight;
surface.mSize = static_cast<std::size_t>(ESM::Land::LAND_SIZE);
return {surface, landData.mMinHeight, landData.mMaxHeight};
}
}
WorldspaceNavMeshInput::WorldspaceNavMeshInput(std::string worldspace, const DetourNavigator::RecastSettings& settings)
: mWorldspace(std::move(worldspace))
, mTileCachedRecastMeshManager(settings)
{
mAabb.m_min = btVector3(0, 0, 0);
mAabb.m_max = btVector3(0, 0, 0);
}
WorldspaceData gatherWorldspaceData(const DetourNavigator::Settings& settings, std::vector<ESM::ESMReader>& readers,
const VFS::Manager& vfs, Resource::BulletShapeManager& bulletShapeManager, const EsmLoader::EsmData& esmData,
bool processInteriorCells)
{
Log(Debug::Info) << "Processing " << esmData.mCells.size() << " cells...";
std::map<std::string_view, std::unique_ptr<WorldspaceNavMeshInput>> navMeshInputs;
WorldspaceData data;
std::size_t objectsCounter = 0;
for (std::size_t i = 0; i < esmData.mCells.size(); ++i)
{
const ESM::Cell& cell = esmData.mCells[i];
const bool exterior = cell.isExterior();
if (!exterior && !processInteriorCells)
{
Log(Debug::Info) << "Skipped interior"
<< " cell (" << (i + 1) << "/" << esmData.mCells.size() << ") \"" << cell.getDescription() << "\"";
continue;
}
Log(Debug::Debug) << "Processing " << (exterior ? "exterior" : "interior")
<< " cell (" << (i + 1) << "/" << esmData.mCells.size() << ") \"" << cell.getDescription() << "\"";
const osg::Vec2i cellPosition(cell.mData.mX, cell.mData.mY);
const std::size_t cellObjectsBegin = data.mObjects.size();
WorldspaceNavMeshInput& navMeshInput = [&] () -> WorldspaceNavMeshInput&
{
auto it = navMeshInputs.find(cell.mCellId.mWorldspace);
if (it == navMeshInputs.end())
{
it = navMeshInputs.emplace(cell.mCellId.mWorldspace,
std::make_unique<WorldspaceNavMeshInput>(cell.mCellId.mWorldspace, settings.mRecast)).first;
it->second->mTileCachedRecastMeshManager.setWorldspace(cell.mCellId.mWorldspace);
}
return *it->second;
} ();
if (exterior)
{
const auto it = std::lower_bound(esmData.mLands.begin(), esmData.mLands.end(), cellPosition, LessByXY {});
const auto [heightfieldShape, minHeight, maxHeight] = makeHeightfieldShape(
it == esmData.mLands.end() ? std::optional<ESM::Land>() : *it,
cellPosition, data.mHeightfields, data.mLandData
);
mergeOrAssign(getAabb(cellPosition, minHeight, maxHeight),
navMeshInput.mAabb, navMeshInput.mAabbInitialized);
navMeshInput.mTileCachedRecastMeshManager.addHeightfield(cellPosition, ESM::Land::REAL_SIZE, heightfieldShape);
navMeshInput.mTileCachedRecastMeshManager.addWater(cellPosition, ESM::Land::REAL_SIZE, -1);
}
else
{
if ((cell.mData.mFlags & ESM::Cell::HasWater) != 0)
navMeshInput.mTileCachedRecastMeshManager.addWater(cellPosition, std::numeric_limits<int>::max(), cell.mWater);
}
forEachObject(cell, esmData, vfs, bulletShapeManager, readers,
[&] (BulletObject object)
{
const btTransform& transform = object.getCollisionObject().getWorldTransform();
const btAABB aabb = BulletHelpers::getAabb(*object.getCollisionObject().getCollisionShape(), transform);
mergeOrAssign(aabb, navMeshInput.mAabb, navMeshInput.mAabbInitialized);
if (const btCollisionShape* avoid = object.getShapeInstance()->mAvoidCollisionShape.get())
navMeshInput.mAabb.merge(BulletHelpers::getAabb(*avoid, transform));
const ObjectId objectId(++objectsCounter);
const CollisionShape shape(object.getShapeInstance(), *object.getCollisionObject().getCollisionShape(), object.getObjectTransform());
navMeshInput.mTileCachedRecastMeshManager.addObject(objectId, shape, transform, DetourNavigator::AreaType_ground);
if (const btCollisionShape* avoid = object.getShapeInstance()->mAvoidCollisionShape.get())
{
const CollisionShape avoidShape(object.getShapeInstance(), *avoid, object.getObjectTransform());
navMeshInput.mTileCachedRecastMeshManager.addObject(objectId, avoidShape, transform, DetourNavigator::AreaType_null);
}
data.mObjects.emplace_back(std::move(object));
});
Log(Debug::Info) << "Processed " << (exterior ? "exterior" : "interior")
<< " cell (" << (i + 1) << "/" << esmData.mCells.size() << ") " << cell.getDescription()
<< " with " << (data.mObjects.size() - cellObjectsBegin) << " objects";
}
data.mNavMeshInputs.reserve(navMeshInputs.size());
std::transform(navMeshInputs.begin(), navMeshInputs.end(), std::back_inserter(data.mNavMeshInputs),
[] (auto& v) { return std::move(v.second); });
Log(Debug::Info) << "Processed " << esmData.mCells.size() << " cells, added "
<< data.mObjects.size() << " objects and " << data.mHeightfields.size() << " height fields";
return data;
}
}

View file

@ -0,0 +1,97 @@
#ifndef OPENMW_NAVMESHTOOL_WORLDSPACEDATA_H
#define OPENMW_NAVMESHTOOL_WORLDSPACEDATA_H
#include <components/bullethelpers/collisionobject.hpp>
#include <components/detournavigator/tilecachedrecastmeshmanager.hpp>
#include <components/esm/loadland.hpp>
#include <components/misc/convert.hpp>
#include <components/resource/bulletshape.hpp>
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
#include <BulletCollision/Gimpact/btBoxCollision.h>
#include <LinearMath/btVector3.h>
#include <memory>
#include <string>
#include <vector>
namespace ESM
{
class ESMReader;
}
namespace VFS
{
class Manager;
}
namespace Resource
{
class BulletShapeManager;
}
namespace EsmLoader
{
struct EsmData;
}
namespace DetourNavigator
{
struct Settings;
}
namespace NavMeshTool
{
using DetourNavigator::TileCachedRecastMeshManager;
using DetourNavigator::ObjectTransform;
struct WorldspaceNavMeshInput
{
std::string mWorldspace;
TileCachedRecastMeshManager mTileCachedRecastMeshManager;
btAABB mAabb;
bool mAabbInitialized = false;
explicit WorldspaceNavMeshInput(std::string worldspace, const DetourNavigator::RecastSettings& settings);
};
class BulletObject
{
public:
BulletObject(osg::ref_ptr<Resource::BulletShapeInstance>&& shapeInstance, const ESM::Position& position,
float localScaling)
: mShapeInstance(std::move(shapeInstance))
, mObjectTransform {position, localScaling}
, mCollisionObject(BulletHelpers::makeCollisionObject(
mShapeInstance->mCollisionShape.get(),
Misc::Convert::toBullet(position.asVec3()),
Misc::Convert::toBullet(Misc::Convert::makeOsgQuat(position))
))
{
mShapeInstance->setLocalScaling(btVector3(localScaling, localScaling, localScaling));
}
const osg::ref_ptr<Resource::BulletShapeInstance>& getShapeInstance() const noexcept { return mShapeInstance; }
const DetourNavigator::ObjectTransform& getObjectTransform() const noexcept { return mObjectTransform; }
btCollisionObject& getCollisionObject() const noexcept { return *mCollisionObject; }
private:
osg::ref_ptr<Resource::BulletShapeInstance> mShapeInstance;
DetourNavigator::ObjectTransform mObjectTransform;
std::unique_ptr<btCollisionObject> mCollisionObject;
};
struct WorldspaceData
{
std::vector<std::unique_ptr<WorldspaceNavMeshInput>> mNavMeshInputs;
std::vector<BulletObject> mObjects;
std::vector<std::unique_ptr<ESM::Land::LandData>> mLandData;
std::vector<std::vector<float>> mHeightfields;
};
WorldspaceData gatherWorldspaceData(const DetourNavigator::Settings& settings, std::vector<ESM::ESMReader>& readers,
const VFS::Manager& vfs, Resource::BulletShapeManager& bulletShapeManager, const EsmLoader::EsmData& esmData,
bool processInteriorCells);
}
#endif

View file

@ -2,8 +2,8 @@
#include <iostream> #include <iostream>
#include <fstream> #include <fstream>
#include <cstdlib>
#include <components/misc/stringops.hpp>
#include <components/nif/niffile.hpp> #include <components/nif/niffile.hpp>
#include <components/files/constrainedfilestream.hpp> #include <components/files/constrainedfilestream.hpp>
#include <components/vfs/manager.hpp> #include <components/vfs/manager.hpp>
@ -21,15 +21,7 @@ namespace bfs = boost::filesystem;
bool hasExtension(std::string filename, std::string extensionToFind) bool hasExtension(std::string filename, std::string extensionToFind)
{ {
std::string extension = filename.substr(filename.find_last_of('.')+1); std::string extension = filename.substr(filename.find_last_of('.')+1);
return Misc::StringUtils::ciEqual(extension, extensionToFind);
//Convert strings to lower case for comparison
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
std::transform(extensionToFind.begin(), extensionToFind.end(), extensionToFind.begin(), ::tolower);
if(extension == extensionToFind)
return true;
else
return false;
} }
///See if the file has the "nif" extension. ///See if the file has the "nif" extension.

View file

@ -183,8 +183,7 @@ if(APPLE)
set(OPENCS_BUNDLE_NAME "OpenMW-CS") set(OPENCS_BUNDLE_NAME "OpenMW-CS")
set(OPENCS_BUNDLE_RESOURCES_DIR "${OpenMW_BINARY_DIR}/${OPENCS_BUNDLE_NAME}.app/Contents/Resources") set(OPENCS_BUNDLE_RESOURCES_DIR "${OpenMW_BINARY_DIR}/${OPENCS_BUNDLE_NAME}.app/Contents/Resources")
set(OPENMW_MYGUI_FILES_ROOT ${OPENCS_BUNDLE_RESOURCES_DIR}) set(OPENMW_RESOURCES_ROOT ${OPENCS_BUNDLE_RESOURCES_DIR})
set(OPENMW_SHADERS_ROOT ${OPENCS_BUNDLE_RESOURCES_DIR})
add_subdirectory(../../files/ ${CMAKE_CURRENT_BINARY_DIR}/files) add_subdirectory(../../files/ ${CMAKE_CURRENT_BINARY_DIR}/files)

View file

@ -88,16 +88,16 @@ std::pair<Files::PathContainer, std::vector<std::string> > CS::Editor::readConfi
boost::program_options::options_description desc("Syntax: openmw-cs <options>\nAllowed options"); boost::program_options::options_description desc("Syntax: openmw-cs <options>\nAllowed options");
desc.add_options() desc.add_options()
("data", boost::program_options::value<Files::EscapePathContainer>()->default_value(Files::EscapePathContainer(), "data")->multitoken()->composing()) ("data", boost::program_options::value<Files::MaybeQuotedPathContainer>()->default_value(Files::MaybeQuotedPathContainer(), "data")->multitoken()->composing())
("data-local", boost::program_options::value<Files::EscapePath>()->default_value(Files::EscapePath(), "")) ("data-local", boost::program_options::value<Files::MaybeQuotedPathContainer::value_type>()->default_value(Files::MaybeQuotedPathContainer::value_type(), ""))
("fs-strict", boost::program_options::value<bool>()->implicit_value(true)->default_value(false)) ("fs-strict", boost::program_options::value<bool>()->implicit_value(true)->default_value(false))
("encoding", boost::program_options::value<Files::EscapeHashString>()->default_value("win1252")) ("encoding", boost::program_options::value<std::string>()->default_value("win1252"))
("resources", boost::program_options::value<Files::EscapePath>()->default_value(Files::EscapePath(), "resources")) ("resources", boost::program_options::value<Files::MaybeQuotedPath>()->default_value(Files::MaybeQuotedPath(), "resources"))
("fallback-archive", boost::program_options::value<Files::EscapeStringVector>()-> ("fallback-archive", boost::program_options::value<std::vector<std::string>>()->
default_value(Files::EscapeStringVector(), "fallback-archive")->multitoken()) default_value(std::vector<std::string>(), "fallback-archive")->multitoken())
("fallback", boost::program_options::value<FallbackMap>()->default_value(FallbackMap(), "") ("fallback", boost::program_options::value<FallbackMap>()->default_value(FallbackMap(), "")
->multitoken()->composing(), "fallback values") ->multitoken()->composing(), "fallback values")
("script-blacklist", boost::program_options::value<Files::EscapeStringVector>()->default_value(Files::EscapeStringVector(), "") ("script-blacklist", boost::program_options::value<std::vector<std::string>>()->default_value(std::vector<std::string>(), "")
->multitoken(), "exclude specified script from the verifier (if the use of the blacklist is enabled)") ->multitoken(), "exclude specified script from the verifier (if the use of the blacklist is enabled)")
("script-blacklist-use", boost::program_options::value<bool>()->implicit_value(true) ("script-blacklist-use", boost::program_options::value<bool>()->implicit_value(true)
->default_value(true), "enable script blacklisting"); ->default_value(true), "enable script blacklisting");
@ -108,24 +108,24 @@ std::pair<Files::PathContainer, std::vector<std::string> > CS::Editor::readConfi
Fallback::Map::init(variables["fallback"].as<FallbackMap>().mMap); Fallback::Map::init(variables["fallback"].as<FallbackMap>().mMap);
mEncodingName = variables["encoding"].as<Files::EscapeHashString>().toStdString(); mEncodingName = variables["encoding"].as<std::string>();
mDocumentManager.setEncoding(ToUTF8::calculateEncoding(mEncodingName)); mDocumentManager.setEncoding(ToUTF8::calculateEncoding(mEncodingName));
mFileDialog.setEncoding (QString::fromUtf8(mEncodingName.c_str())); mFileDialog.setEncoding (QString::fromUtf8(mEncodingName.c_str()));
mDocumentManager.setResourceDir (mResources = variables["resources"].as<Files::EscapePath>().mPath); mDocumentManager.setResourceDir (mResources = variables["resources"].as<Files::MaybeQuotedPath>());
if (variables["script-blacklist-use"].as<bool>()) if (variables["script-blacklist-use"].as<bool>())
mDocumentManager.setBlacklistedScripts ( mDocumentManager.setBlacklistedScripts (
variables["script-blacklist"].as<Files::EscapeStringVector>().toStdStringVector()); variables["script-blacklist"].as<std::vector<std::string>>());
mFsStrict = variables["fs-strict"].as<bool>(); mFsStrict = variables["fs-strict"].as<bool>();
Files::PathContainer dataDirs, dataLocal; Files::PathContainer dataDirs, dataLocal;
if (!variables["data"].empty()) { if (!variables["data"].empty()) {
dataDirs = Files::PathContainer(Files::EscapePath::toPathContainer(variables["data"].as<Files::EscapePathContainer>())); dataDirs = asPathContainer(variables["data"].as<Files::MaybeQuotedPathContainer>());
} }
Files::PathContainer::value_type local(variables["data-local"].as<Files::EscapePath>().mPath); Files::PathContainer::value_type local(variables["data-local"].as<Files::MaybeQuotedPathContainer::value_type>());
if (!local.empty()) if (!local.empty())
dataLocal.push_back(local); dataLocal.push_back(local);
@ -149,13 +149,9 @@ std::pair<Files::PathContainer, std::vector<std::string> > CS::Editor::readConfi
dataDirs.insert (dataDirs.end(), dataLocal.begin(), dataLocal.end()); dataDirs.insert (dataDirs.end(), dataLocal.begin(), dataLocal.end());
//iterate the data directories and add them to the file dialog for loading //iterate the data directories and add them to the file dialog for loading
for (Files::PathContainer::const_reverse_iterator iter = dataDirs.rbegin(); iter != dataDirs.rend(); ++iter) mFileDialog.addFiles(dataDirs);
{
QString path = QString::fromUtf8 (iter->string().c_str());
mFileDialog.addFiles(path);
}
return std::make_pair (dataDirs, variables["fallback-archive"].as<Files::EscapeStringVector>().toStdStringVector()); return std::make_pair (dataDirs, variables["fallback-archive"].as<std::vector<std::string>>());
} }
void CS::Editor::createGame() void CS::Editor::createGame()

View file

@ -17,6 +17,19 @@
#include "textnode.hpp" #include "textnode.hpp"
#include "valuenode.hpp" #include "valuenode.hpp"
namespace
{
bool isAlpha(char c)
{
return std::isalpha(static_cast<unsigned char>(c));
}
bool isDigit(char c)
{
return std::isdigit(static_cast<unsigned char>(c));
}
}
namespace CSMFilter namespace CSMFilter
{ {
struct Token struct Token
@ -103,7 +116,7 @@ CSMFilter::Token CSMFilter::Parser::getStringToken()
{ {
char c = mInput[mIndex]; char c = mInput[mIndex];
if (std::isalpha (c) || c==':' || c=='_' || (!string.empty() && std::isdigit (c)) || c=='"' || if (isAlpha(c) || c==':' || c=='_' || (!string.empty() && isDigit(c)) || c=='"' ||
(!string.empty() && string[0]=='"')) (!string.empty() && string[0]=='"'))
string += c; string += c;
else else
@ -150,7 +163,7 @@ CSMFilter::Token CSMFilter::Parser::getNumberToken()
{ {
char c = mInput[mIndex]; char c = mInput[mIndex];
if (std::isdigit (c)) if (isDigit(c))
{ {
string += c; string += c;
hasDigit = true; hasDigit = true;
@ -225,10 +238,10 @@ CSMFilter::Token CSMFilter::Parser::getNextToken()
case '!': ++mIndex; return Token (Token::Type_OneShot); case '!': ++mIndex; return Token (Token::Type_OneShot);
} }
if (c=='"' || c=='_' || std::isalpha (c) || c==':') if (c=='"' || c=='_' || isAlpha(c) || c==':')
return getStringToken(); return getStringToken();
if (c=='-' || c=='.' || std::isdigit (c)) if (c=='-' || c=='.' || isDigit(c))
return getNumberToken(); return getNumberToken();
error(); error();

View file

@ -223,6 +223,7 @@ void CSMPrefs::State::declare()
declareColour ("scene-night-gradient-colour", "Scene Night Gradient Colour", QColor (47, 51, 51, 255)). declareColour ("scene-night-gradient-colour", "Scene Night Gradient Colour", QColor (47, 51, 51, 255)).
setTooltip("Sets the gradient color to use in conjunction with the night background color. Ignored if " setTooltip("Sets the gradient color to use in conjunction with the night background color. Ignored if "
"the gradient option is disabled."); "the gradient option is disabled.");
declareBool("scene-day-night-switch-nodes", "Use Day/Night Switch Nodes", true);
declareCategory ("Tooltips"); declareCategory ("Tooltips");
declareBool ("scene", "Show Tooltips in 3D scenes", true); declareBool ("scene", "Show Tooltips in 3D scenes", true);

View file

@ -255,7 +255,7 @@ namespace CSMWorld
{ ColumnId_AiWanderDist, "Wander Dist" }, { ColumnId_AiWanderDist, "Wander Dist" },
{ ColumnId_AiDuration, "Ai Duration" }, { ColumnId_AiDuration, "Ai Duration" },
{ ColumnId_AiWanderToD, "Wander ToD" }, { ColumnId_AiWanderToD, "Wander ToD" },
{ ColumnId_AiWanderRepeat, "Wander Repeat" }, { ColumnId_AiWanderRepeat, "Ai Repeat" },
{ ColumnId_AiActivateName, "Activate" }, { ColumnId_AiActivateName, "Activate" },
{ ColumnId_AiTargetId, "Target ID" }, { ColumnId_AiTargetId, "Target ID" },
{ ColumnId_AiTargetCell, "Target Cell" }, { ColumnId_AiTargetCell, "Target Cell" },

View file

@ -8,6 +8,7 @@
#include <stdexcept> #include <stdexcept>
#include <components/esm/cellid.hpp> #include <components/esm/cellid.hpp>
#include <components/misc/stringops.hpp>
#include "collectionbase.hpp" #include "collectionbase.hpp"
#include "columnbase.hpp" #include "columnbase.hpp"
@ -354,8 +355,7 @@ CSMWorld::LandTextureIdTable::ImportResults CSMWorld::LandTextureIdTable::import
for (int i = 0; i < idCollection()->getSize(); ++i) for (int i = 0; i < idCollection()->getSize(); ++i)
{ {
auto& record = static_cast<const Record<LandTexture>&>(idCollection()->getRecord(i)); auto& record = static_cast<const Record<LandTexture>&>(idCollection()->getRecord(i));
std::string texture = record.get().mTexture; std::string texture = Misc::StringUtils::lowerCase(record.get().mTexture);
std::transform(texture.begin(), texture.end(), texture.begin(), tolower);
if (record.isModified()) if (record.isModified())
reverseLookupMap.emplace(texture, idCollection()->getId(i)); reverseLookupMap.emplace(texture, idCollection()->getId(i));
} }
@ -376,8 +376,7 @@ CSMWorld::LandTextureIdTable::ImportResults CSMWorld::LandTextureIdTable::import
// Look for a pre-existing record // Look for a pre-existing record
auto& record = static_cast<const Record<LandTexture>&>(idCollection()->getRecord(oldRow)); auto& record = static_cast<const Record<LandTexture>&>(idCollection()->getRecord(oldRow));
std::string texture = record.get().mTexture; std::string texture = Misc::StringUtils::lowerCase(record.get().mTexture);
std::transform(texture.begin(), texture.end(), texture.begin(), tolower);
auto searchIt = reverseLookupMap.find(texture); auto searchIt = reverseLookupMap.find(texture);
if (searchIt != reverseLookupMap.end()) if (searchIt != reverseLookupMap.end())
{ {

View file

@ -1678,7 +1678,7 @@ namespace CSMWorld
newRow.mWander.mTimeOfDay = 0; newRow.mWander.mTimeOfDay = 0;
for (int i = 0; i < 8; ++i) for (int i = 0; i < 8; ++i)
newRow.mWander.mIdle[i] = 0; newRow.mWander.mIdle[i] = 0;
newRow.mWander.mShouldRepeat = 0; newRow.mWander.mShouldRepeat = 1;
newRow.mCellName = ""; newRow.mCellName = "";
if (position >= (int)list.size()) if (position >= (int)list.size())
@ -1784,9 +1784,15 @@ namespace CSMWorld
return static_cast<int>(content.mWander.mIdle[subColIndex-4]); return static_cast<int>(content.mWander.mIdle[subColIndex-4]);
else else
return QVariant(); return QVariant();
case 12: // wander repeat case 12: // repeat
if (content.mType == ESM::AI_Wander) if (content.mType == ESM::AI_Wander)
return content.mWander.mShouldRepeat != 0; return content.mWander.mShouldRepeat != 0;
else if (content.mType == ESM::AI_Travel)
return content.mTravel.mShouldRepeat != 0;
else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort)
return content.mTarget.mShouldRepeat != 0;
else if (content.mType == ESM::AI_Activate)
return content.mActivate.mShouldRepeat != 0;
else else
return QVariant(); return QVariant();
case 13: // activate name case 13: // activate name
@ -1895,6 +1901,12 @@ namespace CSMWorld
case 12: case 12:
if (content.mType == ESM::AI_Wander) if (content.mType == ESM::AI_Wander)
content.mWander.mShouldRepeat = static_cast<unsigned char>(value.toInt()); content.mWander.mShouldRepeat = static_cast<unsigned char>(value.toInt());
else if (content.mType == ESM::AI_Travel)
content.mTravel.mShouldRepeat = static_cast<unsigned char>(value.toInt());
else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort)
content.mTarget.mShouldRepeat = static_cast<unsigned char>(value.toInt());
else if (content.mType == ESM::AI_Activate)
content.mActivate.mShouldRepeat = static_cast<unsigned char>(value.toInt());
else else
return; // return without saving return; // return without saving

View file

@ -28,9 +28,14 @@ CSVDoc::FileDialog::FileDialog(QWidget *parent) :
mAdjusterWidget = new AdjusterWidget (this); mAdjusterWidget = new AdjusterWidget (this);
} }
void CSVDoc::FileDialog::addFiles(const QString &path) void CSVDoc::FileDialog::addFiles(const std::vector<boost::filesystem::path>& dataDirs)
{ {
for (auto iter = dataDirs.rbegin(); iter != dataDirs.rend(); ++iter)
{
QString path = QString::fromUtf8(iter->string().c_str());
mSelector->addFiles(path); mSelector->addFiles(path);
}
mSelector->sortFiles();
} }
void CSVDoc::FileDialog::setEncoding(const QString &encoding) void CSVDoc::FileDialog::setEncoding(const QString &encoding)

View file

@ -45,7 +45,7 @@ namespace CSVDoc
explicit FileDialog(QWidget *parent = nullptr); explicit FileDialog(QWidget *parent = nullptr);
void showDialog (ContentAction action); void showDialog (ContentAction action);
void addFiles (const QString &path); void addFiles(const std::vector<boost::filesystem::path>& dataDirs);
void setEncoding (const QString &encoding); void setEncoding (const QString &encoding);
void clearFiles (); void clearFiles ();

View file

@ -3,9 +3,12 @@
#include <osg/LightSource> #include <osg/LightSource>
#include <osg/NodeVisitor> #include <osg/NodeVisitor>
#include <osg/Switch> #include <osg/Switch>
#include <osg/ValueObject>
#include <components/misc/constants.hpp> #include <components/misc/constants.hpp>
#include "../../model/prefs/state.hpp"
class DayNightSwitchVisitor : public osg::NodeVisitor class DayNightSwitchVisitor : public osg::NodeVisitor
{ {
public: public:
@ -15,9 +18,34 @@ public:
{ } { }
void apply(osg::Switch &switchNode) override void apply(osg::Switch &switchNode) override
{
constexpr int NoIndex = -1;
int initialIndex = NoIndex;
if (!switchNode.getUserValue("initialIndex", initialIndex))
{
for (size_t i = 0; i < switchNode.getValueList().size(); ++i)
{
if (switchNode.getValueList()[i])
{
initialIndex = i;
break;
}
}
if (initialIndex != NoIndex)
switchNode.setUserValue("initialIndex", initialIndex);
}
if (CSMPrefs::get()["Rendering"]["scene-day-night-switch-nodes"].isTrue())
{ {
if (switchNode.getName() == Constants::NightDayLabel) if (switchNode.getName() == Constants::NightDayLabel)
switchNode.setSingleChildOn(mIndex); switchNode.setSingleChildOn(mIndex);
}
else if (initialIndex != NoIndex)
{
switchNode.setSingleChildOn(initialIndex);
}
traverse(switchNode); traverse(switchNode);
} }

View file

@ -308,7 +308,7 @@ osg::ref_ptr<osg::Node> CSVRender::Object::makeRotateMarker (int axis)
const float OuterRadius = InnerRadius + MarkerShaftWidth; const float OuterRadius = InnerRadius + MarkerShaftWidth;
const float SegmentDistance = 100.f; const float SegmentDistance = 100.f;
const size_t SegmentCount = std::min(64, std::max(24, (int)(OuterRadius * 2 * osg::PI / SegmentDistance))); const size_t SegmentCount = std::clamp<int>(OuterRadius * 2 * osg::PI / SegmentDistance, 24, 64);
const size_t VerticesPerSegment = 4; const size_t VerticesPerSegment = 4;
const size_t IndicesPerSegment = 24; const size_t IndicesPerSegment = 24;

View file

@ -550,6 +550,11 @@ void SceneWidget::settingChanged (const CSMPrefs::Setting *setting)
{ {
updateCameraParameters(); updateCameraParameters();
} }
else if (*setting == "Rendering/scene-day-night-switch-nodes")
{
if (mLighting)
setLighting(mLighting);
}
} }
void RenderWidget::updateCameraParameters(double overrideAspect) void RenderWidget::updateCameraParameters(double overrideAspect)

View file

@ -25,6 +25,8 @@ CSVWorld::PreviewSubView::PreviewSubView (const CSMWorld::UniversalId& id, CSMDo
else else
mScene = new CSVRender::PreviewWidget (document.getData(), id.getId(), true, this); mScene = new CSVRender::PreviewWidget (document.getData(), id.getId(), true, this);
mScene->setExterior(true);
CSVWidget::SceneToolbar *toolbar = new CSVWidget::SceneToolbar (48+6, this); CSVWidget::SceneToolbar *toolbar = new CSVWidget::SceneToolbar (48+6, this);
CSVWidget::SceneToolMode *lightingTool = mScene->makeLightingSelector (toolbar); CSVWidget::SceneToolMode *lightingTool = mScene->makeLightingSelector (toolbar);

View file

@ -27,7 +27,7 @@ add_openmw_dir (mwrender
add_openmw_dir (mwinput add_openmw_dir (mwinput
actions actionmanager bindingsmanager controllermanager controlswitch actions actionmanager bindingsmanager controllermanager controlswitch
inputmanagerimp mousemanager keyboardmanager sdlmappings sensormanager inputmanagerimp mousemanager keyboardmanager sensormanager
) )
add_openmw_dir (mwgui add_openmw_dir (mwgui
@ -74,7 +74,7 @@ add_openmw_dir (mwworld
actionequip timestamp actionalchemy cellstore actionapply actioneat actionequip timestamp actionalchemy cellstore actionapply actioneat
store esmstore fallback actionrepair actionsoulgem livecellref actiondoor store esmstore fallback actionrepair actionsoulgem livecellref actiondoor
contentloader esmloader actiontrap cellreflist cellref weather projectilemanager contentloader esmloader actiontrap cellreflist cellref weather projectilemanager
cellpreloader datetimemanager cellpreloader datetimemanager groundcoverstore magiceffects
) )
add_openmw_dir (mwphysics add_openmw_dir (mwphysics
@ -153,9 +153,12 @@ target_link_libraries(openmw
"osg-ffmpeg-videoplayer" "osg-ffmpeg-videoplayer"
"oics" "oics"
components components
${LUA_LIBRARIES}
) )
if (MSVC AND CMAKE_VERSION VERSION_GREATER_EQUAL 3.16)
target_precompile_headers(openmw PRIVATE ${SOL_INCLUDE_DIR}/sol/sol.hpp)
endif ()
if (ANDROID) if (ANDROID)
target_link_libraries(openmw EGL android log z) target_link_libraries(openmw EGL android log z)
endif (ANDROID) endif (ANDROID)
@ -176,8 +179,7 @@ endif()
if(APPLE) if(APPLE)
set(BUNDLE_RESOURCES_DIR "${APP_BUNDLE_DIR}/Contents/Resources") set(BUNDLE_RESOURCES_DIR "${APP_BUNDLE_DIR}/Contents/Resources")
set(OPENMW_MYGUI_FILES_ROOT ${BUNDLE_RESOURCES_DIR}) set(OPENMW_RESOURCES_ROOT ${BUNDLE_RESOURCES_DIR})
set(OPENMW_SHADERS_ROOT ${BUNDLE_RESOURCES_DIR})
add_subdirectory(../../files/ ${CMAKE_CURRENT_BINARY_DIR}/files) add_subdirectory(../../files/ ${CMAKE_CURRENT_BINARY_DIR}/files)

View file

@ -1,4 +1,6 @@
#ifndef stderr
int stderr = 0; // Hack: fix linker error int stderr = 0; // Hack: fix linker error
#endif
#include "SDL_main.h" #include "SDL_main.h"
#include <SDL_gamecontroller.h> #include <SDL_gamecontroller.h>

View file

@ -8,6 +8,8 @@
#include <boost/filesystem/fstream.hpp> #include <boost/filesystem/fstream.hpp>
#include <osg/Version>
#include <osgViewer/ViewerEventHandlers> #include <osgViewer/ViewerEventHandlers>
#include <osgDB/ReadFile> #include <osgDB/ReadFile>
#include <osgDB/WriteFile> #include <osgDB/WriteFile>
@ -37,11 +39,11 @@
#include <components/version/version.hpp> #include <components/version/version.hpp>
#include <components/detournavigator/navigator.hpp>
#include <components/misc/frameratelimiter.hpp> #include <components/misc/frameratelimiter.hpp>
#include <components/sceneutil/screencapture.hpp> #include <components/sceneutil/screencapture.hpp>
#include <components/sceneutil/depth.hpp>
#include <components/sceneutil/util.hpp>
#include "mwinput/inputmanagerimp.hpp" #include "mwinput/inputmanagerimp.hpp"
@ -238,6 +240,20 @@ namespace
{ {
void operator()(std::string) const {} void operator()(std::string) const {}
}; };
class IdentifyOpenGLOperation : public osg::GraphicsOperation
{
public:
IdentifyOpenGLOperation() : GraphicsOperation("IdentifyOpenGLOperation", false)
{}
void operator()(osg::GraphicsContext* graphicsContext) override
{
Log(Debug::Info) << "OpenGL Vendor: " << glGetString(GL_VENDOR);
Log(Debug::Info) << "OpenGL Renderer: " << glGetString(GL_RENDERER);
Log(Debug::Info) << "OpenGL Version: " << glGetString(GL_VERSION);
}
};
} }
void OMW::Engine::executeLocalScripts() void OMW::Engine::executeLocalScripts()
@ -293,6 +309,10 @@ bool OMW::Engine::frame(float frametime)
// Main menu opened? Then scripts are also paused. // Main menu opened? Then scripts are also paused.
bool paused = mEnvironment.getWindowManager()->containsMode(MWGui::GM_MainMenu); bool paused = mEnvironment.getWindowManager()->containsMode(MWGui::GM_MainMenu);
// Should be called after input manager update and before any change to the game world.
// It applies to the game world queued changes from the previous frame.
mLuaManager->synchronizedUpdate();
// update game state // update game state
{ {
ScopedProfile<UserStatsType::State> profile(frameStart, frameNumber, *timer, *stats); ScopedProfile<UserStatsType::State> profile(frameStart, frameNumber, *timer, *stats);
@ -546,6 +566,10 @@ void OMW::Engine::createWindow(Settings::Manager& settings)
if(fullscreen) if(fullscreen)
flags |= SDL_WINDOW_FULLSCREEN; flags |= SDL_WINDOW_FULLSCREEN;
// Allows for Windows snapping features to properly work in borderless window
SDL_SetHint("SDL_BORDERLESS_WINDOWED_STYLE", "1");
SDL_SetHint("SDL_BORDERLESS_RESIZABLE_STYLE", "1");
if (!windowBorder) if (!windowBorder)
flags |= SDL_WINDOW_BORDERLESS; flags |= SDL_WINDOW_BORDERLESS;
@ -634,8 +658,12 @@ void OMW::Engine::createWindow(Settings::Manager& settings)
camera->setGraphicsContext(graphicsWindow); camera->setGraphicsContext(graphicsWindow);
camera->setViewport(0, 0, graphicsWindow->getTraits()->width, graphicsWindow->getTraits()->height); camera->setViewport(0, 0, graphicsWindow->getTraits()->width, graphicsWindow->getTraits()->height);
osg::ref_ptr<SceneUtil::OperationSequence> realizeOperations = new SceneUtil::OperationSequence(false);
mViewer->setRealizeOperation(realizeOperations);
realizeOperations->add(new IdentifyOpenGLOperation());
if (Debug::shouldDebugOpenGL()) if (Debug::shouldDebugOpenGL())
mViewer->setRealizeOperation(new Debug::EnableGLDebugOperation()); realizeOperations->add(new Debug::EnableGLDebugOperation());
mViewer->realize(); mViewer->realize();
@ -709,7 +737,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
mViewer->addEventHandler(mScreenCaptureHandler); mViewer->addEventHandler(mScreenCaptureHandler);
mLuaManager = new MWLua::LuaManager(mVFS.get()); mLuaManager = new MWLua::LuaManager(mVFS.get(), (mResDir / "lua_libs").string());
mEnvironment.setLuaManager(mLuaManager); mEnvironment.setLuaManager(mLuaManager);
// Create input and UI first to set up a bootstrapping environment for // Create input and UI first to set up a bootstrapping environment for
@ -753,6 +781,28 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
osg::ref_ptr<osg::GLExtensions> exts = osg::GLExtensions::Get(0, false); osg::ref_ptr<osg::GLExtensions> exts = osg::GLExtensions::Get(0, false);
bool shadersSupported = exts && (exts->glslLanguageVersion >= 1.2f); bool shadersSupported = exts && (exts->glslLanguageVersion >= 1.2f);
bool enableReverseZ = false;
if (Settings::Manager::getBool("reverse z", "Camera"))
{
if (exts && exts->isClipControlSupported)
{
enableReverseZ = true;
Log(Debug::Info) << "Using reverse-z depth buffer";
}
else
Log(Debug::Warning) << "GL_ARB_clip_control not supported: disabling reverse-z depth buffer";
}
else
Log(Debug::Info) << "Using standard depth buffer";
SceneUtil::AutoDepth::setReversed(enableReverseZ);
#if OSG_VERSION_LESS_THAN(3, 6, 6)
// hack fix for https://github.com/openscenegraph/OpenSceneGraph/issues/1028
if (exts)
exts->glRenderbufferStorageMultisampleCoverageNV = nullptr;
#endif
std::string myguiResources = (mResDir / "mygui").string(); std::string myguiResources = (mResDir / "mygui").string();
osg::ref_ptr<osg::Group> guiRoot = new osg::Group; osg::ref_ptr<osg::Group> guiRoot = new osg::Group;
@ -831,6 +881,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
} }
mLuaManager->init(); mLuaManager->init();
mLuaManager->loadPermanentStorage(mCfgMgr.getUserConfigPath().string());
} }
class OMW::Engine::LuaWorker class OMW::Engine::LuaWorker
@ -842,10 +893,8 @@ public:
mThread = std::thread([this]{ threadBody(); }); mThread = std::thread([this]{ threadBody(); });
}; };
void allowUpdate(double dt) void allowUpdate()
{ {
mDt = dt;
mIsGuiMode = mEngine->mEnvironment.getWindowManager()->isGuiMode();
if (!mThread) if (!mThread)
return; return;
{ {
@ -864,7 +913,6 @@ public:
} }
else else
update(); update();
mEngine->mLuaManager->applyQueuedChanges();
}; };
void join() void join()
@ -888,7 +936,7 @@ private:
const unsigned int frameNumber = viewer->getFrameStamp()->getFrameNumber(); const unsigned int frameNumber = viewer->getFrameStamp()->getFrameNumber();
ScopedProfile<UserStatsType::Lua> profile(frameStart, frameNumber, *osg::Timer::instance(), *viewer->getViewerStats()); ScopedProfile<UserStatsType::Lua> profile(frameStart, frameNumber, *osg::Timer::instance(), *viewer->getViewerStats());
mEngine->mLuaManager->update(mIsGuiMode, mDt); mEngine->mLuaManager->update();
} }
void threadBody() void threadBody()
@ -913,8 +961,6 @@ private:
std::condition_variable mCV; std::condition_variable mCV;
bool mUpdateRequest = false; bool mUpdateRequest = false;
bool mJoinRequest = false; bool mJoinRequest = false;
double mDt = 0;
bool mIsGuiMode = false;
std::optional<std::thread> mThread; std::optional<std::thread> mThread;
}; };
@ -1027,7 +1073,7 @@ void OMW::Engine::go()
mEnvironment.getWorld()->updateWindowManager(); mEnvironment.getWorld()->updateWindowManager();
luaWorker.allowUpdate(dt); // if there is a separate Lua thread, it starts the update now luaWorker.allowUpdate(); // if there is a separate Lua thread, it starts the update now
mViewer->renderingTraversals(); mViewer->renderingTraversals();
@ -1058,8 +1104,7 @@ void OMW::Engine::go()
// Save user settings // Save user settings
settings.saveUser(settingspath); settings.saveUser(settingspath);
mLuaManager->savePermanentStorage(mCfgMgr.getUserConfigPath().string());
mViewer->stopThreading();
Log(Debug::Info) << "Quitting peacefully."; Log(Debug::Info) << "Quitting peacefully.";
} }

View file

@ -1,6 +1,5 @@
#include <components/version/version.hpp> #include <components/version/version.hpp>
#include <components/files/configurationmanager.hpp> #include <components/files/configurationmanager.hpp>
#include <components/files/escape.hpp>
#include <components/fallback/fallback.hpp> #include <components/fallback/fallback.hpp>
#include <components/fallback/validate.hpp> #include <components/fallback/validate.hpp>
#include <components/debug/debugging.hpp> #include <components/debug/debugging.hpp>
@ -57,7 +56,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
{ {
cfgMgr.readConfiguration(variables, desc, true); cfgMgr.readConfiguration(variables, desc, true);
Version::Version v = Version::getOpenmwVersion(variables["resources"].as<Files::EscapePath>().mPath.string()); Version::Version v = Version::getOpenmwVersion(variables["resources"].as<Files::MaybeQuotedPath>().string());
getRawStdout() << v.describe() << std::endl; getRawStdout() << v.describe() << std::endl;
return false; return false;
} }
@ -66,38 +65,38 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
cfgMgr.readConfiguration(variables, desc); cfgMgr.readConfiguration(variables, desc);
Files::mergeComposingVariables(variables, composingVariables, desc); Files::mergeComposingVariables(variables, composingVariables, desc);
Version::Version v = Version::getOpenmwVersion(variables["resources"].as<Files::EscapePath>().mPath.string()); Version::Version v = Version::getOpenmwVersion(variables["resources"].as<Files::MaybeQuotedPath>().string());
Log(Debug::Info) << v.describe(); Log(Debug::Info) << v.describe();
engine.setGrabMouse(!variables["no-grab"].as<bool>()); engine.setGrabMouse(!variables["no-grab"].as<bool>());
// Font encoding settings // Font encoding settings
std::string encoding(variables["encoding"].as<Files::EscapeHashString>().toStdString()); std::string encoding(variables["encoding"].as<std::string>());
Log(Debug::Info) << ToUTF8::encodingUsingMessage(encoding); Log(Debug::Info) << ToUTF8::encodingUsingMessage(encoding);
engine.setEncoding(ToUTF8::calculateEncoding(encoding)); engine.setEncoding(ToUTF8::calculateEncoding(encoding));
// directory settings // directory settings
engine.enableFSStrict(variables["fs-strict"].as<bool>()); engine.enableFSStrict(variables["fs-strict"].as<bool>());
Files::PathContainer dataDirs(Files::EscapePath::toPathContainer(variables["data"].as<Files::EscapePathContainer>())); Files::PathContainer dataDirs(asPathContainer(variables["data"].as<Files::MaybeQuotedPathContainer>()));
Files::PathContainer::value_type local(variables["data-local"].as<Files::EscapePath>().mPath); Files::PathContainer::value_type local(variables["data-local"].as<Files::MaybeQuotedPathContainer::value_type>());
if (!local.empty()) if (!local.empty())
dataDirs.push_back(local); dataDirs.push_back(local);
cfgMgr.processPaths(dataDirs); cfgMgr.processPaths(dataDirs);
engine.setResourceDir(variables["resources"].as<Files::EscapePath>().mPath); engine.setResourceDir(variables["resources"].as<Files::MaybeQuotedPath>());
engine.setDataDirs(dataDirs); engine.setDataDirs(dataDirs);
// fallback archives // fallback archives
StringsVector archives = variables["fallback-archive"].as<Files::EscapeStringVector>().toStdStringVector(); StringsVector archives = variables["fallback-archive"].as<StringsVector>();
for (StringsVector::const_iterator it = archives.begin(); it != archives.end(); ++it) for (StringsVector::const_iterator it = archives.begin(); it != archives.end(); ++it)
{ {
engine.addArchive(*it); engine.addArchive(*it);
} }
StringsVector content = variables["content"].as<Files::EscapeStringVector>().toStdStringVector(); StringsVector content = variables["content"].as<StringsVector>();
if (content.empty()) if (content.empty())
{ {
Log(Debug::Error) << "No content file given (esm/esp, nor omwgame/omwaddon). Aborting..."; Log(Debug::Error) << "No content file given (esm/esp, nor omwgame/omwaddon). Aborting...";
@ -118,7 +117,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
engine.addContentFile(file); engine.addContentFile(file);
} }
StringsVector groundcover = variables["groundcover"].as<Files::EscapeStringVector>().toStdStringVector(); StringsVector groundcover = variables["groundcover"].as<StringsVector>();
for (auto& file : groundcover) for (auto& file : groundcover)
{ {
engine.addGroundcoverFile(file); engine.addGroundcoverFile(file);
@ -131,7 +130,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
} }
// startup-settings // startup-settings
engine.setCell(variables["start"].as<Files::EscapeHashString>().toStdString()); engine.setCell(variables["start"].as<std::string>());
engine.setSkipMenu (variables["skip-menu"].as<bool>(), variables["new-game"].as<bool>()); engine.setSkipMenu (variables["skip-menu"].as<bool>(), variables["new-game"].as<bool>());
if (!variables["skip-menu"].as<bool>() && variables["new-game"].as<bool>()) if (!variables["skip-menu"].as<bool>() && variables["new-game"].as<bool>())
Log(Debug::Warning) << "Warning: new-game used without skip-menu -> ignoring it"; Log(Debug::Warning) << "Warning: new-game used without skip-menu -> ignoring it";
@ -140,11 +139,11 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
engine.setCompileAll(variables["script-all"].as<bool>()); engine.setCompileAll(variables["script-all"].as<bool>());
engine.setCompileAllDialogue(variables["script-all-dialogue"].as<bool>()); engine.setCompileAllDialogue(variables["script-all-dialogue"].as<bool>());
engine.setScriptConsoleMode (variables["script-console"].as<bool>()); engine.setScriptConsoleMode (variables["script-console"].as<bool>());
engine.setStartupScript (variables["script-run"].as<Files::EscapeHashString>().toStdString()); engine.setStartupScript (variables["script-run"].as<std::string>());
engine.setWarningsMode (variables["script-warn"].as<int>()); engine.setWarningsMode (variables["script-warn"].as<int>());
engine.setScriptBlacklist (variables["script-blacklist"].as<Files::EscapeStringVector>().toStdStringVector()); engine.setScriptBlacklist (variables["script-blacklist"].as<StringsVector>());
engine.setScriptBlacklistUse (variables["script-blacklist-use"].as<bool>()); engine.setScriptBlacklistUse (variables["script-blacklist-use"].as<bool>());
engine.setSaveGameFile (variables["load-savegame"].as<Files::EscapePath>().mPath.string()); engine.setSaveGameFile (variables["load-savegame"].as<Files::MaybeQuotedPath>().string());
// other settings // other settings
Fallback::Map::init(variables["fallback"].as<FallbackMap>().mMap); Fallback::Map::init(variables["fallback"].as<FallbackMap>().mMap);

View file

@ -49,8 +49,8 @@ namespace MWBase
virtual void setGamepadGuiCursorEnabled(bool enabled) = 0; virtual void setGamepadGuiCursorEnabled(bool enabled) = 0;
virtual void setAttemptJump(bool jumping) = 0; virtual void setAttemptJump(bool jumping) = 0;
virtual void toggleControlSwitch (const std::string& sw, bool value) = 0; virtual void toggleControlSwitch(std::string_view sw, bool value) = 0;
virtual bool getControlSwitch (const std::string& sw) = 0; virtual bool getControlSwitch(std::string_view sw) = 0;
virtual std::string getActionDescription (int action) const = 0; virtual std::string getActionDescription (int action) const = 0;
virtual std::string getActionKeyBindingName (int action) const = 0; virtual std::string getActionKeyBindingName (int action) const = 0;
@ -58,8 +58,8 @@ namespace MWBase
virtual bool actionIsActive(int action) const = 0; virtual bool actionIsActive(int action) const = 0;
virtual float getActionValue(int action) const = 0; // returns value in range [0, 1] virtual float getActionValue(int action) const = 0; // returns value in range [0, 1]
virtual bool isControllerButtonPressed(SDL_GameControllerButton button) const = 0;
virtual float getControllerAxisValue(SDL_GameControllerAxis axis) const = 0; // returns value in range [-1, 1] virtual float getControllerAxisValue(SDL_GameControllerAxis axis) const = 0; // returns value in range [-1, 1]
virtual uint32_t getMouseButtonsState() const = 0;
virtual int getMouseMoveX() const = 0; virtual int getMouseMoveX() const = 0;
virtual int getMouseMoveY() const = 0; virtual int getMouseMoveY() const = 0;

View file

@ -112,6 +112,9 @@ namespace MWBase
/// Makes \a ptr fight \a target. Also shouts a combat taunt. /// Makes \a ptr fight \a target. Also shouts a combat taunt.
virtual void startCombat (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) = 0; virtual void startCombat (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) = 0;
/// Removes an actor and its allies from combat with the actor's targets.
virtual void stopCombat(const MWWorld::Ptr& ptr) = 0;
enum OffenseType enum OffenseType
{ {
OT_Theft, // Taking items owned by an NPC or a faction you are not a member of OT_Theft, // Taking items owned by an NPC or a faction you are not a member of

View file

@ -69,6 +69,7 @@ namespace MWGui
class DialogueWindow; class DialogueWindow;
class WindowModal; class WindowModal;
class JailScreen; class JailScreen;
class MessageBox;
enum ShowInDialogueMode { enum ShowInDialogueMode {
ShowInDialogueMode_IfPossible, ShowInDialogueMode_IfPossible,
@ -145,6 +146,7 @@ namespace MWBase
virtual MWGui::CountDialog* getCountDialog() = 0; virtual MWGui::CountDialog* getCountDialog() = 0;
virtual MWGui::ConfirmationDialog* getConfirmationDialog() = 0; virtual MWGui::ConfirmationDialog* getConfirmationDialog() = 0;
virtual MWGui::TradeWindow* getTradeWindow() = 0; virtual MWGui::TradeWindow* getTradeWindow() = 0;
virtual const std::vector<MWGui::MessageBox*> getActiveMessageBoxes() = 0;
/// Make the player use an item, while updating GUI state accordingly /// Make the player use an item, while updating GUI state accordingly
virtual void useItem(const MWWorld::Ptr& item, bool force=false) = 0; virtual void useItem(const MWWorld::Ptr& item, bool force=false) = 0;
@ -355,6 +357,7 @@ namespace MWBase
virtual const std::string& getVersionDescription() const = 0; virtual const std::string& getVersionDescription() const = 0;
virtual void onDeleteCustomData(const MWWorld::Ptr& ptr) = 0; virtual void onDeleteCustomData(const MWWorld::Ptr& ptr) = 0;
virtual void forceLootMode(const MWWorld::Ptr& ptr) = 0;
}; };
} }

View file

@ -62,6 +62,7 @@ namespace MWPhysics
namespace MWRender namespace MWRender
{ {
class Animation; class Animation;
class Camera;
} }
namespace MWMechanics namespace MWMechanics
@ -433,14 +434,12 @@ namespace MWBase
virtual osg::Matrixf getActorHeadTransform(const MWWorld::ConstPtr& actor) const = 0; virtual osg::Matrixf getActorHeadTransform(const MWWorld::ConstPtr& actor) const = 0;
virtual MWRender::Camera* getCamera() = 0;
virtual void togglePOV(bool force = false) = 0; virtual void togglePOV(bool force = false) = 0;
virtual bool isFirstPerson() const = 0; virtual bool isFirstPerson() const = 0;
virtual bool isPreviewModeEnabled() const = 0; virtual bool isPreviewModeEnabled() const = 0;
virtual void togglePreviewMode(bool enable) = 0;
virtual bool toggleVanityMode(bool enable) = 0; virtual bool toggleVanityMode(bool enable) = 0;
virtual void allowVanityMode(bool allow) = 0;
virtual bool vanityRotateCamera(float * rot) = 0; virtual bool vanityRotateCamera(float * rot) = 0;
virtual void adjustCameraDistance(float dist) = 0;
virtual void applyDeferredPreviewRotationToPlayer(float dt) = 0; virtual void applyDeferredPreviewRotationToPlayer(float dt) = 0;
virtual void disableDeferredPreviewRotation() = 0; virtual void disableDeferredPreviewRotation() = 0;

View file

@ -1,7 +1,5 @@
#include "creature.hpp" #include "creature.hpp"
#include <climits> // INT_MIN
#include <components/misc/rng.hpp> #include <components/misc/rng.hpp>
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include <components/esm/loadcrea.hpp> #include <components/esm/loadcrea.hpp>
@ -758,9 +756,7 @@ namespace MWClass
{ {
if (!ptr.getRefData().getCustomData()) if (!ptr.getRefData().getCustomData())
{ {
// FIXME: the use of mGoldPool can be replaced with another flag the next time if (creatureState.mCreatureStats.mMissingACDT)
// the save file format is changed
if (creatureState.mCreatureStats.mGoldPool == INT_MIN)
ensureCustomData(ptr); ensureCustomData(ptr);
else else
{ {

View file

@ -64,7 +64,13 @@ namespace MWClass
if (customData.mSpawn) if (customData.mSpawn)
return; return;
MWWorld::Ptr creature = (customData.mSpawnActorId == -1) ? MWWorld::Ptr() : MWBase::Environment::get().getWorld()->searchPtrViaActorId(customData.mSpawnActorId); MWWorld::Ptr creature;
if(customData.mSpawnActorId != -1)
{
creature = MWBase::Environment::get().getWorld()->searchPtrViaActorId(customData.mSpawnActorId);
if(creature.isEmpty())
creature = ptr.getCell()->getMovedActor(customData.mSpawnActorId);
}
if (!creature.isEmpty()) if (!creature.isEmpty())
{ {
const MWMechanics::CreatureStats& creatureStats = creature.getClass().getCreatureStats(creature); const MWMechanics::CreatureStats& creatureStats = creature.getClass().getCreatureStats(creature);

View file

@ -1,7 +1,6 @@
#include "npc.hpp" #include "npc.hpp"
#include <memory> #include <memory>
#include <climits> // INT_MIN
#include <components/misc/constants.hpp> #include <components/misc/constants.hpp>
#include <components/misc/rng.hpp> #include <components/misc/rng.hpp>
@ -1302,9 +1301,7 @@ namespace MWClass
{ {
if (!ptr.getRefData().getCustomData()) if (!ptr.getRefData().getCustomData())
{ {
// FIXME: the use of mGoldPool can be replaced with another flag the next time if (npcState.mCreatureStats.mMissingACDT)
// the save file format is changed
if (npcState.mCreatureStats.mGoldPool == INT_MIN)
ensureCustomData(ptr); ensureCustomData(ptr);
else else
// 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)

View file

@ -76,9 +76,10 @@ namespace MWDialogue
mKnownTopics.insert( Misc::StringUtils::lowerCase(topic) ); mKnownTopics.insert( Misc::StringUtils::lowerCase(topic) );
} }
void DialogueManager::parseText (const std::string& text) std::vector<std::string> DialogueManager::parseTopicIdsFromText (const std::string& text)
{ {
updateActorKnownTopics(); std::vector<std::string> topicIdList;
std::vector<HyperTextParser::Token> hypertext = HyperTextParser::parseHyperText(text); std::vector<HyperTextParser::Token> hypertext = HyperTextParser::parseHyperText(text);
for (std::vector<HyperTextParser::Token>::iterator tok = hypertext.begin(); tok != hypertext.end(); ++tok) for (std::vector<HyperTextParser::Token>::iterator tok = hypertext.begin(); tok != hypertext.end(); ++tok)
@ -95,6 +96,18 @@ namespace MWDialogue
topicId = mTranslationDataStorage.topicStandardForm(topicId); topicId = mTranslationDataStorage.topicStandardForm(topicId);
} }
topicIdList.push_back(topicId);
}
return topicIdList;
}
void DialogueManager::addTopicsFromText (const std::string& text)
{
updateActorKnownTopics();
for (const auto& topicId : parseTopicIdsFromText(text))
{
if (mActorKnownTopics.count( topicId )) if (mActorKnownTopics.count( topicId ))
mKnownTopics.insert( topicId ); mKnownTopics.insert( topicId );
} }
@ -136,7 +149,6 @@ namespace MWDialogue
mTalkedTo = creatureStats.hasTalkedToPlayer(); mTalkedTo = creatureStats.hasTalkedToPlayer();
mActorKnownTopics.clear(); mActorKnownTopics.clear();
mActorKnownTopicsFlag.clear();
//greeting //greeting
const MWWorld::Store<ESM::Dialogue> &dialogs = const MWWorld::Store<ESM::Dialogue> &dialogs =
@ -163,7 +175,7 @@ namespace MWDialogue
executeScript (info->mResultScript, mActor); executeScript (info->mResultScript, mActor);
mLastTopic = it->mId; mLastTopic = it->mId;
parseText (info->mResponse); addTopicsFromText (info->mResponse);
return true; return true;
} }
@ -277,7 +289,10 @@ namespace MWDialogue
const ESM::Dialogue& dialogue = *dialogues.find (topic); const ESM::Dialogue& dialogue = *dialogues.find (topic);
const ESM::DialInfo* info = filter.search(dialogue, true); const ESM::DialInfo* info =
mChoice == -1 && mActorKnownTopics.count(topic) ?
mActorKnownTopics[topic].mInfo : filter.search(dialogue, true);
if (info) if (info)
{ {
std::string title; std::string title;
@ -320,7 +335,7 @@ namespace MWDialogue
executeScript (info->mResultScript, mActor); executeScript (info->mResultScript, mActor);
parseText (info->mResponse); addTopicsFromText (info->mResponse);
} }
} }
@ -339,7 +354,6 @@ namespace MWDialogue
updateGlobals(); updateGlobals();
mActorKnownTopics.clear(); mActorKnownTopics.clear();
mActorKnownTopicsFlag.clear();
const auto& dialogs = MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>(); const auto& dialogs = MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>();
@ -354,21 +368,41 @@ namespace MWDialogue
if (answer != nullptr) if (answer != nullptr)
{ {
int flag = 0; int topicFlags = 0;
if(!inJournal(topicId, answer->mId)) if(!inJournal(topicId, answer->mId))
{ {
// Does this dialogue contains some actor-specific answer? // Does this dialogue contains some actor-specific answer?
if (Misc::StringUtils::ciEqual(answer->mActor, mActor.getCellRef().getRefId())) if (Misc::StringUtils::ciEqual(answer->mActor, mActor.getCellRef().getRefId()))
flag |= MWBase::DialogueManager::TopicType::Specific; topicFlags |= MWBase::DialogueManager::TopicType::Specific;
} }
else else
flag |= MWBase::DialogueManager::TopicType::Exhausted; topicFlags |= MWBase::DialogueManager::TopicType::Exhausted;
mActorKnownTopics.insert (dialog.mId); mActorKnownTopics.insert (std::make_pair(dialog.mId, ActorKnownTopicInfo {topicFlags, answer}));
mActorKnownTopicsFlag[dialog.mId] = flag;
} }
} }
} }
// If response to a topic leads to a new topic, the original topic is not exhausted.
for (auto& [dialogId, topicInfo] : mActorKnownTopics)
{
// If the topic is not marked as exhausted, we don't need to do anything about it.
// If the topic will not be shown to the player, the flag actually does not matter.
if (!(topicInfo.mFlags & MWBase::DialogueManager::TopicType::Exhausted) ||
!mKnownTopics.count(dialogId))
continue;
for (const auto& topicId : parseTopicIdsFromText(topicInfo.mInfo->mResponse))
{
if (mActorKnownTopics.count( topicId ) && !mKnownTopics.count( topicId ))
{
topicInfo.mFlags &= ~MWBase::DialogueManager::TopicType::Exhausted;
break;
}
}
}
} }
std::list<std::string> DialogueManager::getAvailableTopics() std::list<std::string> DialogueManager::getAvailableTopics()
@ -377,7 +411,7 @@ namespace MWDialogue
std::list<std::string> keywordList; std::list<std::string> keywordList;
for (const std::string& topic : mActorKnownTopics) for (const auto& [topic, topicInfo] : mActorKnownTopics)
{ {
//does the player know the topic? //does the player know the topic?
if (mKnownTopics.count(topic)) if (mKnownTopics.count(topic))
@ -391,7 +425,7 @@ namespace MWDialogue
int DialogueManager::getTopicFlag(const std::string& topicId) int DialogueManager::getTopicFlag(const std::string& topicId)
{ {
return mActorKnownTopicsFlag[topicId]; return mActorKnownTopics[topicId].mFlags;
} }
void DialogueManager::keywordSelected (const std::string& keyword, ResponseCallback* callback) void DialogueManager::keywordSelected (const std::string& keyword, ResponseCallback* callback)
@ -421,7 +455,7 @@ namespace MWDialogue
// Clamp permanent disposition change so that final disposition doesn't go below 0 (could happen with intimidate) // Clamp permanent disposition change so that final disposition doesn't go below 0 (could happen with intimidate)
npcStats.setBaseDisposition(0); npcStats.setBaseDisposition(0);
int zero = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mActor, false); int zero = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mActor, false);
int disposition = std::min(100 - zero, std::max(mOriginalDisposition + mPermanentDispositionChange, -zero)); int disposition = std::clamp(mOriginalDisposition + mPermanentDispositionChange, -zero, 100 - zero);
npcStats.setBaseDisposition(disposition); npcStats.setBaseDisposition(disposition);
} }
@ -444,7 +478,7 @@ namespace MWDialogue
if (const ESM::DialInfo *info = filter.search (*dialogue, true)) if (const ESM::DialInfo *info = filter.search (*dialogue, true))
{ {
std::string text = info->mResponse; std::string text = info->mResponse;
parseText (text); addTopicsFromText (text);
mChoice = -1; mChoice = -1;
mIsInChoice = false; mIsInChoice = false;
@ -579,7 +613,7 @@ namespace MWDialogue
{ {
const ESM::DialInfo* info = infos[0]; const ESM::DialInfo* info = infos[0];
parseText (info->mResponse); addTopicsFromText (info->mResponse);
const MWWorld::Store<ESM::GameSetting>& gmsts = const MWWorld::Store<ESM::GameSetting>& gmsts =
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>(); MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();

View file

@ -10,6 +10,7 @@
#include <components/compiler/streamerrorhandler.hpp> #include <components/compiler/streamerrorhandler.hpp>
#include <components/translation/translation.hpp> #include <components/translation/translation.hpp>
#include <components/misc/stringops.hpp> #include <components/misc/stringops.hpp>
#include <components/esm/loadinfo.hpp>
#include "../mwworld/ptr.hpp" #include "../mwworld/ptr.hpp"
@ -24,14 +25,19 @@ namespace MWDialogue
{ {
class DialogueManager : public MWBase::DialogueManager class DialogueManager : public MWBase::DialogueManager
{ {
struct ActorKnownTopicInfo
{
int mFlags;
const ESM::DialInfo* mInfo;
};
std::set<std::string, Misc::StringUtils::CiComp> mKnownTopics;// Those are the topics the player knows. std::set<std::string, Misc::StringUtils::CiComp> mKnownTopics;// Those are the topics the player knows.
// Modified faction reactions. <Faction1, <Faction2, Difference> > // Modified faction reactions. <Faction1, <Faction2, Difference> >
typedef std::map<std::string, std::map<std::string, int> > ModFactionReactionMap; typedef std::map<std::string, std::map<std::string, int> > ModFactionReactionMap;
ModFactionReactionMap mChangedFactionReaction; ModFactionReactionMap mChangedFactionReaction;
std::set<std::string, Misc::StringUtils::CiComp> mActorKnownTopics; std::map<std::string, ActorKnownTopicInfo, Misc::StringUtils::CiComp> mActorKnownTopics;
std::unordered_map<std::string, int> mActorKnownTopicsFlag;
Translation::Storage& mTranslationDataStorage; Translation::Storage& mTranslationDataStorage;
MWScript::CompilerContext mCompilerContext; MWScript::CompilerContext mCompilerContext;
@ -51,7 +57,8 @@ namespace MWDialogue
int mCurrentDisposition; int mCurrentDisposition;
int mPermanentDispositionChange; int mPermanentDispositionChange;
void parseText (const std::string& text); std::vector<std::string> parseTopicIdsFromText (const std::string& text);
void addTopicsFromText (const std::string& text);
void updateActorKnownTopics(); void updateActorKnownTopics();
void updateGlobals(); void updateGlobals();

View file

@ -47,19 +47,8 @@ namespace MWDialogue
void tokenizeKeywords(const std::string & text, std::vector<Token> & tokens) void tokenizeKeywords(const std::string & text, std::vector<Token> & tokens)
{ {
const MWWorld::Store<ESM::Dialogue> & dialogs = const auto& keywordSearch =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>(); MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>().getDialogIdKeywordSearch();
std::vector<std::string> keywordList;
keywordList.reserve(dialogs.getSize());
for (const auto& it : dialogs)
keywordList.push_back(Misc::StringUtils::lowerCase(it.mId));
sort(keywordList.begin(), keywordList.end());
KeywordSearch<std::string, int /*unused*/> keywordSearch;
for (const auto& it : keywordList)
keywordSearch.seed(it, 0 /*unused*/);
std::vector<KeywordSearch<std::string, int /*unused*/>::Match> matches; std::vector<KeywordSearch<std::string, int /*unused*/>::Match> matches;
keywordSearch.highlightKeywords(text.begin(), text.end(), matches); keywordSearch.highlightKeywords(text.begin(), text.end(), matches);

View file

@ -74,13 +74,13 @@ public:
return left.mBeg < right.mBeg; return left.mBeg < right.mBeg;
} }
void highlightKeywords (Point beg, Point end, std::vector<Match>& out) void highlightKeywords (Point beg, Point end, std::vector<Match>& out) const
{ {
std::vector<Match> matches; std::vector<Match> matches;
for (Point i = beg; i != end; ++i) for (Point i = beg; i != end; ++i)
{ {
// check first character // check first character
typename Entry::childen_t::iterator candidate = mRoot.mChildren.find (Misc::StringUtils::toLower (*i)); typename Entry::childen_t::const_iterator candidate = mRoot.mChildren.find (Misc::StringUtils::toLower (*i));
// no match, on to next character // no match, on to next character
if (candidate == mRoot.mChildren.end ()) if (candidate == mRoot.mChildren.end ())
@ -91,11 +91,11 @@ public:
// some keywords might be longer variations of other keywords, so we definitely need a list of candidates // some keywords might be longer variations of other keywords, so we definitely need a list of candidates
// the first element in the pair is length of the match, i.e. depth from the first character on // the first element in the pair is length of the match, i.e. depth from the first character on
std::vector< typename std::pair<int, typename Entry::childen_t::iterator> > candidates; std::vector< typename std::pair<int, typename Entry::childen_t::const_iterator> > candidates;
while ((j + 1) != end) while ((j + 1) != end)
{ {
typename Entry::childen_t::iterator next = candidate->second.mChildren.find (Misc::StringUtils::toLower (*++j)); typename Entry::childen_t::const_iterator next = candidate->second.mChildren.find (Misc::StringUtils::toLower (*++j));
if (next == candidate->second.mChildren.end ()) if (next == candidate->second.mChildren.end ())
{ {
@ -116,7 +116,7 @@ public:
// shorter candidates will be added to the vector first. however, we want to check against longer candidates first // shorter candidates will be added to the vector first. however, we want to check against longer candidates first
std::reverse(candidates.begin(), candidates.end()); std::reverse(candidates.begin(), candidates.end());
for (typename std::vector< std::pair<int, typename Entry::childen_t::iterator> >::iterator it = candidates.begin(); for (typename std::vector< std::pair<int, typename Entry::childen_t::const_iterator> >::iterator it = candidates.begin();
it != candidates.end(); ++it) it != candidates.end(); ++it)
{ {
candidate = it->second; candidate = it->second;

View file

@ -8,7 +8,7 @@
#include "MyGUI_FactoryManager.h" #include "MyGUI_FactoryManager.h"
#include <components/misc/utf8stream.hpp> #include <components/misc/utf8stream.hpp>
#include <components/sceneutil/util.hpp> #include <components/sceneutil/depth.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
@ -907,12 +907,6 @@ protected:
return {}; return {};
MyGUI::IntPoint pos (left, top); 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.left -= mCroppedParent->getAbsoluteLeft ();
pos.top -= mCroppedParent->getAbsoluteTop (); pos.top -= mCroppedParent->getAbsoluteTop ();
pos.top += mViewTop; pos.top += mViewTop;
@ -1221,7 +1215,7 @@ public:
RenderXform renderXform (mCroppedParent, textFormat.mRenderItem->getRenderTarget()->getInfo()); RenderXform renderXform (mCroppedParent, textFormat.mRenderItem->getRenderTarget()->getInfo());
float z = SceneUtil::getReverseZ() ? 1.f : -1.f; float z = SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f;
GlyphStream glyphStream(textFormat.mFont, static_cast<float>(mCoord.left), static_cast<float>(mCoord.top - mViewTop), GlyphStream glyphStream(textFormat.mFont, static_cast<float>(mCoord.left), static_cast<float>(mCoord.top - mViewTop),
z /*mNode->getNodeDepth()*/, vertices, renderXform); z /*mNode->getNodeDepth()*/, vertices, renderXform);

View file

@ -38,6 +38,7 @@ namespace MWGui
, mSortModel(nullptr) , mSortModel(nullptr)
, mModel(nullptr) , mModel(nullptr)
, mSelectedItem(-1) , mSelectedItem(-1)
, mTreatNextOpenAsLoot(false)
{ {
getWidget(mDisposeCorpseButton, "DisposeCorpseButton"); getWidget(mDisposeCorpseButton, "DisposeCorpseButton");
getWidget(mTakeButton, "TakeButton"); getWidget(mTakeButton, "TakeButton");
@ -121,13 +122,15 @@ namespace MWGui
void ContainerWindow::setPtr(const MWWorld::Ptr& container) void ContainerWindow::setPtr(const MWWorld::Ptr& container)
{ {
bool lootAnyway = mTreatNextOpenAsLoot;
mTreatNextOpenAsLoot = false;
mPtr = container; mPtr = container;
bool loot = mPtr.getClass().isActor() && mPtr.getClass().getCreatureStats(mPtr).isDead(); bool loot = mPtr.getClass().isActor() && mPtr.getClass().getCreatureStats(mPtr).isDead();
if (mPtr.getClass().hasInventoryStore(mPtr)) if (mPtr.getClass().hasInventoryStore(mPtr))
{ {
if (mPtr.getClass().isNpc() && !loot) if (mPtr.getClass().isNpc() && !loot && !lootAnyway)
{ {
// we are stealing stuff // we are stealing stuff
mModel = new PickpocketItemModel(mPtr, new InventoryItemModel(container), mModel = new PickpocketItemModel(mPtr, new InventoryItemModel(container),

View file

@ -37,6 +37,7 @@ namespace MWGui
void onDeleteCustomData(const MWWorld::Ptr& ptr) override; void onDeleteCustomData(const MWWorld::Ptr& ptr) override;
void treatNextOpenAsLoot() { mTreatNextOpenAsLoot = true; };
private: private:
DragAndDrop* mDragAndDrop; DragAndDrop* mDragAndDrop;
@ -44,7 +45,7 @@ namespace MWGui
SortFilterItemModel* mSortModel; SortFilterItemModel* mSortModel;
ItemModel* mModel; ItemModel* mModel;
int mSelectedItem; int mSelectedItem;
bool mTreatNextOpenAsLoot;
MyGUI::Button* mDisposeCorpseButton; MyGUI::Button* mDisposeCorpseButton;
MyGUI::Button* mTakeButton; MyGUI::Button* mTakeButton;
MyGUI::Button* mCloseButton; MyGUI::Button* mCloseButton;

View file

@ -347,8 +347,7 @@ namespace MWGui
{ {
if (!mScrollBar->getVisible()) if (!mScrollBar->getVisible())
return; return;
mScrollBar->setScrollPosition(std::min(static_cast<int>(mScrollBar->getScrollRange()-1), mScrollBar->setScrollPosition(std::clamp<int>(mScrollBar->getScrollPosition() - _rel*0.3, 0, mScrollBar->getScrollRange() - 1));
std::max(0, static_cast<int>(mScrollBar->getScrollPosition() - _rel*0.3))));
onScrollbarMoved(mScrollBar, mScrollBar->getScrollPosition()); onScrollbarMoved(mScrollBar, mScrollBar->getScrollPosition());
} }

View file

@ -109,7 +109,7 @@ namespace MWGui
{ {
mEnchantmentPoints->setCaption(std::to_string(static_cast<int>(mEnchanting.getEnchantPoints(false))) + " / " + std::to_string(mEnchanting.getMaxEnchantValue())); mEnchantmentPoints->setCaption(std::to_string(static_cast<int>(mEnchanting.getEnchantPoints(false))) + " / " + std::to_string(mEnchanting.getMaxEnchantValue()));
mCharge->setCaption(std::to_string(mEnchanting.getGemCharge())); mCharge->setCaption(std::to_string(mEnchanting.getGemCharge()));
mSuccessChance->setCaption(std::to_string(std::max(0, std::min(100, mEnchanting.getEnchantChance())))); mSuccessChance->setCaption(std::to_string(std::clamp(mEnchanting.getEnchantChance(), 0, 100)));
mCastCost->setCaption(std::to_string(mEnchanting.getEffectiveCastCost())); mCastCost->setCaption(std::to_string(mEnchanting.getEffectiveCastCost()));
mPrice->setCaption(std::to_string(mEnchanting.getEnchantPrice())); mPrice->setCaption(std::to_string(mEnchanting.getEnchantPrice()));

View file

@ -81,7 +81,7 @@ namespace MWGui
, mMinimap(nullptr) , mMinimap(nullptr)
, mCrosshair(nullptr) , mCrosshair(nullptr)
, mCellNameBox(nullptr) , mCellNameBox(nullptr)
, mDrowningFrame(nullptr) , mDrowningBar(nullptr)
, mDrowningFlash(nullptr) , mDrowningFlash(nullptr)
, mHealthManaStaminaBaseLeft(0) , mHealthManaStaminaBaseLeft(0)
, mWeapBoxBaseLeft(0) , mWeapBoxBaseLeft(0)
@ -119,6 +119,7 @@ namespace MWGui
fatigueFrame->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onHMSClicked); fatigueFrame->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onHMSClicked);
//Drowning bar //Drowning bar
getWidget(mDrowningBar, "DrowningBar");
getWidget(mDrowningFrame, "DrowningFrame"); getWidget(mDrowningFrame, "DrowningFrame");
getWidget(mDrowning, "Drowning"); getWidget(mDrowning, "Drowning");
getWidget(mDrowningFlash, "Flash"); getWidget(mDrowningFlash, "Flash");
@ -224,7 +225,7 @@ namespace MWGui
void HUD::setDrowningBarVisible(bool visible) void HUD::setDrowningBarVisible(bool visible)
{ {
mDrowningFrame->setVisible(visible); mDrowningBar->setVisible(visible);
} }
void HUD::onWorldClicked(MyGUI::Widget* _sender) void HUD::onWorldClicked(MyGUI::Widget* _sender)
@ -368,9 +369,6 @@ namespace MWGui
mWeaponSpellBox->setPosition(mWeaponSpellBox->getPosition() + MyGUI::IntPoint(0,20)); mWeaponSpellBox->setPosition(mWeaponSpellBox->getPosition() + MyGUI::IntPoint(0,20));
} }
if (mIsDrowning)
mDrowningFlashTheta += dt * osg::PI*2;
mSpellIcons->updateWidgets(mEffectBox, true); mSpellIcons->updateWidgets(mEffectBox, true);
if (mEnemyActorId != -1 && mEnemyHealth->getVisible()) if (mEnemyActorId != -1 && mEnemyHealth->getVisible())
@ -378,8 +376,13 @@ namespace MWGui
updateEnemyHealthBar(); updateEnemyHealthBar();
} }
if (mDrowningBar->getVisible())
mDrowningBar->setPosition(mMainWidget->getWidth()/2 - mDrowningFrame->getWidth()/2, mMainWidget->getTop());
if (mIsDrowning) if (mIsDrowning)
{ {
mDrowningFlashTheta += dt * osg::PI*2;
float intensity = (cos(mDrowningFlashTheta) + 2.0f) / 3.0f; float intensity = (cos(mDrowningFlashTheta) + 2.0f) / 3.0f;
mDrowningFlash->setAlpha(intensity); mDrowningFlash->setAlpha(intensity);
@ -610,7 +613,7 @@ namespace MWGui
static const float fNPCHealthBarFade = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fNPCHealthBarFade")->mValue.getFloat(); static const float fNPCHealthBarFade = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fNPCHealthBarFade")->mValue.getFloat();
if (fNPCHealthBarFade > 0.f) if (fNPCHealthBarFade > 0.f)
mEnemyHealth->setAlpha(std::max(0.f, std::min(1.f, mEnemyHealthTimer/fNPCHealthBarFade))); mEnemyHealth->setAlpha(std::clamp(mEnemyHealthTimer / fNPCHealthBarFade, 0.f, 1.f));
} }

View file

@ -73,7 +73,7 @@ namespace MWGui
MyGUI::ImageBox* mCrosshair; MyGUI::ImageBox* mCrosshair;
MyGUI::TextBox* mCellNameBox; MyGUI::TextBox* mCellNameBox;
MyGUI::TextBox* mWeaponSpellBox; MyGUI::TextBox* mWeaponSpellBox;
MyGUI::Widget *mDrowningFrame, *mDrowningFlash; MyGUI::Widget *mDrowningBar, *mDrowningFrame, *mDrowningFlash;
// bottom left elements // bottom left elements
int mHealthManaStaminaBaseLeft, mWeapBoxBaseLeft, mSpellBoxBaseLeft, mSneakBoxBaseLeft; int mHealthManaStaminaBaseLeft, mWeapBoxBaseLeft, mSpellBoxBaseLeft, mSneakBoxBaseLeft;

View file

@ -79,7 +79,7 @@ void KeyboardNavigation::restoreFocus(int mode)
if (found != mKeyFocus.end()) if (found != mKeyFocus.end())
{ {
MyGUI::Widget* w = found->second; MyGUI::Widget* w = found->second;
if (w && w->getVisible() && w->getEnabled()) if (w && w->getVisible() && w->getEnabled() && w->getInheritedVisible() && w->getInheritedEnabled())
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(found->second); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(found->second);
} }
} }
@ -93,19 +93,6 @@ void KeyboardNavigation::_unlinkWidget(MyGUI::Widget *widget)
mCurrentFocus = nullptr; mCurrentFocus = nullptr;
} }
#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
void styleFocusedButton(MyGUI::Widget* w)
{
if (w)
{
if (MyGUI::Button* b = w->castType<MyGUI::Button>(false))
{
b->_setWidgetState("highlighted");
}
}
}
#endif
bool isRootParent(MyGUI::Widget* widget, MyGUI::Widget* root) bool isRootParent(MyGUI::Widget* widget, MyGUI::Widget* root)
{ {
while (widget && widget->getParent()) while (widget && widget->getParent())
@ -128,9 +115,6 @@ void KeyboardNavigation::onFrame()
if (focus == mCurrentFocus) if (focus == mCurrentFocus)
{ {
#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
styleFocusedButton(mCurrentFocus);
#endif
return; return;
} }
@ -143,19 +127,8 @@ void KeyboardNavigation::onFrame()
if (focus != mCurrentFocus) if (focus != mCurrentFocus)
{ {
#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
if (mCurrentFocus)
{
if (MyGUI::Button* b = mCurrentFocus->castType<MyGUI::Button>(false))
b->_setWidgetState("normal");
}
#endif
mCurrentFocus = focus; mCurrentFocus = focus;
} }
#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
styleFocusedButton(mCurrentFocus);
#endif
} }
void KeyboardNavigation::setDefaultFocus(MyGUI::Widget *window, MyGUI::Widget *defaultFocus) void KeyboardNavigation::setDefaultFocus(MyGUI::Widget *window, MyGUI::Widget *defaultFocus)
@ -273,7 +246,7 @@ bool KeyboardNavigation::switchFocus(int direction, bool wrap)
if (wrap) if (wrap)
index = (index + keyFocusList.size())%keyFocusList.size(); index = (index + keyFocusList.size())%keyFocusList.size();
else else
index = std::min(std::max(0, index), static_cast<int>(keyFocusList.size())-1); index = std::clamp<int>(index, 0, keyFocusList.size() - 1);
MyGUI::Widget* next = keyFocusList[index]; MyGUI::Widget* next = keyFocusList[index];
int vertdiff = next->getTop() - focus->getTop(); int vertdiff = next->getTop() - focus->getTop();

View file

@ -4,6 +4,8 @@
#include <stdint.h> #include <stdint.h>
#include <memory> #include <memory>
#include <osg/Vec2f>
#include "windowpinnablebase.hpp" #include "windowpinnablebase.hpp"
#include <components/esm/cellid.hpp> #include <components/esm/cellid.hpp>

View file

@ -145,7 +145,6 @@ namespace MWGui
return mInterMessageBoxe != nullptr; return mInterMessageBoxe != nullptr;
} }
bool MessageBoxManager::removeMessageBox (MessageBox *msgbox) bool MessageBoxManager::removeMessageBox (MessageBox *msgbox)
{ {
std::vector<MessageBox*>::iterator it; std::vector<MessageBox*>::iterator it;
@ -161,6 +160,11 @@ namespace MWGui
return false; return false;
} }
const std::vector<MessageBox*> MessageBoxManager::getActiveMessageBoxes()
{
return mMessageBoxes;
}
int MessageBoxManager::readPressedButton (bool reset) int MessageBoxManager::readPressedButton (bool reset)
{ {
int pressed = mLastButtonPressed; int pressed = mLastButtonPressed;

View file

@ -49,6 +49,8 @@ namespace MWGui
void setVisible(bool value); void setVisible(bool value);
const std::vector<MessageBox*> getActiveMessageBoxes();
private: private:
std::vector<MessageBox*> mMessageBoxes; std::vector<MessageBox*> mMessageBoxes;
InteractiveMessageBox* mInterMessageBoxe; InteractiveMessageBox* mInterMessageBoxe;
@ -63,6 +65,7 @@ namespace MWGui
public: public:
MessageBox (MessageBoxManager& parMessageBoxManager, const std::string& message); MessageBox (MessageBoxManager& parMessageBoxManager, const std::string& message);
void setMessage (const std::string& message); void setMessage (const std::string& message);
const std::string& getMessage() { return mMessage; };
int getHeight (); int getHeight ();
void update (int height); void update (int height);
void setVisible(bool value); void setVisible(bool value);
@ -72,7 +75,7 @@ namespace MWGui
protected: protected:
MessageBoxManager& mMessageBoxManager; MessageBoxManager& mMessageBoxManager;
const std::string& mMessage; std::string mMessage;
MyGUI::EditBox* mMessageWidget; MyGUI::EditBox* mMessageWidget;
int mBottomPadding; int mBottomPadding;
int mNextBoxPadding; int mNextBoxPadding;

View file

@ -79,17 +79,11 @@ namespace MWGui
delete mMagicSelectionDialog; delete mMagicSelectionDialog;
} }
void QuickKeysMenu::onOpen() inline void QuickKeysMenu::validate(int index)
{ {
WindowBase::onOpen();
MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::Ptr player = MWMechanics::getPlayer();
MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player);
switch (mKey[index].type)
// Check if quick keys are still valid
for (int i=0; i<10; ++i)
{
switch (mKey[i].type)
{ {
case Type_Unassigned: case Type_Unassigned:
case Type_HandToHand: case Type_HandToHand:
@ -98,23 +92,33 @@ namespace MWGui
case Type_Item: case Type_Item:
case Type_MagicItem: case Type_MagicItem:
{ {
MWWorld::Ptr item = *mKey[i].button->getUserData<MWWorld::Ptr>(); MWWorld::Ptr item = *mKey[index].button->getUserData<MWWorld::Ptr>();
// Make sure the item is available and is not broken // Make sure the item is available and is not broken
if (!item || item.getRefData().getCount() < 1 || if (!item || item.getRefData().getCount() < 1 ||
(item.getClass().hasItemHealth(item) && (item.getClass().hasItemHealth(item) &&
item.getClass().getItemHealth(item) <= 0)) item.getClass().getItemHealth(item) <= 0))
{ {
// Try searching for a compatible replacement // Try searching for a compatible replacement
item = store.findReplacement(mKey[i].id); item = store.findReplacement(mKey[index].id);
if (item) if (item)
mKey[i].button->setUserData(MWWorld::Ptr(item)); mKey[index].button->setUserData(MWWorld::Ptr(item));
break; break;
} }
} }
} }
} }
void QuickKeysMenu::onOpen()
{
WindowBase::onOpen();
// Quick key index
for (int index = 0; index < 10; ++index)
{
validate(index);
}
} }
void QuickKeysMenu::unassign(keyData* key) void QuickKeysMenu::unassign(keyData* key)
@ -334,6 +338,8 @@ namespace MWGui
MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player);
const MWMechanics::CreatureStats &playerStats = player.getClass().getCreatureStats(player); const MWMechanics::CreatureStats &playerStats = player.getClass().getCreatureStats(player);
validate(index-1);
// Delay action executing, // Delay action executing,
// if player is busy for now (casting a spell, attacking someone, etc.) // if player is busy for now (casting a spell, attacking someone, etc.)
bool isDelayNeeded = MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(player) bool isDelayNeeded = MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(player)

View file

@ -76,7 +76,8 @@ namespace MWGui
void onQuickKeyButtonClicked(MyGUI::Widget* sender); void onQuickKeyButtonClicked(MyGUI::Widget* sender);
void onOkButtonClicked(MyGUI::Widget* sender); void onOkButtonClicked(MyGUI::Widget* sender);
// Check if quick key is still valid
inline void validate(int index);
void unassign(keyData* key); void unassign(keyData* key);
}; };

View file

@ -171,7 +171,7 @@ namespace MWGui
else else
valueStr = MyGUI::utility::toString(int(value)); valueStr = MyGUI::utility::toString(int(value));
value = std::max(min, std::min(value, max)); value = std::clamp(value, min, max);
value = (value-min)/(max-min); value = (value-min)/(max-min);
scroll->setScrollPosition(static_cast<size_t>(value * (scroll->getScrollRange() - 1))); scroll->setScrollPosition(static_cast<size_t>(value * (scroll->getScrollRange() - 1)));
@ -232,6 +232,7 @@ namespace MWGui
getWidget(mControllerSwitch, "ControllerButton"); getWidget(mControllerSwitch, "ControllerButton");
getWidget(mWaterTextureSize, "WaterTextureSize"); getWidget(mWaterTextureSize, "WaterTextureSize");
getWidget(mWaterReflectionDetail, "WaterReflectionDetail"); getWidget(mWaterReflectionDetail, "WaterReflectionDetail");
getWidget(mWaterRainRippleDetail, "WaterRainRippleDetail");
getWidget(mLightingMethodButton, "LightingMethodButton"); getWidget(mLightingMethodButton, "LightingMethodButton");
getWidget(mLightsResetButton, "LightsResetButton"); getWidget(mLightsResetButton, "LightsResetButton");
getWidget(mMaxLights, "MaxLights"); getWidget(mMaxLights, "MaxLights");
@ -259,6 +260,7 @@ 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);
mWaterRainRippleDetail->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterRainRippleDetailChanged);
mLightingMethodButton->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onLightingMethodButtonChanged); mLightingMethodButton->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onLightingMethodButtonChanged);
mLightsResetButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onLightsResetButtonClicked); mLightsResetButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onLightsResetButtonClicked);
@ -267,6 +269,8 @@ namespace MWGui
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);
computeMinimumWindowSize();
center(); center();
mResetControlsButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onResetDefaultBindings); mResetControlsButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onResetDefaultBindings);
@ -305,10 +309,12 @@ namespace MWGui
if (waterTextureSize >= 2048) if (waterTextureSize >= 2048)
mWaterTextureSize->setIndexSelected(2); mWaterTextureSize->setIndexSelected(2);
int waterReflectionDetail = Settings::Manager::getInt("reflection detail", "Water"); int waterReflectionDetail = std::clamp(Settings::Manager::getInt("reflection detail", "Water"), 0, 5);
waterReflectionDetail = std::min(5, std::max(0, waterReflectionDetail));
mWaterReflectionDetail->setIndexSelected(waterReflectionDetail); mWaterReflectionDetail->setIndexSelected(waterReflectionDetail);
int waterRainRippleDetail = std::clamp(Settings::Manager::getInt("rain ripple detail", "Water"), 0, 2);
mWaterRainRippleDetail->setIndexSelected(waterRainRippleDetail);
updateMaxLightsComboBox(mMaxLights); updateMaxLightsComboBox(mMaxLights);
mWindowBorderButton->setEnabled(!Settings::Manager::getBool("fullscreen", "Video")); mWindowBorderButton->setEnabled(!Settings::Manager::getBool("fullscreen", "Video"));
@ -392,11 +398,18 @@ namespace MWGui
void SettingsWindow::onWaterReflectionDetailChanged(MyGUI::ComboBox* _sender, size_t pos) void SettingsWindow::onWaterReflectionDetailChanged(MyGUI::ComboBox* _sender, size_t pos)
{ {
unsigned int level = std::min((unsigned int)5, (unsigned int)pos); unsigned int level = static_cast<unsigned int>(std::min<size_t>(pos, 5));
Settings::Manager::setInt("reflection detail", "Water", level); Settings::Manager::setInt("reflection detail", "Water", level);
apply(); apply();
} }
void SettingsWindow::onWaterRainRippleDetailChanged(MyGUI::ComboBox* _sender, size_t pos)
{
unsigned int level = static_cast<unsigned int>(std::min<size_t>(pos, 2));
Settings::Manager::setInt("rain ripple detail", "Water", level);
apply();
}
void SettingsWindow::onLightingMethodButtonChanged(MyGUI::ComboBox* _sender, size_t pos) void SettingsWindow::onLightingMethodButtonChanged(MyGUI::ComboBox* _sender, size_t pos)
{ {
if (pos == MyGUI::ITEM_NONE) if (pos == MyGUI::ITEM_NONE)
@ -739,6 +752,32 @@ namespace MWGui
layoutControlsBox(); layoutControlsBox();
} }
void SettingsWindow::computeMinimumWindowSize()
{
auto* window = mMainWidget->castType<MyGUI::Window>();
auto minSize = window->getMinSize();
// Window should be at minimum wide enough to show all tabs.
int tabBarWidth = 0;
for (uint32_t i = 0; i < mSettingsTab->getItemCount(); i++)
{
tabBarWidth += mSettingsTab->getButtonWidthAt(i);
}
// Need to include window margins
int margins = mMainWidget->getWidth() - mSettingsTab->getWidth();
int minimumWindowWidth = tabBarWidth + margins;
if (minimumWindowWidth > minSize.width)
{
minSize.width = minimumWindowWidth;
window->setMinSize(minSize);
// Make a dummy call to setSize so MyGUI can apply any resize resulting from the change in MinSize
mMainWidget->setSize(mMainWidget->getSize());
}
}
void SettingsWindow::resetScrollbars() void SettingsWindow::resetScrollbars()
{ {
mResolutionList->setScrollPosition(0); mResolutionList->setScrollPosition(0);

View file

@ -31,6 +31,7 @@ namespace MWGui
MyGUI::ComboBox* mWaterTextureSize; MyGUI::ComboBox* mWaterTextureSize;
MyGUI::ComboBox* mWaterReflectionDetail; MyGUI::ComboBox* mWaterReflectionDetail;
MyGUI::ComboBox* mWaterRainRippleDetail;
MyGUI::ComboBox* mMaxLights; MyGUI::ComboBox* mMaxLights;
MyGUI::ComboBox* mLightingMethodButton; MyGUI::ComboBox* mLightingMethodButton;
@ -55,6 +56,7 @@ 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 onWaterRainRippleDetailChanged(MyGUI::ComboBox* _sender, size_t pos);
void onLightingMethodButtonChanged(MyGUI::ComboBox* _sender, size_t pos); void onLightingMethodButtonChanged(MyGUI::ComboBox* _sender, size_t pos);
void onLightsResetButtonClicked(MyGUI::Widget* _sender); void onLightsResetButtonClicked(MyGUI::Widget* _sender);
@ -76,6 +78,8 @@ namespace MWGui
void layoutControlsBox(); void layoutControlsBox();
void computeMinimumWindowSize();
private: private:
void resetScrollbars(); void resetScrollbars();
}; };

View file

@ -599,8 +599,7 @@ namespace MWGui
text += "\n#{fontcolourhtml=normal}#{sExpelled}"; text += "\n#{fontcolourhtml=normal}#{sExpelled}";
else else
{ {
int rank = factionPair.second; const int rank = std::clamp(factionPair.second, 0, 9);
rank = std::max(0, std::min(9, rank));
text += std::string("\n#{fontcolourhtml=normal}") + faction->mRanks[rank]; text += std::string("\n#{fontcolourhtml=normal}") + faction->mRanks[rank];
if (rank < 9) if (rank < 9)

View file

@ -493,6 +493,7 @@ namespace MWGui
std::vector<MyGUI::Widget*> effectItems; std::vector<MyGUI::Widget*> effectItems;
int flag = info.isPotion ? Widgets::MWEffectList::EF_NoTarget : 0; int flag = info.isPotion ? Widgets::MWEffectList::EF_NoTarget : 0;
flag |= info.isIngredient ? Widgets::MWEffectList::EF_NoMagnitude : 0; flag |= info.isIngredient ? Widgets::MWEffectList::EF_NoMagnitude : 0;
flag |= info.isIngredient ? Widgets::MWEffectList::EF_Constant : 0;
effectsWidget->createEffectWidgets(effectItems, effectArea, coord, true, flag); effectsWidget->createEffectWidgets(effectItems, effectArea, coord, true, flag);
totalSize.height += coord.top-6; totalSize.height += coord.top-6;
totalSize.width = std::max(totalSize.width, coord.width); totalSize.width = std::max(totalSize.width, coord.width);
@ -649,7 +650,7 @@ namespace MWGui
std::string ToolTips::getSoulString(const MWWorld::CellRef& cellref) std::string ToolTips::getSoulString(const MWWorld::CellRef& cellref)
{ {
std::string soul = cellref.getSoul(); const std::string& soul = cellref.getSoul();
if (soul.empty()) if (soul.empty())
return std::string(); return std::string();
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
@ -665,7 +666,7 @@ namespace MWGui
{ {
std::string ret; std::string ret;
ret += getMiscString(cellref.getOwner(), "Owner"); ret += getMiscString(cellref.getOwner(), "Owner");
const std::string factionId = cellref.getFaction(); const std::string& factionId = cellref.getFaction();
if (!factionId.empty()) if (!factionId.empty())
{ {
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();

View file

@ -53,6 +53,8 @@
#include <components/misc/resourcehelpers.hpp> #include <components/misc/resourcehelpers.hpp>
#include <components/misc/frameratelimiter.hpp> #include <components/misc/frameratelimiter.hpp>
#include <components/lua_ui/widgetlist.hpp>
#include "../mwbase/inputmanager.hpp" #include "../mwbase/inputmanager.hpp"
#include "../mwbase/statemanager.hpp" #include "../mwbase/statemanager.hpp"
#include "../mwbase/soundmanager.hpp" #include "../mwbase/soundmanager.hpp"
@ -163,6 +165,7 @@ namespace MWGui
, mScreenFader(nullptr) , mScreenFader(nullptr)
, mDebugWindow(nullptr) , mDebugWindow(nullptr)
, mJailScreen(nullptr) , mJailScreen(nullptr)
, mContainerWindow(nullptr)
, mTranslationDataStorage (translationDataStorage) , mTranslationDataStorage (translationDataStorage)
, mCharGen(nullptr) , mCharGen(nullptr)
, mInputBlocker(nullptr) , mInputBlocker(nullptr)
@ -220,6 +223,7 @@ namespace MWGui
ItemWidget::registerComponents(); ItemWidget::registerComponents();
SpellView::registerComponents(); SpellView::registerComponents();
Gui::registerAllWidgets(); Gui::registerAllWidgets();
LuaUi::registerAllWidgets();
MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Controllers::ControllerFollowMouse>("Controller"); MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Controllers::ControllerFollowMouse>("Controller");
@ -357,10 +361,10 @@ namespace MWGui
mGuiModeStates[GM_Dialogue] = GuiModeState(mDialogueWindow); mGuiModeStates[GM_Dialogue] = GuiModeState(mDialogueWindow);
mTradeWindow->eventTradeDone += MyGUI::newDelegate(mDialogueWindow, &DialogueWindow::onTradeComplete); mTradeWindow->eventTradeDone += MyGUI::newDelegate(mDialogueWindow, &DialogueWindow::onTradeComplete);
ContainerWindow* containerWindow = new ContainerWindow(mDragAndDrop); mContainerWindow = new ContainerWindow(mDragAndDrop);
mWindows.push_back(containerWindow); mWindows.push_back(mContainerWindow);
trackWindow(containerWindow, "container"); trackWindow(mContainerWindow, "container");
mGuiModeStates[GM_Container] = GuiModeState({containerWindow, mInventoryWindow}); mGuiModeStates[GM_Container] = GuiModeState({mContainerWindow, mInventoryWindow});
mHud = new HUD(mCustomMarkers, mDragAndDrop, mLocalMapRender); mHud = new HUD(mCustomMarkers, mDragAndDrop, mLocalMapRender);
mWindows.push_back(mHud); mWindows.push_back(mHud);
@ -635,6 +639,7 @@ namespace MWGui
mMap->setVisible(false); mMap->setVisible(false);
mStatsWindow->setVisible(false); mStatsWindow->setVisible(false);
mSpellWindow->setVisible(false); mSpellWindow->setVisible(false);
mHud->setDrowningBarVisible(false);
mInventoryWindow->setVisible(getMode() == GM_Container || getMode() == GM_Barter || getMode() == GM_Companion); mInventoryWindow->setVisible(getMode() == GM_Container || getMode() == GM_Barter || getMode() == GM_Companion);
} }
@ -768,6 +773,11 @@ namespace MWGui
mMessageBoxManager->removeStaticMessageBox(); mMessageBoxManager->removeStaticMessageBox();
} }
const std::vector<MWGui::MessageBox*> WindowManager::getActiveMessageBoxes()
{
return mMessageBoxManager->getActiveMessageBoxes();
}
int WindowManager::readPressedButton () int WindowManager::readPressedButton ()
{ {
return mMessageBoxManager->readPressedButton(); return mMessageBoxManager->readPressedButton();
@ -1095,12 +1105,21 @@ namespace MWGui
void WindowManager::windowResized(int x, int y) void WindowManager::windowResized(int x, int y)
{ {
// Note: this is a side effect of resolution change or window resize.
// There is no need to track these changes.
Settings::Manager::setInt("resolution x", "Video", x); Settings::Manager::setInt("resolution x", "Video", x);
Settings::Manager::setInt("resolution y", "Video", y); Settings::Manager::setInt("resolution y", "Video", y);
Settings::Manager::resetPendingChange("resolution x", "Video");
Settings::Manager::resetPendingChange("resolution y", "Video"); // We only want to process changes to window-size related settings.
Settings::CategorySettingVector filter = {{"Video", "resolution x"},
{"Video", "resolution y"}};
// If the HUD has not been initialised, the World singleton will not be available.
if (mHud)
{
MWBase::Environment::get().getWorld()->processChangedSettings(
Settings::Manager::getPendingChanges(filter));
}
Settings::Manager::resetPendingChanges(filter);
mGuiPlatform->getRenderManagerPtr()->setViewSize(x, y); mGuiPlatform->getRenderManagerPtr()->setViewSize(x, y);
@ -1163,6 +1182,16 @@ namespace MWGui
} }
void WindowManager::pushGuiMode(GuiMode mode, const MWWorld::Ptr& arg) void WindowManager::pushGuiMode(GuiMode mode, const MWWorld::Ptr& arg)
{
pushGuiMode(mode, arg, false);
}
void WindowManager::forceLootMode(const MWWorld::Ptr& ptr)
{
pushGuiMode(MWGui::GM_Container, ptr, true);
}
void WindowManager::pushGuiMode(GuiMode mode, const MWWorld::Ptr& arg, bool force)
{ {
if (mode==GM_Inventory && mAllowed==GW_None) if (mode==GM_Inventory && mAllowed==GW_None)
return; return;
@ -1185,6 +1214,8 @@ namespace MWGui
mGuiModeStates[mode].update(true); mGuiModeStates[mode].update(true);
playSound(mGuiModeStates[mode].mOpenSound); playSound(mGuiModeStates[mode].mOpenSound);
} }
if(force)
mContainerWindow->treatNextOpenAsLoot();
for (WindowBase* window : mGuiModeStates[mode].mWindows) for (WindowBase* window : mGuiModeStates[mode].mWindows)
window->setPtr(arg); window->setPtr(arg);

View file

@ -187,6 +187,7 @@ namespace MWGui
MWGui::CountDialog* getCountDialog() override; MWGui::CountDialog* getCountDialog() override;
MWGui::ConfirmationDialog* getConfirmationDialog() override; MWGui::ConfirmationDialog* getConfirmationDialog() override;
MWGui::TradeWindow* getTradeWindow() override; MWGui::TradeWindow* getTradeWindow() override;
const std::vector<MWGui::MessageBox*> getActiveMessageBoxes() override;
/// Make the player use an item, while updating GUI state accordingly /// Make the player use an item, while updating GUI state accordingly
void useItem(const MWWorld::Ptr& item, bool bypassBeastRestrictions=false) override; void useItem(const MWWorld::Ptr& item, bool bypassBeastRestrictions=false) override;
@ -389,6 +390,7 @@ namespace MWGui
const std::string& getVersionDescription() const override; const std::string& getVersionDescription() const override;
void onDeleteCustomData(const MWWorld::Ptr& ptr) override; void onDeleteCustomData(const MWWorld::Ptr& ptr) override;
void forceLootMode(const MWWorld::Ptr& ptr) override;
private: private:
unsigned int mOldUpdateMask; unsigned int mOldCullMask; unsigned int mOldUpdateMask; unsigned int mOldCullMask;
@ -447,6 +449,7 @@ namespace MWGui
ScreenFader* mScreenFader; ScreenFader* mScreenFader;
DebugWindow* mDebugWindow; DebugWindow* mDebugWindow;
JailScreen* mJailScreen; JailScreen* mJailScreen;
ContainerWindow* mContainerWindow;
std::vector<WindowBase*> mWindows; std::vector<WindowBase*> mWindows;
@ -573,6 +576,8 @@ namespace MWGui
void enableScene(bool enable); void enableScene(bool enable);
void handleScheduledMessageBoxes(); void handleScheduledMessageBoxes();
void pushGuiMode(GuiMode mode, const MWWorld::Ptr& arg, bool force);
}; };
} }

View file

@ -22,12 +22,13 @@
#include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/npcstats.hpp"
#include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/actorutil.hpp"
#include "../mwgui/messagebox.hpp"
#include "actions.hpp" #include "actions.hpp"
#include "bindingsmanager.hpp" #include "bindingsmanager.hpp"
namespace MWInput namespace MWInput
{ {
const float ZOOM_SCALE = 10.f; /// Used for scrolling camera in and out
ActionManager::ActionManager(BindingsManager* bindingsManager, ActionManager::ActionManager(BindingsManager* bindingsManager,
osgViewer::ScreenCaptureHandler::CaptureOperation* screenCaptureOperation, osgViewer::ScreenCaptureHandler::CaptureOperation* screenCaptureOperation,
@ -40,8 +41,6 @@ namespace MWInput
, mAlwaysRunActive(Settings::Manager::getBool("always run", "Input")) , mAlwaysRunActive(Settings::Manager::getBool("always run", "Input"))
, mSneaking(false) , mSneaking(false)
, mAttemptJump(false) , mAttemptJump(false)
, mOverencumberedMessageDelay(0.f)
, mPreviewPOVDelay(0.f)
, mTimeIdle(0.f) , mTimeIdle(0.f)
{ {
} }
@ -90,43 +89,26 @@ namespace MWInput
{ {
player.setUpDown(1); player.setUpDown(1);
triedToMove = true; triedToMove = true;
mOverencumberedMessageDelay = 0.f;
} }
// if player tried to start moving, but can't (due to being overencumbered), display a notification. // if player tried to start moving, but can't (due to being overencumbered), display a notification.
if (triedToMove) if (triedToMove)
{ {
MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld ()->getPlayerPtr(); MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld ()->getPlayerPtr();
mOverencumberedMessageDelay -= dt;
if (playerPtr.getClass().getEncumbrance(playerPtr) > playerPtr.getClass().getCapacity(playerPtr)) if (playerPtr.getClass().getEncumbrance(playerPtr) > playerPtr.getClass().getCapacity(playerPtr))
{ {
player.setAutoMove (false); player.setAutoMove (false);
if (mOverencumberedMessageDelay <= 0) std::vector<MWGui::MessageBox*> msgboxs = MWBase::Environment::get().getWindowManager()->getActiveMessageBoxes();
const std::vector<MWGui::MessageBox*>::iterator it = std::find_if(msgboxs.begin(), msgboxs.end(), [](MWGui::MessageBox*& msgbox)
{ {
MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage59}"); return (msgbox->getMessage() == "#{sNotifyMessage59}");
mOverencumberedMessageDelay = 1.0; });
}
}
}
if (MWBase::Environment::get().getInputManager()->getControlSwitch("playerviewswitch")) // if an overencumbered messagebox is already present, reset its expiry timer, otherwise create new one.
{ if (it != msgboxs.end())
const float switchLimit = 0.25; (*it)->mCurrentTime = 0;
MWBase::World* world = MWBase::Environment::get().getWorld();
if (mBindingsManager->actionIsActive(A_TogglePOV))
{
if (world->isFirstPerson() ? mPreviewPOVDelay > switchLimit : mPreviewPOVDelay == 0)
world->togglePreviewMode(true);
mPreviewPOVDelay += dt;
}
else else
{ MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage59}");
//disable preview mode
if (mPreviewPOVDelay > 0)
world->togglePreviewMode(false);
if (mPreviewPOVDelay > 0.f && mPreviewPOVDelay <= switchLimit)
world->togglePOV();
mPreviewPOVDelay = 0.f;
} }
} }
@ -162,38 +144,16 @@ namespace MWInput
resetIdleTime(); resetIdleTime();
} }
else else
{ mTimeIdle += dt;
updateIdleTime(dt);
}
mAttemptJump = false; mAttemptJump = false;
} }
bool ActionManager::isPreviewModeEnabled()
{
return MWBase::Environment::get().getWorld()->isPreviewModeEnabled();
}
void ActionManager::resetIdleTime() void ActionManager::resetIdleTime()
{ {
if (mTimeIdle < 0)
MWBase::Environment::get().getWorld()->toggleVanityMode(false);
mTimeIdle = 0.f; mTimeIdle = 0.f;
} }
void ActionManager::updateIdleTime(float dt)
{
static const float vanityDelay = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
.find("fVanityDelay")->mValue.getFloat();
if (mTimeIdle >= 0.f)
mTimeIdle += dt;
if (mTimeIdle > vanityDelay)
{
MWBase::Environment::get().getWorld()->toggleVanityMode(true);
mTimeIdle = -1.f;
}
}
void ActionManager::executeAction(int action) void ActionManager::executeAction(int action)
{ {
MWBase::Environment::get().getLuaManager()->inputEvent({MWBase::LuaManager::InputEvent::Action, action}); MWBase::Environment::get().getLuaManager()->inputEvent({MWBase::LuaManager::InputEvent::Action, action});
@ -281,14 +241,6 @@ namespace MWInput
case A_ToggleDebug: case A_ToggleDebug:
windowManager->toggleDebugWindow(); windowManager->toggleDebugWindow();
break; break;
case A_ZoomIn:
if (inputManager->getControlSwitch("playerviewswitch") && inputManager->getControlSwitch("playercontrols") && !windowManager->isGuiMode())
MWBase::Environment::get().getWorld()->adjustCameraDistance(-ZOOM_SCALE);
break;
case A_ZoomOut:
if (inputManager->getControlSwitch("playerviewswitch") && inputManager->getControlSwitch("playercontrols") && !windowManager->isGuiMode())
MWBase::Environment::get().getWorld()->adjustCameraDistance(ZOOM_SCALE);
break;
case A_QuickSave: case A_QuickSave:
quickSave(); quickSave();
break; break;

View file

@ -55,13 +55,9 @@ namespace MWInput
void setAttemptJump(bool enabled) { mAttemptJump = enabled; } void setAttemptJump(bool enabled) { mAttemptJump = enabled; }
bool isPreviewModeEnabled();
private: private:
void handleGuiArrowKey(int action); void handleGuiArrowKey(int action);
void updateIdleTime(float dt);
BindingsManager* mBindingsManager; BindingsManager* mBindingsManager;
osg::ref_ptr<osgViewer::Viewer> mViewer; osg::ref_ptr<osgViewer::Viewer> mViewer;
osg::ref_ptr<osgViewer::ScreenCaptureHandler> mScreenCaptureHandler; osg::ref_ptr<osgViewer::ScreenCaptureHandler> mScreenCaptureHandler;
@ -71,8 +67,6 @@ namespace MWInput
bool mSneaking; bool mSneaking;
bool mAttemptJump; bool mAttemptJump;
float mOverencumberedMessageDelay;
float mPreviewPOVDelay;
float mTimeIdle; float mTimeIdle;
}; };
} }

View file

@ -5,6 +5,8 @@
#include <extern/oics/ICSChannelListener.h> #include <extern/oics/ICSChannelListener.h>
#include <extern/oics/ICSInputControlSystem.h> #include <extern/oics/ICSInputControlSystem.h>
#include <components/sdlutil/sdlmappings.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/inputmanager.hpp" #include "../mwbase/inputmanager.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
@ -13,7 +15,6 @@
#include "../mwworld/player.hpp" #include "../mwworld/player.hpp"
#include "actions.hpp" #include "actions.hpp"
#include "sdlmappings.hpp"
namespace MWInput namespace MWInput
{ {
@ -546,9 +547,9 @@ namespace MWInput
ICS::Control* c = mInputBinder->getChannel(action)->getAttachedControls().front().control; ICS::Control* c = mInputBinder->getChannel(action)->getAttachedControls().front().control;
if (mInputBinder->getJoystickAxisBinding(c, sFakeDeviceId, ICS::Control::INCREASE) != ICS::InputControlSystem::UNASSIGNED) if (mInputBinder->getJoystickAxisBinding(c, sFakeDeviceId, ICS::Control::INCREASE) != ICS::InputControlSystem::UNASSIGNED)
return sdlControllerAxisToString(mInputBinder->getJoystickAxisBinding(c, sFakeDeviceId, ICS::Control::INCREASE)); return SDLUtil::sdlControllerAxisToString(mInputBinder->getJoystickAxisBinding(c, sFakeDeviceId, ICS::Control::INCREASE));
else if (mInputBinder->getJoystickButtonBinding(c, sFakeDeviceId, ICS::Control::INCREASE) != ICS_MAX_DEVICE_BUTTONS) else if (mInputBinder->getJoystickButtonBinding(c, sFakeDeviceId, ICS::Control::INCREASE) != ICS_MAX_DEVICE_BUTTONS)
return sdlControllerButtonToString(mInputBinder->getJoystickButtonBinding(c, sFakeDeviceId, ICS::Control::INCREASE)); return SDLUtil::sdlControllerButtonToString(mInputBinder->getJoystickButtonBinding(c, sFakeDeviceId, ICS::Control::INCREASE));
else else
return "#{sNone}"; return "#{sNone}";
} }
@ -653,14 +654,13 @@ namespace MWInput
return mInputBinder->getKeyBinding(mInputBinder->getControl(actionId), ICS::Control::INCREASE); return mInputBinder->getKeyBinding(mInputBinder->getControl(actionId), ICS::Control::INCREASE);
} }
float BindingsManager::getControllerAxisValue(SDL_GameControllerAxis axis) const SDL_GameController* BindingsManager::getControllerOrNull() const
{ {
const auto& controllers = mInputBinder->getJoystickInstanceMap(); const auto& controllers = mInputBinder->getJoystickInstanceMap();
if (controllers.empty()) if (controllers.empty())
return 0; return nullptr;
SDL_GameController* cntrl = controllers.begin()->second; else
constexpr int AXIS_MAX_ABSOLUTE_VALUE = 32768; return controllers.begin()->second;
return SDL_GameControllerGetAxis(cntrl, axis) / static_cast<float>(AXIS_MAX_ABSOLUTE_VALUE);
} }
void BindingsManager::actionValueChanged(int action, float currentValue, float previousValue) void BindingsManager::actionValueChanged(int action, float currentValue, float previousValue)

View file

@ -43,7 +43,8 @@ namespace MWInput
bool actionIsActive(int id) const; bool actionIsActive(int id) const;
float getActionValue(int id) const; // returns value in range [0, 1] float getActionValue(int id) const; // returns value in range [0, 1]
float getControllerAxisValue(SDL_GameControllerAxis axis) const; // returns value in range [-1, 1]
SDL_GameController* getControllerOrNull() const;
void mousePressed(const SDL_MouseButtonEvent &evt, int deviceID); void mousePressed(const SDL_MouseButtonEvent &evt, int deviceID);
void mouseReleased(const SDL_MouseButtonEvent &arg, int deviceID); void mouseReleased(const SDL_MouseButtonEvent &arg, int deviceID);

View file

@ -5,6 +5,7 @@
#include <MyGUI_Widget.h> #include <MyGUI_Widget.h>
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include <components/sdlutil/sdlmappings.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/inputmanager.hpp" #include "../mwbase/inputmanager.hpp"
@ -19,7 +20,6 @@
#include "actionmanager.hpp" #include "actionmanager.hpp"
#include "bindingsmanager.hpp" #include "bindingsmanager.hpp"
#include "mousemanager.hpp" #include "mousemanager.hpp"
#include "sdlmappings.hpp"
namespace MWInput namespace MWInput
{ {
@ -34,12 +34,10 @@ namespace MWInput
, 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"))
, mSneakToggleShortcutTimer(0.f) , mSneakToggleShortcutTimer(0.f)
, mGamepadZoom(0)
, mGamepadGuiCursorEnabled(true) , mGamepadGuiCursorEnabled(true)
, mGuiCursorEnabled(true) , mGuiCursorEnabled(true)
, mJoystickLastUsed(false) , mJoystickLastUsed(false)
, mSneakGamepadShortcut(false) , mSneakGamepadShortcut(false)
, mGamepadPreviewMode(false)
{ {
if (!controllerBindingsFile.empty()) if (!controllerBindingsFile.empty())
{ {
@ -70,7 +68,7 @@ namespace MWInput
} }
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::clamp(deadZoneRadius, 0.f, 0.5f);
mBindingsManager->setJoystickDeadZone(deadZoneRadius); mBindingsManager->setJoystickDeadZone(deadZoneRadius);
} }
@ -85,8 +83,6 @@ namespace MWInput
bool ControllerManager::update(float dt) bool ControllerManager::update(float dt)
{ {
mGamepadPreviewMode = mActionManager->isPreviewModeEnabled();
if (mGuiCursorEnabled && !(mJoystickLastUsed && !mGamepadGuiCursorEnabled)) if (mGuiCursorEnabled && !(mJoystickLastUsed && !mGamepadGuiCursorEnabled))
{ {
float xAxis = mBindingsManager->getActionValue(A_MoveLeftRight) * 2.0f - 1.0f; float xAxis = mBindingsManager->getActionValue(A_MoveLeftRight) * 2.0f - 1.0f;
@ -115,7 +111,6 @@ namespace MWInput
if (MWBase::Environment::get().getWindowManager()->isGuiMode() if (MWBase::Environment::get().getWindowManager()->isGuiMode()
|| MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_Running) || MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_Running)
{ {
mGamepadZoom = 0;
return false; return false;
} }
@ -182,15 +177,6 @@ namespace MWInput
} }
} }
if (MWBase::Environment::get().getInputManager()->getControlSwitch("playerviewswitch"))
{
if (!mBindingsManager->actionIsActive(A_TogglePOV))
mGamepadZoom = 0;
if (mGamepadZoom)
MWBase::Environment::get().getWorld()->adjustCameraDistance(-mGamepadZoom);
}
return triedToMove; return triedToMove;
} }
@ -229,7 +215,7 @@ namespace MWInput
mBindingsManager->setPlayerControlsEnabled(true); mBindingsManager->setPlayerControlsEnabled(true);
//esc, to leave initial movie screen //esc, to leave initial movie screen
auto kc = sdlKeyToMyGUI(SDLK_ESCAPE); auto kc = SDLUtil::sdlKeyToMyGUI(SDLK_ESCAPE);
mBindingsManager->setPlayerControlsEnabled(!MyGUI::InputManager::getInstance().injectKeyPress(kc, 0)); mBindingsManager->setPlayerControlsEnabled(!MyGUI::InputManager::getInstance().injectKeyPress(kc, 0));
if (!MWBase::Environment::get().getInputManager()->controlsDisabled()) if (!MWBase::Environment::get().getInputManager()->controlsDisabled())
@ -273,7 +259,7 @@ namespace MWInput
mBindingsManager->setPlayerControlsEnabled(true); mBindingsManager->setPlayerControlsEnabled(true);
//esc, to leave initial movie screen //esc, to leave initial movie screen
auto kc = sdlKeyToMyGUI(SDLK_ESCAPE); auto kc = SDLUtil::sdlKeyToMyGUI(SDLK_ESCAPE);
mBindingsManager->setPlayerControlsEnabled(!MyGUI::InputManager::getInstance().injectKeyRelease(kc)); mBindingsManager->setPlayerControlsEnabled(!MyGUI::InputManager::getInstance().injectKeyRelease(kc));
mBindingsManager->controllerButtonReleased(deviceID, arg); mBindingsManager->controllerButtonReleased(deviceID, arg);
@ -289,21 +275,11 @@ namespace MWInput
{ {
gamepadToGuiControl(arg); gamepadToGuiControl(arg);
} }
else else if (MWBase::Environment::get().getWorld()->isPreviewModeEnabled() &&
(arg.axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT || arg.axis == SDL_CONTROLLER_AXIS_TRIGGERLEFT))
{ {
if (mGamepadPreviewMode) // Preview Mode Gamepad Zooming // Preview Mode Gamepad Zooming; do not propagate to mBindingsManager
{ return;
if (arg.axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT)
{
mGamepadZoom = arg.value * 0.85f / 1000.f / 12.f;
return; // Do not propagate event.
}
else if (arg.axis == SDL_CONTROLLER_AXIS_TRIGGERLEFT)
{
mGamepadZoom = -arg.value * 0.85f / 1000.f / 12.f;
return; // Do not propagate event.
}
}
} }
mBindingsManager->controllerAxisMoved(deviceID, arg); mBindingsManager->controllerAxisMoved(deviceID, arg);
} }
@ -403,4 +379,24 @@ namespace MWInput
return true; return true;
} }
float ControllerManager::getAxisValue(SDL_GameControllerAxis axis) const
{
SDL_GameController* cntrl = mBindingsManager->getControllerOrNull();
constexpr int AXIS_MAX_ABSOLUTE_VALUE = 32768;
if (cntrl)
return SDL_GameControllerGetAxis(cntrl, axis) / static_cast<float>(AXIS_MAX_ABSOLUTE_VALUE);
else
return 0;
}
bool ControllerManager::isButtonPressed(SDL_GameControllerButton button) const
{
SDL_GameController* cntrl = mBindingsManager->getControllerOrNull();
if (cntrl)
return SDL_GameControllerGetButton(cntrl, button) > 0;
else
return false;
}
} }

View file

@ -34,12 +34,15 @@ namespace MWInput
void processChangedSettings(const Settings::CategorySettingVector& changed); void processChangedSettings(const Settings::CategorySettingVector& changed);
void setJoystickLastUsed(bool enabled) { mJoystickLastUsed = enabled; } void setJoystickLastUsed(bool enabled) { mJoystickLastUsed = enabled; }
bool joystickLastUsed() { return mJoystickLastUsed; } bool joystickLastUsed() const { return mJoystickLastUsed; }
void setGuiCursorEnabled(bool enabled) { mGuiCursorEnabled = enabled; } void setGuiCursorEnabled(bool enabled) { mGuiCursorEnabled = enabled; }
void setGamepadGuiCursorEnabled(bool enabled) { mGamepadGuiCursorEnabled = enabled; } void setGamepadGuiCursorEnabled(bool enabled) { mGamepadGuiCursorEnabled = enabled; }
bool gamepadGuiCursorEnabled() { return mGamepadGuiCursorEnabled; } bool gamepadGuiCursorEnabled() const { return mGamepadGuiCursorEnabled; }
float getAxisValue(SDL_GameControllerAxis axis) const; // returns value in range [-1, 1]
bool isButtonPressed(SDL_GameControllerButton button) const;
private: private:
// Return true if GUI consumes input. // Return true if GUI consumes input.
@ -53,12 +56,10 @@ namespace MWInput
bool mJoystickEnabled; bool mJoystickEnabled;
float mGamepadCursorSpeed; float mGamepadCursorSpeed;
float mSneakToggleShortcutTimer; float mSneakToggleShortcutTimer;
float mGamepadZoom;
bool mGamepadGuiCursorEnabled; bool mGamepadGuiCursorEnabled;
bool mGuiCursorEnabled; bool mGuiCursorEnabled;
bool mJoystickLastUsed; bool mJoystickLastUsed;
bool mSneakGamepadShortcut; bool mSneakGamepadShortcut;
bool mGamepadPreviewMode;
}; };
} }
#endif #endif

View file

@ -29,12 +29,15 @@ namespace MWInput
mSwitches["vanitymode"] = true; mSwitches["vanitymode"] = true;
} }
bool ControlSwitch::get(const std::string& key) bool ControlSwitch::get(std::string_view key)
{ {
return mSwitches[key]; auto it = mSwitches.find(key);
if (it == mSwitches.end())
throw std::runtime_error("Incorrect ControlSwitch: " + std::string(key));
return it->second;
} }
void ControlSwitch::set(const std::string& key, bool value) void ControlSwitch::set(std::string_view key, bool value)
{ {
MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer();
@ -51,15 +54,14 @@ namespace MWInput
/// \fixme maybe crouching at this time /// \fixme maybe crouching at this time
player.setUpDown(0); player.setUpDown(0);
} }
else if (key == "vanitymode")
{
MWBase::Environment::get().getWorld()->allowVanityMode(value);
}
else if (key == "playerlooking" && !value) else if (key == "playerlooking" && !value)
{ {
MWBase::Environment::get().getWorld()->rotateObject(player.getPlayer(), osg::Vec3f()); MWBase::Environment::get().getWorld()->rotateObject(player.getPlayer(), osg::Vec3f());
} }
mSwitches[key] = value; auto it = mSwitches.find(key);
if (it == mSwitches.end())
throw std::runtime_error("Incorrect ControlSwitch: " + std::string(key));
it->second = value;
} }
void ControlSwitch::write(ESM::ESMWriter& writer, Loading::Listener& /*progress*/) void ControlSwitch::write(ESM::ESMWriter& writer, Loading::Listener& /*progress*/)

View file

@ -3,6 +3,7 @@
#include <map> #include <map>
#include <string> #include <string>
#include <string_view>
namespace ESM namespace ESM
{ {
@ -23,8 +24,8 @@ namespace MWInput
public: public:
ControlSwitch(); ControlSwitch();
bool get(const std::string& key); bool get(std::string_view key);
void set(const std::string& key, bool value); void set(std::string_view key, bool value);
void clear(); void clear();
void write(ESM::ESMWriter& writer, Loading::Listener& progress); void write(ESM::ESMWriter& writer, Loading::Listener& progress);
@ -32,7 +33,7 @@ namespace MWInput
int countSavedGameRecords() const; int countSavedGameRecords() const;
private: private:
std::map<std::string, bool> mSwitches; std::map<std::string, bool, std::less<>> mSwitches;
}; };
} }
#endif #endif

View file

@ -18,7 +18,6 @@
#include "controlswitch.hpp" #include "controlswitch.hpp"
#include "keyboardmanager.hpp" #include "keyboardmanager.hpp"
#include "mousemanager.hpp" #include "mousemanager.hpp"
#include "sdlmappings.hpp"
#include "sensormanager.hpp" #include "sensormanager.hpp"
namespace MWInput namespace MWInput
@ -101,8 +100,6 @@ namespace MWInput
mMouseManager->update(dt); mMouseManager->update(dt);
mSensorManager->update(dt); mSensorManager->update(dt);
mActionManager->update(dt, controllerMove); mActionManager->update(dt, controllerMove);
MWBase::Environment::get().getWorld()->applyDeferredPreviewRotationToPlayer(dt);
} }
void InputManager::setDragDrop(bool dragDrop) void InputManager::setDragDrop(bool dragDrop)
@ -135,12 +132,12 @@ namespace MWInput
mSensorManager->processChangedSettings(changed); mSensorManager->processChangedSettings(changed);
} }
bool InputManager::getControlSwitch(const std::string& sw) bool InputManager::getControlSwitch(std::string_view sw)
{ {
return mControlSwitch->get(sw); return mControlSwitch->get(sw);
} }
void InputManager::toggleControlSwitch(const std::string& sw, bool value) void InputManager::toggleControlSwitch(std::string_view sw, bool value)
{ {
mControlSwitch->set(sw, value); mControlSwitch->set(sw, value);
} }
@ -180,14 +177,14 @@ namespace MWInput
return mBindingsManager->getActionValue(action); return mBindingsManager->getActionValue(action);
} }
float InputManager::getControllerAxisValue(SDL_GameControllerAxis axis) const bool InputManager::isControllerButtonPressed(SDL_GameControllerButton button) const
{ {
return mBindingsManager->getControllerAxisValue(axis); return mControllerManager->isButtonPressed(button);
} }
uint32_t InputManager::getMouseButtonsState() const float InputManager::getControllerAxisValue(SDL_GameControllerAxis axis) const
{ {
return mMouseManager->getButtonsState(); return mControllerManager->getAxisValue(axis);
} }
int InputManager::getMouseMoveX() const int InputManager::getMouseMoveX() const

View file

@ -70,8 +70,8 @@ namespace MWInput
void setGamepadGuiCursorEnabled(bool enabled) override; void setGamepadGuiCursorEnabled(bool enabled) override;
void setAttemptJump(bool jumping) override; void setAttemptJump(bool jumping) override;
void toggleControlSwitch (const std::string& sw, bool value) override; void toggleControlSwitch(std::string_view sw, bool value) override;
bool getControlSwitch (const std::string& sw) override; bool getControlSwitch(std::string_view sw) override;
std::string getActionDescription (int action) const override; std::string getActionDescription (int action) const override;
std::string getActionKeyBindingName (int action) const override; std::string getActionKeyBindingName (int action) const override;
@ -79,8 +79,8 @@ namespace MWInput
bool actionIsActive(int action) const override; bool actionIsActive(int action) const override;
float getActionValue(int action) const override; float getActionValue(int action) const override;
bool isControllerButtonPressed(SDL_GameControllerButton button) const override;
float getControllerAxisValue(SDL_GameControllerAxis axis) const override; float getControllerAxisValue(SDL_GameControllerAxis axis) const override;
uint32_t getMouseButtonsState() const override;
int getMouseMoveX() const override; int getMouseMoveX() const override;
int getMouseMoveY() const override; int getMouseMoveY() const override;

View file

@ -4,6 +4,8 @@
#include <MyGUI_InputManager.h> #include <MyGUI_InputManager.h>
#include <components/sdlutil/sdlmappings.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/inputmanager.hpp" #include "../mwbase/inputmanager.hpp"
#include "../mwbase/luamanager.hpp" #include "../mwbase/luamanager.hpp"
@ -13,7 +15,6 @@
#include "actions.hpp" #include "actions.hpp"
#include "bindingsmanager.hpp" #include "bindingsmanager.hpp"
#include "sdlmappings.hpp"
namespace MWInput namespace MWInput
{ {
@ -35,16 +36,16 @@ namespace MWInput
// HACK: to make default keybinding for the console work without printing an extra "^" upon closing // HACK: to make default keybinding for the console work without printing an extra "^" upon closing
// This assumes that SDL_TextInput events always come *after* the key event // This assumes that SDL_TextInput events always come *after* the key event
// (which is somewhat reasonable, and hopefully true for all SDL platforms) // (which is somewhat reasonable, and hopefully true for all SDL platforms)
auto kc = sdlKeyToMyGUI(arg.keysym.sym); auto kc = SDLUtil::sdlKeyToMyGUI(arg.keysym.sym);
if (mBindingsManager->getKeyBinding(A_Console) == arg.keysym.scancode if (mBindingsManager->getKeyBinding(A_Console) == arg.keysym.scancode
&& MWBase::Environment::get().getWindowManager()->isConsoleMode()) && MWBase::Environment::get().getWindowManager()->isConsoleMode())
SDL_StopTextInput(); SDL_StopTextInput();
bool consumed = SDL_IsTextInputActive() && // Little trick to check if key is printable bool consumed = SDL_IsTextInputActive() && // Little trick to check if key is printable
(!(SDLK_SCANCODE_MASK & arg.keysym.sym) && (!(SDLK_SCANCODE_MASK & arg.keysym.sym) &&
(std::isprint(arg.keysym.sym) ||
// Don't trust isprint for symbols outside the extended ASCII range // Don't trust isprint for symbols outside the extended ASCII range
(kc == MyGUI::KeyCode::None && arg.keysym.sym > 0xff))); ((kc == MyGUI::KeyCode::None && arg.keysym.sym > 0xff) ||
(arg.keysym.sym >= 0 && arg.keysym.sym <= 255 && std::isprint(arg.keysym.sym))));
if (kc != MyGUI::KeyCode::None && !mBindingsManager->isDetectingBindingState()) if (kc != MyGUI::KeyCode::None && !mBindingsManager->isDetectingBindingState())
{ {
if (MWBase::Environment::get().getWindowManager()->injectKeyPress(kc, 0, arg.repeat)) if (MWBase::Environment::get().getWindowManager()->injectKeyPress(kc, 0, arg.repeat))
@ -71,7 +72,7 @@ namespace MWInput
void KeyboardManager::keyReleased(const SDL_KeyboardEvent &arg) void KeyboardManager::keyReleased(const SDL_KeyboardEvent &arg)
{ {
MWBase::Environment::get().getInputManager()->setJoystickLastUsed(false); MWBase::Environment::get().getInputManager()->setJoystickLastUsed(false);
auto kc = sdlKeyToMyGUI(arg.keysym.sym); auto kc = SDLUtil::sdlKeyToMyGUI(arg.keysym.sym);
if (!mBindingsManager->isDetectingBindingState()) if (!mBindingsManager->isDetectingBindingState())
mBindingsManager->setPlayerControlsEnabled(!MyGUI::InputManager::getInstance().injectKeyRelease(kc)); mBindingsManager->setPlayerControlsEnabled(!MyGUI::InputManager::getInstance().injectKeyRelease(kc));

View file

@ -7,6 +7,7 @@
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include <components/sdlutil/sdlinputwrapper.hpp> #include <components/sdlutil/sdlinputwrapper.hpp>
#include <components/sdlutil/sdlmappings.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/inputmanager.hpp" #include "../mwbase/inputmanager.hpp"
@ -17,7 +18,6 @@
#include "actions.hpp" #include "actions.hpp"
#include "bindingsmanager.hpp" #include "bindingsmanager.hpp"
#include "sdlmappings.hpp"
namespace MWInput namespace MWInput
{ {
@ -34,7 +34,6 @@ namespace MWInput
, mMouseWheel(0) , mMouseWheel(0)
, mMouseLookEnabled(false) , mMouseLookEnabled(false)
, mGuiCursorEnabled(true) , mGuiCursorEnabled(true)
, mButtonsState(0)
, mMouseMoveX(0) , mMouseMoveX(0)
, mMouseMoveY(0) , mMouseMoveY(0)
{ {
@ -126,7 +125,11 @@ namespace MWInput
else else
{ {
bool guiMode = MWBase::Environment::get().getWindowManager()->isGuiMode(); bool guiMode = MWBase::Environment::get().getWindowManager()->isGuiMode();
guiMode = MyGUI::InputManager::getInstance().injectMouseRelease(static_cast<int>(mGuiCursorX), static_cast<int>(mGuiCursorY), sdlButtonToMyGUI(id)) && guiMode; guiMode = MyGUI::InputManager::getInstance().injectMouseRelease(
static_cast<int>(mGuiCursorX),
static_cast<int>(mGuiCursorY),
SDLUtil::sdlMouseButtonToMyGui(id)
) && guiMode;
if (mBindingsManager->isDetectingBindingState()) if (mBindingsManager->isDetectingBindingState())
return; // don't allow same mouseup to bind as initiated bind return; // don't allow same mouseup to bind as initiated bind
@ -154,7 +157,11 @@ namespace MWInput
if (id == SDL_BUTTON_LEFT || id == SDL_BUTTON_RIGHT) // MyGUI only uses these mouse events if (id == SDL_BUTTON_LEFT || id == SDL_BUTTON_RIGHT) // MyGUI only uses these mouse events
{ {
guiMode = MWBase::Environment::get().getWindowManager()->isGuiMode(); guiMode = MWBase::Environment::get().getWindowManager()->isGuiMode();
guiMode = MyGUI::InputManager::getInstance().injectMousePress(static_cast<int>(mGuiCursorX), static_cast<int>(mGuiCursorY), sdlButtonToMyGUI(id)) && guiMode; guiMode = MyGUI::InputManager::getInstance().injectMousePress(
static_cast<int>(mGuiCursorX),
static_cast<int>(mGuiCursorY),
SDLUtil::sdlMouseButtonToMyGui(id)
) && guiMode;
if (MyGUI::InputManager::getInstance().getMouseFocusWidget () != nullptr) if (MyGUI::InputManager::getInstance().getMouseFocusWidget () != nullptr)
{ {
MyGUI::Button* b = MyGUI::InputManager::getInstance().getMouseFocusWidget()->castType<MyGUI::Button>(false); MyGUI::Button* b = MyGUI::InputManager::getInstance().getMouseFocusWidget()->castType<MyGUI::Button>(false);
@ -199,7 +206,7 @@ namespace MWInput
void MouseManager::update(float dt) void MouseManager::update(float dt)
{ {
mButtonsState = SDL_GetRelativeMouseState(&mMouseMoveX, &mMouseMoveY); SDL_GetRelativeMouseState(&mMouseMoveX, &mMouseMoveY);
if (!mMouseLookEnabled) if (!mMouseLookEnabled)
return; return;
@ -230,12 +237,18 @@ namespace MWInput
bool MouseManager::injectMouseButtonPress(Uint8 button) bool MouseManager::injectMouseButtonPress(Uint8 button)
{ {
return MyGUI::InputManager::getInstance().injectMousePress(static_cast<int>(mGuiCursorX), static_cast<int>(mGuiCursorY), sdlButtonToMyGUI(button)); return MyGUI::InputManager::getInstance().injectMousePress(
static_cast<int>(mGuiCursorX),
static_cast<int>(mGuiCursorY),
SDLUtil::sdlMouseButtonToMyGui(button));
} }
bool MouseManager::injectMouseButtonRelease(Uint8 button) bool MouseManager::injectMouseButtonRelease(Uint8 button)
{ {
return MyGUI::InputManager::getInstance().injectMouseRelease(static_cast<int>(mGuiCursorX), static_cast<int>(mGuiCursorY), sdlButtonToMyGUI(button)); return MyGUI::InputManager::getInstance().injectMouseRelease(
static_cast<int>(mGuiCursorX),
static_cast<int>(mGuiCursorY),
SDLUtil::sdlMouseButtonToMyGui(button));
} }
void MouseManager::injectMouseMove(float xMove, float yMove, float mouseWheelMove) void MouseManager::injectMouseMove(float xMove, float yMove, float mouseWheelMove)
@ -245,8 +258,8 @@ namespace MWInput
mMouseWheel += mouseWheelMove; mMouseWheel += mouseWheelMove;
const MyGUI::IntSize& viewSize = MyGUI::RenderManager::getInstance().getViewSize(); const MyGUI::IntSize& viewSize = MyGUI::RenderManager::getInstance().getViewSize();
mGuiCursorX = std::max(0.f, std::min(mGuiCursorX, float(viewSize.width - 1))); mGuiCursorX = std::clamp<float>(mGuiCursorX, 0.f, viewSize.width - 1);
mGuiCursorY = std::max(0.f, std::min(mGuiCursorY, float(viewSize.height - 1))); mGuiCursorY = std::clamp<float>(mGuiCursorY, 0.f, viewSize.height - 1);
MyGUI::InputManager::getInstance().injectMouseMove(static_cast<int>(mGuiCursorX), static_cast<int>(mGuiCursorY), static_cast<int>(mMouseWheel)); MyGUI::InputManager::getInstance().injectMouseMove(static_cast<int>(mGuiCursorX), static_cast<int>(mGuiCursorY), static_cast<int>(mMouseWheel));
} }

View file

@ -38,7 +38,6 @@ namespace MWInput
void setMouseLookEnabled(bool enabled) { mMouseLookEnabled = enabled; } void setMouseLookEnabled(bool enabled) { mMouseLookEnabled = enabled; }
void setGuiCursorEnabled(bool enabled) { mGuiCursorEnabled = enabled; } void setGuiCursorEnabled(bool enabled) { mGuiCursorEnabled = enabled; }
uint32_t getButtonsState() const { return mButtonsState; }
int getMouseMoveX() const { return mMouseMoveX; } int getMouseMoveX() const { return mMouseMoveX; }
int getMouseMoveY() const { return mMouseMoveY; } int getMouseMoveY() const { return mMouseMoveY; }
@ -58,7 +57,6 @@ namespace MWInput
bool mMouseLookEnabled; bool mMouseLookEnabled;
bool mGuiCursorEnabled; bool mGuiCursorEnabled;
uint32_t mButtonsState;
int mMouseMoveX; int mMouseMoveX;
int mMouseMoveY; int mMouseMoveY;
}; };

View file

@ -1,218 +0,0 @@
#include "sdlmappings.hpp"
#include <map>
#include <MyGUI_MouseButton.h>
#include <SDL_gamecontroller.h>
#include <SDL_mouse.h>
namespace MWInput
{
std::string sdlControllerButtonToString(int button)
{
switch(button)
{
case SDL_CONTROLLER_BUTTON_A:
return "A Button";
case SDL_CONTROLLER_BUTTON_B:
return "B Button";
case SDL_CONTROLLER_BUTTON_BACK:
return "Back Button";
case SDL_CONTROLLER_BUTTON_DPAD_DOWN:
return "DPad Down";
case SDL_CONTROLLER_BUTTON_DPAD_LEFT:
return "DPad Left";
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT:
return "DPad Right";
case SDL_CONTROLLER_BUTTON_DPAD_UP:
return "DPad Up";
case SDL_CONTROLLER_BUTTON_GUIDE:
return "Guide Button";
case SDL_CONTROLLER_BUTTON_LEFTSHOULDER:
return "Left Shoulder";
case SDL_CONTROLLER_BUTTON_LEFTSTICK:
return "Left Stick Button";
case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER:
return "Right Shoulder";
case SDL_CONTROLLER_BUTTON_RIGHTSTICK:
return "Right Stick Button";
case SDL_CONTROLLER_BUTTON_START:
return "Start Button";
case SDL_CONTROLLER_BUTTON_X:
return "X Button";
case SDL_CONTROLLER_BUTTON_Y:
return "Y Button";
default:
return "Button " + std::to_string(button);
}
}
std::string sdlControllerAxisToString(int axis)
{
switch(axis)
{
case SDL_CONTROLLER_AXIS_LEFTX:
return "Left Stick X";
case SDL_CONTROLLER_AXIS_LEFTY:
return "Left Stick Y";
case SDL_CONTROLLER_AXIS_RIGHTX:
return "Right Stick X";
case SDL_CONTROLLER_AXIS_RIGHTY:
return "Right Stick Y";
case SDL_CONTROLLER_AXIS_TRIGGERLEFT:
return "Left Trigger";
case SDL_CONTROLLER_AXIS_TRIGGERRIGHT:
return "Right Trigger";
default:
return "Axis " + std::to_string(axis);
}
}
MyGUI::MouseButton sdlButtonToMyGUI(Uint8 button)
{
//The right button is the second button, according to MyGUI
if(button == SDL_BUTTON_RIGHT)
button = SDL_BUTTON_MIDDLE;
else if(button == SDL_BUTTON_MIDDLE)
button = SDL_BUTTON_RIGHT;
//MyGUI's buttons are 0 indexed
return MyGUI::MouseButton::Enum(button - 1);
}
void initKeyMap(std::map<SDL_Keycode, MyGUI::KeyCode>& keyMap)
{
keyMap[SDLK_UNKNOWN] = MyGUI::KeyCode::None;
keyMap[SDLK_ESCAPE] = MyGUI::KeyCode::Escape;
keyMap[SDLK_1] = MyGUI::KeyCode::One;
keyMap[SDLK_2] = MyGUI::KeyCode::Two;
keyMap[SDLK_3] = MyGUI::KeyCode::Three;
keyMap[SDLK_4] = MyGUI::KeyCode::Four;
keyMap[SDLK_5] = MyGUI::KeyCode::Five;
keyMap[SDLK_6] = MyGUI::KeyCode::Six;
keyMap[SDLK_7] = MyGUI::KeyCode::Seven;
keyMap[SDLK_8] = MyGUI::KeyCode::Eight;
keyMap[SDLK_9] = MyGUI::KeyCode::Nine;
keyMap[SDLK_0] = MyGUI::KeyCode::Zero;
keyMap[SDLK_MINUS] = MyGUI::KeyCode::Minus;
keyMap[SDLK_EQUALS] = MyGUI::KeyCode::Equals;
keyMap[SDLK_BACKSPACE] = MyGUI::KeyCode::Backspace;
keyMap[SDLK_TAB] = MyGUI::KeyCode::Tab;
keyMap[SDLK_q] = MyGUI::KeyCode::Q;
keyMap[SDLK_w] = MyGUI::KeyCode::W;
keyMap[SDLK_e] = MyGUI::KeyCode::E;
keyMap[SDLK_r] = MyGUI::KeyCode::R;
keyMap[SDLK_t] = MyGUI::KeyCode::T;
keyMap[SDLK_y] = MyGUI::KeyCode::Y;
keyMap[SDLK_u] = MyGUI::KeyCode::U;
keyMap[SDLK_i] = MyGUI::KeyCode::I;
keyMap[SDLK_o] = MyGUI::KeyCode::O;
keyMap[SDLK_p] = MyGUI::KeyCode::P;
keyMap[SDLK_RETURN] = MyGUI::KeyCode::Return;
keyMap[SDLK_a] = MyGUI::KeyCode::A;
keyMap[SDLK_s] = MyGUI::KeyCode::S;
keyMap[SDLK_d] = MyGUI::KeyCode::D;
keyMap[SDLK_f] = MyGUI::KeyCode::F;
keyMap[SDLK_g] = MyGUI::KeyCode::G;
keyMap[SDLK_h] = MyGUI::KeyCode::H;
keyMap[SDLK_j] = MyGUI::KeyCode::J;
keyMap[SDLK_k] = MyGUI::KeyCode::K;
keyMap[SDLK_l] = MyGUI::KeyCode::L;
keyMap[SDLK_SEMICOLON] = MyGUI::KeyCode::Semicolon;
keyMap[SDLK_QUOTE] = MyGUI::KeyCode::Apostrophe;
keyMap[SDLK_BACKQUOTE] = MyGUI::KeyCode::Grave;
keyMap[SDLK_LSHIFT] = MyGUI::KeyCode::LeftShift;
keyMap[SDLK_BACKSLASH] = MyGUI::KeyCode::Backslash;
keyMap[SDLK_z] = MyGUI::KeyCode::Z;
keyMap[SDLK_x] = MyGUI::KeyCode::X;
keyMap[SDLK_c] = MyGUI::KeyCode::C;
keyMap[SDLK_v] = MyGUI::KeyCode::V;
keyMap[SDLK_b] = MyGUI::KeyCode::B;
keyMap[SDLK_n] = MyGUI::KeyCode::N;
keyMap[SDLK_m] = MyGUI::KeyCode::M;
keyMap[SDLK_COMMA] = MyGUI::KeyCode::Comma;
keyMap[SDLK_PERIOD] = MyGUI::KeyCode::Period;
keyMap[SDLK_SLASH] = MyGUI::KeyCode::Slash;
keyMap[SDLK_RSHIFT] = MyGUI::KeyCode::RightShift;
keyMap[SDLK_KP_MULTIPLY] = MyGUI::KeyCode::Multiply;
keyMap[SDLK_LALT] = MyGUI::KeyCode::LeftAlt;
keyMap[SDLK_SPACE] = MyGUI::KeyCode::Space;
keyMap[SDLK_CAPSLOCK] = MyGUI::KeyCode::Capital;
keyMap[SDLK_F1] = MyGUI::KeyCode::F1;
keyMap[SDLK_F2] = MyGUI::KeyCode::F2;
keyMap[SDLK_F3] = MyGUI::KeyCode::F3;
keyMap[SDLK_F4] = MyGUI::KeyCode::F4;
keyMap[SDLK_F5] = MyGUI::KeyCode::F5;
keyMap[SDLK_F6] = MyGUI::KeyCode::F6;
keyMap[SDLK_F7] = MyGUI::KeyCode::F7;
keyMap[SDLK_F8] = MyGUI::KeyCode::F8;
keyMap[SDLK_F9] = MyGUI::KeyCode::F9;
keyMap[SDLK_F10] = MyGUI::KeyCode::F10;
keyMap[SDLK_NUMLOCKCLEAR] = MyGUI::KeyCode::NumLock;
keyMap[SDLK_SCROLLLOCK] = MyGUI::KeyCode::ScrollLock;
keyMap[SDLK_KP_7] = MyGUI::KeyCode::Numpad7;
keyMap[SDLK_KP_8] = MyGUI::KeyCode::Numpad8;
keyMap[SDLK_KP_9] = MyGUI::KeyCode::Numpad9;
keyMap[SDLK_KP_MINUS] = MyGUI::KeyCode::Subtract;
keyMap[SDLK_KP_4] = MyGUI::KeyCode::Numpad4;
keyMap[SDLK_KP_5] = MyGUI::KeyCode::Numpad5;
keyMap[SDLK_KP_6] = MyGUI::KeyCode::Numpad6;
keyMap[SDLK_KP_PLUS] = MyGUI::KeyCode::Add;
keyMap[SDLK_KP_1] = MyGUI::KeyCode::Numpad1;
keyMap[SDLK_KP_2] = MyGUI::KeyCode::Numpad2;
keyMap[SDLK_KP_3] = MyGUI::KeyCode::Numpad3;
keyMap[SDLK_KP_0] = MyGUI::KeyCode::Numpad0;
keyMap[SDLK_KP_PERIOD] = MyGUI::KeyCode::Decimal;
keyMap[SDLK_F11] = MyGUI::KeyCode::F11;
keyMap[SDLK_F12] = MyGUI::KeyCode::F12;
keyMap[SDLK_F13] = MyGUI::KeyCode::F13;
keyMap[SDLK_F14] = MyGUI::KeyCode::F14;
keyMap[SDLK_F15] = MyGUI::KeyCode::F15;
keyMap[SDLK_KP_EQUALS] = MyGUI::KeyCode::NumpadEquals;
keyMap[SDLK_COLON] = MyGUI::KeyCode::Colon;
keyMap[SDLK_KP_ENTER] = MyGUI::KeyCode::NumpadEnter;
keyMap[SDLK_KP_DIVIDE] = MyGUI::KeyCode::Divide;
keyMap[SDLK_SYSREQ] = MyGUI::KeyCode::SysRq;
keyMap[SDLK_RALT] = MyGUI::KeyCode::RightAlt;
keyMap[SDLK_HOME] = MyGUI::KeyCode::Home;
keyMap[SDLK_UP] = MyGUI::KeyCode::ArrowUp;
keyMap[SDLK_PAGEUP] = MyGUI::KeyCode::PageUp;
keyMap[SDLK_LEFT] = MyGUI::KeyCode::ArrowLeft;
keyMap[SDLK_RIGHT] = MyGUI::KeyCode::ArrowRight;
keyMap[SDLK_END] = MyGUI::KeyCode::End;
keyMap[SDLK_DOWN] = MyGUI::KeyCode::ArrowDown;
keyMap[SDLK_PAGEDOWN] = MyGUI::KeyCode::PageDown;
keyMap[SDLK_INSERT] = MyGUI::KeyCode::Insert;
keyMap[SDLK_DELETE] = MyGUI::KeyCode::Delete;
keyMap[SDLK_APPLICATION] = MyGUI::KeyCode::AppMenu;
//The function of the Ctrl and Meta keys are switched on macOS compared to other platforms.
//For instance] = Cmd+C versus Ctrl+C to copy from the system clipboard
#if defined(__APPLE__)
keyMap[SDLK_LGUI] = MyGUI::KeyCode::LeftControl;
keyMap[SDLK_RGUI] = MyGUI::KeyCode::RightControl;
keyMap[SDLK_LCTRL] = MyGUI::KeyCode::LeftWindows;
keyMap[SDLK_RCTRL] = MyGUI::KeyCode::RightWindows;
#else
keyMap[SDLK_LGUI] = MyGUI::KeyCode::LeftWindows;
keyMap[SDLK_RGUI] = MyGUI::KeyCode::RightWindows;
keyMap[SDLK_LCTRL] = MyGUI::KeyCode::LeftControl;
keyMap[SDLK_RCTRL] = MyGUI::KeyCode::RightControl;
#endif
}
MyGUI::KeyCode sdlKeyToMyGUI(SDL_Keycode code)
{
static std::map<SDL_Keycode, MyGUI::KeyCode> keyMap;
if (keyMap.empty())
initKeyMap(keyMap);
MyGUI::KeyCode kc = MyGUI::KeyCode::None;
auto foundKey = keyMap.find(code);
if (foundKey != keyMap.end())
kc = foundKey->second;
return kc;
}
}

View file

@ -3,6 +3,8 @@
#include <cstring> #include <cstring>
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include <components/lua/luastate.hpp>
#include <components/settings/settings.hpp>
#include "../mwworld/cellstore.hpp" #include "../mwworld/cellstore.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
@ -11,15 +13,35 @@
namespace MWLua namespace MWLua
{ {
Action::Action(LuaUtil::LuaState* state)
{
static const bool luaDebug = Settings::Manager::getBool("lua debug", "Lua");
if (luaDebug)
mCallerTraceback = state->debugTraceback();
}
void Action::safeApply(WorldView& w) const
{
try
{
apply(w);
}
catch (const std::exception& e)
{
Log(Debug::Error) << "Error in " << this->toString() << ": " << e.what();
if (mCallerTraceback.empty())
Log(Debug::Error) << "Set 'lua_debug=true' in settings.cfg to enable action tracebacks";
else
Log(Debug::Error) << "Caller " << mCallerTraceback;
}
}
void TeleportAction::apply(WorldView& worldView) const void TeleportAction::apply(WorldView& worldView) const
{ {
MWWorld::CellStore* cell = worldView.findCell(mCell, mPos); MWWorld::CellStore* cell = worldView.findCell(mCell, mPos);
if (!cell) if (!cell)
{ throw std::runtime_error(std::string("cell not found: '") + mCell + "'");
Log(Debug::Error) << "LuaManager::applyTeleport -> cell not found: '" << mCell << "'";
return;
}
MWBase::World* world = MWBase::Environment::get().getWorld(); MWBase::World* world = MWBase::Environment::get().getWorld();
MWWorld::Ptr obj = worldView.getObjectRegistry()->getPtr(mObject, false); MWWorld::Ptr obj = worldView.getObjectRegistry()->getPtr(mObject, false);

View file

@ -6,6 +6,11 @@
#include "object.hpp" #include "object.hpp"
#include "worldview.hpp" #include "worldview.hpp"
namespace LuaUtil
{
class LuaState;
}
namespace MWLua namespace MWLua
{ {
@ -16,17 +21,25 @@ namespace MWLua
class Action class Action
{ {
public: public:
Action(LuaUtil::LuaState* state);
virtual ~Action() {} virtual ~Action() {}
void safeApply(WorldView&) const;
virtual void apply(WorldView&) const = 0; virtual void apply(WorldView&) const = 0;
virtual std::string toString() const = 0;
private:
std::string mCallerTraceback;
}; };
class TeleportAction final : public Action class TeleportAction final : public Action
{ {
public: public:
TeleportAction(ObjectId object, std::string cell, const osg::Vec3f& pos, const osg::Vec3f& rot) TeleportAction(LuaUtil::LuaState* state, ObjectId object, std::string cell, const osg::Vec3f& pos, const osg::Vec3f& rot)
: mObject(object), mCell(std::move(cell)), mPos(pos), mRot(rot) {} : Action(state), mObject(object), mCell(std::move(cell)), mPos(pos), mRot(rot) {}
void apply(WorldView&) const override; void apply(WorldView&) const override;
std::string toString() const override { return "TeleportAction"; }
private: private:
ObjectId mObject; ObjectId mObject;
@ -41,9 +54,11 @@ namespace MWLua
using Item = std::variant<std::string, ObjectId>; // recordId or ObjectId using Item = std::variant<std::string, ObjectId>; // recordId or ObjectId
using Equipment = std::map<int, Item>; // slot to item using Equipment = std::map<int, Item>; // slot to item
SetEquipmentAction(ObjectId actor, Equipment equipment) : mActor(actor), mEquipment(std::move(equipment)) {} SetEquipmentAction(LuaUtil::LuaState* state, ObjectId actor, Equipment equipment)
: Action(state), mActor(actor), mEquipment(std::move(equipment)) {}
void apply(WorldView&) const override; void apply(WorldView&) const override;
std::string toString() const override { return "SetEquipmentAction"; }
private: private:
ObjectId mActor; ObjectId mActor;

View file

@ -19,36 +19,36 @@ namespace MWLua
sol::function getAsyncPackageInitializer(const Context& context) sol::function getAsyncPackageInitializer(const Context& context)
{ {
using TimeUnit = LuaUtil::ScriptsContainer::TimeUnit; using TimerType = LuaUtil::ScriptsContainer::TimerType;
sol::usertype<AsyncPackageId> api = context.mLua->sol().new_usertype<AsyncPackageId>("AsyncPackage"); sol::usertype<AsyncPackageId> api = context.mLua->sol().new_usertype<AsyncPackageId>("AsyncPackage");
api["registerTimerCallback"] = [](const AsyncPackageId& asyncId, std::string_view name, sol::function callback) api["registerTimerCallback"] = [](const AsyncPackageId& asyncId, std::string_view name, sol::function callback)
{ {
asyncId.mContainer->registerTimerCallback(asyncId.mScriptId, name, std::move(callback)); asyncId.mContainer->registerTimerCallback(asyncId.mScriptId, name, std::move(callback));
return TimerCallback{asyncId, std::string(name)}; return TimerCallback{asyncId, std::string(name)};
}; };
api["newTimerInSeconds"] = [world=context.mWorldView](const AsyncPackageId&, double delay, api["newSimulationTimer"] = [world=context.mWorldView](const AsyncPackageId&, double delay,
const TimerCallback& callback, sol::object callbackArg) const TimerCallback& callback, sol::object callbackArg)
{ {
callback.mAsyncId.mContainer->setupSerializableTimer( callback.mAsyncId.mContainer->setupSerializableTimer(
TimeUnit::SECONDS, world->getGameTimeInSeconds() + delay, TimerType::SIMULATION_TIME, world->getSimulationTime() + delay,
callback.mAsyncId.mScriptId, callback.mName, std::move(callbackArg)); callback.mAsyncId.mScriptId, callback.mName, std::move(callbackArg));
}; };
api["newTimerInHours"] = [world=context.mWorldView](const AsyncPackageId&, double delay, api["newGameTimer"] = [world=context.mWorldView](const AsyncPackageId&, double delay,
const TimerCallback& callback, sol::object callbackArg) const TimerCallback& callback, sol::object callbackArg)
{ {
callback.mAsyncId.mContainer->setupSerializableTimer( callback.mAsyncId.mContainer->setupSerializableTimer(
TimeUnit::HOURS, world->getGameTimeInHours() + delay, TimerType::GAME_TIME, world->getGameTime() + delay,
callback.mAsyncId.mScriptId, callback.mName, std::move(callbackArg)); callback.mAsyncId.mScriptId, callback.mName, std::move(callbackArg));
}; };
api["newUnsavableTimerInSeconds"] = [world=context.mWorldView](const AsyncPackageId& asyncId, double delay, sol::function callback) api["newUnsavableSimulationTimer"] = [world=context.mWorldView](const AsyncPackageId& asyncId, double delay, sol::function callback)
{ {
asyncId.mContainer->setupUnsavableTimer( asyncId.mContainer->setupUnsavableTimer(
TimeUnit::SECONDS, world->getGameTimeInSeconds() + delay, asyncId.mScriptId, std::move(callback)); TimerType::SIMULATION_TIME, world->getSimulationTime() + delay, asyncId.mScriptId, std::move(callback));
}; };
api["newUnsavableTimerInHours"] = [world=context.mWorldView](const AsyncPackageId& asyncId, double delay, sol::function callback) api["newUnsavableGameTimer"] = [world=context.mWorldView](const AsyncPackageId& asyncId, double delay, sol::function callback)
{ {
asyncId.mContainer->setupUnsavableTimer( asyncId.mContainer->setupUnsavableTimer(
TimeUnit::HOURS, world->getGameTimeInHours() + delay, asyncId.mScriptId, std::move(callback)); TimerType::GAME_TIME, world->getGameTime() + delay, asyncId.mScriptId, std::move(callback));
}; };
api["callback"] = [](const AsyncPackageId& asyncId, sol::function fn) api["callback"] = [](const AsyncPackageId& asyncId, sol::function fn)
{ {

View file

@ -1,12 +1,82 @@
#include "luabindings.hpp" #include "luabindings.hpp"
#include "../mwrender/camera.hpp"
namespace MWLua namespace MWLua
{ {
using CameraMode = MWRender::Camera::Mode;
sol::table initCameraPackage(const Context& context) sol::table initCameraPackage(const Context& context)
{ {
MWRender::Camera* camera = MWBase::Environment::get().getWorld()->getCamera();
sol::table api(context.mLua->sol(), sol::create); sol::table api(context.mLua->sol(), sol::create);
// TODO api["MODE"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with(
"Static", CameraMode::Static,
"FirstPerson", CameraMode::FirstPerson,
"ThirdPerson", CameraMode::ThirdPerson,
"Vanity", CameraMode::Vanity,
"Preview", CameraMode::Preview
));
api["getMode"] = [camera]() -> int { return static_cast<int>(camera->getMode()); };
api["getQueuedMode"] = [camera]() -> sol::optional<int>
{
std::optional<CameraMode> mode = camera->getQueuedMode();
if (mode)
return static_cast<int>(*mode);
else
return sol::nullopt;
};
api["setMode"] = [camera](int mode, sol::optional<bool> force)
{
camera->setMode(static_cast<CameraMode>(mode), force ? *force : false);
};
api["allowCharacterDeferredRotation"] = [camera](bool v) { camera->allowCharacterDeferredRotation(v); };
api["showCrosshair"] = [camera](bool v) { camera->showCrosshair(v); };
api["getTrackedPosition"] = [camera]() -> osg::Vec3f { return camera->getTrackedPosition(); };
api["getPosition"] = [camera]() -> osg::Vec3f { return camera->getPosition(); };
// All angles are negated in order to make camera rotation consistent with objects rotation.
// TODO: Fix the inconsistency of rotation direction in camera.cpp.
api["getPitch"] = [camera]() { return -camera->getPitch(); };
api["getYaw"] = [camera]() { return -camera->getYaw(); };
api["getRoll"] = [camera]() { return -camera->getRoll(); };
api["setStaticPosition"] = [camera](const osg::Vec3f& pos) { camera->setStaticPosition(pos); };
api["setPitch"] = [camera](float v)
{
camera->setPitch(-v, true);
if (camera->getMode() == CameraMode::ThirdPerson)
camera->calculateDeferredRotation();
};
api["setYaw"] = [camera](float v)
{
camera->setYaw(-v, true);
if (camera->getMode() == CameraMode::ThirdPerson)
camera->calculateDeferredRotation();
};
api["setRoll"] = [camera](float v) { camera->setRoll(-v); };
api["setExtraPitch"] = [camera](float v) { camera->setExtraPitch(-v); };
api["setExtraYaw"] = [camera](float v) { camera->setExtraYaw(-v); };
api["getExtraPitch"] = [camera]() { return -camera->getExtraPitch(); };
api["getExtraYaw"] = [camera]() { return -camera->getExtraYaw(); };
api["getThirdPersonDistance"] = [camera]() { return camera->getCameraDistance(); };
api["setPreferredThirdPersonDistance"] = [camera](float v) { camera->setPreferredCameraDistance(v); };
api["getFirstPersonOffset"] = [camera]() { return camera->getFirstPersonOffset(); };
api["setFirstPersonOffset"] = [camera](const osg::Vec3f& v) { camera->setFirstPersonOffset(v); };
api["getFocalPreferredOffset"] = [camera]() -> osg::Vec2f { return camera->getFocalPointTargetOffset(); };
api["setFocalPreferredOffset"] = [camera](const osg::Vec2f& v) { camera->setFocalPointTargetOffset(v); };
api["getFocalTransitionSpeed"] = [camera]() { return camera->getFocalPointTransitionSpeed(); };
api["setFocalTransitionSpeed"] = [camera](float v) { camera->setFocalPointTransitionSpeed(v); };
api["instantTransition"] = [camera]() { camera->instantTransition(); };
return LuaUtil::makeReadOnly(api); return LuaUtil::makeReadOnly(api);
} }

View file

@ -4,6 +4,14 @@
#include "../mwworld/cellstore.hpp" #include "../mwworld/cellstore.hpp"
namespace sol
{
template <>
struct is_automagical<MWLua::LCell> : std::false_type {};
template <>
struct is_automagical<MWLua::GCell> : std::false_type {};
}
namespace MWLua namespace MWLua
{ {

View file

@ -7,6 +7,7 @@ namespace LuaUtil
{ {
class LuaState; class LuaState;
class UserdataSerializer; class UserdataSerializer;
class I18nManager;
} }
namespace MWLua namespace MWLua
@ -20,6 +21,7 @@ namespace MWLua
LuaManager* mLuaManager; LuaManager* mLuaManager;
LuaUtil::LuaState* mLua; LuaUtil::LuaState* mLua;
LuaUtil::UserdataSerializer* mSerializer; LuaUtil::UserdataSerializer* mSerializer;
LuaUtil::I18nManager* mI18n;
WorldView* mWorldView; WorldView* mWorldView;
LocalEventQueue* mLocalEventQueue; LocalEventQueue* mLocalEventQueue;
GlobalEventQueue* mGlobalEventQueue; GlobalEventQueue* mGlobalEventQueue;

View file

@ -2,6 +2,7 @@
#include <SDL_events.h> #include <SDL_events.h>
#include <SDL_gamecontroller.h> #include <SDL_gamecontroller.h>
#include <SDL_mouse.h>
#include "../mwbase/inputmanager.hpp" #include "../mwbase/inputmanager.hpp"
#include "../mwinput/actions.hpp" #include "../mwinput/actions.hpp"
@ -18,9 +19,14 @@ namespace MWLua
sol::table initInputPackage(const Context& context) sol::table initInputPackage(const Context& context)
{ {
sol::usertype<SDL_Keysym> keyEvent = context.mLua->sol().new_usertype<SDL_Keysym>("KeyEvent"); sol::usertype<SDL_Keysym> keyEvent = context.mLua->sol().new_usertype<SDL_Keysym>("KeyEvent");
keyEvent["symbol"] = sol::readonly_property([](const SDL_Keysym& e) { return std::string(1, static_cast<char>(e.sym)); }); keyEvent["symbol"] = sol::readonly_property([](const SDL_Keysym& e)
keyEvent["code"] = sol::readonly_property([](const SDL_Keysym& e) -> int { return e.sym; }); {
keyEvent["modifiers"] = sol::readonly_property([](const SDL_Keysym& e) -> int { return e.mod; }); if (e.sym > 0 && e.sym <= 255)
return std::string(1, static_cast<char>(e.sym));
else
return std::string();
});
keyEvent["code"] = sol::readonly_property([](const SDL_Keysym& e) -> int { return e.scancode; });
keyEvent["withShift"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_SHIFT; }); keyEvent["withShift"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_SHIFT; });
keyEvent["withCtrl"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_CTRL; }); keyEvent["withCtrl"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_CTRL; });
keyEvent["withAlt"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_ALT; }); keyEvent["withAlt"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_ALT; });
@ -31,9 +37,26 @@ namespace MWLua
api["isIdle"] = [input]() { return input->isIdle(); }; api["isIdle"] = [input]() { return input->isIdle(); };
api["isActionPressed"] = [input](int action) { return input->actionIsActive(action); }; api["isActionPressed"] = [input](int action) { return input->actionIsActive(action); };
api["isMouseButtonPressed"] = [input](int button) -> bool api["isKeyPressed"] = [](SDL_Scancode code) -> bool
{ {
return input->getMouseButtonsState() & (1 << (button - 1)); int maxCode;
const auto* state = SDL_GetKeyboardState(&maxCode);
if (code >= 0 && code < maxCode)
return state[code] != 0;
else
return false;
};
api["isShiftPressed"] = []() -> bool { return SDL_GetModState() & KMOD_SHIFT; };
api["isCtrlPressed"] = []() -> bool { return SDL_GetModState() & KMOD_CTRL; };
api["isAltPressed"] = []() -> bool { return SDL_GetModState() & KMOD_ALT; };
api["isSuperPressed"] = []() -> bool { return SDL_GetModState() & KMOD_GUI; };
api["isControllerButtonPressed"] = [input](int button)
{
return input->isControllerButtonPressed(static_cast<SDL_GameControllerButton>(button));
};
api["isMouseButtonPressed"] = [](int button) -> bool
{
return SDL_GetMouseState(nullptr, nullptr) & SDL_BUTTON(button);
}; };
api["getMouseMoveX"] = [input]() { return input->getMouseMoveX(); }; api["getMouseMoveX"] = [input]() { return input->getMouseMoveX(); };
api["getMouseMoveY"] = [input]() { return input->getMouseMoveY(); }; api["getMouseMoveY"] = [input]() { return input->getMouseMoveY(); };
@ -45,104 +68,219 @@ namespace MWLua
return input->getActionValue(axis - SDL_CONTROLLER_AXIS_MAX) * 2 - 1; return input->getActionValue(axis - SDL_CONTROLLER_AXIS_MAX) * 2 - 1;
}; };
api["getControlSwitch"] = [input](const std::string& key) { return input->getControlSwitch(key); }; api["getControlSwitch"] = [input](std::string_view key) { return input->getControlSwitch(key); };
api["setControlSwitch"] = [input](const std::string& key, bool v) { input->toggleControlSwitch(key, v); }; api["setControlSwitch"] = [input](std::string_view key, bool v) { input->toggleControlSwitch(key, v); };
api["ACTION"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with( api["getKeyName"] = [](SDL_Scancode code) {
"GameMenu", MWInput::A_GameMenu, return SDL_GetKeyName(SDL_GetKeyFromScancode(code));
"Screenshot", MWInput::A_Screenshot, };
"Inventory", MWInput::A_Inventory,
"Console", MWInput::A_Console,
"MoveLeft", MWInput::A_MoveLeft, api["ACTION"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs<std::string_view, MWInput::Actions>({
"MoveRight", MWInput::A_MoveRight, {"GameMenu", MWInput::A_GameMenu},
"MoveForward", MWInput::A_MoveForward, {"Screenshot", MWInput::A_Screenshot},
"MoveBackward", MWInput::A_MoveBackward, {"Inventory", MWInput::A_Inventory},
{"Console", MWInput::A_Console},
"Activate", MWInput::A_Activate, {"MoveLeft", MWInput::A_MoveLeft},
"Use", MWInput::A_Use, {"MoveRight", MWInput::A_MoveRight},
"Jump", MWInput::A_Jump, {"MoveForward", MWInput::A_MoveForward},
"AutoMove", MWInput::A_AutoMove, {"MoveBackward", MWInput::A_MoveBackward},
"Rest", MWInput::A_Rest,
"Journal", MWInput::A_Journal,
"Weapon", MWInput::A_Weapon,
"Spell", MWInput::A_Spell,
"Run", MWInput::A_Run,
"CycleSpellLeft", MWInput::A_CycleSpellLeft,
"CycleSpellRight", MWInput::A_CycleSpellRight,
"CycleWeaponLeft", MWInput::A_CycleWeaponLeft,
"CycleWeaponRight", MWInput::A_CycleWeaponRight,
"ToggleSneak", MWInput::A_ToggleSneak,
"AlwaysRun", MWInput::A_AlwaysRun,
"Sneak", MWInput::A_Sneak,
"QuickSave", MWInput::A_QuickSave, {"Activate", MWInput::A_Activate},
"QuickLoad", MWInput::A_QuickLoad, {"Use", MWInput::A_Use},
"QuickMenu", MWInput::A_QuickMenu, {"Jump", MWInput::A_Jump},
"ToggleWeapon", MWInput::A_ToggleWeapon, {"AutoMove", MWInput::A_AutoMove},
"ToggleSpell", MWInput::A_ToggleSpell, {"Rest", MWInput::A_Rest},
"TogglePOV", MWInput::A_TogglePOV, {"Journal", MWInput::A_Journal},
{"Weapon", MWInput::A_Weapon},
{"Spell", MWInput::A_Spell},
{"Run", MWInput::A_Run},
{"CycleSpellLeft", MWInput::A_CycleSpellLeft},
{"CycleSpellRight", MWInput::A_CycleSpellRight},
{"CycleWeaponLeft", MWInput::A_CycleWeaponLeft},
{"CycleWeaponRight", MWInput::A_CycleWeaponRight},
{"ToggleSneak", MWInput::A_ToggleSneak},
{"AlwaysRun", MWInput::A_AlwaysRun},
{"Sneak", MWInput::A_Sneak},
"QuickKey1", MWInput::A_QuickKey1, {"QuickSave", MWInput::A_QuickSave},
"QuickKey2", MWInput::A_QuickKey2, {"QuickLoad", MWInput::A_QuickLoad},
"QuickKey3", MWInput::A_QuickKey3, {"QuickMenu", MWInput::A_QuickMenu},
"QuickKey4", MWInput::A_QuickKey4, {"ToggleWeapon", MWInput::A_ToggleWeapon},
"QuickKey5", MWInput::A_QuickKey5, {"ToggleSpell", MWInput::A_ToggleSpell},
"QuickKey6", MWInput::A_QuickKey6, {"TogglePOV", MWInput::A_TogglePOV},
"QuickKey7", MWInput::A_QuickKey7,
"QuickKey8", MWInput::A_QuickKey8,
"QuickKey9", MWInput::A_QuickKey9,
"QuickKey10", MWInput::A_QuickKey10,
"QuickKeysMenu", MWInput::A_QuickKeysMenu,
"ToggleHUD", MWInput::A_ToggleHUD, {"QuickKey1", MWInput::A_QuickKey1},
"ToggleDebug", MWInput::A_ToggleDebug, {"QuickKey2", MWInput::A_QuickKey2},
{"QuickKey3", MWInput::A_QuickKey3},
{"QuickKey4", MWInput::A_QuickKey4},
{"QuickKey5", MWInput::A_QuickKey5},
{"QuickKey6", MWInput::A_QuickKey6},
{"QuickKey7", MWInput::A_QuickKey7},
{"QuickKey8", MWInput::A_QuickKey8},
{"QuickKey9", MWInput::A_QuickKey9},
{"QuickKey10", MWInput::A_QuickKey10},
{"QuickKeysMenu", MWInput::A_QuickKeysMenu},
"ZoomIn", MWInput::A_ZoomIn, {"ToggleHUD", MWInput::A_ToggleHUD},
"ZoomOut", MWInput::A_ZoomOut {"ToggleDebug", MWInput::A_ToggleDebug},
));
api["CONTROL_SWITCH"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with( {"ZoomIn", MWInput::A_ZoomIn},
"Controls", "playercontrols", {"ZoomOut", MWInput::A_ZoomOut}
"Fighting", "playerfighting", }));
"Jumping", "playerjumping",
"Looking", "playerlooking",
"Magic", "playermagic",
"ViewMode", "playerviewswitch",
"VanityMode", "vanitymode"
));
api["CONTROLLER_BUTTON"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with( api["CONTROL_SWITCH"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs<std::string_view, std::string_view>({
"A", SDL_CONTROLLER_BUTTON_A, {"Controls", "playercontrols"},
"B", SDL_CONTROLLER_BUTTON_B, {"Fighting", "playerfighting"},
"X", SDL_CONTROLLER_BUTTON_X, {"Jumping", "playerjumping"},
"Y", SDL_CONTROLLER_BUTTON_Y, {"Looking", "playerlooking"},
"Back", SDL_CONTROLLER_BUTTON_BACK, {"Magic", "playermagic"},
"Guide", SDL_CONTROLLER_BUTTON_GUIDE, {"ViewMode", "playerviewswitch"},
"Start", SDL_CONTROLLER_BUTTON_START, {"VanityMode", "vanitymode"}
"LeftStick", SDL_CONTROLLER_BUTTON_LEFTSTICK, }));
"RightStick", SDL_CONTROLLER_BUTTON_RIGHTSTICK,
"LeftShoulder", SDL_CONTROLLER_BUTTON_LEFTSHOULDER,
"RightShoulder", SDL_CONTROLLER_BUTTON_RIGHTSHOULDER,
"DPadUp", SDL_CONTROLLER_BUTTON_DPAD_UP,
"DPadDown", SDL_CONTROLLER_BUTTON_DPAD_DOWN,
"DPadLeft", SDL_CONTROLLER_BUTTON_DPAD_LEFT,
"DPadRight", SDL_CONTROLLER_BUTTON_DPAD_RIGHT
));
api["CONTROLLER_AXIS"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with( api["CONTROLLER_BUTTON"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs<std::string_view, SDL_GameControllerButton>({
"LeftX", SDL_CONTROLLER_AXIS_LEFTX, {"A", SDL_CONTROLLER_BUTTON_A},
"LeftY", SDL_CONTROLLER_AXIS_LEFTY, {"B", SDL_CONTROLLER_BUTTON_B},
"RightX", SDL_CONTROLLER_AXIS_RIGHTX, {"X", SDL_CONTROLLER_BUTTON_X},
"RightY", SDL_CONTROLLER_AXIS_RIGHTY, {"Y", SDL_CONTROLLER_BUTTON_Y},
"TriggerLeft", SDL_CONTROLLER_AXIS_TRIGGERLEFT, {"Back", SDL_CONTROLLER_BUTTON_BACK},
"TriggerRight", SDL_CONTROLLER_AXIS_TRIGGERRIGHT, {"Guide", SDL_CONTROLLER_BUTTON_GUIDE},
{"Start", SDL_CONTROLLER_BUTTON_START},
{"LeftStick", SDL_CONTROLLER_BUTTON_LEFTSTICK},
{"RightStick", SDL_CONTROLLER_BUTTON_RIGHTSTICK},
{"LeftShoulder", SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
{"RightShoulder", SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
{"DPadUp", SDL_CONTROLLER_BUTTON_DPAD_UP},
{"DPadDown", SDL_CONTROLLER_BUTTON_DPAD_DOWN},
{"DPadLeft", SDL_CONTROLLER_BUTTON_DPAD_LEFT},
{"DPadRight", SDL_CONTROLLER_BUTTON_DPAD_RIGHT}
}));
"LookUpDown", SDL_CONTROLLER_AXIS_MAX + MWInput::A_LookUpDown, api["CONTROLLER_AXIS"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs<std::string_view, int>({
"LookLeftRight", SDL_CONTROLLER_AXIS_MAX + MWInput::A_LookLeftRight, {"LeftX", SDL_CONTROLLER_AXIS_LEFTX},
"MoveForwardBackward", SDL_CONTROLLER_AXIS_MAX + MWInput::A_MoveForwardBackward, {"LeftY", SDL_CONTROLLER_AXIS_LEFTY},
"MoveLeftRight", SDL_CONTROLLER_AXIS_MAX + MWInput::A_MoveLeftRight {"RightX", SDL_CONTROLLER_AXIS_RIGHTX},
)); {"RightY", SDL_CONTROLLER_AXIS_RIGHTY},
{"TriggerLeft", SDL_CONTROLLER_AXIS_TRIGGERLEFT},
{"TriggerRight", SDL_CONTROLLER_AXIS_TRIGGERRIGHT},
{"LookUpDown", SDL_CONTROLLER_AXIS_MAX + MWInput::A_LookUpDown},
{"LookLeftRight", SDL_CONTROLLER_AXIS_MAX + MWInput::A_LookLeftRight},
{"MoveForwardBackward", SDL_CONTROLLER_AXIS_MAX + MWInput::A_MoveForwardBackward},
{"MoveLeftRight", SDL_CONTROLLER_AXIS_MAX + MWInput::A_MoveLeftRight}
}));
api["KEY"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs<std::string_view, SDL_Scancode>({
{"_0", SDL_SCANCODE_0},
{"_1", SDL_SCANCODE_1},
{"_2", SDL_SCANCODE_2},
{"_3", SDL_SCANCODE_3},
{"_4", SDL_SCANCODE_4},
{"_5", SDL_SCANCODE_5},
{"_6", SDL_SCANCODE_6},
{"_7", SDL_SCANCODE_7},
{"_8", SDL_SCANCODE_8},
{"_9", SDL_SCANCODE_9},
{"NP_0", SDL_SCANCODE_KP_0},
{"NP_1", SDL_SCANCODE_KP_1},
{"NP_2", SDL_SCANCODE_KP_2},
{"NP_3", SDL_SCANCODE_KP_3},
{"NP_4", SDL_SCANCODE_KP_4},
{"NP_5", SDL_SCANCODE_KP_5},
{"NP_6", SDL_SCANCODE_KP_6},
{"NP_7", SDL_SCANCODE_KP_7},
{"NP_8", SDL_SCANCODE_KP_8},
{"NP_9", SDL_SCANCODE_KP_9},
{"NP_Divide", SDL_SCANCODE_KP_DIVIDE},
{"NP_Enter", SDL_SCANCODE_KP_ENTER},
{"NP_Minus", SDL_SCANCODE_KP_MINUS},
{"NP_Multiply", SDL_SCANCODE_KP_MULTIPLY},
{"NP_Delete", SDL_SCANCODE_KP_PERIOD},
{"NP_Plus", SDL_SCANCODE_KP_PLUS},
{"F1", SDL_SCANCODE_F1},
{"F2", SDL_SCANCODE_F2},
{"F3", SDL_SCANCODE_F3},
{"F4", SDL_SCANCODE_F4},
{"F5", SDL_SCANCODE_F5},
{"F6", SDL_SCANCODE_F6},
{"F7", SDL_SCANCODE_F7},
{"F8", SDL_SCANCODE_F8},
{"F9", SDL_SCANCODE_F9},
{"F10", SDL_SCANCODE_F10},
{"F11", SDL_SCANCODE_F11},
{"F12", SDL_SCANCODE_F12},
{"A", SDL_SCANCODE_A},
{"B", SDL_SCANCODE_B},
{"C", SDL_SCANCODE_C},
{"D", SDL_SCANCODE_D},
{"E", SDL_SCANCODE_E},
{"F", SDL_SCANCODE_F},
{"G", SDL_SCANCODE_G},
{"H", SDL_SCANCODE_H},
{"I", SDL_SCANCODE_I},
{"J", SDL_SCANCODE_J},
{"K", SDL_SCANCODE_K},
{"L", SDL_SCANCODE_L},
{"M", SDL_SCANCODE_M},
{"N", SDL_SCANCODE_N},
{"O", SDL_SCANCODE_O},
{"P", SDL_SCANCODE_P},
{"Q", SDL_SCANCODE_Q},
{"R", SDL_SCANCODE_R},
{"S", SDL_SCANCODE_S},
{"T", SDL_SCANCODE_T},
{"U", SDL_SCANCODE_U},
{"V", SDL_SCANCODE_V},
{"W", SDL_SCANCODE_W},
{"X", SDL_SCANCODE_X},
{"Y", SDL_SCANCODE_Y},
{"Z", SDL_SCANCODE_Z},
{"LeftArrow", SDL_SCANCODE_LEFT},
{"RightArrow", SDL_SCANCODE_RIGHT},
{"UpArrow", SDL_SCANCODE_UP},
{"DownArrow", SDL_SCANCODE_DOWN},
{"LeftAlt", SDL_SCANCODE_LALT},
{"LeftCtrl", SDL_SCANCODE_LCTRL},
{"LeftBracket", SDL_SCANCODE_LEFTBRACKET},
{"LeftSuper", SDL_SCANCODE_LGUI},
{"LeftShift", SDL_SCANCODE_LSHIFT},
{"RightAlt", SDL_SCANCODE_RALT},
{"RightCtrl", SDL_SCANCODE_RCTRL},
{"RightSuper", SDL_SCANCODE_RGUI},
{"RightBracket", SDL_SCANCODE_RIGHTBRACKET},
{"RightShift", SDL_SCANCODE_RSHIFT},
{"Apostrophe", SDL_SCANCODE_APOSTROPHE},
{"BackSlash", SDL_SCANCODE_BACKSLASH},
{"Backspace", SDL_SCANCODE_BACKSPACE},
{"CapsLock", SDL_SCANCODE_CAPSLOCK},
{"Comma", SDL_SCANCODE_COMMA},
{"Delete", SDL_SCANCODE_DELETE},
{"End", SDL_SCANCODE_END},
{"Enter", SDL_SCANCODE_RETURN},
{"Equals", SDL_SCANCODE_EQUALS},
{"Escape", SDL_SCANCODE_ESCAPE},
{"Home", SDL_SCANCODE_HOME},
{"Insert", SDL_SCANCODE_INSERT},
{"Minus", SDL_SCANCODE_MINUS},
{"NumLock", SDL_SCANCODE_NUMLOCKCLEAR},
{"PageDown", SDL_SCANCODE_PAGEDOWN},
{"PageUp", SDL_SCANCODE_PAGEUP},
{"Period", SDL_SCANCODE_PERIOD},
{"Pause", SDL_SCANCODE_PAUSE},
{"PrintScreen", SDL_SCANCODE_PRINTSCREEN},
{"ScrollLock", SDL_SCANCODE_SCROLLLOCK},
{"Semicolon", SDL_SCANCODE_SEMICOLON},
{"Slash", SDL_SCANCODE_SLASH},
{"Space", SDL_SCANCODE_SPACE},
{"Tab", SDL_SCANCODE_TAB}
}));
return LuaUtil::makeReadOnly(api); return LuaUtil::makeReadOnly(api);
} }

View file

@ -39,7 +39,7 @@ namespace MWLua
selfAPI["controls"] = sol::readonly_property([](SelfObject& self) { return &self.mControls; }); selfAPI["controls"] = sol::readonly_property([](SelfObject& self) { return &self.mControls; });
selfAPI["isActive"] = [](SelfObject& self) { return &self.mIsActive; }; selfAPI["isActive"] = [](SelfObject& self) { return &self.mIsActive; };
selfAPI["enableAI"] = [](SelfObject& self, bool v) { self.mControls.mDisableAI = !v; }; selfAPI["enableAI"] = [](SelfObject& self, bool v) { self.mControls.mDisableAI = !v; };
selfAPI["setEquipment"] = [manager=context.mLuaManager](const SelfObject& obj, sol::table equipment) selfAPI["setEquipment"] = [context](const SelfObject& obj, sol::table equipment)
{ {
if (!obj.ptr().getClass().hasInventoryStore(obj.ptr())) if (!obj.ptr().getClass().hasInventoryStore(obj.ptr()))
{ {
@ -56,7 +56,7 @@ namespace MWLua
else else
eqp[slot] = value.as<std::string>(); eqp[slot] = value.as<std::string>();
} }
manager->addAction(std::make_unique<SetEquipmentAction>(obj.id(), std::move(eqp))); context.mLuaManager->addAction(std::make_unique<SetEquipmentAction>(context.mLua, obj.id(), std::move(eqp)));
}; };
selfAPI["getCombatTarget"] = [worldView=context.mWorldView](SelfObject& self) -> sol::optional<LObject> selfAPI["getCombatTarget"] = [worldView=context.mWorldView](SelfObject& self) -> sol::optional<LObject>
{ {

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