1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-01-26 09:35:28 +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:
- 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:
- docker
- linux
image: debian:bullseye
image: ubuntu:focal
rules:
- if: $CI_PIPELINE_SOURCE == "push"
.Debian:
extends: .Debian_Image
.Ubuntu:
extends: .Ubuntu_Image
cache:
paths:
- apt-cache/
@ -35,7 +43,7 @@ stages:
- build/install/
Clang_Tidy:
extends: .Debian_Image
extends: .Ubuntu_Image
stage: build
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'
@ -50,34 +58,41 @@ Clang_Tidy:
CXX: clang++
CI_CLANG_TIDY: 1
timeout: 8h
artifacts:
paths: []
expire_in: 1 minute
Coverity:
extends: .Debian_Image
extends: .Ubuntu_Image
stage: build
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
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
- tar xfz /tmp/cov-analysis-linux64.tgz
script:
- CI/before_script.linux.sh
# Remove the specific targets and build everything once we can do it under 3h
- cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build build -- -j $(nproc) openmw esmtool bsatool niftest openmw-wizard openmw-launcher openmw-iniimporter openmw-essimporter
- cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build build -- -j $(nproc) openmw esmtool bsatool niftest openmw-wizard openmw-launcher openmw-iniimporter openmw-essimporter openmw-navmeshtool openmw-cs
after_script:
- tar cfz cov-int.tar.gz cov-int
- curl https://scan.coverity.com/builds?project=$COVERITY_SCAN_PROJECT_NAME
--form token=$COVERITY_SCAN_TOKEN --form email=$GITLAB_USER_EMAIL
--form file=@cov-int.tar.gz --form version="`git describe --tags`"
--form description="`git describe --tags` / $CI_COMMIT_TITLE / $CI_COMMIT_REF_NAME:$CI_PIPELINE_ID"
--form file=@cov-int.tar.gz --form version="$CI_COMMIT_REF_NAME:$CI_COMMIT_SHORT_SHA"
--form description="CI_COMMIT_SHORT_SHA / $CI_COMMIT_TITLE / $CI_COMMIT_REF_NAME:$CI_PIPELINE_ID"
variables:
CC: gcc
CXX: g++
CC: clang
CXX: clang++
CXXFLAGS: -O0
artifacts:
paths:
- /builds/OpenMW/openmw/cov-int/build-log.txt
Debian_GCC:
extends: .Debian
Ubuntu_GCC:
extends: .Ubuntu
cache:
key: Debian_GCC.v2
key: Ubuntu_GCC.v2
before_script:
- CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic
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.
timeout: 2h
Debian_GCC_tests:
extends: Debian_GCC
Ubuntu_GCC_tests:
extends: Ubuntu_GCC
cache:
key: Debian_GCC_tests.v2
key: Ubuntu_GCC_tests.v2
variables:
CCACHE_SIZE: 1G
BUILD_TESTS_ONLY: 1
artifacts:
paths: []
expire_in: 1 minute
Debian_GCC_tests_Debug:
extends: Debian_GCC
Ubuntu_GCC_tests_Debug:
extends: Ubuntu_GCC
cache:
key: Debian_GCC_tests_Debug.v1
key: Ubuntu_GCC_tests_Debug.v1
variables:
CCACHE_SIZE: 1G
BUILD_TESTS_ONLY: 1
CMAKE_BUILD_TYPE: Debug
artifacts:
paths: []
expire_in: 1 minute
Debian_GCC_Static_Deps:
extends: Debian_GCC
Ubuntu_GCC_Static_Deps:
extends: Ubuntu_GCC
rules:
- if: $CI_PIPELINE_SOURCE == "push"
changes:
- "**/CMakeLists.txt"
- "cmake/**/*"
- "CI/**/*"
- ".gitlab-ci.yml"
allow_failure: true
cache:
key: Debian_GCC_Static_Deps
key: Ubuntu_GCC_Static_Deps
paths:
- apt-cache/
- ccache/
@ -118,20 +147,23 @@ Debian_GCC_Static_Deps:
CI_OPENMW_USE_STATIC_DEPS: 1
timeout: 3h
Debian_GCC_Static_Deps_tests:
extends: Debian_GCC_Static_Deps
Ubuntu_GCC_Static_Deps_tests:
extends: Ubuntu_GCC_Static_Deps
cache:
key: Debian_GCC_Static_Deps_tests
key: Ubuntu_GCC_Static_Deps_tests
variables:
CCACHE_SIZE: 1G
BUILD_TESTS_ONLY: 1
artifacts:
paths: []
expire_in: 1 minute
Debian_Clang:
extends: .Debian
Ubuntu_Clang:
extends: .Ubuntu
before_script:
- CI/install_debian_deps.sh clang openmw-deps openmw-deps-dynamic
cache:
key: Debian_Clang.v2
key: Ubuntu_Clang.v2
variables:
CC: 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.
timeout: 2h
Debian_Clang_tests:
extends: Debian_Clang
Ubuntu_Clang_tests:
extends: Ubuntu_Clang
cache:
key: Debian_Clang_tests.v2
key: Ubuntu_Clang_tests.v2
variables:
CCACHE_SIZE: 1G
BUILD_TESTS_ONLY: 1
artifacts:
paths: []
expire_in: 1 minute
Debian_Clang_tests_Debug:
extends: Debian_Clang
Ubuntu_Clang_tests_Debug:
extends: Ubuntu_Clang
cache:
key: Debian_Clang_tests_Debug.v1
key: Ubuntu_Clang_tests_Debug.v1
variables:
CCACHE_SIZE: 1G
BUILD_TESTS_ONLY: 1
CMAKE_BUILD_TYPE: Debug
artifacts:
paths: []
expire_in: 1 minute
.MacOS:
image: macos-11-xcode-12
@ -163,8 +201,7 @@ Debian_Clang_tests_Debug:
stage: build
only:
variables:
- $CI_PROJECT_ID == "7107382"
- $CI_PIPELINE_SOURCE == "push"
- $CI_PROJECT_ID == "7107382" && $CI_PIPELINE_SOURCE == "push"
cache:
paths:
- ccache/
@ -177,7 +214,7 @@ Debian_Clang_tests_Debug:
- ccache -z -M "${CCACHE_SIZE}"
- CI/before_script.osx.sh
- cd build; make -j $(sysctl -n hw.logicalcpu) package
- for dmg in *.dmg; do mv "$dmg" "${dmg%.dmg}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}.dmg"; done
- for dmg in *.dmg; do mv "$dmg" "${dmg%.dmg}_${CI_COMMIT_REF_NAME##*/}_${CI_JOB_ID}.dmg"; done
- ccache -s
artifacts:
paths:
@ -192,21 +229,20 @@ macOS11_Xcode12:
variables:
CCACHE_SIZE: 3G
macOS10.15_Xcode11:
macOS12_Xcode13:
extends: .MacOS
image: macos-10.15-xcode-11
allow_failure: true
image: macos-12-xcode-13
cache:
key: macOS10.15_Xcode11.v1
key: macOS12_Xcode13.v1
variables:
CCACHE_SIZE: 3G
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"
variables: &cs-targets
targets: "openmw-cs,bsatool,esmtool,niftest"
targets: "openmw-cs,bsatool,esmtool,niftest,openmw-essimporter"
package: "CS"
variables: &tests-targets
@ -330,6 +366,9 @@ Windows_Ninja_Tests_RelWithDebInfo:
config: "RelWithDebInfo"
# Gitlab can't successfully execute following binaries due to unknown reason
# executables: "openmw_test_suite.exe,openmw_detournavigator_navmeshtilescache_benchmark.exe"
artifacts:
paths: []
expire_in: 1 minute
.Windows_MSBuild_Base:
tags:
@ -396,21 +435,16 @@ Windows_Ninja_Tests_RelWithDebInfo:
- 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:
extends:
- .Windows_MSBuild_Base
variables:
<<: *engine-targets
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:
extends:
@ -432,6 +466,10 @@ Windows_MSBuild_CS_Release:
variables:
<<: *cs-targets
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:
extends:
@ -455,27 +493,28 @@ Windows_MSBuild_Tests_RelWithDebInfo:
config: "RelWithDebInfo"
# Gitlab can't successfully execute following binaries due to unknown reason
# 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:
- linux
image: debian:bullseye
image: psi29a/android-ndk:focal-ndk22
rules:
- if: $CI_PIPELINE_SOURCE == "push"
variables:
CCACHE_SIZE: 3G
cache:
key: Debian_AndroidNDK_arm64-v8a.v3
key: Ubuntu__Focal_AndroidNDK_r22b_arm64-v8a.v1
paths:
- apt-cache/
- ccache/
- build/extern/fetched/
before_script:
- export APT_CACHE_DIR=`pwd`/apt-cache && mkdir -pv $APT_CACHE_DIR
- echo "deb http://deb.debian.org/debian unstable main contrib" > /etc/apt/sources.list
- echo "google-android-ndk-installer google-android-installers/mirror select https://dl.google.com" | debconf-set-selections
- apt-get update -yq
- apt-get -q -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake ccache curl unzip git build-essential google-android-ndk-installer
- apt-get -q -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake ccache curl unzip git build-essential
stage: build
script:
- 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.
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
Andreas Stöckel
Andrei Kortunov (akortunov)
Andrew Appuhamy (andrew-app)
AnyOldName3
Ardekantur
Armin Preiml
@ -92,6 +93,7 @@ Programmers
Haoda Wang (h313)
hristoast
Internecine
Ivan Beloborodov (myrix)
Jackerty
Jacob Essex (Yacoby)
Jacob Turnbull (Tankinfrank)
@ -113,6 +115,7 @@ Programmers
John Blomberg (fstp)
Jordan Ayers
Jordan Milne
Josquin Frei
Josua Grawitter
Jules Blok (Armada651)
julianko
@ -215,6 +218,7 @@ Programmers
tlmullis
tri4ng1e
Thoronador
Tom Lowe (Vulpen)
Tom Mason (wheybags)
Torben Leif Carrington (TorbenC)
unelsson
@ -230,7 +234,7 @@ Programmers
Yuri Krupenin
zelurker
Noah Gooder
Andrew Appuhamy (andrew-app)
Documentation
-------------

