diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b4c6212b9..419b62bc5 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -16,8 +16,6 @@ stages:
- apt-cache/
- ccache/
stage: build
- rules:
- - if: '$CI_PIPELINE_SOURCE != "schedule"'
script:
- export CCACHE_BASEDIR="`pwd`"
- export CCACHE_DIR="`pwd`/ccache" && mkdir -pv "$CCACHE_DIR"
@@ -27,6 +25,7 @@ stages:
- cmake --build . -- -j $(nproc)
- cmake --install .
- if [[ "${BUILD_TESTS_ONLY}" ]]; then ./openmw_test_suite; fi
+ - if [[ "${BUILD_TESTS_ONLY}" ]]; then ./openmw_detournavigator_navmeshtilescache_benchmark; fi
- ccache -s
artifacts:
paths:
@@ -149,6 +148,7 @@ Debian_Clang_tests:
macOS11_Xcode12:
extends: .MacOS
image: macos-11-xcode-12
+ allow_failure: true
cache:
key: macOS11_Xcode12.v1
variables:
@@ -157,7 +157,6 @@ macOS11_Xcode12:
macOS10.15_Xcode11:
extends: .MacOS
image: macos-10.15-xcode-11
- allow_failure: true
cache:
key: macOS10.15_Xcode11.v1
variables:
@@ -171,6 +170,10 @@ variables: &cs-targets
targets: "openmw-cs,bsatool,esmtool,niftest"
package: "CS"
+variables: &tests-targets
+ targets: "openmw_test_suite,openmw_detournavigator_navmeshtilescache_benchmark"
+ package: "Tests"
+
.Windows_Ninja_Base:
tags:
- windows
@@ -185,13 +188,11 @@ variables: &cs-targets
- choco install python -y
- refreshenv
stage: build
- rules:
- - if: '$CI_PIPELINE_SOURCE != "schedule"'
script:
- $time = (Get-Date -Format "HH:mm:ss")
- echo ${time}
- echo "started by ${GITLAB_USER_NAME}"
- - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -N
+ - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -N -b -t
- cd MSVC2019_64_Ninja
- .\ActivateMSVC.ps1
- cmake --build . --config $config --target ($targets.Split(','))
@@ -203,6 +204,7 @@ variables: &cs-targets
Get-ChildItem -Recurse *.pdb | Remove-Item
}
- 7z a -tzip ..\..\OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}.zip '*'
+ - if ($executables) { foreach ($exe in $executables.Split(',')) { & .\$exe } }
after_script:
- Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log
cache:
@@ -266,6 +268,15 @@ Windows_Ninja_CS_RelWithDebInfo:
<<: *cs-targets
config: "RelWithDebInfo"
+Windows_Ninja_Tests_RelWithDebInfo:
+ extends: .Windows_Ninja_Base
+ stage: build
+ variables:
+ <<: *tests-targets
+ config: "RelWithDebInfo"
+ # Gitlab can't successfully execute following binaries due to unknown reason
+ # executables: "openmw_test_suite.exe,openmw_detournavigator_navmeshtilescache_benchmark.exe"
+
.Windows_MSBuild_Base:
tags:
- windows
@@ -279,13 +290,11 @@ Windows_Ninja_CS_RelWithDebInfo:
- choco install python -y
- refreshenv
stage: build
- rules:
- - if: '$CI_PIPELINE_SOURCE != "schedule"'
script:
- $time = (Get-Date -Format "HH:mm:ss")
- echo ${time}
- echo "started by ${GITLAB_USER_NAME}"
- - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V
+ - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -b -t
- cd MSVC2019_64
- cmake --build . --config $config --target ($targets.Split(','))
- cd $config
@@ -296,6 +305,7 @@ Windows_Ninja_CS_RelWithDebInfo:
Get-ChildItem -Recurse *.pdb | Remove-Item
}
- 7z a -tzip ..\..\OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}.zip '*'
+ - if ($executables) { foreach ($exe in $executables.Split(',')) { & .\$exe } }
after_script:
- Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log
cache:
@@ -359,6 +369,15 @@ Windows_MSBuild_CS_RelWithDebInfo:
<<: *cs-targets
config: "RelWithDebInfo"
+Windows_MSBuild_Tests_RelWithDebInfo:
+ extends: .Windows_MSBuild_Base
+ stage: build
+ variables:
+ <<: *tests-targets
+ config: "RelWithDebInfo"
+ # Gitlab can't successfully execute following binaries due to unknown reason
+ # executables: "openmw_test_suite.exe,openmw_detournavigator_navmeshtilescache_benchmark.exe"
+
#Debian_AndroidNDK_arm64-v8a:
# tags:
# - linux
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 000000000..e0b39ec49
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,10 @@
+version: 2
+
+sphinx:
+ configuration: docs/source/conf.py
+
+python:
+ version: 3.8
+ install:
+ - requirements: docs/requirements.txt
+
diff --git a/.travis.yml b/.travis.yml
index 8d2ab43f3..1322dfca1 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -22,9 +22,9 @@ addons:
]
matrix:
include:
- - name: OpenMW (all) on MacOS 10.15 with Xcode 10.2
+ - name: OpenMW (all) on MacOS 10.15 with Xcode 11.6
os: osx
- osx_image: xcode10.2
+ osx_image: xcode11.6
- name: OpenMW (all) on Ubuntu Focal with GCC
os: linux
dist: focal
@@ -74,7 +74,7 @@ notifications:
irc:
if: repository_slug = OpenMW/openmw AND branch = master
channels:
- - "chat.freenode.net#openmw"
+ - "irc.libera.chat#openmw"
on_success: change
on_failure: always
use_notice: true
diff --git a/AUTHORS.md b/AUTHORS.md
index 75302908e..5ade21d41 100644
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -222,6 +222,7 @@ Programmers
Yuri Krupenin
zelurker
Noah Gooder
+ Andrew Appuhamy (andrew-app)
Documentation
-------------
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dec5e1e7f..8dacef572 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,7 +7,6 @@
Bug #2069: Fireflies in Fireflies invade Morrowind look wrong
Bug #2311: Targeted scripts are not properly supported on non-unique RefIDs
Bug #2473: Unable to overstock merchants
- Bug #2798: Mutable ESM records
Bug #2976: [reopened]: Issues combining settings from the command line and both config files
Bug #3137: Walking into a wall prevents jumping
Bug #3372: Projectiles and magic bolts go through moving targets
@@ -20,7 +19,6 @@
Bug #4039: Multiple followers should have the same following distance
Bug #4055: Local scripts don't inherit variables from their base record
Bug #4083: Door animation freezes when colliding with actors
- Bug #4201: Projectile-projectile collision
Bug #4247: Cannot walk up stairs in Ebonheart docks
Bug #4357: OpenMW-CS: TopicInfos index sorting and rearranging isn't fully functional
Bug #4363: OpenMW-CS: Defect in Clone Function for Dialogue Info records
@@ -64,6 +62,7 @@
Bug #5452: Autowalk is being included in savegames
Bug #5469: Local map is reset when re-entering certain cells
Bug #5472: Mistify mod causes CTD in 0.46 on Mac
+ Bug #5473: OpenMW-CS: Cell border lines don't update properly on terrain change
Bug #5479: NPCs who should be walking around town are standing around without walking
Bug #5484: Zero value items shouldn't be able to be bought or sold for 1 gold
Bug #5485: Intimidate doesn't increase disposition on marginal wins
@@ -80,6 +79,7 @@
Bug #5603: Setting constant effect cast style doesn't correct effects view
Bug #5604: Only one valid NIF root node is loaded from a single file
Bug #5611: Usable items with "0 Uses" should be used only once
+ Bug #5619: Input events are queued during save loading
Bug #5622: Can't properly interact with the console when in pause menu
Bug #5627: Bookart not shown if it isn't followed by
statement
Bug #5633: Damage Spells in effect before god mode is enabled continue to hurt the player character and can kill them
@@ -87,7 +87,7 @@
Bug #5644: Summon effects running on the player during game initialization cause crashes
Bug #5656: Sneaking characters block hits while standing
Bug #5661: Region sounds don't play at the right interval
- Bug #5675: OpenMW-cs. FRMR subrecords are saved with the wrong MastIdx
+ Bug #5675: OpenMW-CS: FRMR subrecords are saved with the wrong MastIdx
Bug #5680: Bull Netches incorrectly aim over the player character's head and always miss
Bug #5681: Player character can clip or pass through bridges instead of colliding against them
Bug #5687: Bound items covering the same inventory slot expiring at the same time freezes the game
@@ -100,6 +100,7 @@
Bug #5739: Saving and loading the save a second or two before hitting the ground doesn't count fall damage
Bug #5758: Paralyzed actors behavior is inconsistent with vanilla
Bug #5762: Movement solver is insufficiently robust
+ Bug #5800: Equipping a CE enchanted ring deselects an already equipped and selected enchanted ring from the spell menu
Bug #5807: Video decoding crash on ARM
Bug #5821: NPCs from mods getting removed if mod order was changed
Bug #5835: OpenMW doesn't accept negative values for NPC's hello, alarm, fight, and flee
@@ -118,14 +119,31 @@
Bug #5923: Clicking on empty spaces between journal entries might show random topics
Bug #5934: AddItem command doesn't accept negative values
Bug #5975: NIF controllers from sheath meshes are used
+ Bug #5991: Activate should always be allowed for inventory items
+ Bug #5995: NiUVController doesn't calculate the UV offset properly
+ Bug #6007: Crash when ending cutscene is playing
+ Bug #6016: Greeting interrupts Fargoth's sneak-walk
+ Bug #6022: OpenMW-CS: Terrain selection is not updated when undoing/redoing terrain changes
+ Bug #6023: OpenMW-CS: Clicking on a reference in "Terrain land editing" mode discards corresponding select/edit action
+ Bug #6028: Particle system controller values are incorrectly used
+ Bug #6035: OpenMW-CS: Circle brush in "Terrain land editing" mode sometimes includes vertices outside its radius
+ Bug #6036: OpenMW-CS: Terrain selection at the border of cells omits certain corner vertices
+ Bug #6043: Actor can have torch missing when torch animation is played
+ Bug #6047: Mouse bindings can be triggered during save loading
+ Bug #6136: Game freezes when NPCs try to open doors that are about to be closed
+ Bug #6294: Game crashes with empty pathgrid
Feature #390: 3rd person look "over the shoulder"
Feature #832: OpenMW-CS: Handle deleted references
Feature #1536: Show more information about level on menu
+ Feature #2159: "Graying out" exhausted dialogue topics
Feature #2386: Distant Statics in the form of Object Paging
Feature #2404: Levelled List can not be placed into a container
Feature #2686: Timestamps in openmw.log
+ Feature #2798: Mutable ESM records
Feature #3171: OpenMW-CS: Instance drag selection
Feature #3983: Wizard: Add link to buy Morrowind
+ Feature #4201: Projectile-projectile collision
+ Feature #4486: Handle crashes on Windows
Feature #4894: Consider actors as obstacles for pathfinding
Feature #4899: Alpha-To-Coverage Anti-Aliasing for alpha testing
Feature #4917: Do not trigger NavMesh update when RecastMesh update should not change NavMesh
@@ -138,10 +156,12 @@
Feature #5456: Basic collada animation support
Feature #5457: Realistic diagonal movement
Feature #5486: Fixes trainers to choose their training skills based on their base skill points
+ Feature #5500: Prepare enough navmesh tiles before scene loading ends
Feature #5511: Add in game option to toggle HRTF support in OpenMW
Feature #5519: Code Patch tab in launcher
Feature #5524: Resume failed script execution after reload
Feature #5545: Option to allow stealing from an unconscious NPC during combat
+ Feature #5551: Do not reboot PC after OpenMW installation on Windows
Feature #5563: Run physics update in background thread
Feature #5579: MCP SetAngle enhancement
Feature #5580: Service refusal filtering
@@ -156,9 +176,12 @@
Feature #5814: Bsatool should be able to create BSA archives, not only to extract it
Feature #5828: Support more than 8 lights
Feature #5910: Fall back to delta time when physics can't keep up
+ Feature #5980: Support Bullet with double precision instead of one with single precision
+ Feature #6024: OpenMW-CS: Selecting terrain in "Terrain land editing" should support "Add to selection" and "Remove from selection" modes
+ Feature #6033: Include pathgrid to navigation mesh
+ Feature #6034: Find path based on area cost depending on NPC stats
Task #5480: Drop Qt4 support
Task #5520: Improve cell name autocompleter implementation
- Task #5844: Update 'toggle sneak' documentation
0.46.0
------
@@ -168,7 +191,7 @@
Bug #2395: Duplicated plugins in the launcher when multiple data directories provide the same plugin
Bug #2679: Unable to map mouse wheel under control settings
Bug #2969: Scripted items can stack
- Bug #2976: Data lines in global openmw.cfg take priority over user openmw.cfg
+ Bug #2976: [reopened in 0.47] Data lines in global openmw.cfg take priority over user openmw.cfg
Bug #2987: Editor: some chance and AI data fields can overflow
Bug #3006: 'else if' operator breaks script compilation
Bug #3109: SetPos/Position handles actors differently
@@ -186,7 +209,6 @@
Bug #4009: Launcher does not show data files on the first run after installing
Bug #4077: Enchanted items are not recharged if they are not in the player's inventory
Bug #4141: PCSkipEquip isn't set to 1 when reading books/scrolls
- Bug #4202: Open .omwaddon files without needing toopen openmw-cs first
Bug #4240: Ash storm origin coordinates and hand shielding animation behavior are incorrect
Bug #4262: Rain settings are hardcoded
Bug #4270: Closing doors while they are obstructed desyncs closing sfx
@@ -276,7 +298,6 @@
Bug #4964: Multiple effect spell projectile sounds play louder than vanilla
Bug #4965: Global light attenuation settings setup is lacking
Bug #4969: "Miss" sound plays for any actor
- Bug #4971: OpenMW-CS: Make rotations display as degrees instead of radians
Bug #4972: Player is able to use quickkeys while disableplayerfighting is active
Bug #4979: AiTravel maximum range depends on "actors processing range" setting
Bug #4980: Drowning mechanics is applied for actors indifferently from distance to player
@@ -374,7 +395,6 @@
Bug #5350: An attempt to launch magic bolt causes "AL error invalid value" error
Bug #5352: Light source items' duration is decremented while they aren't visible
Feature #1724: Handle AvoidNode
- Feature #2159: "Graying out" exhausted dialogue topics
Feature #2229: Improve pathfinding AI
Feature #3025: Analogue gamepad movement controls
Feature #3442: Default values for fallbacks from ini file
@@ -387,6 +407,7 @@
Feature #4001: Toggle sneak controller shortcut
Feature #4068: OpenMW-CS: Add a button to reset key bindings to defaults
Feature #4129: Beta Comment to File
+ Feature #4202: Open .omwaddon files without needing to open openmw-cs first
Feature #4209: Editor: Faction rank sub-table
Feature #4255: Handle broken RepairedOnMe script function
Feature #4316: Implement RaiseRank/LowerRank functions properly
@@ -409,6 +430,7 @@
Feature #4958: Support eight blood types
Feature #4962: Add casting animations for magic items
Feature #4968: Scalable UI widget skins
+ Feature #4971: OpenMW-CS: Make rotations display as degrees instead of radians
Feature #4994: Persistent pinnable windows hiding
Feature #5000: Compressed BSA format support
Feature #5005: Editor: Instance window via Scene window
diff --git a/CHANGELOG_PR.md b/CHANGELOG_PR.md
index cfe709e97..100bc376f 100644
--- a/CHANGELOG_PR.md
+++ b/CHANGELOG_PR.md
@@ -38,9 +38,15 @@ Editor Bug Fixes:
- Disabled record sorting in Topic and Journal Info tables, implemented drag-move for records (#4357)
- Topic and Journal Info records can now be cloned with a different parent Topic/Journal Id (#4363)
- Verifier no longer checks for alleged 'race' entries in clothing body parts (#5400)
+- Cell borders are now properly redrawn when undoing/redoing terrain changes (#5473)
- Loading mods now keeps the master index (#5675)
- Flicker and crashing on XFCE4 fixed (#5703)
- Collada models render properly in the Editor (#5713)
+- Terrain-selection grid is now properly updated when undoing/redoing terrain changes (#6022)
+- Tool outline and select/edit actions in "Terrain land editing" mode now ignore references (#6023)
+- Primary-select and secondary-select actions in "Terrain land editing" mode now behave like in "Instance editing" mode (#6024)
+- Using the circle brush to select terrain in the "Terrain land editing" mode no longer selects vertices outside the circle (#6035)
+- Vertices at the NW and SE corners of a cell can now also be selected in "Terrain land editing" mode if the adjacent cells aren't loaded yet (#6036)
Miscellaneous:
- Prevent save-game bloating by using an appropriate fog texture format (#5108)
diff --git a/CI/activate_msvc.sh b/CI/activate_msvc.sh
index 47f2c246f..233f01743 100644
--- a/CI/activate_msvc.sh
+++ b/CI/activate_msvc.sh
@@ -30,55 +30,11 @@ command -v unixPathAsWindows >/dev/null 2>&1 || function unixPathAsWindows {
fi
}
-function windowsSystemPathAsUnix {
- if command -v cygpath >/dev/null 2>&1; then
- cygpath -u -p $1
- else
- IFS=';' read -r -a paths <<< "$1"
- declare -a convertedPaths
- for entry in paths; do
- convertedPaths+=(windowsPathAsUnix $entry)
- done
- convertedPath=printf ":%s" ${convertedPaths[@]}
- echo ${convertedPath:1}
- fi
-}
-
-# capture CMD environment so we know what's been changed
-declare -A originalCmdEnv
-originalIFS="$IFS"
-IFS=$'\n\r'
-for pair in $(cmd //c "set"); do
- IFS='=' read -r -a separatedPair <<< "${pair}"
- if [ ${#separatedPair[@]} -ne 2 ]; then
- echo "Parsed '$pair' as ${#separatedPair[@]} parts, expected 2."
- continue
- fi
- originalCmdEnv["${separatedPair[0]}"]="${separatedPair[1]}"
-done
# capture CMD environment in a shell with MSVC activated
-cmdEnv="$(cmd //c "$(unixPathAsWindows "$(dirname "${BASH_SOURCE[0]}")")\ActivateMSVC.bat" "&&" set)"
-
-declare -A cmdEnvChanges
-for pair in $cmdEnv; do
- if [ -n "$pair" ]; then
- IFS='=' read -r -a separatedPair <<< "${pair}"
- if [ ${#separatedPair[@]} -ne 2 ]; then
- echo "Parsed '$pair' as ${#separatedPair[@]} parts, expected 2."
- continue
- fi
- key="${separatedPair[0]}"
- value="${separatedPair[1]}"
- if ! [ ${originalCmdEnv[$key]+_} ] || [ "${originalCmdEnv[$key]}" != "$value" ]; then
- if [ $key != 'PATH' ] && [ $key != 'path' ] && [ $key != 'Path' ]; then
- export "$key=$value"
- else
- export PATH=$(windowsSystemPathAsUnix $value)
- fi
- fi
- fi
-done
+cmd //c "$(unixPathAsWindows "$(dirname "${BASH_SOURCE[0]}")")\ActivateMSVC.bat" "&&" "bash" "-c" "declare -px > declared_env.sh"
+source ./declared_env.sh
+rm declared_env.sh
MISSINGTOOLS=0
@@ -93,6 +49,4 @@ if [ $MISSINGTOOLS -ne 0 ]; then
return 1
fi
-IFS="$originalIFS"
-
-restoreOldSettings
\ No newline at end of file
+restoreOldSettings
diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh
index c03341b86..1ca0fc611 100755
--- a/CI/before_install.osx.sh
+++ b/CI/before_install.osx.sh
@@ -1,9 +1,9 @@
#!/bin/sh -ex
# workaround python issue on travis
-[-z "${TRAVIS}"] && HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies python@3.8 || true
-[-z "${TRAVIS}"] && HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies python@3.9 || true
-[-z "${TRAVIS}"] && HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies qt@6 || true
+[ -z "${TRAVIS}" ] && HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies python@3.8 || true
+[ -z "${TRAVIS}" ] && HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies python@3.9 || true
+[ -z "${TRAVIS}" ] && HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies qt@6 || true
# Some of these tools can come from places other than brew, so check before installing
command -v ccache >/dev/null 2>&1 || brew install ccache
@@ -15,5 +15,8 @@ ccache --version
cmake --version
qmake --version
-curl -fSL -R -J https://downloads.openmw.org/osx/dependencies/openmw-deps-f8918dd.zip -o ~/openmw-deps.zip
+curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20210617.zip -o ~/openmw-deps.zip
unzip -o ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null
+
+# additional libraries
+[ -z "${TRAVIS}" ] && HOMEBREW_NO_AUTO_UPDATE=1 brew install fontconfig
\ No newline at end of file
diff --git a/CI/before_script.linux.sh b/CI/before_script.linux.sh
index 5f74b4714..17292e4e9 100755
--- a/CI/before_script.linux.sh
+++ b/CI/before_script.linux.sh
@@ -4,9 +4,14 @@ set -xeo pipefail
free -m
+BUILD_UNITTESTS=OFF
+BUILD_BENCHMARKS=OFF
+
if [[ "${BUILD_TESTS_ONLY}" ]]; then
export GOOGLETEST_DIR="${PWD}/googletest/build/install"
env GENERATOR='Unix Makefiles' CONFIGURATION=Release CI/build_googletest.sh
+ BUILD_UNITTESTS=ON
+ BUILD_BENCHMARKS=ON
fi
declare -a CMAKE_CONF_OPTS=(
@@ -43,7 +48,8 @@ if [[ "${BUILD_TESTS_ONLY}" ]]; then
-DBUILD_ESSIMPORTER=OFF \
-DBUILD_OPENCS=OFF \
-DBUILD_WIZARD=OFF \
- -DBUILD_UNITTESTS=ON \
+ -DBUILD_UNITTESTS=${BUILD_UNITTESTS} \
+ -DBUILD_BENCHMARKS=${BUILD_BENCHMARKS} \
-DGTEST_ROOT="${GOOGLETEST_DIR}" \
-DGMOCK_ROOT="${GOOGLETEST_DIR}" \
..
diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh
old mode 100644
new mode 100755
index 478c40144..1cf510e34
--- a/CI/before_script.msvc.sh
+++ b/CI/before_script.msvc.sh
@@ -73,10 +73,7 @@ CONFIGURATIONS=()
TEST_FRAMEWORK=""
GOOGLE_INSTALL_ROOT=""
INSTALL_PREFIX="."
-BULLET_DOUBLE=true
-BULLET_DBL=""
-BULLET_DBL_DISPLAY="Single precision"
-SKIP_VR=""
+BUILD_BENCHMARKS=""
ACTIVATE_MSVC=""
SINGLE_CONFIG=""
@@ -103,9 +100,6 @@ while [ $# -gt 0 ]; do
d )
SKIP_DOWNLOAD=true ;;
- D )
- BULLET_DOUBLE=true ;;
-
e )
SKIP_EXTRACT=true ;;
@@ -143,6 +137,9 @@ while [ $# -gt 0 ]; do
INSTALL_PREFIX=$(echo "$1" | sed 's;\\;/;g' | sed -E 's;/+;/;g')
shift ;;
+ b )
+ BUILD_BENCHMARKS=true ;;
+
h )
cat </dev/null 2>&1 || { echo "Error: vswhere is not on the path."; wrappedExit 1; }
diff --git a/CI/before_script.osx.sh b/CI/before_script.osx.sh
index f9191eb89..265e05b8e 100755
--- a/CI/before_script.osx.sh
+++ b/CI/before_script.osx.sh
@@ -25,6 +25,5 @@ cmake \
-D BUILD_BSATOOL=TRUE \
-D BUILD_ESSIMPORTER=TRUE \
-D BUILD_NIFTEST=TRUE \
--D BULLET_USE_DOUBLES=TRUE \
-G"Unix Makefiles" \
..
diff --git a/CI/install_debian_deps.sh b/CI/install_debian_deps.sh
index 1f35ccebc..2f905314b 100755
--- a/CI/install_debian_deps.sh
+++ b/CI/install_debian_deps.sh
@@ -21,14 +21,13 @@ 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
- libxcb-glx0-dev libx11-dev
+ libbullet-dev liblz4-dev libpng-dev libjpeg-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"
-
[coverity]="curl"
# Pre-requisites for building MyGUI and OSG for static linking.
@@ -65,4 +64,4 @@ 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 "${deps[@]}"
+apt-get -q -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends "${deps[@]}"
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e322497a6..6e8c5f870 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -13,9 +13,6 @@ if(POLICY CMP0083)
cmake_policy(SET CMP0083 NEW)
endif()
-# Detect OS
-include(cmake/OSIdentity.cmake)
-
option(OPENMW_GL4ES_MANUAL_INIT "Manually initialize gl4es. This is more reliable on platforms without a windowing system. Requires gl4es to be configured with -DNOEGL=ON -DNO_LOADER=ON -DNO_INIT_CONSTRUCTOR=ON." OFF)
if(OPENMW_GL4ES_MANUAL_INIT)
add_definitions(-DOPENMW_GL4ES_MANUAL_INIT)
@@ -35,7 +32,7 @@ option(BUILD_DOCS "Build documentation." OFF )
option(BUILD_OPENMW_VR "Build VR support using OpenXR" ON)
option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF)
option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF)
-option(BULLET_USE_DOUBLES "Use double precision for Bullet" ON)
+option(BUILD_BENCHMARKS "Build benchmarks with Google Benchmark" OFF)
set(OpenGL_GL_PREFERENCE LEGACY) # Use LEGACY as we use GL2; GLNVD is for GL3 and up.
@@ -265,16 +262,16 @@ if(FFmpeg_FOUND)
set(FFVER_OK FALSE)
endif()
endif()
+
+ if(NOT FFVER_OK AND NOT APPLE) # unable to detect on version on MacOS < 11.0
+ message(FATAL_ERROR "FFmpeg version is too old, 3.2 is required" )
+ endif()
endif()
if(NOT FFmpeg_FOUND)
message(FATAL_ERROR "FFmpeg was not found" )
endif()
-if(NOT FFVER_OK)
- message(FATAL_ERROR "FFmpeg version is too old, 3.2 is required" )
-endif()
-
if(WIN32)
message("Can not detect FFmpeg version, at least the 3.2 is required" )
endif()
@@ -313,7 +310,31 @@ if(OPENMW_USE_SYSTEM_BULLET)
set(REQUIRED_BULLET_VERSION 283) # but for build testing, 283 is fine
endif()
- find_package(Bullet ${REQUIRED_BULLET_VERSION} REQUIRED COMPONENTS BulletCollision LinearMath)
+ # First, try BulletConfig-float64.cmake which comes with Debian derivatives.
+ # This file does not define the Bullet version in a CMake-friendly way.
+ find_package(Bullet CONFIGS BulletConfig-float64.cmake QUIET COMPONENTS BulletCollision LinearMath)
+ if (BULLET_FOUND)
+ string(REPLACE "." "" _bullet_version_num ${BULLET_VERSION_STRING})
+ if (_bullet_version_num VERSION_LESS REQUIRED_BULLET_VERSION)
+ message(FATAL_ERROR "System bullet version too old, OpenMW requires at least ${REQUIRED_BULLET_VERSION}, got ${_bullet_version_num}")
+ endif()
+ # Fix the relative include:
+ set(BULLET_INCLUDE_DIRS "${BULLET_ROOT_DIR}/${BULLET_INCLUDE_DIRS}")
+ include(FindPackageMessage)
+ find_package_message(Bullet "Found Bullet: ${BULLET_LIBRARIES} ${BULLET_VERSION_STRING}" "${BULLET_VERSION_STRING}-float64")
+ else()
+ find_package(Bullet ${REQUIRED_BULLET_VERSION} REQUIRED COMPONENTS BulletCollision LinearMath)
+ endif()
+
+ # Only link the Bullet libraries that we need:
+ string(REGEX MATCHALL "((optimized|debug);)?[^;]*(BulletCollision|LinearMath)[^;]*" BULLET_LIBRARIES "${BULLET_LIBRARIES}")
+
+ include(cmake/CheckBulletPrecision.cmake)
+ if (HAS_DOUBLE_PRECISION_BULLET)
+ message(STATUS "Bullet uses double precision")
+ else()
+ message(FATAL_ERROR "Bullet does not uses double precision")
+ endif()
endif()
if (NOT WIN32 AND BUILD_WIZARD) # windows users can just run the morrowind installer
@@ -342,6 +363,11 @@ endif()
if(OPENMW_USE_SYSTEM_OSG)
find_package(OpenSceneGraph ${OSG_VERSION_REQUIRED} REQUIRED ${USED_OSG_COMPONENTS})
+
+ if (${OPENSCENEGRAPH_VERSION} VERSION_GREATER 3.6.2 AND ${OPENSCENEGRAPH_VERSION} VERSION_LESS 3.6.5)
+ message(FATAL_ERROR "OpenSceneGraph version ${OPENSCENEGRAPH_VERSION} has critical regressions which cause crashes. Please upgrade to 3.6.5 or later. We strongly recommend using the tip of the official 'OpenSceneGraph-3.6' branch or the tip of '3.6' OpenMW/osg (OSGoS).")
+ endif()
+
if(OSG_STATIC)
find_package(OSGPlugins REQUIRED COMPONENTS ${USED_OSG_PLUGINS})
endif()
@@ -424,8 +450,8 @@ endif (APPLE)
# Other files
-configure_resource_file(${OpenMW_SOURCE_DIR}/files/settings-default.cfg
- "${OpenMW_BINARY_DIR}" "settings-default.cfg")
+pack_resource_file(${OpenMW_SOURCE_DIR}/files/settings-default.cfg
+ "${OpenMW_BINARY_DIR}" "defaults.bin")
configure_resource_file(${OpenMW_SOURCE_DIR}/files/settings-overrides-vr.cfg
"${OpenMW_BINARY_DIR}" "settings-overrides-vr.cfg")
@@ -443,8 +469,8 @@ else ()
"${OpenMW_BINARY_DIR}/openmw.cfg")
endif ()
-configure_resource_file(${OpenMW_SOURCE_DIR}/files/openmw-cs.cfg
- "${OpenMW_BINARY_DIR}" "openmw-cs.cfg")
+pack_resource_file(${OpenMW_SOURCE_DIR}/files/openmw-cs.cfg
+ "${OpenMW_BINARY_DIR}" "defaults-cs.bin")
# Needs the copy version because the configure version assumes the end of the file has been reached when a null character is reached and there are no CMake expressions to evaluate.
copy_resource_file(${OpenMW_SOURCE_DIR}/files/opencs/defaultfilters
@@ -499,16 +525,13 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang)
if (CMAKE_CXX_COMPILER_ID STREQUAL GNU AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.6 OR CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 4.6)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-but-set-parameter")
endif()
-
- if (CMAKE_CXX_COMPILER_ID STREQUAL GNU AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 5.0 OR CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 5.0)
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsuggest-override")
- endif()
endif (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang)
# Extern
add_subdirectory (extern/osg-ffmpeg-videoplayer)
add_subdirectory (extern/oics)
+add_subdirectory (extern/Base64)
if (BUILD_OPENCS)
add_subdirectory (extern/osgQt)
endif()
@@ -559,6 +582,10 @@ if (BUILD_UNITTESTS)
add_subdirectory( apps/openmw_test_suite )
endif()
+if (BUILD_BENCHMARKS)
+ add_subdirectory(apps/benchmarks)
+endif()
+
if (WIN32)
if (MSVC)
if (OPENMW_MP_BUILD)
@@ -606,63 +633,13 @@ if (WIN32)
# Play a bit with the warning levels
- set(WARNINGS "/Wall") # Since windows can only disable specific warnings, not enable them
+ set(WARNINGS "/W4")
set(WARNINGS_DISABLE
- # Warnings that aren't enabled normally and don't need to be enabled
- # They're unneeded and sometimes completely retarded warnings that /Wall enables
- # Not going to bother commenting them as they tend to warn on every standard library file
- 4061 4263 4264 4266 4350 4371 4435 4514 4548 4571 4582 4583 4610 4619 4623 4625
- 4626 4628 4640 4668 4710 4711 4768 4820 4826 4917 4946 5032 5039 5045 5219 5220
-
- # Warnings that are thrown on standard libraries and not OpenMW
- 4347 # Non-template function with same name and parameter count as template function
- 4365 # Variable signed/unsigned mismatch
- 4510 4512 # Unable to generate copy constructor/assignment operator as it's not public in the base
- 4706 # Assignment in conditional expression
- 4738 # Storing 32-bit float result in memory, possible loss of performance
- 4774 # Format string expected in argument is not a string literal
- 4986 # Undocumented warning that occurs in the crtdbg.h file
- 4987 # nonstandard extension used (triggered by setjmp.h)
- 4996 # Function was declared deprecated
-
- # caused by OSG
- 4589 # Constructor of abstract class 'osg::Operation' ignores initializer for virtual base class 'osg::Referenced' (False warning)
-
- # caused by boost
- 4191 # 'type cast' : unsafe conversion (1.56, thread_primitives.hpp, normally off)
- 4643 # Forward declaring 'X' in namespace std is not permitted by the C++ Standard. (in *_std_fwd.h files)
- 5204 # Class has virtual functions, but its trivial destructor is not virtual
-
- # caused by MyGUI
- 4297 # function assumed not to throw an exception but does
-
- # OpenMW specific warnings
- 4099 # Type mismatch, declared class or struct is defined with other type
4100 # Unreferenced formal parameter (-Wunused-parameter)
- 4101 # Unreferenced local variable (-Wunused-variable)
4127 # Conditional expression is constant
- 4242 # Storing value in a variable of a smaller type, possible loss of data
- 4244 # Storing value of one type in variable of another (size_t in int, for example)
- 4245 # Signed/unsigned mismatch
- 4267 # Conversion from 'size_t' to 'int', possible loss of data
- 4305 # Truncating value (double to float, for example)
- 4309 # Variable overflow, trying to store 128 in a signed char for example
- 4351 # New behavior: elements of array 'array' will be default initialized (desired behavior)
- 4355 # Using 'this' in member initialization list
- 4464 # relative include path contains '..'
- 4505 # Unreferenced local function has been removed
- 4701 # Potentially uninitialized local variable used
- 4702 # Unreachable code
- 4714 # function 'QString QString::trimmed(void) &&' marked as __forceinline not inlined
- 4800 # Boolean optimization warning, e.g. myBool = (myInt != 0) instead of myBool = myInt
- )
-
- if (MSVC_VERSION GREATER 1800)
- set(WARNINGS_DISABLE ${WARNINGS_DISABLE} 5026 5027
- 5031 # #pragma warning(pop): likely mismatch, popping warning state pushed in different file (config_begin.hpp, config_end.hpp)
+ 4996 # Function was declared deprecated
)
- endif()
if( "${MyGUI_VERSION}" VERSION_LESS_EQUAL "3.4.0" )
set(WARNINGS_DISABLE ${WARNINGS_DISABLE}
@@ -730,6 +707,10 @@ if (WIN32)
if (BUILD_WIZARD)
set_target_properties(openmw-wizard PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
endif()
+
+ if (BUILD_BENCHMARKS)
+ set_target_properties(openmw_detournavigator_navmeshtilescache_benchmark 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
@@ -869,7 +850,7 @@ elseif(NOT APPLE)
INSTALL(FILES "${OpenMW_SOURCE_DIR}/README.md" DESTINATION "." RENAME "README.txt")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/LICENSE" DESTINATION "." RENAME "LICENSE.txt")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/mygui/DejaVuFontLicense.txt" DESTINATION ".")
- INSTALL(FILES "${INSTALL_SOURCE}/settings-default.cfg" DESTINATION ".")
+ INSTALL(FILES "${INSTALL_SOURCE}/defaults.bin" DESTINATION ".")
INSTALL(FILES "${INSTALL_SOURCE}/gamecontrollerdb.txt" DESTINATION ".")
INSTALL(FILES "${INSTALL_SOURCE}/xrcontrollersuggestions.xml" DESTINATION ".")
@@ -912,13 +893,13 @@ elseif(NOT APPLE)
SET(VCREDIST32 "${OpenMW_BINARY_DIR}/vcredist_x86.exe")
if(EXISTS ${VCREDIST32})
INSTALL(FILES ${VCREDIST32} DESTINATION "redist")
- SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ExecWait '\\\"$INSTDIR\\\\redist\\\\vcredist_x86.exe\\\" /q'" )
+ SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ExecWait '\\\"$INSTDIR\\\\redist\\\\vcredist_x86.exe\\\" /q /norestart'" )
endif(EXISTS ${VCREDIST32})
SET(VCREDIST64 "${OpenMW_BINARY_DIR}/vcredist_x64.exe")
if(EXISTS ${VCREDIST64})
INSTALL(FILES ${VCREDIST64} DESTINATION "redist")
- SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ExecWait '\\\"$INSTDIR\\\\redist\\\\vcredist_x64.exe\\\" /q'" )
+ SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ExecWait '\\\"$INSTDIR\\\\redist\\\\vcredist_x64.exe\\\" /q /norestart'" )
endif(EXISTS ${VCREDIST64})
SET(OALREDIST "${OpenMW_BINARY_DIR}/oalinst.exe")
@@ -978,14 +959,14 @@ elseif(NOT APPLE)
ENDIF(BUILD_OPENCS)
# Install global configuration files
- INSTALL(FILES "${INSTALL_SOURCE}/settings-default.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
+ INSTALL(FILES "${INSTALL_SOURCE}/defaults.bin" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
INSTALL(FILES "${INSTALL_SOURCE}/openmw.cfg.install" DESTINATION "${SYSCONFDIR}" RENAME "openmw.cfg" COMPONENT "openmw")
INSTALL(FILES "${INSTALL_SOURCE}/resources/version" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
INSTALL(FILES "${INSTALL_SOURCE}/gamecontrollerdb.txt" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
INSTALL(FILES "${INSTALL_SOURCE}/xrcontrollersuggestions.xml" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
IF(BUILD_OPENCS)
- INSTALL(FILES "${INSTALL_SOURCE}/openmw-cs.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "opencs")
+ INSTALL(FILES "${INSTALL_SOURCE}/defaults-cs.bin" DESTINATION "${SYSCONFDIR}" COMPONENT "opencs")
ENDIF(BUILD_OPENCS)
# Install resources
diff --git a/README.md b/README.md
index c49e863a6..4d1ea740f 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ OpenMW also comes with OpenMW-CS, a replacement for Bethesda's Construction Set.
* Version: 0.47.0
* License: GPLv3 (see [LICENSE](https://github.com/OpenMW/openmw/blob/master/LICENSE) for more information)
* Website: https://www.openmw.org
-* IRC: #openmw on irc.freenode.net
+* IRC: #openmw on irc.libera.chat
Font Licenses:
* DejaVuLGCSansMono.ttf: custom (see [files/mygui/DejaVuFontLicense.txt](https://github.com/OpenMW/openmw/blob/master/files/mygui/DejaVuFontLicense.txt) for more information)
diff --git a/apps/benchmarks/CMakeLists.txt b/apps/benchmarks/CMakeLists.txt
new file mode 100644
index 000000000..f9aa9aad4
--- /dev/null
+++ b/apps/benchmarks/CMakeLists.txt
@@ -0,0 +1,27 @@
+cmake_minimum_required(VERSION 3.11)
+
+set(BENCHMARK_ENABLE_TESTING OFF)
+set(BENCHMARK_ENABLE_INSTALL OFF)
+set(BENCHMARK_ENABLE_GTEST_TESTS OFF)
+
+set(SAVED_CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+
+string(REPLACE "-Wsuggest-override" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+string(REPLACE "-Wundef" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+include(FetchContent)
+FetchContent_Declare(benchmark
+ URL https://github.com/google/benchmark/archive/refs/tags/v1.5.2.zip
+ URL_HASH MD5=49395b757a7c4656d70f1328d93efd00
+ SOURCE_DIR fetched/benchmark
+)
+FetchContent_MakeAvailableExcludeFromAll(benchmark)
+
+set(CMAKE_CXX_FLAGS "${SAVED_CMAKE_CXX_FLAGS}")
+
+openmw_add_executable(openmw_detournavigator_navmeshtilescache_benchmark detournavigator/navmeshtilescache.cpp)
+target_compile_features(openmw_detournavigator_navmeshtilescache_benchmark PRIVATE cxx_std_17)
+target_link_libraries(openmw_detournavigator_navmeshtilescache_benchmark benchmark::benchmark components)
+
+if (UNIX AND NOT APPLE)
+ target_link_libraries(openmw_detournavigator_navmeshtilescache_benchmark ${CMAKE_THREAD_LIBS_INIT})
+endif()
diff --git a/apps/benchmarks/detournavigator/navmeshtilescache.cpp b/apps/benchmarks/detournavigator/navmeshtilescache.cpp
new file mode 100644
index 000000000..2c7a981ad
--- /dev/null
+++ b/apps/benchmarks/detournavigator/navmeshtilescache.cpp
@@ -0,0 +1,214 @@
+#include
+
+#include
+
+#include
+#include
+#include
+
+namespace
+{
+ using namespace DetourNavigator;
+
+ struct Key
+ {
+ osg::Vec3f mAgentHalfExtents;
+ TilePosition mTilePosition;
+ RecastMesh mRecastMesh;
+ std::vector mOffMeshConnections;
+ };
+
+ struct Item
+ {
+ Key mKey;
+ NavMeshData mValue;
+ };
+
+ template
+ TilePosition generateTilePosition(int max, Random& random)
+ {
+ std::uniform_int_distribution distribution(0, max);
+ return TilePosition(distribution(random), distribution(random));
+ }
+
+ template
+ osg::Vec3f generateAgentHalfExtents(float min, float max, Random& random)
+ {
+ std::uniform_int_distribution distribution(min, max);
+ return osg::Vec3f(distribution(random), distribution(random), distribution(random));
+ }
+
+ template
+ void generateVertices(OutputIterator out, std::size_t number, Random& random)
+ {
+ std::uniform_real_distribution distribution(0.0, 1.0);
+ std::generate_n(out, 3 * (number - number % 3), [&] { return distribution(random); });
+ }
+
+ template
+ void generateIndices(OutputIterator out, int max, std::size_t number, Random& random)
+ {
+ std::uniform_int_distribution distribution(0, max);
+ std::generate_n(out, number - number % 3, [&] { return distribution(random); });
+ }
+
+ AreaType toAreaType(int index)
+ {
+ switch (index)
+ {
+ case 0: return AreaType_null;
+ case 1: return AreaType_water;
+ case 2: return AreaType_door;
+ case 3: return AreaType_pathgrid;
+ case 4: return AreaType_ground;
+ }
+ return AreaType_null;
+ }
+
+ template
+ AreaType generateAreaType(Random& random)
+ {
+ std::uniform_int_distribution distribution(0, 4);
+ return toAreaType(distribution(random));;
+ }
+
+ template
+ void generateAreaTypes(OutputIterator out, std::size_t triangles, Random& random)
+ {
+ std::generate_n(out, triangles, [&] { return generateAreaType(random); });
+ }
+
+ template
+ void generateWater(OutputIterator out, std::size_t count, Random& random)
+ {
+ std::uniform_real_distribution distribution(0.0, 1.0);
+ std::generate_n(out, count, [&] {
+ const btVector3 shift(distribution(random), distribution(random), distribution(random));
+ return RecastMesh::Water {1, btTransform(btMatrix3x3::getIdentity(), shift)};
+ });
+ }
+
+ template
+ void generateOffMeshConnection(OutputIterator out, std::size_t count, Random& random)
+ {
+ std::uniform_real_distribution distribution(0.0, 1.0);
+ std::generate_n(out, count, [&] {
+ const osg::Vec3f start(distribution(random), distribution(random), distribution(random));
+ const osg::Vec3f end(distribution(random), distribution(random), distribution(random));
+ return OffMeshConnection {start, end, generateAreaType(random)};
+ });
+ }
+
+ template
+ 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 std::size_t generation = std::uniform_int_distribution(0, 100)(random);
+ const std::size_t revision = std::uniform_int_distribution(0, 10000)(random);
+ std::vector vertices;
+ generateVertices(std::back_inserter(vertices), triangles * 1.98, random);
+ std::vector indices;
+ generateIndices(std::back_inserter(indices), static_cast(vertices.size() / 3) - 1, vertices.size() * 1.53, random);
+ std::vector areaTypes;
+ generateAreaTypes(std::back_inserter(areaTypes), indices.size() / 3, random);
+ std::vector water;
+ generateWater(std::back_inserter(water), 2, random);
+ RecastMesh recastMesh(generation, revision, std::move(indices), std::move(vertices),
+ std::move(areaTypes), std::move(water));
+ std::vector offMeshConnections;
+ generateOffMeshConnection(std::back_inserter(offMeshConnections), 300, random);
+ return Key {agentHalfExtents, tilePosition, std::move(recastMesh), std::move(offMeshConnections)};
+ }
+
+ constexpr std::size_t trianglesPerTile = 310;
+
+ template
+ void generateKeys(OutputIterator out, std::size_t count, Random& random)
+ {
+ std::generate_n(out, count, [&] { return generateKey(trianglesPerTile, random); });
+ }
+
+ template
+ void fillCache(OutputIterator out, Random& random, NavMeshTilesCache& cache)
+ {
+ std::size_t size = cache.getStats().mNavMeshCacheSize;
+
+ while (true)
+ {
+ Key key = generateKey(trianglesPerTile, random);
+ cache.set(key.mAgentHalfExtents, key.mTilePosition, key.mRecastMesh, key.mOffMeshConnections, NavMeshData());
+ *out++ = std::move(key);
+ const std::size_t newSize = cache.getStats().mNavMeshCacheSize;
+ if (size >= newSize)
+ break;
+ size = newSize;
+ }
+ }
+
+ template
+ void getFromFilledCache(benchmark::State& state)
+ {
+ NavMeshTilesCache cache(maxCacheSize);
+ std::minstd_rand random;
+ std::vector keys;
+ fillCache(std::back_inserter(keys), random, cache);
+ generateKeys(std::back_inserter(keys), keys.size() * (100 - hitPercentage) / 100, random);
+ std::size_t n = 0;
+
+ while (state.KeepRunning())
+ {
+ const auto& key = keys[n++ % keys.size()];
+ const auto result = cache.get(key.mAgentHalfExtents, key.mTilePosition, key.mRecastMesh, key.mOffMeshConnections);
+ benchmark::DoNotOptimize(result);
+ }
+ }
+
+ constexpr auto getFromFilledCache_1m_100hit = getFromFilledCache<1 * 1024 * 1024, 100>;
+ constexpr auto getFromFilledCache_4m_100hit = getFromFilledCache<4 * 1024 * 1024, 100>;
+ constexpr auto getFromFilledCache_16m_100hit = getFromFilledCache<16 * 1024 * 1024, 100>;
+ constexpr auto getFromFilledCache_64m_100hit = getFromFilledCache<64 * 1024 * 1024, 100>;
+ constexpr auto getFromFilledCache_1m_70hit = getFromFilledCache<1 * 1024 * 1024, 70>;
+ constexpr auto getFromFilledCache_4m_70hit = getFromFilledCache<4 * 1024 * 1024, 70>;
+ constexpr auto getFromFilledCache_16m_70hit = getFromFilledCache<16 * 1024 * 1024, 70>;
+ constexpr auto getFromFilledCache_64m_70hit = getFromFilledCache<64 * 1024 * 1024, 70>;
+
+ template
+ void setToBoundedNonEmptyCache(benchmark::State& state)
+ {
+ NavMeshTilesCache cache(maxCacheSize);
+ std::minstd_rand random;
+ std::vector keys;
+ fillCache(std::back_inserter(keys), random, cache);
+ generateKeys(std::back_inserter(keys), keys.size() * 2, random);
+ std::reverse(keys.begin(), keys.end());
+ std::size_t n = 0;
+
+ while (state.KeepRunning())
+ {
+ const auto& key = keys[n++ % keys.size()];
+ const auto result = cache.set(key.mAgentHalfExtents, key.mTilePosition, key.mRecastMesh, key.mOffMeshConnections, NavMeshData());
+ benchmark::DoNotOptimize(result);
+ }
+ }
+
+ constexpr auto setToBoundedNonEmptyCache_1m = setToBoundedNonEmptyCache<1 * 1024 * 1024>;
+ constexpr auto setToBoundedNonEmptyCache_4m = setToBoundedNonEmptyCache<4 * 1024 * 1024>;
+ constexpr auto setToBoundedNonEmptyCache_16m = setToBoundedNonEmptyCache<16 * 1024 * 1024>;
+ constexpr auto setToBoundedNonEmptyCache_64m = setToBoundedNonEmptyCache<64 * 1024 * 1024>;
+} // namespace
+
+BENCHMARK(getFromFilledCache_1m_100hit);
+BENCHMARK(getFromFilledCache_4m_100hit);
+BENCHMARK(getFromFilledCache_16m_100hit);
+BENCHMARK(getFromFilledCache_64m_100hit);
+BENCHMARK(getFromFilledCache_1m_70hit);
+BENCHMARK(getFromFilledCache_4m_70hit);
+BENCHMARK(getFromFilledCache_16m_70hit);
+BENCHMARK(getFromFilledCache_64m_70hit);
+BENCHMARK(setToBoundedNonEmptyCache_1m);
+BENCHMARK(setToBoundedNonEmptyCache_4m);
+BENCHMARK(setToBoundedNonEmptyCache_16m);
+BENCHMARK(setToBoundedNonEmptyCache_64m);
+
+BENCHMARK_MAIN();
diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp
index 579f5f67d..60483f981 100644
--- a/apps/esmtool/esmtool.cpp
+++ b/apps/esmtool/esmtool.cpp
@@ -2,6 +2,7 @@
#include
#include
#include
+#include
#include
")
.arg(versionInfo
diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp
index 8cca3a849..95f9f606d 100644
--- a/apps/opencs/view/doc/viewmanager.cpp
+++ b/apps/opencs/view/doc/viewmanager.cpp
@@ -11,7 +11,6 @@
#include "../../model/doc/documentmanager.hpp"
#include "../../model/doc/document.hpp"
#include "../../model/world/columns.hpp"
-#include "../../model/world/universalid.hpp"
#include "../../model/world/idcompletionmanager.hpp"
#include "../../model/prefs/state.hpp"
diff --git a/apps/opencs/view/render/cellborder.cpp b/apps/opencs/view/render/cellborder.cpp
index 6073807ce..d8ff63801 100644
--- a/apps/opencs/view/render/cellborder.cpp
+++ b/apps/opencs/view/render/cellborder.cpp
@@ -2,7 +2,6 @@
#include
#include
-#include
#include
#include
@@ -13,15 +12,23 @@
#include "../../model/world/cellcoordinates.hpp"
const int CSVRender::CellBorder::CellSize = ESM::Land::REAL_SIZE;
-const int CSVRender::CellBorder::VertexCount = (ESM::Land::LAND_SIZE * 4) - 3;
+
+/*
+ The number of vertices per cell border is equal to the number of vertices per edge
+ minus the duplicated corner vertices. An additional vertex to close the loop is NOT needed.
+*/
+const int CSVRender::CellBorder::VertexCount = (ESM::Land::LAND_SIZE * 4) - 4;
CSVRender::CellBorder::CellBorder(osg::Group* cellNode, const CSMWorld::CellCoordinates& coords)
: mParentNode(cellNode)
{
+ mBorderGeometry = new osg::Geometry();
+
mBaseNode = new osg::PositionAttitudeTransform();
mBaseNode->setNodeMask(Mask_CellBorder);
mBaseNode->setPosition(osg::Vec3f(coords.getX() * CellSize, coords.getY() * CellSize, 10));
+ mBaseNode->addChild(mBorderGeometry);
mParentNode->addChild(mBaseNode);
}
@@ -38,56 +45,59 @@ void CSVRender::CellBorder::buildShape(const ESM::Land& esmLand)
if (!landData)
return;
- osg::ref_ptr geometry = new osg::Geometry();
+ mBaseNode->removeChild(mBorderGeometry);
+ mBorderGeometry = new osg::Geometry();
- // Vertices
osg::ref_ptr vertices = new osg::Vec3Array();
- int x = 0, y = 0;
- for (; x < ESM::Land::LAND_SIZE; ++x)
+ int x = 0;
+ int y = 0;
+
+ /*
+ Traverse the cell border counter-clockwise starting at the SW corner vertex (0, 0).
+ Each loop starts at a corner vertex and ends right before the next corner vertex.
+ */
+ for (; x < ESM::Land::LAND_SIZE - 1; ++x)
vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), landData->mHeights[landIndex(x, y)]));
x = ESM::Land::LAND_SIZE - 1;
- for (; y < ESM::Land::LAND_SIZE; ++y)
+ for (; y < ESM::Land::LAND_SIZE - 1; ++y)
vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), landData->mHeights[landIndex(x, y)]));
y = ESM::Land::LAND_SIZE - 1;
- for (; x >= 0; --x)
+ for (; x > 0; --x)
vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), landData->mHeights[landIndex(x, y)]));
x = 0;
- for (; y >= 0; --y)
+ for (; y > 0; --y)
vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), landData->mHeights[landIndex(x, y)]));
- geometry->setVertexArray(vertices);
+ mBorderGeometry->setVertexArray(vertices);
- // Color
osg::ref_ptr colors = new osg::Vec4Array();
colors->push_back(osg::Vec4f(0.f, 0.5f, 0.f, 1.f));
- geometry->setColorArray(colors, osg::Array::BIND_PER_PRIMITIVE_SET);
+ mBorderGeometry->setColorArray(colors, osg::Array::BIND_PER_PRIMITIVE_SET);
- // Primitive
osg::ref_ptr primitives =
- new osg::DrawElementsUShort(osg::PrimitiveSet::LINE_STRIP, VertexCount+1);
+ new osg::DrawElementsUShort(osg::PrimitiveSet::LINE_STRIP, VertexCount + 1);
+ // Assign one primitive to each vertex.
for (size_t i = 0; i < VertexCount; ++i)
primitives->setElement(i, i);
+ // Assign the last primitive to the first vertex to close the loop.
primitives->setElement(VertexCount, 0);
- geometry->addPrimitiveSet(primitives);
- geometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
-
+ mBorderGeometry->addPrimitiveSet(primitives);
+ mBorderGeometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
- osg::ref_ptr geode = new osg::Geode();
- geode->addDrawable(geometry);
- mBaseNode->addChild(geode);
+ mBaseNode->addChild(mBorderGeometry);
}
size_t CSVRender::CellBorder::landIndex(int x, int y)
{
- return y * ESM::Land::LAND_SIZE + x;
+ return static_cast(y) * ESM::Land::LAND_SIZE + x;
}
float CSVRender::CellBorder::scaleToWorld(int value)
diff --git a/apps/opencs/view/render/cellborder.hpp b/apps/opencs/view/render/cellborder.hpp
index c91aa46c6..be2e18eee 100644
--- a/apps/opencs/view/render/cellborder.hpp
+++ b/apps/opencs/view/render/cellborder.hpp
@@ -7,6 +7,7 @@
namespace osg
{
+ class Geometry;
class Group;
class PositionAttitudeTransform;
}
@@ -47,7 +48,7 @@ namespace CSVRender
osg::Group* mParentNode;
osg::ref_ptr mBaseNode;
-
+ osg::ref_ptr mBorderGeometry;
};
}
diff --git a/apps/opencs/view/render/commands.cpp b/apps/opencs/view/render/commands.cpp
new file mode 100644
index 000000000..699bf5d01
--- /dev/null
+++ b/apps/opencs/view/render/commands.cpp
@@ -0,0 +1,44 @@
+#include "commands.hpp"
+
+#include
+
+#include
+#include
+
+#include "editmode.hpp"
+#include "terrainselection.hpp"
+#include "terrainshapemode.hpp"
+#include "terraintexturemode.hpp"
+#include "worldspacewidget.hpp"
+
+CSVRender::DrawTerrainSelectionCommand::DrawTerrainSelectionCommand(WorldspaceWidget* worldspaceWidget, QUndoCommand* parent)
+ : mWorldspaceWidget(worldspaceWidget)
+{ }
+
+void CSVRender::DrawTerrainSelectionCommand::redo()
+{
+ tryUpdate();
+}
+
+void CSVRender::DrawTerrainSelectionCommand::undo()
+{
+ tryUpdate();
+}
+
+void CSVRender::DrawTerrainSelectionCommand::tryUpdate()
+{
+ if (!mWorldspaceWidget)
+ {
+ Log(Debug::Verbose) << "Can't update terrain selection, no WorldspaceWidget found!";
+ return;
+ }
+
+ auto terrainMode = dynamic_cast(mWorldspaceWidget->getEditMode());
+ if (!terrainMode)
+ {
+ Log(Debug::Verbose) << "Can't update terrain selection in current EditMode";
+ return;
+ }
+
+ terrainMode->getTerrainSelection()->update();
+}
diff --git a/apps/opencs/view/render/commands.hpp b/apps/opencs/view/render/commands.hpp
new file mode 100644
index 000000000..62b7fbfdc
--- /dev/null
+++ b/apps/opencs/view/render/commands.hpp
@@ -0,0 +1,42 @@
+#ifndef CSV_RENDER_COMMANDS_HPP
+#define CSV_RENDER_COMMANDS_HPP
+
+#include
+
+#include
+
+#include "worldspacewidget.hpp"
+
+namespace CSVRender
+{
+ class TerrainSelection;
+
+ /*
+ Current solution to force a redrawing of the terrain-selection grid
+ when undoing/redoing changes in the editor.
+ This only triggers a simple redraw of the grid, so only use it in
+ conjunction with actual data changes which deform the grid.
+
+ Please note that this command needs to be put onto the QUndoStack twice:
+ at the start and at the end of the related terrain manipulation.
+ This makes sure that the grid is always updated after all changes have
+ been undone or redone -- but it also means that the selection is redrawn
+ once at the beginning of either action. Future refinement may solve that.
+ */
+ class DrawTerrainSelectionCommand : public QUndoCommand
+ {
+
+ private:
+ QPointer mWorldspaceWidget;
+
+ public:
+ DrawTerrainSelectionCommand(WorldspaceWidget* worldspaceWidget, QUndoCommand* parent = nullptr);
+
+ void redo() override;
+ void undo() override;
+
+ void tryUpdate();
+ };
+}
+
+#endif
diff --git a/apps/opencs/view/render/orbitcameramode.cpp b/apps/opencs/view/render/orbitcameramode.cpp
index 79ac0167a..c81402ed1 100644
--- a/apps/opencs/view/render/orbitcameramode.cpp
+++ b/apps/opencs/view/render/orbitcameramode.cpp
@@ -3,7 +3,6 @@
#include
#include "../../model/prefs/shortcut.hpp"
-#include "../../model/prefs/shortcuteventhandler.hpp"
#include "worldspacewidget.hpp"
diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp
index ed3558422..a0b4de979 100644
--- a/apps/opencs/view/render/pagedworldspacewidget.cpp
+++ b/apps/opencs/view/render/pagedworldspacewidget.cpp
@@ -7,18 +7,14 @@
#include
#include
-#include
-
#include
#include "../../model/prefs/shortcut.hpp"
-#include "../../model/world/tablemimedata.hpp"
#include "../../model/world/idtable.hpp"
#include "../widget/scenetooltoggle2.hpp"
#include "../widget/scenetoolmode.hpp"
-#include "../widget/scenetooltoggle2.hpp"
#include "editmode.hpp"
#include "mask.hpp"
diff --git a/apps/opencs/view/render/pathgridmode.cpp b/apps/opencs/view/render/pathgridmode.cpp
index 7a3fc8ecf..193cb664d 100644
--- a/apps/opencs/view/render/pathgridmode.cpp
+++ b/apps/opencs/view/render/pathgridmode.cpp
@@ -9,8 +9,6 @@
#include "../../model/world/commands.hpp"
#include "../../model/world/commandmacro.hpp"
-#include "../../model/world/idtable.hpp"
-#include "../../model/world/idtree.hpp"
#include "../widget/scenetoolbar.hpp"
diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp
index 97f4c6ce2..c444cc8c6 100644
--- a/apps/opencs/view/render/scenewidget.cpp
+++ b/apps/opencs/view/render/scenewidget.cpp
@@ -26,7 +26,6 @@
#include "../../model/prefs/state.hpp"
#include "../../model/prefs/shortcut.hpp"
-#include "../../model/prefs/shortcuteventhandler.hpp"
#include "lighting.hpp"
#include "mask.hpp"
diff --git a/apps/opencs/view/render/terrainselection.cpp b/apps/opencs/view/render/terrainselection.cpp
index 4e209af57..0593917e0 100644
--- a/apps/opencs/view/render/terrainselection.cpp
+++ b/apps/opencs/view/render/terrainselection.cpp
@@ -39,64 +39,23 @@ std::vector> CSVRender::TerrainSelection::getTerrainSelectio
void CSVRender::TerrainSelection::onlySelect(const std::vector> &localPositions)
{
mSelection = localPositions;
+
update();
}
-void CSVRender::TerrainSelection::addSelect(const std::pair &localPos)
+void CSVRender::TerrainSelection::addSelect(const std::vector>& localPositions, bool toggleInProgress)
{
- if (std::find(mSelection.begin(), mSelection.end(), localPos) == mSelection.end())
- {
- mSelection.emplace_back(localPos);
- update();
- }
+ handleSelection(localPositions, toggleInProgress, SelectionMethod::AddSelect);
}
-void CSVRender::TerrainSelection::toggleSelect(const std::vector> &localPositions, bool toggleInProgress)
+void CSVRender::TerrainSelection::removeSelect(const std::vector>& localPositions, bool toggleInProgress)
{
- if (toggleInProgress)
- {
- for(auto const& localPos: localPositions)
- {
- auto iterTemp = std::find(mTemporarySelection.begin(), mTemporarySelection.end(), localPos);
- mDraggedOperationFlag = true;
-
- if (iterTemp == mTemporarySelection.end())
- {
- auto iter = std::find(mSelection.begin(), mSelection.end(), localPos);
- if (iter != mSelection.end())
- {
- mSelection.erase(iter);
- }
- else
- {
- mSelection.emplace_back(localPos);
- }
- }
+ handleSelection(localPositions, toggleInProgress, SelectionMethod::RemoveSelect);
+}
- mTemporarySelection.push_back(localPos);
- }
- }
- else if (mDraggedOperationFlag == false)
- {
- for(auto const& localPos: localPositions)
- {
- const auto iter = std::find(mSelection.begin(), mSelection.end(), localPos);
- if (iter != mSelection.end())
- {
- mSelection.erase(iter);
- }
- else
- {
- mSelection.emplace_back(localPos);
- }
- }
- }
- else
- {
- mDraggedOperationFlag = false;
- mTemporarySelection.clear();
- }
- update();
+void CSVRender::TerrainSelection::toggleSelect(const std::vector>& localPositions, bool toggleInProgress)
+{
+ handleSelection(localPositions, toggleInProgress, SelectionMethod::ToggleSelect);
}
void CSVRender::TerrainSelection::activate()
@@ -239,6 +198,100 @@ void CSVRender::TerrainSelection::drawTextureSelection(const osg::ref_ptr>& localPositions, bool toggleInProgress, SelectionMethod selectionMethod)
+{
+ if (toggleInProgress)
+ {
+ for (auto const& localPos : localPositions)
+ {
+ auto iterTemp = std::find(mTemporarySelection.begin(), mTemporarySelection.end(), localPos);
+ mDraggedOperationFlag = true;
+
+ if (iterTemp == mTemporarySelection.end())
+ {
+ auto iter = std::find(mSelection.begin(), mSelection.end(), localPos);
+
+ switch (selectionMethod)
+ {
+ case SelectionMethod::AddSelect:
+ if (iter == mSelection.end())
+ {
+ mSelection.emplace_back(localPos);
+ }
+
+ break;
+ case SelectionMethod::RemoveSelect:
+ if (iter != mSelection.end())
+ {
+ mSelection.erase(iter);
+ }
+
+ break;
+ case SelectionMethod::ToggleSelect:
+ if (iter == mSelection.end())
+ {
+ mSelection.emplace_back(localPos);
+ }
+ else
+ {
+ mSelection.erase(iter);
+ }
+
+ break;
+ default: break;
+ }
+ }
+
+ mTemporarySelection.push_back(localPos);
+ }
+ }
+ else if (mDraggedOperationFlag == false)
+ {
+ for (auto const& localPos : localPositions)
+ {
+ const auto iter = std::find(mSelection.begin(), mSelection.end(), localPos);
+
+ switch (selectionMethod)
+ {
+ case SelectionMethod::AddSelect:
+ if (iter == mSelection.end())
+ {
+ mSelection.emplace_back(localPos);
+ }
+
+ break;
+ case SelectionMethod::RemoveSelect:
+ if (iter != mSelection.end())
+ {
+ mSelection.erase(iter);
+ }
+
+ break;
+ case SelectionMethod::ToggleSelect:
+ if (iter == mSelection.end())
+ {
+ mSelection.emplace_back(localPos);
+ }
+ else
+ {
+ mSelection.erase(iter);
+ }
+
+ break;
+ default: break;
+ }
+ }
+ }
+ else
+ {
+ mDraggedOperationFlag = false;
+
+ mTemporarySelection.clear();
+ }
+
+ update();
+}
+
bool CSVRender::TerrainSelection::noCell(const std::string& cellId)
{
CSMDoc::Document& document = mWorldspaceWidget->getDocument();
diff --git a/apps/opencs/view/render/terrainselection.hpp b/apps/opencs/view/render/terrainselection.hpp
index 84ee6f25a..18622ad13 100644
--- a/apps/opencs/view/render/terrainselection.hpp
+++ b/apps/opencs/view/render/terrainselection.hpp
@@ -27,6 +27,14 @@ namespace CSVRender
Shape
};
+ enum class SelectionMethod
+ {
+ OnlySelect,
+ AddSelect,
+ RemoveSelect,
+ ToggleSelect
+ };
+
/// \brief Class handling the terrain selection data and rendering
class TerrainSelection
{
@@ -36,7 +44,8 @@ namespace CSVRender
~TerrainSelection();
void onlySelect(const std::vector> &localPositions);
- void addSelect(const std::pair &localPos);
+ void addSelect(const std::vector>& localPositions, bool toggleInProgress);
+ void removeSelect(const std::vector>& localPositions, bool toggleInProgress);
void toggleSelect(const std::vector> &localPositions, bool toggleInProgress);
void activate();
@@ -55,6 +64,8 @@ namespace CSVRender
private:
+ void handleSelection(const std::vector>& localPositions, bool toggleInProgress, SelectionMethod selectionMethod);
+
bool noCell(const std::string& cellId);
bool noLand(const std::string& cellId);
diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp
index e495d2355..050494495 100644
--- a/apps/opencs/view/render/terrainshapemode.cpp
+++ b/apps/opencs/view/render/terrainshapemode.cpp
@@ -25,18 +25,16 @@
#include "../../model/doc/document.hpp"
#include "../../model/prefs/state.hpp"
-#include "../../model/world/columnbase.hpp"
-#include "../../model/world/commandmacro.hpp"
#include "../../model/world/commands.hpp"
#include "../../model/world/data.hpp"
#include "../../model/world/idtable.hpp"
#include "../../model/world/idtree.hpp"
#include "../../model/world/land.hpp"
-#include "../../model/world/resourcetable.hpp"
#include "../../model/world/tablemimedata.hpp"
#include "../../model/world/universalid.hpp"
#include "brushdraw.hpp"
+#include "commands.hpp"
#include "editmode.hpp"
#include "pagedworldspacewidget.hpp"
#include "mask.hpp"
@@ -45,7 +43,7 @@
#include "worldspacewidget.hpp"
CSVRender::TerrainShapeMode::TerrainShapeMode (WorldspaceWidget *worldspaceWidget, osg::Group* parentNode, QWidget *parent)
-: EditMode (worldspaceWidget, QIcon {":scenetoolbar/editing-terrain-shape"}, Mask_Terrain | Mask_Reference, "Terrain land editing", parent),
+: EditMode (worldspaceWidget, QIcon {":scenetoolbar/editing-terrain-shape"}, Mask_Terrain, "Terrain land editing", parent),
mParentNode(parentNode)
{
}
@@ -288,6 +286,9 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges()
undoStack.beginMacro ("Edit shape and normal records");
+ // One command at the beginning of the macro for redrawing the terrain-selection grid when undoing the changes.
+ undoStack.push(new DrawTerrainSelectionCommand(&getWorldspaceWidget()));
+
for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells)
{
std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY());
@@ -356,6 +357,9 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges()
}
pushNormalsEditToCommand(landNormalsNew, document, landTable, cellId);
}
+ // One command at the end of the macro for redrawing the terrain-selection grid when redoing the changes.
+ undoStack.push(new DrawTerrainSelectionCommand(&getWorldspaceWidget()));
+
undoStack.endMacro();
clearTransientEdits();
}
@@ -433,7 +437,9 @@ void CSVRender::TerrainShapeMode::editTerrainShapeGrid(const std::pair
float smoothedByDistance = 0.0f;
if (mShapeEditTool == ShapeEditTool_Drag) smoothedByDistance = calculateBumpShape(distance, r, mTotalDiffY);
if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) smoothedByDistance = calculateBumpShape(distance, r, r + mShapeEditToolStrength);
- if (distance <= r)
+
+ // Using floating-point radius here to prevent selecting too few vertices.
+ if (distance <= mBrushSize / 2.0f)
{
if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, smoothedByDistance);
if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower)
@@ -1036,10 +1042,35 @@ void CSVRender::TerrainShapeMode::handleSelection(int globalSelectionX, int glob
return;
int selectionX = globalSelectionX;
int selectionY = globalSelectionY;
- if (xIsAtCellBorder)
+
+ /*
+ The northern and eastern edges don't belong to the current cell.
+ If the corresponding adjacent cell is not loaded, some special handling is necessary to select border vertices.
+ */
+ if (xIsAtCellBorder && yIsAtCellBorder)
+ {
+ /*
+ Handle the NW, NE, and SE corner vertices.
+ NW corner: (+1, -1) offset to reach current cell.
+ NE corner: (-1, -1) offset to reach current cell.
+ SE corner: (-1, +1) offset to reach current cell.
+ */
+ if (isInCellSelection(globalSelectionX - 1, globalSelectionY - 1)
+ || isInCellSelection(globalSelectionX + 1, globalSelectionY - 1)
+ || isInCellSelection(globalSelectionX - 1, globalSelectionY + 1))
+ {
+ selections->emplace_back(globalSelectionX, globalSelectionY);
+ }
+ }
+ else if (xIsAtCellBorder)
+ {
selectionX--;
- if (yIsAtCellBorder)
+ }
+ else if (yIsAtCellBorder)
+ {
selectionY--;
+ }
+
if (isInCellSelection(selectionX, selectionY))
selections->emplace_back(globalSelectionX, globalSelectionY);
}
@@ -1074,8 +1105,11 @@ void CSVRender::TerrainShapeMode::selectTerrainShapes(const std::pair&
{
int distanceX = abs(i - vertexCoords.first);
int distanceY = abs(j - vertexCoords.second);
- int distance = std::round(sqrt(pow(distanceX, 2)+pow(distanceY, 2)));
- if (distance <= r) handleSelection(i, j, &selections);
+ float distance = sqrt(pow(distanceX, 2)+pow(distanceY, 2));
+
+ // Using floating-point radius here to prevent selecting too few vertices.
+ if (distance <= mBrushSize / 2.0f)
+ handleSelection(i, j, &selections);
}
}
}
@@ -1092,9 +1126,21 @@ void CSVRender::TerrainShapeMode::selectTerrainShapes(const std::pair&
}
}
- if(selectMode == 0) mTerrainShapeSelection->onlySelect(selections);
- if(selectMode == 1) mTerrainShapeSelection->toggleSelect(selections, dragOperation);
+ std::string selectAction;
+ if (selectMode == 0)
+ selectAction = CSMPrefs::get()["3D Scene Editing"]["primary-select-action"].toString();
+ else
+ selectAction = CSMPrefs::get()["3D Scene Editing"]["secondary-select-action"].toString();
+
+ if (selectAction == "Select only")
+ mTerrainShapeSelection->onlySelect(selections);
+ else if (selectAction == "Add to selection")
+ mTerrainShapeSelection->addSelect(selections, dragOperation);
+ else if (selectAction == "Remove from selection")
+ mTerrainShapeSelection->removeSelect(selections, dragOperation);
+ else if (selectAction == "Invert selection")
+ mTerrainShapeSelection->toggleSelect(selections, dragOperation);
}
void CSVRender::TerrainShapeMode::pushEditToCommand(const CSMWorld::LandHeightsColumn::DataType& newLandGrid, CSMDoc::Document& document,
@@ -1398,6 +1444,11 @@ void CSVRender::TerrainShapeMode::mouseMoveEvent (QMouseEvent *event)
mBrushDraw->hide();
}
+std::shared_ptr CSVRender::TerrainShapeMode::getTerrainSelection()
+{
+ return mTerrainShapeSelection;
+}
+
void CSVRender::TerrainShapeMode::setBrushSize(int brushSize)
{
mBrushSize = brushSize;
diff --git a/apps/opencs/view/render/terrainshapemode.hpp b/apps/opencs/view/render/terrainshapemode.hpp
index a88e60c9c..abc4b8aba 100644
--- a/apps/opencs/view/render/terrainshapemode.hpp
+++ b/apps/opencs/view/render/terrainshapemode.hpp
@@ -92,6 +92,8 @@ namespace CSVRender
void dragMoveEvent (QDragMoveEvent *event) override;
void mouseMoveEvent (QMouseEvent *event) override;
+ std::shared_ptr getTerrainSelection();
+
private:
/// Remove duplicates and sort mAlteredCells, then limitAlteredHeights forward and reverse
@@ -176,7 +178,7 @@ namespace CSVRender
int mDragMode = InteractionType_None;
osg::Group* mParentNode;
bool mIsEditing = false;
- std::unique_ptr mTerrainShapeSelection;
+ std::shared_ptr mTerrainShapeSelection;
int mTotalDiffY = 0;
std::vector mAlteredCells;
osg::Vec3d mEditingPos;
diff --git a/apps/opencs/view/render/terraintexturemode.cpp b/apps/opencs/view/render/terraintexturemode.cpp
index fa46cf673..dfcc29ae0 100644
--- a/apps/opencs/view/render/terraintexturemode.cpp
+++ b/apps/opencs/view/render/terraintexturemode.cpp
@@ -12,23 +12,17 @@
#include
-#include
-
#include "../widget/modebutton.hpp"
#include "../widget/scenetoolbar.hpp"
#include "../widget/scenetooltexturebrush.hpp"
#include "../../model/doc/document.hpp"
#include "../../model/prefs/state.hpp"
-#include "../../model/world/columnbase.hpp"
-#include "../../model/world/commandmacro.hpp"
#include "../../model/world/commands.hpp"
#include "../../model/world/data.hpp"
#include "../../model/world/idtable.hpp"
#include "../../model/world/idtree.hpp"
-#include "../../model/world/land.hpp"
#include "../../model/world/landtexture.hpp"
-#include "../../model/world/resourcetable.hpp"
#include "../../model/world/tablemimedata.hpp"
#include "../../model/world/universalid.hpp"
#include "../widget/brushshapes.hpp"
@@ -332,7 +326,7 @@ void CSVRender::TerrainTextureMode::editTerrainTextureGrid(const WorldspaceHitRe
int textureColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandTexturesIndex);
- std::size_t hashlocation = mBrushTexture.find("#");
+ std::size_t hashlocation = mBrushTexture.find('#');
std::string mBrushTextureInt = mBrushTexture.substr (hashlocation+1);
int brushInt = stoi(mBrushTexture.substr (hashlocation+1))+1; // All indices are offset by +1
@@ -718,6 +712,11 @@ void CSVRender::TerrainTextureMode::mouseMoveEvent (QMouseEvent *event)
mBrushDraw->hide();
}
+std::shared_ptr CSVRender::TerrainTextureMode::getTerrainSelection()
+{
+ return mTerrainTextureSelection;
+}
+
void CSVRender::TerrainTextureMode::setBrushSize(int brushSize)
{
diff --git a/apps/opencs/view/render/terraintexturemode.hpp b/apps/opencs/view/render/terraintexturemode.hpp
index 31932df21..e0c6e4b40 100644
--- a/apps/opencs/view/render/terraintexturemode.hpp
+++ b/apps/opencs/view/render/terraintexturemode.hpp
@@ -85,6 +85,8 @@ namespace CSVRender
void mouseMoveEvent (QMouseEvent *event) override;
+ std::shared_ptr getTerrainSelection();
+
private:
/// \brief Handle brush mechanics, maths regarding worldspace hit etc.
void editTerrainTextureGrid (const WorldspaceHitResult& hit);
@@ -115,7 +117,7 @@ namespace CSVRender
int mDragMode;
osg::Group* mParentNode;
bool mIsEditing;
- std::unique_ptr mTerrainTextureSelection;
+ std::shared_ptr mTerrainTextureSelection;
const int cellSize {ESM::Land::REAL_SIZE};
const int landTextureSize {ESM::Land::LAND_TEXTURE_SIZE};
diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp
index c4fef45dd..20dc5b8d1 100644
--- a/apps/opencs/view/render/unpagedworldspacewidget.cpp
+++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp
@@ -12,7 +12,6 @@
#include "../../model/world/idtable.hpp"
#include "../../model/world/tablemimedata.hpp"
-#include "../widget/scenetooltoggle.hpp"
#include "../widget/scenetooltoggle2.hpp"
#include "cameracontroller.hpp"
diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp
index 6eb437daf..2ebc083f2 100644
--- a/apps/opencs/view/render/worldspacewidget.cpp
+++ b/apps/opencs/view/render/worldspacewidget.cpp
@@ -17,7 +17,6 @@
#include "../../model/world/idtable.hpp"
#include "../../model/prefs/shortcut.hpp"
-#include "../../model/prefs/shortcuteventhandler.hpp"
#include "../../model/prefs/state.hpp"
#include "../render/orbitcameramode.hpp"
@@ -453,6 +452,11 @@ CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick (const QPo
return hit;
}
+CSVRender::EditMode *CSVRender::WorldspaceWidget::getEditMode()
+{
+ return dynamic_cast (mEditMode->getCurrent());
+}
+
void CSVRender::WorldspaceWidget::abortDrag()
{
if (mDragging)
@@ -698,11 +702,6 @@ void CSVRender::WorldspaceWidget::handleInteractionPress (const WorldspaceHitRes
editMode.primaryOpenPressed (hit);
}
-CSVRender::EditMode *CSVRender::WorldspaceWidget::getEditMode()
-{
- return dynamic_cast (mEditMode->getCurrent());
-}
-
void CSVRender::WorldspaceWidget::primaryOpen(bool activate)
{
handleInteraction(InteractionType_PrimaryOpen, activate);
diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp
index 5e224b380..cf244ce71 100644
--- a/apps/opencs/view/render/worldspacewidget.hpp
+++ b/apps/opencs/view/render/worldspacewidget.hpp
@@ -189,6 +189,8 @@ namespace CSVRender
/// Erase all overrides and restore the visual representation to its true state.
virtual void reset (unsigned int elementMask) = 0;
+ EditMode *getEditMode();
+
protected:
/// Visual elements in a scene
@@ -215,8 +217,6 @@ namespace CSVRender
void settingChanged (const CSMPrefs::Setting *setting) override;
- EditMode *getEditMode();
-
bool getSpeedMode();
private:
diff --git a/apps/opencs/view/widget/scenetoolrun.cpp b/apps/opencs/view/widget/scenetoolrun.cpp
index b53282036..24bcf3f13 100644
--- a/apps/opencs/view/widget/scenetoolrun.cpp
+++ b/apps/opencs/view/widget/scenetoolrun.cpp
@@ -30,7 +30,7 @@ void CSVWidget::SceneToolRun::updateIcon()
void CSVWidget::SceneToolRun::updatePanel()
{
- mTable->setRowCount (mProfiles.size());
+ mTable->setRowCount (static_cast(mProfiles.size()));
int i = 0;
diff --git a/apps/opencs/view/widget/scenetoolshapebrush.cpp b/apps/opencs/view/widget/scenetoolshapebrush.cpp
index 4b2d20004..d0419a13a 100644
--- a/apps/opencs/view/widget/scenetoolshapebrush.cpp
+++ b/apps/opencs/view/widget/scenetoolshapebrush.cpp
@@ -27,11 +27,6 @@
#include "../../model/doc/document.hpp"
#include "../../model/prefs/state.hpp"
#include "../../model/world/commands.hpp"
-#include "../../model/world/data.hpp"
-#include "../../model/world/idcollection.hpp"
-#include "../../model/world/idtable.hpp"
-#include "../../model/world/landtexture.hpp"
-#include "../../model/world/universalid.hpp"
CSVWidget::ShapeBrushSizeControls::ShapeBrushSizeControls(const QString &title, QWidget *parent)
diff --git a/apps/opencs/view/widget/scenetooltoggle.cpp b/apps/opencs/view/widget/scenetooltoggle.cpp
index fa0be3155..04ac3322b 100644
--- a/apps/opencs/view/widget/scenetooltoggle.cpp
+++ b/apps/opencs/view/widget/scenetooltoggle.cpp
@@ -80,7 +80,7 @@ QRect CSVWidget::SceneToolToggle::getIconBox (int index) const
int y = index / xMax;
int x = index % xMax;
- int total = mButtons.size();
+ int total = static_cast(mButtons.size());
int actualYIcons = total/xMax;
@@ -154,7 +154,7 @@ void CSVWidget::SceneToolToggle::addButton (const std::string& icon, unsigned in
desc.mMask = mask;
desc.mSmallIcon = smallIcon;
desc.mName = name;
- desc.mIndex = mButtons.size();
+ desc.mIndex = static_cast(mButtons.size());
mButtons.insert (std::make_pair (button, desc));
diff --git a/apps/opencs/view/widget/scenetooltoggle2.cpp b/apps/opencs/view/widget/scenetooltoggle2.cpp
index cf9bfe349..363cae570 100644
--- a/apps/opencs/view/widget/scenetooltoggle2.cpp
+++ b/apps/opencs/view/widget/scenetooltoggle2.cpp
@@ -99,7 +99,7 @@ void CSVWidget::SceneToolToggle2::addButton (unsigned int id, unsigned int mask,
desc.mButtonId = id;
desc.mMask = mask;
desc.mName = name;
- desc.mIndex = mButtons.size();
+ desc.mIndex = static_cast(mButtons.size());
mButtons.insert (std::make_pair (button, desc));
diff --git a/apps/opencs/view/world/colordelegate.cpp b/apps/opencs/view/world/colordelegate.cpp
index 15a07b42c..3b5f692fe 100644
--- a/apps/opencs/view/world/colordelegate.cpp
+++ b/apps/opencs/view/world/colordelegate.cpp
@@ -3,8 +3,6 @@
#include
#include
-#include "../widget/coloreditor.hpp"
-
CSVWorld::ColorDelegate::ColorDelegate(CSMWorld::CommandDispatcher *dispatcher,
CSMDoc::Document& document,
QObject *parent)
diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp
index 3d3b3cdbe..f2360b137 100644
--- a/apps/opencs/view/world/dialoguesubview.cpp
+++ b/apps/opencs/view/world/dialoguesubview.cpp
@@ -28,7 +28,6 @@
#include "../../model/world/columns.hpp"
#include "../../model/world/record.hpp"
#include "../../model/world/tablemimedata.hpp"
-#include "../../model/world/idtree.hpp"
#include "../../model/world/commands.hpp"
#include "../../model/doc/document.hpp"
diff --git a/apps/opencs/view/world/idvalidator.cpp b/apps/opencs/view/world/idvalidator.cpp
index 1092d7217..442157ac5 100644
--- a/apps/opencs/view/world/idvalidator.cpp
+++ b/apps/opencs/view/world/idvalidator.cpp
@@ -42,7 +42,7 @@ QValidator::State CSVWorld::IdValidator::validate (QString& input, int& pos) con
if (!mNamespace.empty())
{
- std::string namespace_ = input.left (mNamespace.size()).toUtf8().constData();
+ std::string namespace_ = input.left (static_cast(mNamespace.size())).toUtf8().constData();
if (Misc::StringUtils::lowerCase (namespace_)!=mNamespace)
return QValidator::Invalid; // incorrect namespace
diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp
index 44542c529..58d159a17 100644
--- a/apps/opencs/view/world/scenesubview.cpp
+++ b/apps/opencs/view/world/scenesubview.cpp
@@ -15,7 +15,6 @@
#include "../render/pagedworldspacewidget.hpp"
#include "../render/unpagedworldspacewidget.hpp"
-#include "../render/editmode.hpp"
#include "../widget/scenetoolbar.hpp"
#include "../widget/scenetoolmode.hpp"
diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp
index 096fc8a9e..6eab7aaa5 100644
--- a/apps/opencs/view/world/scriptsubview.cpp
+++ b/apps/opencs/view/world/scriptsubview.cpp
@@ -12,7 +12,6 @@
#include "../../model/doc/document.hpp"
#include "../../model/world/universalid.hpp"
#include "../../model/world/data.hpp"
-#include "../../model/world/columnbase.hpp"
#include "../../model/world/commands.hpp"
#include "../../model/world/idtable.hpp"
#include "../../model/prefs/state.hpp"
diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt
index e0d2ea998..02348452f 100644
--- a/apps/openmw/CMakeLists.txt
+++ b/apps/openmw/CMakeLists.txt
@@ -184,7 +184,7 @@ if(APPLE)
add_subdirectory(../../files/ ${CMAKE_CURRENT_BINARY_DIR}/files)
- configure_file("${OpenMW_BINARY_DIR}/settings-default.cfg" ${BUNDLE_RESOURCES_DIR} COPYONLY)
+ configure_file("${OpenMW_BINARY_DIR}/defaults.bin" ${BUNDLE_RESOURCES_DIR} COPYONLY)
configure_file("${OpenMW_BINARY_DIR}/openmw.cfg" ${BUNDLE_RESOURCES_DIR} COPYONLY)
configure_file("${OpenMW_BINARY_DIR}/gamecontrollerdb.txt" ${BUNDLE_RESOURCES_DIR} COPYONLY)
diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp
index 677174877..35e550582 100644
--- a/apps/openmw/engine.cpp
+++ b/apps/openmw/engine.cpp
@@ -496,8 +496,8 @@ void OMW::Engine::setSkipMenu (bool skipMenu, bool newGame)
std::string OMW::Engine::loadSettings (Settings::Manager & settings)
{
// Create the settings manager and load default settings file
- const std::string localdefault = (mCfgMgr.getLocalPath() / "settings-default.cfg").string();
- const std::string globaldefault = (mCfgMgr.getGlobalPath() / "settings-default.cfg").string();
+ const std::string localdefault = (mCfgMgr.getLocalPath() / "defaults.bin").string();
+ const std::string globaldefault = (mCfgMgr.getGlobalPath() / "defaults.bin").string();
// prefer local
if (boost::filesystem::exists(localdefault))
@@ -505,7 +505,7 @@ std::string OMW::Engine::loadSettings (Settings::Manager & settings)
else if (boost::filesystem::exists(globaldefault))
settings.loadDefault(globaldefault);
else
- throw std::runtime_error ("No default settings file found! Make sure the file \"settings-default.cfg\" was properly installed.");
+ throw std::runtime_error ("No default settings file found! Make sure the file \"defaults.bin\" was properly installed.");
// load user settings if they exist
const std::string settingspath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string();
@@ -1081,6 +1081,8 @@ void OMW::Engine::go()
// Save user settings
settings.saveUser(settingspath);
+ mViewer->stopThreading();
+
Log(Debug::Info) << "Quitting peacefully.";
}
diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp
index 709ffda2c..8f7e16058 100644
--- a/apps/openmw/main.cpp
+++ b/apps/openmw/main.cpp
@@ -44,6 +44,10 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
desc.add_options()
("help", "print help message")
("version", "print version information and quit")
+
+ ("replace", bpo::value()->default_value(Files::EscapeStringVector(), "")
+ ->multitoken()->composing(), "settings where the values from the current source should replace those from lower-priority sources instead of being appended")
+
("data", bpo::value()->default_value(Files::EscapePathContainer(), "data")
->multitoken()->composing(), "set data directories (later directories have higher priority)")
@@ -192,6 +196,15 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
Log(Debug::Error) << "No content file given (esm/esp, nor omwgame/omwaddon). Aborting...";
return false;
}
+ std::set contentDedupe;
+ for (const auto& contentFile : content)
+ {
+ if (!contentDedupe.insert(contentFile).second)
+ {
+ Log(Debug::Error) << "Content file specified more than once: " << contentFile << ". Aborting...";
+ return false;
+ }
+ }
for (auto& file : content)
{
diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp
index 570399e91..b0c38f98d 100644
--- a/apps/openmw/mwbase/world.hpp
+++ b/apps/openmw/mwbase/world.hpp
@@ -301,7 +301,7 @@ namespace MWBase
virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z, bool movePhysics=true) = 0;
///< @return an updated Ptr
- virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, osg::Vec3f vec, bool moveToActive) = 0;
+ virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, osg::Vec3f vec, bool moveToActive, bool ignoreCollisions) = 0;
///< @return an updated Ptr
virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0;
diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp
index 692ca72bb..b3c792bde 100644
--- a/apps/openmw/mwclass/creature.cpp
+++ b/apps/openmw/mwclass/creature.cpp
@@ -26,7 +26,6 @@
#include "../mwworld/failedaction.hpp"
#include "../mwworld/customdata.hpp"
#include "../mwworld/containerstore.hpp"
-#include "../mwphysics/physicssystem.hpp"
#include "../mwworld/cellstore.hpp"
#include "../mwworld/localscripts.hpp"
diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp
index 3a5ff0d9a..849960a09 100644
--- a/apps/openmw/mwclass/door.cpp
+++ b/apps/openmw/mwclass/door.cpp
@@ -132,12 +132,14 @@ namespace MWClass
MWBase::Environment::get().getWorld()->getMaxActivationDistance())
{
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr);
+ if(animation)
+ {
+ const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
+ int index = ESM::MagicEffect::effectStringToId("sEffectTelekinesis");
+ const ESM::MagicEffect *effect = store.get().find(index);
- const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
- int index = ESM::MagicEffect::effectStringToId("sEffectTelekinesis");
- const ESM::MagicEffect *effect = store.get().find(index);
-
- animation->addSpellCastGlow(effect, 1); // 1 second glow to match the time taken for a door opening or closing
+ animation->addSpellCastGlow(effect, 1); // 1 second glow to match the time taken for a door opening or closing
+ }
}
const std::string keyId = ptr.getCellRef().getKey();
diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp
index 9d2c45b10..50206b32c 100644
--- a/apps/openmw/mwclass/npc.cpp
+++ b/apps/openmw/mwclass/npc.cpp
@@ -36,7 +36,6 @@
#include "../mwworld/failedaction.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwworld/customdata.hpp"
-#include "../mwphysics/physicssystem.hpp"
#include "../mwworld/cellstore.hpp"
#include "../mwworld/localscripts.hpp"
diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp
index 34fd5828f..3c6349ad8 100644
--- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp
+++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp
@@ -343,7 +343,7 @@ namespace MWDialogue
if(!inJournal(topicId, answer->mId))
{
// Does this dialogue contains some actor-specific answer?
- if (answer->mActor == mActor.getCellRef().getRefId())
+ if (Misc::StringUtils::ciEqual(answer->mActor, mActor.getCellRef().getRefId()))
flag |= MWBase::DialogueManager::TopicType::Specific;
}
else
diff --git a/apps/openmw/mwdialogue/journalimp.hpp b/apps/openmw/mwdialogue/journalimp.hpp
index a6501e54e..0c778d2ba 100644
--- a/apps/openmw/mwdialogue/journalimp.hpp
+++ b/apps/openmw/mwdialogue/journalimp.hpp
@@ -3,7 +3,6 @@
#include "../mwbase/journal.hpp"
-#include "journalentry.hpp"
#include "quest.hpp"
namespace MWDialogue
diff --git a/apps/openmw/mwgui/bookpage.hpp b/apps/openmw/mwgui/bookpage.hpp
index f9fd93089..512a43992 100644
--- a/apps/openmw/mwgui/bookpage.hpp
+++ b/apps/openmw/mwgui/bookpage.hpp
@@ -10,7 +10,6 @@
#include
#include
-#include
#include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp"
diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp
index d0d3b87a6..b9004fcdb 100644
--- a/apps/openmw/mwgui/console.cpp
+++ b/apps/openmw/mwgui/console.cpp
@@ -9,6 +9,10 @@
#include
#include
+#include
+#include
+#include
+#include
#include "../mwscript/extensions.hpp"
diff --git a/apps/openmw/mwgui/console.hpp b/apps/openmw/mwgui/console.hpp
index 52aa42f2a..2b8cecbc0 100644
--- a/apps/openmw/mwgui/console.hpp
+++ b/apps/openmw/mwgui/console.hpp
@@ -6,12 +6,8 @@
#include
#include
-#include
-#include
-#include
#include
#include
-#include
#include "../mwscript/compilercontext.hpp"
#include "../mwscript/interpretercontext.hpp"
diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp
index b0913219f..c980ef3ea 100644
--- a/apps/openmw/mwgui/container.cpp
+++ b/apps/openmw/mwgui/container.cpp
@@ -12,7 +12,9 @@
#include "../mwworld/class.hpp"
#include "../mwworld/inventorystore.hpp"
+#include "../mwmechanics/aipackage.hpp"
#include "../mwmechanics/creaturestats.hpp"
+#include "../mwmechanics/summoning.hpp"
#include "../mwscript/interpretercontext.hpp"
@@ -268,6 +270,23 @@ namespace MWGui
for (const auto& creature : creatureMap)
MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(ptr, creature.second);
creatureMap.clear();
+
+ // Check if we are a summon and inform our master we've bit the dust
+ for(const auto& package : creatureStats.getAiSequence())
+ {
+ if(package->followTargetThroughDoors() && !package->getTarget().isEmpty())
+ {
+ const auto& summoner = package->getTarget();
+ auto& summons = summoner.getClass().getCreatureStats(summoner).getSummonedCreatureMap();
+ auto it = std::find_if(summons.begin(), summons.end(), [&] (const auto& entry) { return entry.second == creatureStats.getActorId(); });
+ if(it != summons.end())
+ {
+ MWMechanics::purgeSummonEffect(summoner, *it);
+ summons.erase(it);
+ break;
+ }
+ }
+ }
}
MWBase::Environment::get().getWorld()->deleteObject(ptr);
diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp
index eb2b36c8a..43c8f4ffe 100644
--- a/apps/openmw/mwgui/dialogue.cpp
+++ b/apps/openmw/mwgui/dialogue.cpp
@@ -310,7 +310,7 @@ namespace MWGui
deleteLater();
for (Link* link : mLinks)
delete link;
- for (auto link : mTopicLinks)
+ for (const auto& link : mTopicLinks)
delete link.second;
for (auto history : mHistoryContents)
delete history;
diff --git a/apps/openmw/mwgui/itemmodel.cpp b/apps/openmw/mwgui/itemmodel.cpp
index cf88efaae..4e4d77da4 100644
--- a/apps/openmw/mwgui/itemmodel.cpp
+++ b/apps/openmw/mwgui/itemmodel.cpp
@@ -2,7 +2,6 @@
#include "../mwworld/class.hpp"
#include "../mwworld/containerstore.hpp"
-#include "../mwworld/esmstore.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
diff --git a/apps/openmw/mwgui/itemview.cpp b/apps/openmw/mwgui/itemview.cpp
index 94dcc77c5..14f2c1dd9 100644
--- a/apps/openmw/mwgui/itemview.cpp
+++ b/apps/openmw/mwgui/itemview.cpp
@@ -5,9 +5,7 @@
#include
#include
#include
-#include
#include
-#include
#include "../mwworld/class.hpp"
diff --git a/apps/openmw/mwgui/journalbooks.cpp b/apps/openmw/mwgui/journalbooks.cpp
index 065a503e6..40053b3c8 100644
--- a/apps/openmw/mwgui/journalbooks.cpp
+++ b/apps/openmw/mwgui/journalbooks.cpp
@@ -278,7 +278,7 @@ BookTypesetter::Ptr JournalBooks::createCyrillicJournalIndex ()
mIndexPagesCount = 2;
}
- unsigned char ch[2] = {0xd0, 0x90}; // CYRILLIC CAPITAL A is a 0xd090 in UTF-8
+ unsigned char ch[3] = {0xd0, 0x90, 0x00}; // CYRILLIC CAPITAL A is a 0xd090 in UTF-8
for (int i = 0; i < 32; ++i)
{
diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp
index 86f92db6f..14de3fd27 100644
--- a/apps/openmw/mwgui/levelupdialog.cpp
+++ b/apps/openmw/mwgui/levelupdialog.cpp
@@ -12,7 +12,6 @@
#include "../mwbase/soundmanager.hpp"
#include "../mwworld/class.hpp"
-#include "../mwworld/cellstore.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/npcstats.hpp"
diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp
index 6501284fd..9fab582b5 100644
--- a/apps/openmw/mwgui/loadingscreen.cpp
+++ b/apps/openmw/mwgui/loadingscreen.cpp
@@ -26,8 +26,6 @@
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/inputmanager.hpp"
-#include "../mwrender/vismask.hpp"
-
#include "backgroundimage.hpp"
namespace MWGui
@@ -103,7 +101,7 @@ namespace MWGui
Log(Debug::Warning) << "Warning: no splash screens found!";
}
- void LoadingScreen::setLabel(const std::string &label, bool important, bool center)
+ void LoadingScreen::setLabel(const std::string &label, bool important)
{
mImportantLabel = important;
@@ -113,7 +111,7 @@ namespace MWGui
size.width = std::max(300, size.width);
mLoadingBox->setSize(size);
- if (center)
+ if (MWBase::Environment::get().getWindowManager()->getMessagesCount() > 0)
mLoadingBox->setPosition(mMainWidget->getWidth()/2 - mLoadingBox->getWidth()/2, mMainWidget->getHeight()/2 - mLoadingBox->getHeight()/2);
else
mLoadingBox->setPosition(mMainWidget->getWidth()/2 - mLoadingBox->getWidth()/2, mMainWidget->getHeight() - mLoadingBox->getHeight() - 8);
diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp
index e1c652386..c64396534 100644
--- a/apps/openmw/mwgui/loadingscreen.hpp
+++ b/apps/openmw/mwgui/loadingscreen.hpp
@@ -3,7 +3,6 @@
#include
-#include
#include
#include
@@ -38,7 +37,7 @@ namespace MWGui
virtual ~LoadingScreen();
/// Overridden from Loading::Listener, see the Loading::Listener documentation for usage details
- void setLabel (const std::string& label, bool important, bool center) override;
+ void setLabel (const std::string& label, bool important) override;
void loadingOn(bool visible=true) override;
void loadingOff() override;
void setProgressRange (size_t range) override;
diff --git a/apps/openmw/mwgui/pickpocketitemmodel.cpp b/apps/openmw/mwgui/pickpocketitemmodel.cpp
index b4de5cb50..5daea8f3f 100644
--- a/apps/openmw/mwgui/pickpocketitemmodel.cpp
+++ b/apps/openmw/mwgui/pickpocketitemmodel.cpp
@@ -7,7 +7,6 @@
#include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/pickpocket.hpp"
-#include "../mwworld/containerstore.hpp"
#include "../mwworld/class.hpp"
#include "../mwbase/environment.hpp"
diff --git a/apps/openmw/mwgui/quickkeysmenu.hpp b/apps/openmw/mwgui/quickkeysmenu.hpp
index d0e950979..b2742df79 100644
--- a/apps/openmw/mwgui/quickkeysmenu.hpp
+++ b/apps/openmw/mwgui/quickkeysmenu.hpp
@@ -1,8 +1,6 @@
#ifndef MWGUI_QUICKKEYS_H
#define MWGUI_QUICKKEYS_H
-#include "../mwworld/ptr.hpp"
-
#include "windowbase.hpp"
#include "spellmodel.hpp"
diff --git a/apps/openmw/mwgui/recharge.cpp b/apps/openmw/mwgui/recharge.cpp
index f8d89c0cb..df6962c78 100644
--- a/apps/openmw/mwgui/recharge.cpp
+++ b/apps/openmw/mwgui/recharge.cpp
@@ -1,7 +1,6 @@
#include "recharge.hpp"
#include
-#include
#include
@@ -15,10 +14,10 @@
#include "../mwworld/class.hpp"
#include "../mwworld/esmstore.hpp"
-#include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/actorutil.hpp"
#include "../mwmechanics/recharge.hpp"
+#include "itemselection.hpp"
#include "itemwidget.hpp"
#include "itemchargeview.hpp"
#include "sortfilteritemmodel.hpp"
diff --git a/apps/openmw/mwgui/recharge.hpp b/apps/openmw/mwgui/recharge.hpp
index 3d469bac5..c260b1554 100644
--- a/apps/openmw/mwgui/recharge.hpp
+++ b/apps/openmw/mwgui/recharge.hpp
@@ -3,8 +3,6 @@
#include "windowbase.hpp"
-#include "itemselection.hpp"
-
namespace MWWorld
{
class Ptr;
diff --git a/apps/openmw/mwgui/repair.cpp b/apps/openmw/mwgui/repair.cpp
index ea79e0326..351b97603 100644
--- a/apps/openmw/mwgui/repair.cpp
+++ b/apps/openmw/mwgui/repair.cpp
@@ -3,8 +3,6 @@
#include
#include
-#include
-#include
#include
@@ -17,6 +15,7 @@
#include "../mwworld/containerstore.hpp"
#include "../mwworld/class.hpp"
+#include "itemselection.hpp"
#include "itemwidget.hpp"
#include "itemchargeview.hpp"
#include "sortfilteritemmodel.hpp"
diff --git a/apps/openmw/mwgui/repair.hpp b/apps/openmw/mwgui/repair.hpp
index 594ad2823..701009f54 100644
--- a/apps/openmw/mwgui/repair.hpp
+++ b/apps/openmw/mwgui/repair.hpp
@@ -3,8 +3,6 @@
#include "windowbase.hpp"
-#include "itemselection.hpp"
-
#include "../mwmechanics/repair.hpp"
namespace MWGui
diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp
index dcd94b768..26149e147 100644
--- a/apps/openmw/mwgui/spellwindow.cpp
+++ b/apps/openmw/mwgui/spellwindow.cpp
@@ -1,9 +1,7 @@
#include "spellwindow.hpp"
-#include
#include
#include
-#include
#include
#include
diff --git a/apps/openmw/mwgui/spellwindow.hpp b/apps/openmw/mwgui/spellwindow.hpp
index cf5e88f8e..786a7d877 100644
--- a/apps/openmw/mwgui/spellwindow.hpp
+++ b/apps/openmw/mwgui/spellwindow.hpp
@@ -2,7 +2,6 @@
#define MWGUI_SPELLWINDOW_H
#include "windowpinnablebase.hpp"
-#include "../mwworld/ptr.hpp"
#include "spellmodel.hpp"
diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp
index df56684f7..356945dd0 100644
--- a/apps/openmw/mwgui/tooltips.cpp
+++ b/apps/openmw/mwgui/tooltips.cpp
@@ -272,14 +272,14 @@ namespace MWGui
std::map userStrings = focus->getUserStrings();
for (auto& userStringPair : userStrings)
{
- size_t underscorePos = userStringPair.first.find("_");
+ size_t underscorePos = userStringPair.first.find('_');
if (underscorePos == std::string::npos)
continue;
std::string key = userStringPair.first.substr(0, underscorePos);
std::string widgetName = userStringPair.first.substr(underscorePos+1, userStringPair.first.size()-(underscorePos+1));
type = "Property";
- size_t caretPos = key.find("^");
+ size_t caretPos = key.find('^');
if (caretPos != std::string::npos)
{
type = key.substr(0, caretPos);
diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp
index 0ccf1a144..c88f8048c 100644
--- a/apps/openmw/mwgui/tradewindow.cpp
+++ b/apps/openmw/mwgui/tradewindow.cpp
@@ -26,7 +26,6 @@
#include "containeritemmodel.hpp"
#include "tradeitemmodel.hpp"
#include "countdialog.hpp"
-#include "controllers.hpp"
#include "tooltips.hpp"
namespace
diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp
index 18cc187c1..42a70dcfa 100644
--- a/apps/openmw/mwgui/waitdialog.cpp
+++ b/apps/openmw/mwgui/waitdialog.cpp
@@ -23,8 +23,6 @@
#include "../mwmechanics/npcstats.hpp"
#include "../mwmechanics/actorutil.hpp"
-#include "../mwstate/charactermanager.hpp"
-
namespace MWGui
{
diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp
index 21bdb261e..541fcd82b 100644
--- a/apps/openmw/mwgui/windowmanagerimp.cpp
+++ b/apps/openmw/mwgui/windowmanagerimp.cpp
@@ -9,7 +9,6 @@
#include
#include
-#include
#include
#include
#include
@@ -65,7 +64,6 @@
#include "../mwworld/cellstore.hpp"
#include "../mwworld/esmstore.hpp"
-#include "../mwmechanics/stat.hpp"
#include "../mwmechanics/npcstats.hpp"
#include "../mwmechanics/actorutil.hpp"
@@ -1061,8 +1059,9 @@ namespace MWGui
if(tag.compare(0, MyGuiPrefixLength, MyGuiPrefix) == 0)
{
tag = tag.substr(MyGuiPrefixLength, tag.length());
- std::string settingSection = tag.substr(0, tag.find(","));
- std::string settingTag = tag.substr(tag.find(",")+1, tag.length());
+ size_t comma_pos = tag.find(',');
+ std::string settingSection = tag.substr(0, comma_pos);
+ std::string settingTag = tag.substr(comma_pos+1, tag.length());
_result = Settings::Manager::getString(settingTag, settingSection);
}
diff --git a/apps/openmw/mwinput/controllermanager.cpp b/apps/openmw/mwinput/controllermanager.cpp
index 03d492c9c..abb214710 100644
--- a/apps/openmw/mwinput/controllermanager.cpp
+++ b/apps/openmw/mwinput/controllermanager.cpp
@@ -98,8 +98,8 @@ namespace MWInput
// We keep track of our own mouse position, so that moving the mouse while in
// game mode does not move the position of the GUI cursor
float uiScale = MWBase::Environment::get().getWindowManager()->getScalingFactor();
- float xMove = xAxis * dt * 1500.0f / uiScale;
- float yMove = yAxis * dt * 1500.0f / uiScale;
+ float xMove = xAxis * dt * 1500.0f / uiScale * mGamepadCursorSpeed;
+ float yMove = yAxis * dt * 1500.0f / uiScale * mGamepadCursorSpeed;
float mouseWheelMove = -zAxis * dt * 1500.0f;
if (xMove != 0 || yMove != 0 || mouseWheelMove != 0)
diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp
index 690183c57..436eab7ad 100644
--- a/apps/openmw/mwinput/inputmanagerimp.cpp
+++ b/apps/openmw/mwinput/inputmanagerimp.cpp
@@ -5,7 +5,6 @@
#include
#include
#include
-#include
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/environment.hpp"
diff --git a/apps/openmw/mwinput/keyboardmanager.hpp b/apps/openmw/mwinput/keyboardmanager.hpp
index f97f6b9e6..ca58461a2 100644
--- a/apps/openmw/mwinput/keyboardmanager.hpp
+++ b/apps/openmw/mwinput/keyboardmanager.hpp
@@ -1,7 +1,6 @@
#ifndef MWINPUT_MWKEYBOARDMANAGER_H
#define MWINPUT_MWKEYBOARDMANAGER_H
-#include
#include
namespace MWInput
diff --git a/apps/openmw/mwinput/mousemanager.cpp b/apps/openmw/mwinput/mousemanager.cpp
index 4843da5f1..7b6d00082 100644
--- a/apps/openmw/mwinput/mousemanager.cpp
+++ b/apps/openmw/mwinput/mousemanager.cpp
@@ -147,7 +147,8 @@ namespace MWInput
void MouseManager::mousePressed(const SDL_MouseButtonEvent &arg, Uint8 id)
{
- MWBase::Environment::get().getInputManager()->setJoystickLastUsed(false);
+ MWBase::InputManager* input = MWBase::Environment::get().getInputManager();
+ input->setJoystickLastUsed(false);
bool guiMode = false;
if (id == SDL_BUTTON_LEFT || id == SDL_BUTTON_RIGHT) // MyGUI only uses these mouse events
@@ -168,7 +169,8 @@ namespace MWInput
mBindingsManager->setPlayerControlsEnabled(!guiMode);
// Don't trigger any mouse bindings while in settings menu, otherwise rebinding controls becomes impossible
- if (MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_Settings)
+ // Also do not trigger bindings when input controls are disabled, e.g. during save loading
+ if (MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_Settings && !input->controlsDisabled())
mBindingsManager->mousePressed(arg, id);
}
diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp
index 4d36c717e..54f662fc2 100644
--- a/apps/openmw/mwmechanics/activespells.hpp
+++ b/apps/openmw/mwmechanics/activespells.hpp
@@ -5,7 +5,6 @@
#include
#include
-#include
#include
#include "../mwworld/timestamp.hpp"
diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp
index e9467a679..c9fa3309d 100644
--- a/apps/openmw/mwmechanics/actors.cpp
+++ b/apps/openmw/mwmechanics/actors.cpp
@@ -1,5 +1,7 @@
#include "actors.hpp"
+#include
+
#include
#include
@@ -40,7 +42,6 @@
#include "aiwander.hpp"
#include "actor.hpp"
#include "summoning.hpp"
-#include "combat.hpp"
#include "actorutil.hpp"
#include "tickableeffects.hpp"
@@ -174,6 +175,15 @@ namespace MWMechanics
static const int GREETING_COOLDOWN = 40; // how many updates should pass before NPC can continue movement
static const float DECELERATE_DISTANCE = 512.f;
+ namespace
+ {
+ float getTimeToDestination(const AiPackage& package, const osg::Vec3f& position, float speed, float duration, const osg::Vec3f& halfExtents)
+ {
+ const auto distanceToNextPathPoint = (package.getNextPathPoint(package.getDestination()) - position).length();
+ return (distanceToNextPathPoint - package.getNextPathPointTolerance(speed, duration, halfExtents)) / speed;
+ }
+ }
+
class GetStuntedMagickaDuration : public MWMechanics::EffectSourceVisitor
{
public:
@@ -590,7 +600,8 @@ namespace MWMechanics
{
greetingTimer++;
- if (greetingTimer <= GREETING_SHOULD_END || MWBase::Environment::get().getSoundManager()->sayActive(actor))
+ if (!stats.getMovementFlag(CreatureStats::Flag_ForceJump) && !stats.getMovementFlag(CreatureStats::Flag_ForceSneak)
+ && (greetingTimer <= GREETING_SHOULD_END || MWBase::Environment::get().getSoundManager()->sayActive(actor)))
turnActorToFacePlayer(actor, actorState, dir);
if (greetingTimer >= GREETING_COOLDOWN)
@@ -1228,7 +1239,7 @@ namespace MWMechanics
// Update bound effects
// Note: in vanilla MW multiple bound items of the same type can be created by different spells.
// As these extra copies are kinda useless this may or may not be important.
- static std::map boundItemsMap;
+ static std::map boundItemsMap;
if (boundItemsMap.empty())
{
boundItemsMap[ESM::MagicEffect::BoundBattleAxe] = "sMagicBoundBattleAxeID";
@@ -1244,28 +1255,30 @@ namespace MWMechanics
boundItemsMap[ESM::MagicEffect::BoundSpear] = "sMagicBoundSpearID";
}
- for (std::map::iterator it = boundItemsMap.begin(); it != boundItemsMap.end(); ++it)
+ if(ptr.getClass().hasInventoryStore(ptr))
{
- bool found = creatureStats.mBoundItems.find(it->first) != creatureStats.mBoundItems.end();
- float magnitude = effects.get(it->first).getMagnitude();
- if (found != (magnitude > 0))
+ for (const auto& [effect, itemGmst] : boundItemsMap)
{
- if (magnitude > 0)
- creatureStats.mBoundItems.insert(it->first);
- else
- creatureStats.mBoundItems.erase(it->first);
-
- std::string itemGmst = it->second;
- std::string item = MWBase::Environment::get().getWorld()->getStore().get().find(
- itemGmst)->mValue.getString();
+ bool found = creatureStats.mBoundItems.find(effect) != creatureStats.mBoundItems.end();
+ float magnitude = effects.get(effect).getMagnitude();
+ if (found != (magnitude > 0))
+ {
+ if (magnitude > 0)
+ creatureStats.mBoundItems.insert(effect);
+ else
+ creatureStats.mBoundItems.erase(effect);
- magnitude > 0 ? addBoundItem(item, ptr) : removeBoundItem(item, ptr);
+ std::string item = MWBase::Environment::get().getWorld()->getStore().get().find(
+ itemGmst)->mValue.getString();
- if (it->first == ESM::MagicEffect::BoundGloves)
- {
- item = MWBase::Environment::get().getWorld()->getStore().get().find(
- "sMagicBoundRightGauntletID")->mValue.getString();
magnitude > 0 ? addBoundItem(item, ptr) : removeBoundItem(item, ptr);
+
+ if (effect == ESM::MagicEffect::BoundGloves)
+ {
+ item = MWBase::Environment::get().getWorld()->getStore().get().find(
+ "sMagicBoundRightGauntletID")->mValue.getString();
+ magnitude > 0 ? addBoundItem(item, ptr) : removeBoundItem(item, ptr);
+ }
}
}
}
@@ -1764,7 +1777,7 @@ namespace MWMechanics
}
- void Actors::predictAndAvoidCollisions()
+ void Actors::predictAndAvoidCollisions(float duration)
{
if (!MWBase::Environment::get().getMechanicsManager()->isAIActive())
return;
@@ -1797,16 +1810,18 @@ namespace MWMechanics
// Standing NPCs give way to moving ones if they are not in combat (or pursue) mode and either
// follow player or have a AIWander package with non-empty wander area.
bool shouldAvoidCollision = isMoving;
+ bool shouldGiveWay = false;
bool shouldTurnToApproachingActor = !isMoving;
MWWorld::Ptr currentTarget; // Combat or pursue target (NPCs should not avoid collision with their targets).
- for (const auto& package : ptr.getClass().getCreatureStats(ptr).getAiSequence())
+ const auto& aiSequence = ptr.getClass().getCreatureStats(ptr).getAiSequence();
+ for (const auto& package : aiSequence)
{
if (package->getTypeId() == AiPackageTypeId::Follow)
shouldAvoidCollision = true;
else if (package->getTypeId() == AiPackageTypeId::Wander && giveWayWhenIdle)
{
if (!static_cast(package.get())->isStationary())
- shouldAvoidCollision = true;
+ shouldGiveWay = true;
}
else if (package->getTypeId() == AiPackageTypeId::Combat || package->getTypeId() == AiPackageTypeId::Pursue)
{
@@ -1816,7 +1831,7 @@ namespace MWMechanics
break;
}
}
- if (!shouldAvoidCollision)
+ if (!shouldAvoidCollision && !shouldGiveWay)
continue;
osg::Vec2f baseSpeed = origMovement * maxSpeed;
@@ -1825,7 +1840,11 @@ namespace MWMechanics
osg::Vec3f halfExtents = world->getHalfExtents(ptr);
float maxDistToCheck = isMoving ? maxDistForPartialAvoiding : maxDistForStrictAvoiding;
- float timeToCollision = maxTimeToCheck;
+ float timeToCheck = maxTimeToCheck;
+ if (!shouldGiveWay && !aiSequence.isEmpty())
+ timeToCheck = std::min(timeToCheck, getTimeToDestination(**aiSequence.begin(), basePos, maxSpeed, duration, halfExtents));
+
+ float timeToCollision = timeToCheck;
osg::Vec2f movementCorrection(0, 0);
float angleToApproachingActor = 0;
@@ -1885,7 +1904,7 @@ namespace MWMechanics
movementCorrection.y() *= 0.5f;
}
- if (timeToCollision < maxTimeToCheck)
+ if (timeToCollision < timeToCheck)
{
// Try to evade the nearest collision.
osg::Vec2f newMovement = origMovement + movementCorrection;
@@ -2075,7 +2094,7 @@ namespace MWMechanics
static const bool avoidCollisions = Settings::Manager::getBool("NPCs avoid collisions", "Game");
if (avoidCollisions)
- predictAndAvoidCollisions();
+ predictAndAvoidCollisions(duration);
timerUpdateHeadTrack += duration;
timerUpdateEquippedLight += duration;
diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp
index 2de0728d5..0ae968757 100644
--- a/apps/openmw/mwmechanics/actors.hpp
+++ b/apps/openmw/mwmechanics/actors.hpp
@@ -63,7 +63,7 @@ namespace MWMechanics
void purgeSpellEffects (int casterActorId);
- void predictAndAvoidCollisions();
+ void predictAndAvoidCollisions(float duration);
public:
diff --git a/apps/openmw/mwmechanics/aiavoiddoor.hpp b/apps/openmw/mwmechanics/aiavoiddoor.hpp
index 1781c5e4a..183f429f3 100644
--- a/apps/openmw/mwmechanics/aiavoiddoor.hpp
+++ b/apps/openmw/mwmechanics/aiavoiddoor.hpp
@@ -3,10 +3,6 @@
#include "typedaipackage.hpp"
-#include
-
-#include
-
#include "../mwworld/class.hpp"
#include "pathfinding.hpp"
diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp
index b5e7594f3..1adc2531b 100644
--- a/apps/openmw/mwmechanics/aicombat.cpp
+++ b/apps/openmw/mwmechanics/aicombat.cpp
@@ -346,7 +346,7 @@ namespace MWMechanics
bool runFallback = true;
- if (pathgrid && !actor.getClass().isPureWaterCreature(actor))
+ if (pathgrid != nullptr && !pathgrid->mPoints.empty() && !actor.getClass().isPureWaterCreature(actor))
{
ESM::Pathgrid::PointList points;
Misc::CoordinateConverter coords(storage.mCell->getCell());
@@ -715,16 +715,18 @@ osg::Vec3f AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& t
float t_collision;
float projVelDirSquared = projSpeed * projSpeed - velPerp * velPerp;
+ if (projVelDirSquared > 0)
+ {
+ osg::Vec3f vTargetMoveDirNormalized = vTargetMoveDir;
+ vTargetMoveDirNormalized.normalize();
- osg::Vec3f vTargetMoveDirNormalized = vTargetMoveDir;
- vTargetMoveDirNormalized.normalize();
-
- float projDistDiff = vDirToTarget * vTargetMoveDirNormalized; // dot product
- projDistDiff = std::sqrt(distToTarget * distToTarget - projDistDiff * projDistDiff);
+ float projDistDiff = vDirToTarget * vTargetMoveDirNormalized; // dot product
+ projDistDiff = std::sqrt(distToTarget * distToTarget - projDistDiff * projDistDiff);
- if (projVelDirSquared > 0)
t_collision = projDistDiff / (std::sqrt(projVelDirSquared) - velDir);
- else t_collision = 0; // speed of projectile is not enough to reach moving target
+ }
+ else
+ t_collision = 0; // speed of projectile is not enough to reach moving target
return vDirToTarget + vTargetMoveDir * t_collision;
}
diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp
index 0f42c6e2d..5425f1af0 100644
--- a/apps/openmw/mwmechanics/aicombat.hpp
+++ b/apps/openmw/mwmechanics/aicombat.hpp
@@ -9,7 +9,6 @@
#include "pathfinding.hpp"
#include "movement.hpp"
-#include "obstacle.hpp"
#include "aitimer.hpp"
namespace ESM
diff --git a/apps/openmw/mwmechanics/aicombataction.hpp b/apps/openmw/mwmechanics/aicombataction.hpp
index 77a19f804..d17d5a313 100644
--- a/apps/openmw/mwmechanics/aicombataction.hpp
+++ b/apps/openmw/mwmechanics/aicombataction.hpp
@@ -3,8 +3,6 @@
#include
-#include
-
#include "../mwworld/ptr.hpp"
#include "../mwworld/containerstore.hpp"
diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp
index e6aeebb24..c4ac5eb3f 100644
--- a/apps/openmw/mwmechanics/aifollow.hpp
+++ b/apps/openmw/mwmechanics/aifollow.hpp
@@ -9,8 +9,6 @@
#include "../mwworld/ptr.hpp"
-#include "pathfinding.hpp"
-
namespace ESM
{
namespace AiSequence
diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp
index 204cf2f3f..8ad944751 100644
--- a/apps/openmw/mwmechanics/aipackage.cpp
+++ b/apps/openmw/mwmechanics/aipackage.cpp
@@ -2,7 +2,6 @@
#include
#include
-#include
#include
#include
#include
@@ -15,8 +14,6 @@
#include "../mwworld/cellstore.hpp"
#include "../mwworld/inventorystore.hpp"
-#include "../mwphysics/collisiontype.hpp"
-
#include "pathgrid.hpp"
#include "creaturestats.hpp"
#include "movement.hpp"
@@ -31,6 +28,12 @@ namespace
{
return divisor == 0 ? std::numeric_limits::max() * std::numeric_limits::epsilon() : dividend / divisor;
}
+
+ float getPointTolerance(float speed, float duration, const osg::Vec3f& halfExtents)
+ {
+ const float actorTolerance = 2 * speed * duration + 1.2 * std::max(halfExtents.x(), halfExtents.y());
+ return std::max(MWMechanics::MIN_TOLERANCE, actorTolerance);
+ }
}
MWMechanics::AiPackage::AiPackage(AiPackageTypeId typeId, const Options& options) :
@@ -52,6 +55,11 @@ MWWorld::Ptr MWMechanics::AiPackage::getTarget() const
if (mTargetActorId == -1)
{
+ if (mTargetActorRefId.empty())
+ {
+ mTargetActorId = -2;
+ return MWWorld::Ptr();
+ }
MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mTargetActorRefId, false);
if (target.isEmpty())
{
@@ -101,6 +109,8 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
return false;
}
+ mLastDestinationTolerance = destTolerance;
+
const float distToTarget = distance(position, dest);
const bool isDestReached = (distToTarget <= destTolerance);
const bool actorCanMoveByZ = canActorMoveByZAxis(actor);
@@ -151,13 +161,12 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
}
}
- const float actorTolerance = 2 * actor.getClass().getMaxSpeed(actor) * duration
- + 1.2 * std::max(halfExtents.x(), halfExtents.y());
- const float pointTolerance = std::max(MIN_TOLERANCE, actorTolerance);
+ const float pointTolerance = getPointTolerance(actor.getClass().getMaxSpeed(actor), duration, halfExtents);
static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game");
mPathFinder.update(position, pointTolerance, DEFAULT_TOLERANCE,
- /*shortenIfAlmostStraight=*/smoothMovement, actorCanMoveByZ);
+ /*shortenIfAlmostStraight=*/smoothMovement, actorCanMoveByZ,
+ halfExtents, getNavigatorFlags(actor));
if (isDestReached || mPathFinder.checkPathCompleted()) // if path is finished
{
@@ -184,7 +193,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
zTurn(actor, zAngleToNext);
smoothTurn(actor, mPathFinder.getXAngleToNext(position.x(), position.y(), position.z()), 0);
- const auto destination = mPathFinder.getPath().empty() ? dest : mPathFinder.getPath().front();
+ const auto destination = getNextPathPoint(dest);
mObstacleCheck.update(actor, destination, duration);
if (smoothMovement)
@@ -464,3 +473,15 @@ DetourNavigator::AreaCosts MWMechanics::AiPackage::getAreaCosts(const MWWorld::P
return costs;
}
+
+osg::Vec3f MWMechanics::AiPackage::getNextPathPoint(const osg::Vec3f& destination) const
+{
+ return mPathFinder.getPath().empty() ? destination : mPathFinder.getPath().front();
+}
+
+float MWMechanics::AiPackage::getNextPathPointTolerance(float speed, float duration, const osg::Vec3f& halfExtents) const
+{
+ if (mPathFinder.getPathSize() <= 1)
+ return std::max(DEFAULT_TOLERANCE, mLastDestinationTolerance);
+ return getPointTolerance(speed, duration, halfExtents);
+}
diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp
index 81b09c8b9..6d8af0d92 100644
--- a/apps/openmw/mwmechanics/aipackage.hpp
+++ b/apps/openmw/mwmechanics/aipackage.hpp
@@ -3,7 +3,6 @@
#include
-#include
#include
#include "pathfinding.hpp"
@@ -123,6 +122,10 @@ namespace MWMechanics
/// Return if actor's rotation speed is sufficient to rotate to the destination pathpoint on the run. Otherwise actor should rotate while standing.
static bool isReachableRotatingOnTheRun(const MWWorld::Ptr& actor, const osg::Vec3f& dest);
+ osg::Vec3f getNextPathPoint(const osg::Vec3f& destination) const;
+
+ float getNextPathPointTolerance(float speed, float duration, const osg::Vec3f& halfExtents) const;
+
protected:
/// Handles path building and shortcutting with obstacles avoiding
/** \return If the actor has arrived at his destination **/
@@ -167,6 +170,7 @@ namespace MWMechanics
bool mIsShortcutting; // if shortcutting at the moment
bool mShortcutProhibited; // shortcutting may be prohibited after unsuccessful attempt
osg::Vec3f mShortcutFailPos; // position of last shortcut fail
+ float mLastDestinationTolerance = 0;
private:
bool isNearInactiveCell(osg::Vec3f position);
diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp
index 5af73887c..05605529a 100644
--- a/apps/openmw/mwmechanics/aipursue.cpp
+++ b/apps/openmw/mwmechanics/aipursue.cpp
@@ -8,7 +8,6 @@
#include "../mwbase/world.hpp"
#include "../mwworld/class.hpp"
-#include "../mwworld/action.hpp"
#include "movement.hpp"
#include "creaturestats.hpp"
diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp
index 575a03434..fada7761d 100644
--- a/apps/openmw/mwmechanics/aisequence.cpp
+++ b/apps/openmw/mwmechanics/aisequence.cpp
@@ -5,8 +5,6 @@
#include
#include
-#include "../mwbase/world.hpp"
-
#include "aipackage.hpp"
#include "aistate.hpp"
#include "aiwander.hpp"
diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp
index b2a506ca6..8e5372c46 100644
--- a/apps/openmw/mwmechanics/aitravel.cpp
+++ b/apps/openmw/mwmechanics/aitravel.cpp
@@ -52,14 +52,15 @@ namespace MWMechanics
bool AiTravel::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration)
{
MWBase::MechanicsManager* mechMgr = MWBase::Environment::get().getMechanicsManager();
+ auto& stats = actor.getClass().getCreatureStats(actor);
- if (mechMgr->isTurningToPlayer(actor) || mechMgr->getGreetingState(actor) == Greet_InProgress)
+ if (!stats.getMovementFlag(CreatureStats::Flag_ForceJump) && !stats.getMovementFlag(CreatureStats::Flag_ForceSneak)
+ && (mechMgr->isTurningToPlayer(actor) || mechMgr->getGreetingState(actor) == Greet_InProgress))
return false;
const osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
const osg::Vec3f targetPos(mX, mY, mZ);
- auto& stats = actor.getClass().getCreatureStats(actor);
stats.setMovementFlag(CreatureStats::Flag_Run, false);
stats.setDrawState(DrawState_Nothing);
diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp
index 72b8757bf..175836b11 100644
--- a/apps/openmw/mwmechanics/aiwander.cpp
+++ b/apps/openmw/mwmechanics/aiwander.cpp
@@ -11,7 +11,6 @@
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
-#include "../mwbase/dialoguemanager.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/esmstore.hpp"
@@ -210,14 +209,17 @@ namespace MWMechanics
storage.setState(AiWanderStorage::Wander_Walking);
}
- GreetingState greetingState = MWBase::Environment::get().getMechanicsManager()->getGreetingState(actor);
- if (greetingState == Greet_InProgress)
+ if(!cStats.getMovementFlag(CreatureStats::Flag_ForceJump) && !cStats.getMovementFlag(CreatureStats::Flag_ForceSneak))
{
- if (storage.mState == AiWanderStorage::Wander_Walking)
+ GreetingState greetingState = MWBase::Environment::get().getMechanicsManager()->getGreetingState(actor);
+ if (greetingState == Greet_InProgress)
{
- stopMovement(actor);
- mObstacleCheck.clear();
- storage.setState(AiWanderStorage::Wander_IdleNow);
+ if (storage.mState == AiWanderStorage::Wander_Walking)
+ {
+ stopMovement(actor);
+ mObstacleCheck.clear();
+ storage.setState(AiWanderStorage::Wander_IdleNow);
+ }
}
}
@@ -752,6 +754,9 @@ namespace MWMechanics
const ESM::Pathgrid *pathgrid =
MWBase::Environment::get().getWorld()->getStore().get().search(*currentCell->getCell());
+ if (pathgrid == nullptr || pathgrid->mPoints.empty())
+ return;
+
int index = PathFinder::getClosestPoint(pathgrid, PathFinder::makeOsgVec3(dest));
getPathGridGraph(currentCell).getNeighbouringPoints(index, points);
diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp
index 52a926d14..f8506ff59 100644
--- a/apps/openmw/mwmechanics/aiwander.hpp
+++ b/apps/openmw/mwmechanics/aiwander.hpp
@@ -5,8 +5,6 @@
#include
-#include "../mwworld/timestamp.hpp"
-
#include "pathfinding.hpp"
#include "obstacle.hpp"
#include "aistate.hpp"
@@ -128,7 +126,6 @@ namespace MWMechanics
short unsigned getRandomIdle();
void setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos);
void evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage);
- void turnActorToFacePlayer(const osg::Vec3f& actorPosition, const osg::Vec3f& playerPosition, AiWanderStorage& storage);
void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage);
void onIdleStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage);
void onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage);
diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp
index 6d06724f0..61fec3b54 100644
--- a/apps/openmw/mwmechanics/alchemy.cpp
+++ b/apps/openmw/mwmechanics/alchemy.cpp
@@ -21,7 +21,6 @@
#include "../mwworld/containerstore.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/cellstore.hpp"
-#include "../mwworld/manualref.hpp"
#include "magiceffects.hpp"
#include "creaturestats.hpp"
diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp
index 342b4ca39..c7c77cf2d 100644
--- a/apps/openmw/mwmechanics/character.cpp
+++ b/apps/openmw/mwmechanics/character.cpp
@@ -973,7 +973,7 @@ void CharacterController::handleTextKey(const std::string &groupname, SceneUtil:
// The event can optionally contain volume and pitch modifiers
float volume=1.f, pitch=1.f;
- if (soundgen.find(" ") != std::string::npos)
+ if (soundgen.find(' ') != std::string::npos)
{
std::vector tokens;
split(soundgen, ' ', tokens);
@@ -1887,8 +1887,10 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
MWWorld::ConstContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name()
&& updateCarriedLeftVisible(mWeaponType))
-
{
+ if (mAnimation->isPlaying("shield"))
+ mAnimation->disable("shield");
+
mAnimation->play("torch", Priority_Torch, MWRender::Animation::BlendMask_LeftArm,
false, 1.0f, "start", "stop", 0.0f, (~(size_t)0), true);
}
@@ -2414,9 +2416,6 @@ void CharacterController::update(float duration)
if (!mMovementAnimationControlled)
world->queueMovement(mPtr, vec);
}
- else
- // We must always queue movement, even if there is none, to apply gravity.
- world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f));
movement = vec;
movementSettings.mPosition[0] = movementSettings.mPosition[1] = 0;
@@ -2438,8 +2437,6 @@ void CharacterController::update(float duration)
if (cls.isPersistent(mPtr) || cls.getCreatureStats(mPtr).isDeathAnimationFinished())
playDeath(1.f, mDeathState);
}
- // We must always queue movement, even if there is none, to apply gravity.
- world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f));
}
bool isPersist = isPersistentAnimPlaying();
@@ -2472,7 +2469,7 @@ void CharacterController::update(float duration)
if (mFloatToSurface && cls.isActor())
{
if (cls.getCreatureStats(mPtr).isDead()
- || (!godmode && cls.getCreatureStats(mPtr).isParalyzed()))
+ || (!godmode && cls.getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0))
{
moved.z() = 1.0;
}
diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp
index 0821b3225..3f7fa4e1b 100644
--- a/apps/openmw/mwmechanics/character.hpp
+++ b/apps/openmw/mwmechanics/character.hpp
@@ -3,8 +3,6 @@
#include
-#include
-
#include "../mwworld/ptr.hpp"
#include "../mwworld/containerstore.hpp"
diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp
index 1d5fe8347..c6561af96 100644
--- a/apps/openmw/mwmechanics/creaturestats.cpp
+++ b/apps/openmw/mwmechanics/creaturestats.cpp
@@ -11,7 +11,6 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
-#include "../mwbase/mechanicsmanager.hpp"
namespace MWMechanics
{
@@ -602,6 +601,28 @@ namespace MWMechanics
mAiSequence.readState(state.mAiSequence);
mMagicEffects.readState(state.mMagicEffects);
+ // Rebuild the bound item cache
+ for(int effectId = ESM::MagicEffect::BoundDagger; effectId <= ESM::MagicEffect::BoundGloves; effectId++)
+ {
+ if(mMagicEffects.get(effectId).getMagnitude() > 0)
+ mBoundItems.insert(effectId);
+ else
+ {
+ // Check active spell effects
+ // We can't use mActiveSpells::getMagicEffects here because it doesn't include expired effects
+ auto spell = std::find_if(mActiveSpells.begin(), mActiveSpells.end(), [&] (const auto& spell)
+ {
+ const auto& effects = spell.second.mEffects;
+ return std::find_if(effects.begin(), effects.end(), [&] (const auto& effect)
+ {
+ return effect.mEffectId == effectId;
+ }) != effects.end();
+ });
+ if(spell != mActiveSpells.end())
+ mBoundItems.insert(effectId);
+ }
+ }
+
mSummonedCreatures = state.mSummonedCreatureMap;
mSummonGraveyard = state.mSummonGraveyard;
diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp
index 781b897a7..0e704cd46 100644
--- a/apps/openmw/mwmechanics/pathfinding.cpp
+++ b/apps/openmw/mwmechanics/pathfinding.cpp
@@ -3,7 +3,6 @@
#include
#include
-#include
#include
#include
#include
@@ -106,6 +105,19 @@ namespace
return checkAngle && checkDist;
}
+
+ struct IsValidShortcut
+ {
+ const DetourNavigator::Navigator* mNavigator;
+ const osg::Vec3f mHalfExtents;
+ const DetourNavigator::Flags mFlags;
+
+ bool operator()(const osg::Vec3f& start, const osg::Vec3f& end) const
+ {
+ const auto position = mNavigator->raycast(mHalfExtents, start, end, mFlags);
+ return position.has_value() && std::abs((position.value() - start).length2() - (end - start).length2()) <= 1;
+ }
+ };
}
namespace MWMechanics
@@ -194,9 +206,6 @@ namespace MWMechanics
endPointInLocalCoords,
startNode);
- if (!endNode.second)
- return;
-
// if it's shorter for actor to travel from start to end, than to travel from either
// start or end to nearest pathgrid point, just travel from start to end.
float startToEndLength2 = (endPointInLocalCoords - startPointInLocalCoords).length2();
@@ -267,7 +276,8 @@ namespace MWMechanics
// unreachable pathgrid point.
//
// The AI routines will have to deal with such situations.
- *out++ = endPoint;
+ if (endNode.second)
+ *out++ = endPoint;
}
float PathFinder::getZAngleToNext(float x, float y) const
@@ -297,7 +307,8 @@ namespace MWMechanics
}
void PathFinder::update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance,
- bool shortenIfAlmostStraight, bool canMoveByZ)
+ bool shortenIfAlmostStraight, bool canMoveByZ, const osg::Vec3f& halfExtents,
+ const DetourNavigator::Flags flags)
{
if (mPath.empty())
return;
@@ -307,9 +318,15 @@ namespace MWMechanics
if (shortenIfAlmostStraight)
{
- while (mPath.size() > 2 && isAlmostStraight(mPath[0], mPath[1], mPath[2], pointTolerance))
+ const IsValidShortcut isValidShortcut {
+ MWBase::Environment::get().getWorld()->getNavigator(),
+ halfExtents, flags
+ };
+ while (mPath.size() > 2 && isAlmostStraight(mPath[0], mPath[1], mPath[2], pointTolerance)
+ && isValidShortcut(mPath[0], mPath[2]))
mPath.erase(mPath.begin() + 1);
- if (mPath.size() > 1 && isAlmostStraight(position, mPath[0], mPath[1], pointTolerance))
+ if (mPath.size() > 1 && isAlmostStraight(position, mPath[0], mPath[1], pointTolerance)
+ && isValidShortcut(position, mPath[1]))
mPath.pop_front();
}
@@ -350,7 +367,13 @@ namespace MWMechanics
mPath.clear();
// If it's not possible to build path over navmesh due to disabled navmesh generation fallback to straight path
- if (!buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags, areaCosts, std::back_inserter(mPath)))
+ DetourNavigator::Status status = buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags,
+ areaCosts, std::back_inserter(mPath));
+
+ if (status != DetourNavigator::Status::Success)
+ mPath.clear();
+
+ if (status == DetourNavigator::Status::NavMeshNotFound)
mPath.push_back(endPoint);
mConstructed = !mPath.empty();
@@ -363,25 +386,33 @@ namespace MWMechanics
mPath.clear();
mCell = cell;
- bool hasNavMesh = false;
+ DetourNavigator::Status status = DetourNavigator::Status::NavMeshNotFound;
if (!actor.getClass().isPureWaterCreature(actor) && !actor.getClass().isPureFlyingCreature(actor))
- hasNavMesh = buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags, areaCosts, std::back_inserter(mPath));
+ {
+ status = buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags, areaCosts, std::back_inserter(mPath));
+ if (status != DetourNavigator::Status::Success)
+ mPath.clear();
+ }
- if (hasNavMesh && mPath.empty())
- buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents,
+ if (status != DetourNavigator::Status::NavMeshNotFound && mPath.empty())
+ {
+ status = buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents,
flags | DetourNavigator::Flag_usePathgrid, areaCosts, std::back_inserter(mPath));
+ if (status != DetourNavigator::Status::Success)
+ mPath.clear();
+ }
if (mPath.empty())
buildPathByPathgridImpl(startPoint, endPoint, pathgridGraph, std::back_inserter(mPath));
- if (!hasNavMesh && mPath.empty())
+ if (status == DetourNavigator::Status::NavMeshNotFound && mPath.empty())
mPath.push_back(endPoint);
mConstructed = !mPath.empty();
}
- bool PathFinder::buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
+ DetourNavigator::Status PathFinder::buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags,
const DetourNavigator::AreaCosts& areaCosts, std::back_insert_iterator> out)
{
@@ -390,9 +421,6 @@ namespace MWMechanics
const auto navigator = world->getNavigator();
const auto status = navigator->findPath(halfExtents, stepSize, startPoint, endPoint, flags, areaCosts, out);
- if (status == DetourNavigator::Status::NavMeshNotFound)
- return false;
-
if (status != DetourNavigator::Status::Success)
{
Log(Debug::Debug) << "Build path by navigator error: \"" << DetourNavigator::getMessage(status)
@@ -401,7 +429,7 @@ namespace MWMechanics
<< DetourNavigator::WriteFlags {flags} << ")";
}
- return true;
+ return status;
}
void PathFinder::buildPathByNavMeshToNextPoint(const MWWorld::ConstPtr& actor, const osg::Vec3f& halfExtents,
diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp
index ed88a57ca..0d281d1e2 100644
--- a/apps/openmw/mwmechanics/pathfinding.hpp
+++ b/apps/openmw/mwmechanics/pathfinding.hpp
@@ -7,6 +7,7 @@
#include
#include
+#include
#include
#include
@@ -107,7 +108,8 @@ namespace MWMechanics
/// Remove front point if exist and within tolerance
void update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance,
- bool shortenIfAlmostStraight, bool canMoveByZ);
+ bool shortenIfAlmostStraight, bool canMoveByZ, const osg::Vec3f& halfExtents,
+ const DetourNavigator::Flags flags);
bool checkPathCompleted() const
{
@@ -208,9 +210,10 @@ namespace MWMechanics
void buildPathByPathgridImpl(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
const PathgridGraph& pathgridGraph, std::back_insert_iterator> out);
- bool buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
- const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags,
- const DetourNavigator::AreaCosts& areaCosts, std::back_insert_iterator> out);
+ [[nodiscard]] DetourNavigator::Status buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor,
+ const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents,
+ const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts,
+ std::back_insert_iterator> out);
};
}
diff --git a/apps/openmw/mwmechanics/spellabsorption.cpp b/apps/openmw/mwmechanics/spellabsorption.cpp
index bab290fda..1a2dfe65a 100644
--- a/apps/openmw/mwmechanics/spellabsorption.cpp
+++ b/apps/openmw/mwmechanics/spellabsorption.cpp
@@ -44,14 +44,14 @@ namespace MWMechanics
}
};
- bool absorbSpell (const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target)
+ int getAbsorbChance(const MWWorld::Ptr& caster, const MWWorld::Ptr& target)
{
- if (spellId.empty() || target.isEmpty() || caster == target || !target.getClass().isActor())
- return false;
+ if(target.isEmpty() || caster == target || !target.getClass().isActor())
+ return 0;
CreatureStats& stats = target.getClass().getCreatureStats(target);
if (stats.getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude() <= 0.f)
- return false;
+ return 0;
GetAbsorptionProbability check;
stats.getActiveSpells().visitEffectSources(check);
@@ -59,9 +59,12 @@ namespace MWMechanics
if (target.getClass().hasInventoryStore(target))
target.getClass().getInventoryStore(target).visitEffectSources(check);
- int chance = check.mProbability * 100;
- if (Misc::Rng::roll0to99() >= chance)
- return false;
+ return check.mProbability * 100;
+ }
+
+ void absorbSpell (const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target)
+ {
+ CreatureStats& stats = target.getClass().getCreatureStats(target);
const auto& esmStore = MWBase::Environment::get().getWorld()->getStore();
const ESM::Static* absorbStatic = esmStore.get().find("VFX_Absorb");
@@ -85,7 +88,6 @@ namespace MWMechanics
DynamicStat magicka = stats.getMagicka();
magicka.setCurrent(magicka.getCurrent() + spellCost);
stats.setMagicka(magicka);
- return true;
}
}
diff --git a/apps/openmw/mwmechanics/spellabsorption.hpp b/apps/openmw/mwmechanics/spellabsorption.hpp
index 0fe501df9..5537483a4 100644
--- a/apps/openmw/mwmechanics/spellabsorption.hpp
+++ b/apps/openmw/mwmechanics/spellabsorption.hpp
@@ -10,8 +10,9 @@ namespace MWWorld
namespace MWMechanics
{
- // Try to absorb a spell based on the magnitude of every Spell Absorption effect source on the target.
- bool absorbSpell(const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target);
+ void absorbSpell(const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target);
+ // Calculate the chance to absorb a spell based on the magnitude of every Spell Absorption effect source on the target.
+ int getAbsorbChance(const MWWorld::Ptr& caster, const MWWorld::Ptr& target);
}
#endif
diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp
index 142914c35..0011515ba 100644
--- a/apps/openmw/mwmechanics/spellcasting.cpp
+++ b/apps/openmw/mwmechanics/spellcasting.cpp
@@ -60,10 +60,8 @@ namespace MWMechanics
void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster,
const ESM::EffectList &effects, ESM::RangeType range, bool reflected, bool exploded)
{
- if (target.isEmpty())
- return;
-
- if (target.getClass().isActor())
+ const bool targetIsActor = !target.isEmpty() && target.getClass().isActor();
+ if (targetIsActor)
{
// Early-out for characters that have departed.
const auto& stats = target.getClass().getCreatureStats(target);
@@ -85,7 +83,7 @@ namespace MWMechanics
return;
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search (mId);
- if (spell && target.getClass().isActor() && (spell->mData.mType == ESM::Spell::ST_Disease || spell->mData.mType == ESM::Spell::ST_Blight))
+ if (spell && targetIsActor && (spell->mData.mType == ESM::Spell::ST_Disease || spell->mData.mType == ESM::Spell::ST_Blight))
{
int requiredResistance = (spell->mData.mType == ESM::Spell::ST_Disease) ?
ESM::MagicEffect::ResistCommonDisease
@@ -108,25 +106,24 @@ namespace MWMechanics
// This is required for Weakness effects in a spell to apply to any subsequent effects in the spell.
// Otherwise, they'd only apply after the whole spell was added.
MagicEffects targetEffects;
- if (target.getClass().isActor())
+ if (targetIsActor)
targetEffects += target.getClass().getCreatureStats(target).getMagicEffects();
bool castByPlayer = (!caster.isEmpty() && caster == getPlayer());
ActiveSpells targetSpells;
- if (target.getClass().isActor())
+ if (targetIsActor)
targetSpells = target.getClass().getCreatureStats(target).getActiveSpells();
bool canCastAnEffect = false; // For bound equipment.If this remains false
// throughout the iteration of this spell's
// effects, we display a "can't re-cast" message
- // Try absorbing the spell. Some handling must still happen for absorbed effects.
- bool absorbed = absorbSpell(mId, caster, target);
+ int absorbChance = getAbsorbChance(caster, target);
int currentEffectIndex = 0;
for (std::vector::const_iterator effectIt (effects.mList.begin());
- effectIt != effects.mList.end(); ++effectIt, ++currentEffectIndex)
+ !target.isEmpty() && effectIt != effects.mList.end(); ++effectIt, ++currentEffectIndex)
{
if (effectIt->mRange != range)
continue;
@@ -144,6 +141,13 @@ namespace MWMechanics
}
canCastAnEffect = true;
+ // Try absorbing the effect
+ if(absorbChance && Misc::Rng::roll0to99() < absorbChance)
+ {
+ absorbSpell(mId, caster, target);
+ continue;
+ }
+
if (!checkEffectTarget(effectIt->mEffectID, target, caster, castByPlayer))
continue;
@@ -157,10 +161,6 @@ namespace MWMechanics
if (target.getClass().isActor() && target != caster && !caster.isEmpty() && isHarmful)
target.getClass().onHit(target, 0.0f, true, MWWorld::Ptr(), caster, osg::Vec3f(), true);
- // Avoid proceeding further for absorbed spells.
- if (absorbed)
- continue;
-
// Reflect harmful effects
if (!reflected && reflectEffect(*effectIt, magicEffect, caster, target, reflectedEffects))
continue;
@@ -270,7 +270,7 @@ namespace MWMechanics
}
// Re-casting a summon effect will remove the creature from previous castings of that effect.
- if (isSummoningEffect(effectIt->mEffectID) && target.getClass().isActor())
+ if (isSummoningEffect(effectIt->mEffectID) && targetIsActor)
{
CreatureStats& targetStats = target.getClass().getCreatureStats(target);
ESM::SummonKey key(effectIt->mEffectID, mId, currentEffectIndex);
@@ -313,16 +313,19 @@ namespace MWMechanics
if (!exploded)
MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, effects, caster, target, range, mId, mSourceName, mFromProjectile);
- if (!reflectedEffects.mList.empty())
- inflict(caster, target, reflectedEffects, range, true, exploded);
-
- if (!appliedLastingEffects.empty())
+ if (!target.isEmpty())
{
- int casterActorId = -1;
- if (!caster.isEmpty() && caster.getClass().isActor())
- casterActorId = caster.getClass().getCreatureStats(caster).getActorId();
- target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects,
- mSourceName, casterActorId);
+ if (!reflectedEffects.mList.empty())
+ inflict(caster, target, reflectedEffects, range, true, exploded);
+
+ if (!appliedLastingEffects.empty())
+ {
+ int casterActorId = -1;
+ if (!caster.isEmpty() && caster.getClass().isActor())
+ casterActorId = caster.getClass().getCreatureStats(caster).getActorId();
+ target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects,
+ mSourceName, casterActorId);
+ }
}
}
diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp
index 45431bbc6..cc525d2db 100644
--- a/apps/openmw/mwmechanics/spellcasting.hpp
+++ b/apps/openmw/mwmechanics/spellcasting.hpp
@@ -2,7 +2,6 @@
#define MWMECHANICS_SPELLCASTING_H
#include
-#include
#include "../mwworld/ptr.hpp"
diff --git a/apps/openmw/mwmechanics/spelllist.cpp b/apps/openmw/mwmechanics/spelllist.cpp
index d8fbcf25a..9328d533e 100644
--- a/apps/openmw/mwmechanics/spelllist.cpp
+++ b/apps/openmw/mwmechanics/spelllist.cpp
@@ -3,7 +3,6 @@
#include
#include
-#include
#include "spells.hpp"
@@ -71,7 +70,7 @@ namespace MWMechanics
auto& id = spell->mId;
bool changed = withBaseRecord([&] (auto& spells)
{
- for(auto it : spells)
+ for(const auto& it : spells)
{
if(Misc::StringUtils::ciEqual(id, it))
return false;
diff --git a/apps/openmw/mwmechanics/spelllist.hpp b/apps/openmw/mwmechanics/spelllist.hpp
index c95ee812b..5920949d6 100644
--- a/apps/openmw/mwmechanics/spelllist.hpp
+++ b/apps/openmw/mwmechanics/spelllist.hpp
@@ -9,8 +9,6 @@
#include
-#include "magiceffects.hpp"
-
namespace ESM
{
struct SpellState;
diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp
index 81658193d..bd9c5f7cb 100644
--- a/apps/openmw/mwmechanics/spellpriority.cpp
+++ b/apps/openmw/mwmechanics/spellpriority.cpp
@@ -17,7 +17,6 @@
#include "creaturestats.hpp"
#include "spellresistance.hpp"
#include "weapontype.hpp"
-#include "combat.hpp"
#include "summoning.hpp"
#include "spellutil.hpp"
diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp
index 055339795..9737b72cd 100644
--- a/apps/openmw/mwmechanics/spells.hpp
+++ b/apps/openmw/mwmechanics/spells.hpp
@@ -4,7 +4,6 @@
#include
#include