View File

@ -2,21 +2,31 @@
------
Bug #1751: Birthsign abilities increase modified attribute values instead of base ones
Bug #1930: Followers are still fighting if a target stops combat with a leader
Bug #2036: SetStat and ModStat instructions aren't implemented the same way as in Morrowind
Bug #3246: ESSImporter: Most NPCs are dead on save load
Bug #3488: AI combat aiming is too slow
Bug #3514: Editing a reference's position after loading an esp file makes the reference disappear
Bug #3737: Scripts from The Underground 2 .esp do not play (all patched versions)
Bug #3792: 1 frame late magicka recalc breaks early scripted magicka reactions to Intelligence change
Bug #3846: Strings starting with "-" fail to compile if not enclosed in quotes
Bug #3855: AI sometimes spams defensive spells
Bug #3905: Great House Dagoth issues
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 #4700: Editor: Incorrect command implementation
Bug #4744: Invisible particles must still be processed
Bug #4949: Incorrect particle lighting
Bug #5088: Sky abruptly changes direction during certain weather transitions
Bug #5100: Persuasion doesn't always clamp the resulting disposition
Bug #5120: Scripted object spawning updates physics system
Bug #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 #5394: Windows snapping no longer works
Bug #5434: Pinned windows shouldn't cover breath progress bar
Bug #5453: Magic effect VFX are offset for creatures
Bug #5483: AutoCalc flag is not used to calculate spells cost
Bug #5508: Engine binary links to Qt without using it
@ -28,6 +38,7 @@
Bug #5842: GetDisposition adds temporary disposition change from different actors
Bug #5863: GetEffect should return true after the player has teleported
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 #6037: Morrowind Content Language Cannot be Set to English in OpenMW Launcher
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 #6172: Some creatures can't open doors
Bug #6174: Spellmaking and Enchanting sliders differences from vanilla
Bug #6177: Followers of player follower stop following after waiting for a day
Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla
Bug #6191: Encumbrance messagebox timer works incorrectly
Bug #6197: Infinite Casting Loop
Bug #6253: Multiple instances of Reflect stack additively
Bug #6255: Reflect is different from vanilla
@ -59,15 +72,36 @@
Bug #6289: Keyword search in dialogues expected the text to be all ASCII characters
Bug #6291: Can't pickup the dead mage's journal from the mysterious hunter mod
Bug #6302: Teleporting disabled actor breaks its disabled state
Bug #6303: After "go to jail" weapon can stuck in the ready to attack state
Bug #6307: Pathfinding in Infidelities quest from Tribunal addon is broken
Bug #6321: Arrow enchantments should always be applied to the target
Bug #6322: Total sold/cost should reset to 0 when there are no items offered
Bug #6323: Wyrmhaven: Alboin doesn't follower the player character out of his house
Bug #6324: Special Slave Companions: Can't buy the slave companions
Bug #6326: Detect Enchantment/Key should detect items in unresolved containers
Bug #6327: Blocking roots the character in place
Bug #6333: Werewolf stat changes should be implemented as damage/fortifications
Bug #6343: Magic projectile speed doesn't take race weight into account
Bug #6347: PlaceItem/PlaceItemCell/PlaceAt should work with levelled creatures
Bug #6354: SFX abruptly cut off after crossing max distance; 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 #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 #1465: "Reset" argument for AI functions
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 #3616: Allow Zoom levels on the World Map
@ -82,12 +116,18 @@
Feature #6017: Separate persistent and temporary cell references when saving
Feature #6032: Reverse-z 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 #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 #6248: Embedded error marker mesh
Feature #6249: Alpha testing support for Collada
Feature #6251: OpenMW-CS: Set instance movement based on camera zoom
Feature #6288: Preserve the "blocked" record flag for referenceable objects.
Feature #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 #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 #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 #6142: Groundcover plugins change cells flags
Bug #6276: Deleted groundcover instances are not deleted in game
Bug #6294: Game crashes with empty pathgrid
Feature #390: 3rd person look "over the shoulder"
Feature #832: OpenMW-CS: Handle deleted references

View File

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

View File

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

View File

@ -46,7 +46,7 @@ fi
if [[ $CI_CLANG_TIDY ]]; then
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
@ -71,6 +71,7 @@ if [[ "${BUILD_TESTS_ONLY}" ]]; then
-DBUILD_ESSIMPORTER=OFF \
-DBUILD_OPENCS=OFF \
-DBUILD_WIZARD=OFF \
-DBUILD_NAVMESHTOOL=OFF \
-DBUILD_UNITTESTS=${BUILD_UNITTESTS} \
-DBUILD_BENCHMARKS=${BUILD_BENCHMARKS} \
-DGTEST_ROOT="${GOOGLETEST_DIR}" \

View File

@ -566,9 +566,9 @@ if [ -z $SKIP_DOWNLOAD ]; then
fi
# SDL2
download "SDL 2.0.12" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/SDL2-2.0.12.zip" \
"SDL2-2.0.12.zip"
download "SDL 2.0.18" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/SDL2-2.0.18.zip" \
"SDL2-2.0.18.zip"
# LZ4
download "LZ4 1.9.2" \
@ -898,17 +898,17 @@ fi
cd $DEPS
echo
# 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. "
elif [ -z $SKIP_EXTRACT ]; then
rm -rf SDL2-2.0.12
eval 7z x -y SDL2-2.0.12.zip $STRIP
rm -rf SDL2-2.0.18
eval 7z x -y SDL2-2.0.18.zip $STRIP
fi
export SDL2DIR="$(real_pwd)/SDL2-2.0.12"
export SDL2DIR="$(real_pwd)/SDL2-2.0.18"
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
echo Done.
}

View File

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

View File

@ -22,9 +22,8 @@ declare -rA GROUPED_DEPS=(
libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev
libsdl2-dev libqt5opengl5-dev libopenal-dev libunshield-dev libtinyxml-dev
libbullet-dev liblz4-dev libpng-dev libjpeg-dev libluajit-5.1-dev
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.
[openmw-deps-dynamic]="libmygui-dev libopenscenegraph-dev libsqlite3-dev"
@ -64,5 +63,7 @@ done
export APT_CACHE_DIR="${PWD}/apt-cache"
set -x
mkdir -pv "$APT_CACHE_DIR"
apt-get update -yq
apt-get -q -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends "${deps[@]}"
apt-get update -yqq
apt-get -qq -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends software-properties-common >/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_UNITTESTS "Enable Unittests with Google C++ Unittest" 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.
@ -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})
if(OPENMW_USE_SYSTEM_MYGUI)
find_package(MyGUI 3.2.2 REQUIRED)
find_package(MyGUI 3.4.1 REQUIRED)
endif()
find_package(SDL2 2.0.9 REQUIRED)
find_package(OpenAL REQUIRED)
@ -414,7 +415,8 @@ else(USE_LUAJIT)
endif(USE_LUAJIT)
# 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(
BEFORE SYSTEM
@ -426,7 +428,8 @@ include_directories(
${OPENGL_INCLUDE_DIR}
${BULLET_INCLUDE_DIRS}
${LUA_INCLUDE_DIR}
${SOL_INCLUDE_DIRS}
${SOL_INCLUDE_DIR}
${SOL_CONFIG_DIR}
)
link_directories(${SDL2_LIBRARY_DIRS} ${Boost_LIBRARY_DIRS})
@ -443,9 +446,8 @@ if (APPLE)
"${APP_BUNDLE_DIR}/Contents/Resources/OpenMW.icns" COPYONLY)
endif (APPLE)
if (NOT APPLE)
set(OPENMW_MYGUI_FILES_ROOT ${OpenMW_BINARY_DIR})
set(OPENMW_SHADERS_ROOT ${OpenMW_BINARY_DIR})
if (NOT APPLE) # this is modified for macOS use later in "apps/open[mw|cs]/CMakeLists.txt"
set(OPENMW_RESOURCES_ROOT ${OpenMW_BINARY_DIR})
endif ()
add_subdirectory(files/)
@ -602,6 +604,10 @@ if (BUILD_BENCHMARKS)
add_subdirectory(apps/benchmarks)
endif()
if (BUILD_NAVMESHTOOL)
add_subdirectory(apps/navmeshtool)
endif()
if (WIN32)
if (MSVC)
if (OPENMW_MP_BUILD)
@ -701,6 +707,10 @@ if (WIN32)
if (BUILD_BENCHMARKS)
set_target_properties(openmw_detournavigator_navmeshtilescache_benchmark PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
endif()
if (BUILD_NAVMESHTOOL)
set_target_properties(openmw-navmeshtool PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
endif()
endif(MSVC)
# 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()
if (BUILD_OPENMW AND APPLE)
# Without these flags LuaJit crashes on startup on OSX
set_target_properties(openmw PROPERTIES LINK_FLAGS "-pagezero_size 10000 -image_base 100000000")
if (USE_LUAJIT)
# Without these flags LuaJit crashes on startup on OSX
set_target_properties(openmw PROPERTIES LINK_FLAGS "-pagezero_size 10000 -image_base 100000000")
endif(USE_LUAJIT)
target_compile_definitions(components PRIVATE GL_SILENCE_DEPRECATION=1)
target_compile_definitions(openmw PRIVATE GL_SILENCE_DEPRECATION=1)
endif()
@ -941,6 +953,9 @@ elseif(NOT APPLE)
IF(BUILD_WIZARD)
INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-wizard" DESTINATION "${BINDIR}" )
ENDIF(BUILD_WIZARD)
if(BUILD_NAVMESHTOOL)
install(PROGRAMS "${INSTALL_SOURCE}/openmw-navmeshtool" DESTINATION "${BINDIR}" )
endif()
# Install licenses
INSTALL(FILES "files/mygui/DejaVuFontLicense.txt" DESTINATION "${LICDIR}" )

View File

@ -1,8 +1,6 @@
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 also comes with OpenMW-CS, a replacement for Bethesda's Construction Set.

View File

@ -25,18 +25,10 @@ namespace
};
template <typename Random>
TilePosition generateTilePosition(int max, Random& random)
osg::Vec2i generateVec2i(int max, Random& random)
{
std::uniform_int_distribution<int> distribution(0, max);
return TilePosition(distribution(random), distribution(random));
}
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)};
return osg::Vec2i(distribution(random), distribution(random));
}
template <typename Random>
@ -91,8 +83,7 @@ namespace
{
std::uniform_real_distribution<float> distribution(0.0, 1.0);
std::generate_n(out, count, [&] {
const osg::Vec3f shift(distribution(random), distribution(random), distribution(random));
return Cell {1, shift};
return CellWater {generateVec2i(1000, random), Water {ESM::Land::REAL_SIZE, distribution(random)}};
});
}
@ -117,16 +108,18 @@ namespace
{
std::uniform_real_distribution<float> distribution(0.0, 1.0);
Heightfield result;
result.mBounds = generateTileBounds(random);
result.mCellPosition = generateVec2i(1000, random);
result.mCellSize = ESM::Land::REAL_SIZE;
result.mMinHeight = distribution(random);
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);
std::generate_n(std::back_inserter(result.mHeights), ESM::Land::LAND_NUM_VERTS, [&]
{
return distribution(random);
});
result.mOriginalSize = ESM::Land::LAND_SIZE;
result.mMinX = 0;
result.mMinY = 0;
return result;
}
@ -135,7 +128,8 @@ namespace
{
std::uniform_real_distribution<float> distribution(0.0, 1.0);
FlatHeightfield result;
result.mBounds = generateTileBounds(random);
result.mCellPosition = generateVec2i(1000, random);
result.mCellSize = ESM::Land::REAL_SIZE;
result.mHeight = distribution(random);
return result;
}
@ -144,14 +138,14 @@ namespace
Key generateKey(std::size_t triangles, Random& 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 revision = std::uniform_int_distribution<std::size_t>(0, 10000)(random);
Mesh mesh = generateMesh(triangles, random);
std::vector<Cell> water;
std::vector<CellWater> water;
generateWater(std::back_inserter(water), 1, random);
RecastMesh recastMesh(generation, revision, std::move(mesh), std::move(water),
{generateHeightfield(random)}, {generateFlatHeightfield(random)});
{generateHeightfield(random)}, {generateFlatHeightfield(random)}, {});
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 << ","
<< p.mTravel.mY << "," << p.mTravel.mZ << ")" << std::endl;
std::cout << " Travel Unknown: " << p.mTravel.mUnk << std::endl;
std::cout << " Should repeat: " << p.mTravel.mShouldRepeat << std::endl;
}
else if (p.mType == ESM::AI_Follow || p.mType == ESM::AI_Escort)
{
@ -38,12 +38,12 @@ void printAIPackage(const ESM::AIPackage& p)
<< p.mTarget.mY << "," << p.mTarget.mZ << ")" << std::endl;
std::cout << " Duration: " << p.mTarget.mDuration << std::endl;
std::cout << " Target ID: " << p.mTarget.mId.toString() << std::endl;
std::cout << " Unknown: " << p.mTarget.mUnk << std::endl;
std::cout << " Should repeat: " << p.mTarget.mShouldRepeat << std::endl;
}
else if (p.mType == ESM::AI_Activate)
{
std::cout << " Name: " << p.mActivate.mName.toString() << std::endl;
std::cout << " Activate Unknown: " << p.mActivate.mUnk << std::endl;
std::cout << " Should repeat: " << p.mActivate.mShouldRepeat << std::endl;
}
else {
std::cout << " BadPackage: " << Misc::StringUtils::format("0x%08X", p.mType) << std::endl;

View File

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

View File

@ -1,6 +1,7 @@
#include "advancedpage.hpp"
#include <array>
#include <string>
#include <components/config/gamesettings.hpp>
#include <QFileDialog>
@ -20,13 +21,13 @@ Launcher::AdvancedPage::AdvancedPage(Config::GameSettings &gameSettings, QWidget
setObjectName ("AdvancedPage");
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();
@ -117,6 +118,11 @@ bool Launcher::AdvancedPage::loadSettings()
loadSettingBool(autoUseTerrainSpecularMapsCheckBox, "auto use terrain specular maps", "Shaders");
loadSettingBool(bumpMapLocalLightingCheckBox, "apply lighting to environment maps", "Shaders");
loadSettingBool(radialFogCheckBox, "radial fog", "Shaders");
loadSettingBool(softParticlesCheckBox, "soft particles", "Shaders");
loadSettingBool(antialiasAlphaTestCheckBox, "antialias alpha test", "Shaders");
if (Settings::Manager::getInt("antialiasing", "Video") == 0) {
antialiasAlphaTestCheckBox->setCheckState(Qt::Unchecked);
}
loadSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game");
connect(animSourcesCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotAnimSourcesToggled(bool)));
loadSettingBool(animSourcesCheckBox, "use additional anim sources", "Game");
@ -137,6 +143,8 @@ bool Launcher::AdvancedPage::loadSettings()
loadSettingBool(activeGridObjectPagingCheckBox, "object paging active grid", "Terrain");
viewingDistanceComboBox->setValue(convertToCells(Settings::Manager::getInt("viewing distance", "Camera")));
objectPagingMinSizeComboBox->setValue(Settings::Manager::getDouble("object paging min size", "Terrain"));
loadSettingBool(nightDaySwitchesCheckBox, "day night switches", "Game");
}
// Audio
@ -207,7 +215,7 @@ bool Launcher::AdvancedPage::loadSettings()
{
// Saves
loadSettingBool(timePlayedCheckbox, "timeplayed", "Saves");
maximumQuicksavesComboBox->setValue(Settings::Manager::getInt("max quicksaves", "Saves"));
loadSettingInt(maximumQuicksavesComboBox,"max quicksaves", "Saves");
// Other Settings
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(swimUpwardCorrectionCheckBox, "swim upward correction", "Game");
saveSettingBool(avoidCollisionsCheckBox, "NPCs avoid collisions", "Game");
int unarmedFactorsStrengthIndex = unarmedFactorsStrengthComboBox->currentIndex();
if (unarmedFactorsStrengthIndex != Settings::Manager::getInt("strength influences hand to hand", "Game"))
Settings::Manager::setInt("strength influences hand to hand", "Game", unarmedFactorsStrengthIndex);
saveSettingInt(unarmedFactorsStrengthComboBox, "strength influences hand to hand", "Game");
saveSettingBool(stealingFromKnockedOutCheckBox, "always allow stealing from knocked out actors", "Game");
saveSettingBool(enableNavigatorCheckBox, "enable", "Navigator");
int numPhysicsThreads = physicsThreadsSpinBox->value();
if (numPhysicsThreads != Settings::Manager::getInt("async num threads", "Physics"))
Settings::Manager::setInt("async num threads", "Physics", numPhysicsThreads);
saveSettingInt(physicsThreadsSpinBox, "async num threads", "Physics");
}
// Visuals
@ -270,6 +274,8 @@ void Launcher::AdvancedPage::saveSettings()
saveSettingBool(autoUseTerrainSpecularMapsCheckBox, "auto use terrain specular maps", "Shaders");
saveSettingBool(bumpMapLocalLightingCheckBox, "apply lighting to environment maps", "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(animSourcesCheckBox, "use additional anim sources", "Game");
saveSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game");
@ -294,6 +300,8 @@ void Launcher::AdvancedPage::saveSettings()
double objectPagingMinSize = objectPagingMinSizeComboBox->value();
if (objectPagingMinSize != Settings::Manager::getDouble("object paging min size", "Terrain"))
Settings::Manager::setDouble("object paging min size", "Terrain", objectPagingMinSize);
saveSettingBool(nightDaySwitchesCheckBox, "day night switches", "Game");
}
// Audio
@ -349,9 +357,7 @@ void Launcher::AdvancedPage::saveSettings()
saveSettingBool(showMeleeInfoCheckBox, "show melee info", "Game");
saveSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game");
saveSettingBool(changeDialogTopicsCheckBox, "color topic enable", "GUI");
int showOwnedCurrentIndex = showOwnedComboBox->currentIndex();
if (showOwnedCurrentIndex != Settings::Manager::getInt("show owned", "Game"))
Settings::Manager::setInt("show owned", "Game", showOwnedCurrentIndex);
saveSettingInt(showOwnedComboBox,"show owned", "Game");
saveSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI");
saveSettingBool(useZoomOnMapCheckBox, "allow zooming", "Map");
saveSettingBool(graphicHerbalismCheckBox, "graphic herbalism", "Game");
@ -370,11 +376,7 @@ void Launcher::AdvancedPage::saveSettings()
{
// Saves Settings
saveSettingBool(timePlayedCheckbox, "timeplayed", "Saves");
int maximumQuicksaves = maximumQuicksavesComboBox->value();
if (maximumQuicksaves != Settings::Manager::getInt("max quicksaves", "Saves"))
{
Settings::Manager::setInt("max quicksaves", "Saves", maximumQuicksaves);
}
saveSettingInt(maximumQuicksavesComboBox, "max quicksaves", "Saves");
// Other Settings
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);
}
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)
{
loadCellsForAutocomplete(cellNames);

View File

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

View File

@ -1,4 +1,5 @@
#include "datafilespage.hpp"
#include "maindialog.hpp"
#include <QDebug>
@ -8,6 +9,7 @@
#include <QSortFilterProxyModel>
#include <thread>
#include <mutex>
#include <algorithm>
#include <apps/launcher/utils/cellnameloader.hpp>
#include <components/files/configurationmanager.hpp>
@ -24,11 +26,14 @@
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)
, mMainDialog(parent)
, mCfgMgr(cfg)
, mGameSettings(gameSettings)
, mLauncherSettings(launcherSettings)
, mNavMeshToolInvoker(new Process::ProcessInvoker(this))
{
ui.setupUi (this);
setObjectName ("DataFilesPage");
@ -57,8 +62,6 @@ Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config:
void Launcher::DataFilesPage::buildView()
{
ui.verticalLayout->insertWidget (0, mSelector->uiWidget());
QToolButton * refreshButton = mSelector->refreshButton();
//tool buttons
@ -89,6 +92,13 @@ void Launcher::DataFilesPage::buildView()
this, SLOT (slotProfileChangedByUser(QString, QString)));
connect(ui.refreshDataFilesAction, SIGNAL(triggered()),this, SLOT(slotRefreshButtonClicked()));
connect(ui.updateNavMeshButton, SIGNAL(clicked()), this, SLOT(startNavMeshTool()));
connect(ui.cancelNavMeshButton, SIGNAL(clicked()), this, SLOT(killNavMeshTool()));
connect(mNavMeshToolInvoker->getProcess(), SIGNAL(readyReadStandardOutput()), this, SLOT(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()
@ -121,6 +131,7 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
for (const QString &path : paths)
mSelector->addFiles(path);
mSelector->sortFiles();
PathIterator pathIterator(paths);
@ -410,3 +421,62 @@ void Launcher::DataFilesPage::reloadCells(QStringList selectedFiles)
std::sort(cellNamesList.begin(), cellNamesList.end());
emit signalLoadedCellsChanged(cellNamesList);
}
void Launcher::DataFilesPage::startNavMeshTool()
{
mMainDialog->writeSettings();
ui.navMeshLogPlainTextEdit->clear();
ui.navMeshProgressBar->setValue(0);
ui.navMeshProgressBar->setMaximum(1);
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
#include "ui_datafilespage.h"
#include <components/process/processinvoker.hpp>
#include <QWidget>
@ -19,6 +22,7 @@ namespace Config { class GameSettings;
namespace Launcher
{
class MainDialog;
class TextInputDialog;
class ProfilesComboBox;
@ -31,7 +35,7 @@ namespace Launcher
public:
explicit DataFilesPage (Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings,
Config::LauncherSettings &launcherSettings, QWidget *parent = nullptr);
Config::LauncherSettings &launcherSettings, MainDialog *parent = nullptr);
QAbstractItemModel* profilesModel() const;
@ -69,12 +73,18 @@ namespace Launcher
void on_cloneProfileAction_triggered();
void on_deleteProfileAction_triggered();
void startNavMeshTool();
void killNavMeshTool();
void updateNavMeshProgress();
void navMeshToolFinished(int exitCode, QProcess::ExitStatus exitStatus);
public:
/// Content List that is always present
const static char *mDefaultContentListName;
private:
MainDialog *mMainDialog;
TextInputDialog *mNewProfileDialog;
TextInputDialog *mCloneProfileDialog;
@ -87,6 +97,8 @@ namespace Launcher
QStringList previousSelectedFiles;
QString mDataLocal;
Process::ProcessInvoker* mNavMeshToolInvoker;
void buildView();
void setProfile (int index, 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(framerateLimitCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotFramerateLimitToggled(bool)));
connect(shadowDistanceCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotShadowDistLimitToggled(bool)));
}
bool Launcher::GraphicsPage::setupSDL()

View File

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

View File

@ -9,9 +9,9 @@
#define ALC_ALL_DEVICES_SPECIFIER 0x1013
#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;
if(alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT"))
@ -22,7 +22,7 @@ std::vector<const char *> Launcher::enumerateOpenALDevices()
{
devnames = alcGetString(nullptr, ALC_DEVICE_SPECIFIER);
}
while(devnames && *devnames)
{
devlist.emplace_back(devnames);
@ -31,9 +31,9 @@ std::vector<const char *> Launcher::enumerateOpenALDevices()
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);
if(device)

View File

@ -1,7 +1,8 @@
#include <vector>
#include <string>
namespace Launcher
{
std::vector<const char *> enumerateOpenALDevices();
std::vector<const char *> enumerateOpenALDevicesHrtf();
}
std::vector<std::string> enumerateOpenALDevices();
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 <fstream>
#include <cstdlib>
#include <components/misc/stringops.hpp>
#include <components/nif/niffile.hpp>
#include <components/files/constrainedfilestream.hpp>
#include <components/vfs/manager.hpp>
@ -18,18 +18,10 @@ namespace bpo = boost::program_options;
namespace bfs = boost::filesystem;
///See if the file has the named extension
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);
//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;
return Misc::StringUtils::ciEqual(extension, extensionToFind);
}
///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_RESOURCES_DIR "${OpenMW_BINARY_DIR}/${OPENCS_BUNDLE_NAME}.app/Contents/Resources")
set(OPENMW_MYGUI_FILES_ROOT ${OPENCS_BUNDLE_RESOURCES_DIR})
set(OPENMW_SHADERS_ROOT ${OPENCS_BUNDLE_RESOURCES_DIR})
set(OPENMW_RESOURCES_ROOT ${OPENCS_BUNDLE_RESOURCES_DIR})
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");
desc.add_options()
("data", boost::program_options::value<Files::EscapePathContainer>()->default_value(Files::EscapePathContainer(), "data")->multitoken()->composing())
("data-local", boost::program_options::value<Files::EscapePath>()->default_value(Files::EscapePath(), ""))
("data", boost::program_options::value<Files::MaybeQuotedPathContainer>()->default_value(Files::MaybeQuotedPathContainer(), "data")->multitoken()->composing())
("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))
("encoding", boost::program_options::value<Files::EscapeHashString>()->default_value("win1252"))
("resources", boost::program_options::value<Files::EscapePath>()->default_value(Files::EscapePath(), "resources"))
("fallback-archive", boost::program_options::value<Files::EscapeStringVector>()->
default_value(Files::EscapeStringVector(), "fallback-archive")->multitoken())
("encoding", boost::program_options::value<std::string>()->default_value("win1252"))
("resources", boost::program_options::value<Files::MaybeQuotedPath>()->default_value(Files::MaybeQuotedPath(), "resources"))
("fallback-archive", boost::program_options::value<std::vector<std::string>>()->
default_value(std::vector<std::string>(), "fallback-archive")->multitoken())
("fallback", boost::program_options::value<FallbackMap>()->default_value(FallbackMap(), "")
->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)")
("script-blacklist-use", boost::program_options::value<bool>()->implicit_value(true)
->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);
mEncodingName = variables["encoding"].as<Files::EscapeHashString>().toStdString();
mEncodingName = variables["encoding"].as<std::string>();
mDocumentManager.setEncoding(ToUTF8::calculateEncoding(mEncodingName));
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>())
mDocumentManager.setBlacklistedScripts (
variables["script-blacklist"].as<Files::EscapeStringVector>().toStdStringVector());
variables["script-blacklist"].as<std::vector<std::string>>());
mFsStrict = variables["fs-strict"].as<bool>();
Files::PathContainer dataDirs, dataLocal;
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())
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());
//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)
{
QString path = QString::fromUtf8 (iter->string().c_str());
mFileDialog.addFiles(path);
}
mFileDialog.addFiles(dataDirs);
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()

View File

@ -17,6 +17,19 @@
#include "textnode.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
{
struct Token
@ -103,7 +116,7 @@ CSMFilter::Token CSMFilter::Parser::getStringToken()
{
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 += c;
else
@ -150,7 +163,7 @@ CSMFilter::Token CSMFilter::Parser::getNumberToken()
{
char c = mInput[mIndex];
if (std::isdigit (c))
if (isDigit(c))
{
string += c;
hasDigit = true;
@ -225,10 +238,10 @@ CSMFilter::Token CSMFilter::Parser::getNextToken()
case '!': ++mIndex; return Token (Token::Type_OneShot);
}
if (c=='"' || c=='_' || std::isalpha (c) || c==':')
if (c=='"' || c=='_' || isAlpha(c) || c==':')
return getStringToken();
if (c=='-' || c=='.' || std::isdigit (c))
if (c=='-' || c=='.' || isDigit(c))
return getNumberToken();
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)).
setTooltip("Sets the gradient color to use in conjunction with the night background color. Ignored if "
"the gradient option is disabled.");
declareBool("scene-day-night-switch-nodes", "Use Day/Night Switch Nodes", true);
declareCategory ("Tooltips");
declareBool ("scene", "Show Tooltips in 3D scenes", true);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,9 +3,12 @@
#include <osg/LightSource>
#include <osg/NodeVisitor>
#include <osg/Switch>
#include <osg/ValueObject>
#include <components/misc/constants.hpp>
#include "../../model/prefs/state.hpp"
class DayNightSwitchVisitor : public osg::NodeVisitor
{
public:
@ -16,8 +19,33 @@ public:
void apply(osg::Switch &switchNode) override
{
if (switchNode.getName() == Constants::NightDayLabel)
switchNode.setSingleChildOn(mIndex);
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)
switchNode.setSingleChildOn(mIndex);
}
else if (initialIndex != NoIndex)
{
switchNode.setSingleChildOn(initialIndex);
}
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 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 IndicesPerSegment = 24;

View File

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

View File

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

View File

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

View File

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

View File

@ -8,6 +8,8 @@
#include <boost/filesystem/fstream.hpp>
#include <osg/Version>
#include <osgViewer/ViewerEventHandlers>
#include <osgDB/ReadFile>
#include <osgDB/WriteFile>
@ -37,11 +39,11 @@
#include <components/version/version.hpp>
#include <components/detournavigator/navigator.hpp>
#include <components/misc/frameratelimiter.hpp>
#include <components/sceneutil/screencapture.hpp>
#include <components/sceneutil/depth.hpp>
#include <components/sceneutil/util.hpp>
#include "mwinput/inputmanagerimp.hpp"
@ -238,6 +240,20 @@ namespace
{
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()
@ -293,6 +309,10 @@ bool OMW::Engine::frame(float frametime)
// Main menu opened? Then scripts are also paused.
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
{
ScopedProfile<UserStatsType::State> profile(frameStart, frameNumber, *timer, *stats);
@ -546,6 +566,10 @@ void OMW::Engine::createWindow(Settings::Manager& settings)
if(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)
flags |= SDL_WINDOW_BORDERLESS;
@ -634,8 +658,12 @@ void OMW::Engine::createWindow(Settings::Manager& settings)
camera->setGraphicsContext(graphicsWindow);
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())
mViewer->setRealizeOperation(new Debug::EnableGLDebugOperation());
realizeOperations->add(new Debug::EnableGLDebugOperation());
mViewer->realize();
@ -709,7 +737,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
mViewer->addEventHandler(mScreenCaptureHandler);
mLuaManager = new MWLua::LuaManager(mVFS.get());
mLuaManager = new MWLua::LuaManager(mVFS.get(), (mResDir / "lua_libs").string());
mEnvironment.setLuaManager(mLuaManager);
// 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);
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();
osg::ref_ptr<osg::Group> guiRoot = new osg::Group;
@ -831,6 +881,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
}
mLuaManager->init();
mLuaManager->loadPermanentStorage(mCfgMgr.getUserConfigPath().string());
}
class OMW::Engine::LuaWorker
@ -842,10 +893,8 @@ public:
mThread = std::thread([this]{ threadBody(); });
};
void allowUpdate(double dt)
void allowUpdate()
{
mDt = dt;
mIsGuiMode = mEngine->mEnvironment.getWindowManager()->isGuiMode();
if (!mThread)
return;
{
@ -864,7 +913,6 @@ public:
}
else
update();
mEngine->mLuaManager->applyQueuedChanges();
};
void join()
@ -888,7 +936,7 @@ private:
const unsigned int frameNumber = viewer->getFrameStamp()->getFrameNumber();
ScopedProfile<UserStatsType::Lua> profile(frameStart, frameNumber, *osg::Timer::instance(), *viewer->getViewerStats());
mEngine->mLuaManager->update(mIsGuiMode, mDt);
mEngine->mLuaManager->update();
}
void threadBody()
@ -913,8 +961,6 @@ private:
std::condition_variable mCV;
bool mUpdateRequest = false;
bool mJoinRequest = false;
double mDt = 0;
bool mIsGuiMode = false;
std::optional<std::thread> mThread;
};
@ -1027,7 +1073,7 @@ void OMW::Engine::go()
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();
@ -1058,8 +1104,7 @@ void OMW::Engine::go()
// Save user settings
settings.saveUser(settingspath);
mViewer->stopThreading();
mLuaManager->savePermanentStorage(mCfgMgr.getUserConfigPath().string());
Log(Debug::Info) << "Quitting peacefully.";
}

View File

@ -1,6 +1,5 @@
#include <components/version/version.hpp>
#include <components/files/configurationmanager.hpp>
#include <components/files/escape.hpp>
#include <components/fallback/fallback.hpp>
#include <components/fallback/validate.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);
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;
return false;
}
@ -66,38 +65,38 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
cfgMgr.readConfiguration(variables, 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();
engine.setGrabMouse(!variables["no-grab"].as<bool>());
// 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);
engine.setEncoding(ToUTF8::calculateEncoding(encoding));
// directory settings
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())
dataDirs.push_back(local);
cfgMgr.processPaths(dataDirs);
engine.setResourceDir(variables["resources"].as<Files::EscapePath>().mPath);
engine.setResourceDir(variables["resources"].as<Files::MaybeQuotedPath>());
engine.setDataDirs(dataDirs);
// 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)
{
engine.addArchive(*it);
}
StringsVector content = variables["content"].as<Files::EscapeStringVector>().toStdStringVector();
StringsVector content = variables["content"].as<StringsVector>();
if (content.empty())
{
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);
}
StringsVector groundcover = variables["groundcover"].as<Files::EscapeStringVector>().toStdStringVector();
StringsVector groundcover = variables["groundcover"].as<StringsVector>();
for (auto& file : groundcover)
{
engine.addGroundcoverFile(file);
@ -131,7 +130,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
}
// 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>());
if (!variables["skip-menu"].as<bool>() && variables["new-game"].as<bool>())
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.setCompileAllDialogue(variables["script-all-dialogue"].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.setScriptBlacklist (variables["script-blacklist"].as<Files::EscapeStringVector>().toStdStringVector());
engine.setScriptBlacklist (variables["script-blacklist"].as<StringsVector>());
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
Fallback::Map::init(variables["fallback"].as<FallbackMap>().mMap);

View File

@ -49,8 +49,8 @@ namespace MWBase
virtual void setGamepadGuiCursorEnabled(bool enabled) = 0;
virtual void setAttemptJump(bool jumping) = 0;
virtual void toggleControlSwitch (const std::string& sw, bool value) = 0;
virtual bool getControlSwitch (const std::string& sw) = 0;
virtual void toggleControlSwitch(std::string_view sw, bool value) = 0;
virtual bool getControlSwitch(std::string_view sw) = 0;
virtual std::string getActionDescription (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 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 uint32_t getMouseButtonsState() const = 0;
virtual int getMouseMoveX() 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.
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
{
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 WindowModal;
class JailScreen;
class MessageBox;
enum ShowInDialogueMode {
ShowInDialogueMode_IfPossible,
@ -145,6 +146,7 @@ namespace MWBase
virtual MWGui::CountDialog* getCountDialog() = 0;
virtual MWGui::ConfirmationDialog* getConfirmationDialog() = 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
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 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
{
class Animation;
class Camera;
}
namespace MWMechanics
@ -433,14 +434,12 @@ namespace MWBase
virtual osg::Matrixf getActorHeadTransform(const MWWorld::ConstPtr& actor) const = 0;
virtual MWRender::Camera* getCamera() = 0;
virtual void togglePOV(bool force = false) = 0;
virtual bool isFirstPerson() const = 0;
virtual bool isPreviewModeEnabled() const = 0;
virtual void togglePreviewMode(bool enable) = 0;
virtual bool toggleVanityMode(bool enable) = 0;
virtual void allowVanityMode(bool allow) = 0;
virtual bool vanityRotateCamera(float * rot) = 0;
virtual void adjustCameraDistance(float dist) = 0;
virtual void applyDeferredPreviewRotationToPlayer(float dt) = 0;
virtual void disableDeferredPreviewRotation() = 0;

View File

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

View File

@ -64,7 +64,13 @@ namespace MWClass
if (customData.mSpawn)
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())
{
const MWMechanics::CreatureStats& creatureStats = creature.getClass().getCreatureStats(creature);

View File

@ -1,7 +1,6 @@
#include "npc.hpp"
#include <memory>
#include <climits> // INT_MIN
#include <components/misc/constants.hpp>
#include <components/misc/rng.hpp>
@ -1302,9 +1301,7 @@ namespace MWClass
{
if (!ptr.getRefData().getCustomData())
{
// FIXME: the use of mGoldPool can be replaced with another flag the next time
// the save file format is changed
if (npcState.mCreatureStats.mGoldPool == INT_MIN)
if (npcState.mCreatureStats.mMissingACDT)
ensureCustomData(ptr);
else
// 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) );
}
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);
for (std::vector<HyperTextParser::Token>::iterator tok = hypertext.begin(); tok != hypertext.end(); ++tok)
@ -95,6 +96,18 @@ namespace MWDialogue
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 ))
mKnownTopics.insert( topicId );
}
@ -136,7 +149,6 @@ namespace MWDialogue
mTalkedTo = creatureStats.hasTalkedToPlayer();
mActorKnownTopics.clear();
mActorKnownTopicsFlag.clear();
//greeting
const MWWorld::Store<ESM::Dialogue> &dialogs =
@ -163,7 +175,7 @@ namespace MWDialogue
executeScript (info->mResultScript, mActor);
mLastTopic = it->mId;
parseText (info->mResponse);
addTopicsFromText (info->mResponse);
return true;
}
@ -277,7 +289,10 @@ namespace MWDialogue
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)
{
std::string title;
@ -320,7 +335,7 @@ namespace MWDialogue
executeScript (info->mResultScript, mActor);
parseText (info->mResponse);
addTopicsFromText (info->mResponse);
}
}
@ -339,7 +354,6 @@ namespace MWDialogue
updateGlobals();
mActorKnownTopics.clear();
mActorKnownTopicsFlag.clear();
const auto& dialogs = MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>();
@ -354,21 +368,41 @@ namespace MWDialogue
if (answer != nullptr)
{
int flag = 0;
int topicFlags = 0;
if(!inJournal(topicId, answer->mId))
{
// Does this dialogue contains some actor-specific answer?
if (Misc::StringUtils::ciEqual(answer->mActor, mActor.getCellRef().getRefId()))
flag |= MWBase::DialogueManager::TopicType::Specific;
topicFlags |= MWBase::DialogueManager::TopicType::Specific;
}
else
flag |= MWBase::DialogueManager::TopicType::Exhausted;
mActorKnownTopics.insert (dialog.mId);
mActorKnownTopicsFlag[dialog.mId] = flag;
topicFlags |= MWBase::DialogueManager::TopicType::Exhausted;
mActorKnownTopics.insert (std::make_pair(dialog.mId, ActorKnownTopicInfo {topicFlags, answer}));
}
}
}
// 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()
@ -377,7 +411,7 @@ namespace MWDialogue
std::list<std::string> keywordList;
for (const std::string& topic : mActorKnownTopics)
for (const auto& [topic, topicInfo] : mActorKnownTopics)
{
//does the player know the topic?
if (mKnownTopics.count(topic))
@ -391,7 +425,7 @@ namespace MWDialogue
int DialogueManager::getTopicFlag(const std::string& topicId)
{
return mActorKnownTopicsFlag[topicId];
return mActorKnownTopics[topicId].mFlags;
}
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)
npcStats.setBaseDisposition(0);
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);
}
@ -444,7 +478,7 @@ namespace MWDialogue
if (const ESM::DialInfo *info = filter.search (*dialogue, true))
{
std::string text = info->mResponse;
parseText (text);
addTopicsFromText (text);
mChoice = -1;
mIsInChoice = false;
@ -579,7 +613,7 @@ namespace MWDialogue
{
const ESM::DialInfo* info = infos[0];
parseText (info->mResponse);
addTopicsFromText (info->mResponse);
const MWWorld::Store<ESM::GameSetting>& gmsts =
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();

View File

@ -10,6 +10,7 @@
#include <components/compiler/streamerrorhandler.hpp>
#include <components/translation/translation.hpp>
#include <components/misc/stringops.hpp>
#include <components/esm/loadinfo.hpp>
#include "../mwworld/ptr.hpp"
@ -24,14 +25,19 @@ namespace MWDialogue
{
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.
// Modified faction reactions. <Faction1, <Faction2, Difference> >
typedef std::map<std::string, std::map<std::string, int> > ModFactionReactionMap;
ModFactionReactionMap mChangedFactionReaction;
std::set<std::string, Misc::StringUtils::CiComp> mActorKnownTopics;
std::unordered_map<std::string, int> mActorKnownTopicsFlag;
std::map<std::string, ActorKnownTopicInfo, Misc::StringUtils::CiComp> mActorKnownTopics;
Translation::Storage& mTranslationDataStorage;
MWScript::CompilerContext mCompilerContext;
@ -51,7 +57,8 @@ namespace MWDialogue
int mCurrentDisposition;
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 updateGlobals();

View File

@ -47,19 +47,8 @@ namespace MWDialogue
void tokenizeKeywords(const std::string & text, std::vector<Token> & tokens)
{
const MWWorld::Store<ESM::Dialogue> & dialogs =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>();
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*/);
const auto& keywordSearch =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>().getDialogIdKeywordSearch();
std::vector<KeywordSearch<std::string, int /*unused*/>::Match> matches;
keywordSearch.highlightKeywords(text.begin(), text.end(), matches);

View File

@ -74,13 +74,13 @@ public:
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;
for (Point i = beg; i != end; ++i)
{
// 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
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
// 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)
{
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 ())
{
@ -116,7 +116,7 @@ public:
// shorter candidates will be added to the vector first. however, we want to check against longer candidates first
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)
{
candidate = it->second;

View File

@ -8,7 +8,7 @@
#include "MyGUI_FactoryManager.h"
#include <components/misc/utf8stream.hpp>
#include <components/sceneutil/util.hpp>
#include <components/sceneutil/depth.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp"
@ -907,12 +907,6 @@ protected:
return {};
MyGUI::IntPoint pos (left, top);
#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
// work around inconsistency in MyGUI where the mouse press coordinates aren't
// transformed by the current Layer (even though mouse *move* events are).
if(!move)
pos = mNode->getLayer()->getPosition(left, top);
#endif
pos.left -= mCroppedParent->getAbsoluteLeft ();
pos.top -= mCroppedParent->getAbsoluteTop ();
pos.top += mViewTop;
@ -1221,7 +1215,7 @@ public:
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),
z /*mNode->getNodeDepth()*/, vertices, renderXform);

View File

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

View File

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

View File

@ -347,8 +347,7 @@ namespace MWGui
{
if (!mScrollBar->getVisible())
return;
mScrollBar->setScrollPosition(std::min(static_cast<int>(mScrollBar->getScrollRange()-1),
std::max(0, static_cast<int>(mScrollBar->getScrollPosition() - _rel*0.3))));
mScrollBar->setScrollPosition(std::clamp<int>(mScrollBar->getScrollPosition() - _rel*0.3, 0, mScrollBar->getScrollRange() - 1));
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()));
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()));
mPrice->setCaption(std::to_string(mEnchanting.getEnchantPrice()));

View File

@ -81,7 +81,7 @@ namespace MWGui
, mMinimap(nullptr)
, mCrosshair(nullptr)
, mCellNameBox(nullptr)
, mDrowningFrame(nullptr)
, mDrowningBar(nullptr)
, mDrowningFlash(nullptr)
, mHealthManaStaminaBaseLeft(0)
, mWeapBoxBaseLeft(0)
@ -119,6 +119,7 @@ namespace MWGui
fatigueFrame->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onHMSClicked);
//Drowning bar
getWidget(mDrowningBar, "DrowningBar");
getWidget(mDrowningFrame, "DrowningFrame");
getWidget(mDrowning, "Drowning");
getWidget(mDrowningFlash, "Flash");
@ -224,7 +225,7 @@ namespace MWGui
void HUD::setDrowningBarVisible(bool visible)
{
mDrowningFrame->setVisible(visible);
mDrowningBar->setVisible(visible);
}
void HUD::onWorldClicked(MyGUI::Widget* _sender)
@ -368,9 +369,6 @@ namespace MWGui
mWeaponSpellBox->setPosition(mWeaponSpellBox->getPosition() + MyGUI::IntPoint(0,20));
}
if (mIsDrowning)
mDrowningFlashTheta += dt * osg::PI*2;
mSpellIcons->updateWidgets(mEffectBox, true);
if (mEnemyActorId != -1 && mEnemyHealth->getVisible())
@ -378,8 +376,13 @@ namespace MWGui
updateEnemyHealthBar();
}
if (mDrowningBar->getVisible())
mDrowningBar->setPosition(mMainWidget->getWidth()/2 - mDrowningFrame->getWidth()/2, mMainWidget->getTop());
if (mIsDrowning)
{
mDrowningFlashTheta += dt * osg::PI*2;
float intensity = (cos(mDrowningFlashTheta) + 2.0f) / 3.0f;
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();
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::TextBox* mCellNameBox;
MyGUI::TextBox* mWeaponSpellBox;
MyGUI::Widget *mDrowningFrame, *mDrowningFlash;
MyGUI::Widget *mDrowningBar, *mDrowningFrame, *mDrowningFlash;
// bottom left elements
int mHealthManaStaminaBaseLeft, mWeapBoxBaseLeft, mSpellBoxBaseLeft, mSneakBoxBaseLeft;

View File

@ -79,7 +79,7 @@ void KeyboardNavigation::restoreFocus(int mode)
if (found != mKeyFocus.end())
{
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);
}
}
@ -93,19 +93,6 @@ void KeyboardNavigation::_unlinkWidget(MyGUI::Widget *widget)
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)
{
while (widget && widget->getParent())
@ -128,9 +115,6 @@ void KeyboardNavigation::onFrame()
if (focus == mCurrentFocus)
{
#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
styleFocusedButton(mCurrentFocus);
#endif
return;
}
@ -143,19 +127,8 @@ void KeyboardNavigation::onFrame()
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;
}
#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
styleFocusedButton(mCurrentFocus);
#endif
}
void KeyboardNavigation::setDefaultFocus(MyGUI::Widget *window, MyGUI::Widget *defaultFocus)
@ -273,7 +246,7 @@ bool KeyboardNavigation::switchFocus(int direction, bool wrap)
if (wrap)
index = (index + keyFocusList.size())%keyFocusList.size();
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];
int vertdiff = next->getTop() - focus->getTop();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -171,7 +171,7 @@ namespace MWGui
else
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);
scroll->setScrollPosition(static_cast<size_t>(value * (scroll->getScrollRange() - 1)));
@ -232,6 +232,7 @@ namespace MWGui
getWidget(mControllerSwitch, "ControllerButton");
getWidget(mWaterTextureSize, "WaterTextureSize");
getWidget(mWaterReflectionDetail, "WaterReflectionDetail");
getWidget(mWaterRainRippleDetail, "WaterRainRippleDetail");
getWidget(mLightingMethodButton, "LightingMethodButton");
getWidget(mLightsResetButton, "LightsResetButton");
getWidget(mMaxLights, "MaxLights");
@ -259,6 +260,7 @@ namespace MWGui
mWaterTextureSize->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterTextureSizeChanged);
mWaterReflectionDetail->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterReflectionDetailChanged);
mWaterRainRippleDetail->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterRainRippleDetailChanged);
mLightingMethodButton->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onLightingMethodButtonChanged);
mLightsResetButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onLightsResetButtonClicked);
@ -267,6 +269,8 @@ namespace MWGui
mKeyboardSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onKeyboardSwitchClicked);
mControllerSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onControllerSwitchClicked);
computeMinimumWindowSize();
center();
mResetControlsButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onResetDefaultBindings);
@ -305,10 +309,12 @@ namespace MWGui
if (waterTextureSize >= 2048)
mWaterTextureSize->setIndexSelected(2);
int waterReflectionDetail = Settings::Manager::getInt("reflection detail", "Water");
waterReflectionDetail = std::min(5, std::max(0, waterReflectionDetail));
int waterReflectionDetail = std::clamp(Settings::Manager::getInt("reflection detail", "Water"), 0, 5);
mWaterReflectionDetail->setIndexSelected(waterReflectionDetail);
int waterRainRippleDetail = std::clamp(Settings::Manager::getInt("rain ripple detail", "Water"), 0, 2);
mWaterRainRippleDetail->setIndexSelected(waterRainRippleDetail);
updateMaxLightsComboBox(mMaxLights);
mWindowBorderButton->setEnabled(!Settings::Manager::getBool("fullscreen", "Video"));
@ -392,11 +398,18 @@ namespace MWGui
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);
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)
{
if (pos == MyGUI::ITEM_NONE)
@ -739,6 +752,32 @@ namespace MWGui
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()
{
mResolutionList->setScrollPosition(0);

View File

@ -31,6 +31,7 @@ namespace MWGui
MyGUI::ComboBox* mWaterTextureSize;
MyGUI::ComboBox* mWaterReflectionDetail;
MyGUI::ComboBox* mWaterRainRippleDetail;
MyGUI::ComboBox* mMaxLights;
MyGUI::ComboBox* mLightingMethodButton;
@ -55,6 +56,7 @@ namespace MWGui
void onWaterTextureSizeChanged(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 onLightsResetButtonClicked(MyGUI::Widget* _sender);
@ -75,6 +77,8 @@ namespace MWGui
void updateSliderLabel(MyGUI::ScrollBar* scroller, const std::string& value);
void layoutControlsBox();
void computeMinimumWindowSize();
private:
void resetScrollbars();

View File

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

View File

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

View File

@ -53,6 +53,8 @@
#include <components/misc/resourcehelpers.hpp>
#include <components/misc/frameratelimiter.hpp>
#include <components/lua_ui/widgetlist.hpp>
#include "../mwbase/inputmanager.hpp"
#include "../mwbase/statemanager.hpp"
#include "../mwbase/soundmanager.hpp"
@ -163,6 +165,7 @@ namespace MWGui
, mScreenFader(nullptr)
, mDebugWindow(nullptr)
, mJailScreen(nullptr)
, mContainerWindow(nullptr)
, mTranslationDataStorage (translationDataStorage)
, mCharGen(nullptr)
, mInputBlocker(nullptr)
@ -220,6 +223,7 @@ namespace MWGui
ItemWidget::registerComponents();
SpellView::registerComponents();
Gui::registerAllWidgets();
LuaUi::registerAllWidgets();
MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Controllers::ControllerFollowMouse>("Controller");
@ -357,10 +361,10 @@ namespace MWGui
mGuiModeStates[GM_Dialogue] = GuiModeState(mDialogueWindow);
mTradeWindow->eventTradeDone += MyGUI::newDelegate(mDialogueWindow, &DialogueWindow::onTradeComplete);
ContainerWindow* containerWindow = new ContainerWindow(mDragAndDrop);
mWindows.push_back(containerWindow);
trackWindow(containerWindow, "container");
mGuiModeStates[GM_Container] = GuiModeState({containerWindow, mInventoryWindow});
mContainerWindow = new ContainerWindow(mDragAndDrop);
mWindows.push_back(mContainerWindow);
trackWindow(mContainerWindow, "container");
mGuiModeStates[GM_Container] = GuiModeState({mContainerWindow, mInventoryWindow});
mHud = new HUD(mCustomMarkers, mDragAndDrop, mLocalMapRender);
mWindows.push_back(mHud);
@ -635,6 +639,7 @@ namespace MWGui
mMap->setVisible(false);
mStatsWindow->setVisible(false);
mSpellWindow->setVisible(false);
mHud->setDrowningBarVisible(false);
mInventoryWindow->setVisible(getMode() == GM_Container || getMode() == GM_Barter || getMode() == GM_Companion);
}
@ -768,6 +773,11 @@ namespace MWGui
mMessageBoxManager->removeStaticMessageBox();
}
const std::vector<MWGui::MessageBox*> WindowManager::getActiveMessageBoxes()
{
return mMessageBoxManager->getActiveMessageBoxes();
}
int WindowManager::readPressedButton ()
{
return mMessageBoxManager->readPressedButton();
@ -1095,12 +1105,21 @@ namespace MWGui
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 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);
@ -1163,6 +1182,16 @@ namespace MWGui
}
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)
return;
@ -1185,6 +1214,8 @@ namespace MWGui
mGuiModeStates[mode].update(true);
playSound(mGuiModeStates[mode].mOpenSound);
}
if(force)
mContainerWindow->treatNextOpenAsLoot();
for (WindowBase* window : mGuiModeStates[mode].mWindows)
window->setPtr(arg);

View File

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

View File

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

View File

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

View File

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

View File

@ -43,7 +43,8 @@ namespace MWInput
bool actionIsActive(int id) const;
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 mouseReleased(const SDL_MouseButtonEvent &arg, int deviceID);

View File

@ -5,6 +5,7 @@
#include <MyGUI_Widget.h>
#include <components/debug/debuglog.hpp>
#include <components/sdlutil/sdlmappings.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/inputmanager.hpp"
@ -19,7 +20,6 @@
#include "actionmanager.hpp"
#include "bindingsmanager.hpp"
#include "mousemanager.hpp"
#include "sdlmappings.hpp"
namespace MWInput
{
@ -34,12 +34,10 @@ namespace MWInput
, mJoystickEnabled (Settings::Manager::getBool("enable controller", "Input"))
, mGamepadCursorSpeed(Settings::Manager::getFloat("gamepad cursor speed", "Input"))
, mSneakToggleShortcutTimer(0.f)
, mGamepadZoom(0)
, mGamepadGuiCursorEnabled(true)
, mGuiCursorEnabled(true)
, mJoystickLastUsed(false)
, mSneakGamepadShortcut(false)
, mGamepadPreviewMode(false)
{
if (!controllerBindingsFile.empty())
{
@ -70,7 +68,7 @@ namespace MWInput
}
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);
}
@ -85,8 +83,6 @@ namespace MWInput
bool ControllerManager::update(float dt)
{
mGamepadPreviewMode = mActionManager->isPreviewModeEnabled();
if (mGuiCursorEnabled && !(mJoystickLastUsed && !mGamepadGuiCursorEnabled))
{
float xAxis = mBindingsManager->getActionValue(A_MoveLeftRight) * 2.0f - 1.0f;
@ -115,7 +111,6 @@ namespace MWInput
if (MWBase::Environment::get().getWindowManager()->isGuiMode()
|| MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_Running)
{
mGamepadZoom = 0;
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;
}
@ -229,7 +215,7 @@ namespace MWInput
mBindingsManager->setPlayerControlsEnabled(true);
//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));
if (!MWBase::Environment::get().getInputManager()->controlsDisabled())
@ -273,7 +259,7 @@ namespace MWInput
mBindingsManager->setPlayerControlsEnabled(true);
//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->controllerButtonReleased(deviceID, arg);
@ -289,21 +275,11 @@ namespace MWInput
{
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
{
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.
}
}
// Preview Mode Gamepad Zooming; do not propagate to mBindingsManager
return;
}
mBindingsManager->controllerAxisMoved(deviceID, arg);
}
@ -403,4 +379,24 @@ namespace MWInput
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 setJoystickLastUsed(bool enabled) { mJoystickLastUsed = enabled; }
bool joystickLastUsed() { return mJoystickLastUsed; }
bool joystickLastUsed() const { return mJoystickLastUsed; }
void setGuiCursorEnabled(bool enabled) { mGuiCursorEnabled = 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:
// Return true if GUI consumes input.
@ -53,12 +56,10 @@ namespace MWInput
bool mJoystickEnabled;
float mGamepadCursorSpeed;
float mSneakToggleShortcutTimer;
float mGamepadZoom;
bool mGamepadGuiCursorEnabled;
bool mGuiCursorEnabled;
bool mJoystickLastUsed;
bool mSneakGamepadShortcut;
bool mGamepadPreviewMode;
};
}
#endif

View File

@ -29,12 +29,15 @@ namespace MWInput
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();
@ -51,15 +54,14 @@ namespace MWInput
/// \fixme maybe crouching at this time
player.setUpDown(0);
}
else if (key == "vanitymode")
{
MWBase::Environment::get().getWorld()->allowVanityMode(value);
}
else if (key == "playerlooking" && !value)
{
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*/)

View File

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

View File

@ -18,7 +18,6 @@
#include "controlswitch.hpp"
#include "keyboardmanager.hpp"
#include "mousemanager.hpp"
#include "sdlmappings.hpp"
#include "sensormanager.hpp"
namespace MWInput
@ -101,8 +100,6 @@ namespace MWInput
mMouseManager->update(dt);
mSensorManager->update(dt);
mActionManager->update(dt, controllerMove);
MWBase::Environment::get().getWorld()->applyDeferredPreviewRotationToPlayer(dt);
}
void InputManager::setDragDrop(bool dragDrop)
@ -135,12 +132,12 @@ namespace MWInput
mSensorManager->processChangedSettings(changed);
}
bool InputManager::getControlSwitch(const std::string& sw)
bool InputManager::getControlSwitch(std::string_view 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);
}
@ -180,14 +177,14 @@ namespace MWInput
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

View File

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

View File

@ -4,6 +4,8 @@
#include <MyGUI_InputManager.h>
#include <components/sdlutil/sdlmappings.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/inputmanager.hpp"
#include "../mwbase/luamanager.hpp"
@ -13,7 +15,6 @@
#include "actions.hpp"
#include "bindingsmanager.hpp"
#include "sdlmappings.hpp"
namespace MWInput
{
@ -35,16 +36,16 @@ namespace MWInput
// 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
// (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
&& MWBase::Environment::get().getWindowManager()->isConsoleMode())
SDL_StopTextInput();
bool consumed = SDL_IsTextInputActive() && // Little trick to check if key is printable
(!(SDLK_SCANCODE_MASK & arg.keysym.sym) &&
(std::isprint(arg.keysym.sym) ||
// 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 (MWBase::Environment::get().getWindowManager()->injectKeyPress(kc, 0, arg.repeat))
@ -71,7 +72,7 @@ namespace MWInput
void KeyboardManager::keyReleased(const SDL_KeyboardEvent &arg)
{
MWBase::Environment::get().getInputManager()->setJoystickLastUsed(false);
auto kc = sdlKeyToMyGUI(arg.keysym.sym);
auto kc = SDLUtil::sdlKeyToMyGUI(arg.keysym.sym);
if (!mBindingsManager->isDetectingBindingState())
mBindingsManager->setPlayerControlsEnabled(!MyGUI::InputManager::getInstance().injectKeyRelease(kc));

View File

@ -7,6 +7,7 @@
#include <components/debug/debuglog.hpp>
#include <components/sdlutil/sdlinputwrapper.hpp>
#include <components/sdlutil/sdlmappings.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/inputmanager.hpp"
@ -17,7 +18,6 @@
#include "actions.hpp"
#include "bindingsmanager.hpp"
#include "sdlmappings.hpp"
namespace MWInput
{
@ -34,7 +34,6 @@ namespace MWInput
, mMouseWheel(0)
, mMouseLookEnabled(false)
, mGuiCursorEnabled(true)
, mButtonsState(0)
, mMouseMoveX(0)
, mMouseMoveY(0)
{
@ -126,7 +125,11 @@ namespace MWInput
else
{
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())
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
{
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)
{
MyGUI::Button* b = MyGUI::InputManager::getInstance().getMouseFocusWidget()->castType<MyGUI::Button>(false);
@ -199,7 +206,7 @@ namespace MWInput
void MouseManager::update(float dt)
{
mButtonsState = SDL_GetRelativeMouseState(&mMouseMoveX, &mMouseMoveY);
SDL_GetRelativeMouseState(&mMouseMoveX, &mMouseMoveY);
if (!mMouseLookEnabled)
return;
@ -230,12 +237,18 @@ namespace MWInput
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)
{
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)
@ -245,8 +258,8 @@ namespace MWInput
mMouseWheel += mouseWheelMove;
const MyGUI::IntSize& viewSize = MyGUI::RenderManager::getInstance().getViewSize();
mGuiCursorX = std::max(0.f, std::min(mGuiCursorX, float(viewSize.width - 1)));
mGuiCursorY = std::max(0.f, std::min(mGuiCursorY, float(viewSize.height - 1)));
mGuiCursorX = std::clamp<float>(mGuiCursorX, 0.f, viewSize.width - 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));
}

View File

@ -38,7 +38,6 @@ namespace MWInput
void setMouseLookEnabled(bool enabled) { mMouseLookEnabled = enabled; }
void setGuiCursorEnabled(bool enabled) { mGuiCursorEnabled = enabled; }
uint32_t getButtonsState() const { return mButtonsState; }
int getMouseMoveX() const { return mMouseMoveX; }
int getMouseMoveY() const { return mMouseMoveY; }
@ -58,7 +57,6 @@ namespace MWInput
bool mMouseLookEnabled;
bool mGuiCursorEnabled;
uint32_t mButtonsState;
int mMouseMoveX;
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 <components/debug/debuglog.hpp>
#include <components/lua/luastate.hpp>
#include <components/settings/settings.hpp>
#include "../mwworld/cellstore.hpp"
#include "../mwworld/class.hpp"
@ -11,15 +13,35 @@
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
{
MWWorld::CellStore* cell = worldView.findCell(mCell, mPos);
if (!cell)
{
Log(Debug::Error) << "LuaManager::applyTeleport -> cell not found: '" << mCell << "'";
return;
}
throw std::runtime_error(std::string("cell not found: '") + mCell + "'");
MWBase::World* world = MWBase::Environment::get().getWorld();
MWWorld::Ptr obj = worldView.getObjectRegistry()->getPtr(mObject, false);

View File

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

View File

@ -19,36 +19,36 @@ namespace MWLua
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");
api["registerTimerCallback"] = [](const AsyncPackageId& asyncId, std::string_view name, sol::function callback)
{
asyncId.mContainer->registerTimerCallback(asyncId.mScriptId, name, std::move(callback));
return TimerCallback{asyncId, std::string(name)};
};
api["newTimerInSeconds"] = [world=context.mWorldView](const AsyncPackageId&, double delay,
const TimerCallback& callback, sol::object callbackArg)
api["newSimulationTimer"] = [world=context.mWorldView](const AsyncPackageId&, double delay,
const TimerCallback& callback, sol::object callbackArg)
{
callback.mAsyncId.mContainer->setupSerializableTimer(
TimeUnit::SECONDS, world->getGameTimeInSeconds() + delay,
TimerType::SIMULATION_TIME, world->getSimulationTime() + delay,
callback.mAsyncId.mScriptId, callback.mName, std::move(callbackArg));
};
api["newTimerInHours"] = [world=context.mWorldView](const AsyncPackageId&, double delay,
const TimerCallback& callback, sol::object callbackArg)
api["newGameTimer"] = [world=context.mWorldView](const AsyncPackageId&, double delay,
const TimerCallback& callback, sol::object callbackArg)
{
callback.mAsyncId.mContainer->setupSerializableTimer(
TimeUnit::HOURS, world->getGameTimeInHours() + delay,
TimerType::GAME_TIME, world->getGameTime() + delay,
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(
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(
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)
{

View File

@ -1,12 +1,82 @@
#include "luabindings.hpp"
#include "../mwrender/camera.hpp"
namespace MWLua
{
using CameraMode = MWRender::Camera::Mode;
sol::table initCameraPackage(const Context& context)
{
MWRender::Camera* camera = MWBase::Environment::get().getWorld()->getCamera();
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);
}

View File

@ -4,6 +4,14 @@
#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
{

View File

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

View File

@ -2,6 +2,7 @@
#include <SDL_events.h>
#include <SDL_gamecontroller.h>
#include <SDL_mouse.h>
#include "../mwbase/inputmanager.hpp"
#include "../mwinput/actions.hpp"
@ -18,9 +19,14 @@ namespace MWLua
sol::table initInputPackage(const Context& context)
{
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["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; });
keyEvent["symbol"] = sol::readonly_property([](const SDL_Keysym& e)
{
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["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; });
@ -31,9 +37,26 @@ namespace MWLua
api["isIdle"] = [input]() { return input->isIdle(); };
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["getMouseMoveY"] = [input]() { return input->getMouseMoveY(); };
@ -45,104 +68,219 @@ namespace MWLua
return input->getActionValue(axis - SDL_CONTROLLER_AXIS_MAX) * 2 - 1;
};
api["getControlSwitch"] = [input](const std::string& key) { return input->getControlSwitch(key); };
api["setControlSwitch"] = [input](const std::string& key, bool v) { input->toggleControlSwitch(key, v); };
api["getControlSwitch"] = [input](std::string_view key) { return input->getControlSwitch(key); };
api["setControlSwitch"] = [input](std::string_view key, bool v) { input->toggleControlSwitch(key, v); };
api["ACTION"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with(
"GameMenu", MWInput::A_GameMenu,
"Screenshot", MWInput::A_Screenshot,
"Inventory", MWInput::A_Inventory,
"Console", MWInput::A_Console,
api["getKeyName"] = [](SDL_Scancode code) {
return SDL_GetKeyName(SDL_GetKeyFromScancode(code));
};
"MoveLeft", MWInput::A_MoveLeft,
"MoveRight", MWInput::A_MoveRight,
"MoveForward", MWInput::A_MoveForward,
"MoveBackward", MWInput::A_MoveBackward,
api["ACTION"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs<std::string_view, MWInput::Actions>({
{"GameMenu", MWInput::A_GameMenu},
{"Screenshot", MWInput::A_Screenshot},
{"Inventory", MWInput::A_Inventory},
{"Console", MWInput::A_Console},
"Activate", MWInput::A_Activate,
"Use", MWInput::A_Use,
"Jump", MWInput::A_Jump,
"AutoMove", MWInput::A_AutoMove,
"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,
{"MoveLeft", MWInput::A_MoveLeft},
{"MoveRight", MWInput::A_MoveRight},
{"MoveForward", MWInput::A_MoveForward},
{"MoveBackward", MWInput::A_MoveBackward},
"QuickSave", MWInput::A_QuickSave,
"QuickLoad", MWInput::A_QuickLoad,
"QuickMenu", MWInput::A_QuickMenu,
"ToggleWeapon", MWInput::A_ToggleWeapon,
"ToggleSpell", MWInput::A_ToggleSpell,
"TogglePOV", MWInput::A_TogglePOV,
{"Activate", MWInput::A_Activate},
{"Use", MWInput::A_Use},
{"Jump", MWInput::A_Jump},
{"AutoMove", MWInput::A_AutoMove},
{"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},
"QuickKey1", MWInput::A_QuickKey1,
"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,
{"QuickSave", MWInput::A_QuickSave},
{"QuickLoad", MWInput::A_QuickLoad},
{"QuickMenu", MWInput::A_QuickMenu},
{"ToggleWeapon", MWInput::A_ToggleWeapon},
{"ToggleSpell", MWInput::A_ToggleSpell},
{"TogglePOV", MWInput::A_TogglePOV},
"ToggleHUD", MWInput::A_ToggleHUD,
"ToggleDebug", MWInput::A_ToggleDebug,
{"QuickKey1", MWInput::A_QuickKey1},
{"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,
"ZoomOut", MWInput::A_ZoomOut
));
{"ToggleHUD", MWInput::A_ToggleHUD},
{"ToggleDebug", MWInput::A_ToggleDebug},
api["CONTROL_SWITCH"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with(
"Controls", "playercontrols",
"Fighting", "playerfighting",
"Jumping", "playerjumping",
"Looking", "playerlooking",
"Magic", "playermagic",
"ViewMode", "playerviewswitch",
"VanityMode", "vanitymode"
));
{"ZoomIn", MWInput::A_ZoomIn},
{"ZoomOut", MWInput::A_ZoomOut}
}));
api["CONTROLLER_BUTTON"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with(
"A", SDL_CONTROLLER_BUTTON_A,
"B", SDL_CONTROLLER_BUTTON_B,
"X", SDL_CONTROLLER_BUTTON_X,
"Y", SDL_CONTROLLER_BUTTON_Y,
"Back", SDL_CONTROLLER_BUTTON_BACK,
"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
));
api["CONTROL_SWITCH"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs<std::string_view, std::string_view>({
{"Controls", "playercontrols"},
{"Fighting", "playerfighting"},
{"Jumping", "playerjumping"},
{"Looking", "playerlooking"},
{"Magic", "playermagic"},
{"ViewMode", "playerviewswitch"},
{"VanityMode", "vanitymode"}
}));
api["CONTROLLER_AXIS"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with(
"LeftX", SDL_CONTROLLER_AXIS_LEFTX,
"LeftY", SDL_CONTROLLER_AXIS_LEFTY,
"RightX", SDL_CONTROLLER_AXIS_RIGHTX,
"RightY", SDL_CONTROLLER_AXIS_RIGHTY,
"TriggerLeft", SDL_CONTROLLER_AXIS_TRIGGERLEFT,
"TriggerRight", SDL_CONTROLLER_AXIS_TRIGGERRIGHT,
api["CONTROLLER_BUTTON"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs<std::string_view, SDL_GameControllerButton>({
{"A", SDL_CONTROLLER_BUTTON_A},
{"B", SDL_CONTROLLER_BUTTON_B},
{"X", SDL_CONTROLLER_BUTTON_X},
{"Y", SDL_CONTROLLER_BUTTON_Y},
{"Back", SDL_CONTROLLER_BUTTON_BACK},
{"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,
"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["CONTROLLER_AXIS"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs<std::string_view, int>({
{"LeftX", SDL_CONTROLLER_AXIS_LEFTX},
{"LeftY", SDL_CONTROLLER_AXIS_LEFTY},
{"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);
}

View File

@ -39,7 +39,7 @@ namespace MWLua
selfAPI["controls"] = sol::readonly_property([](SelfObject& self) { return &self.mControls; });
selfAPI["isActive"] = [](SelfObject& self) { return &self.mIsActive; };
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()))
{
@ -56,7 +56,7 @@ namespace MWLua
else
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>
{

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