mirror of
https://github.com/OpenMW/openmw.git
synced 2025-02-06 22:45:38 +00:00
Merge branch 'master' into HEAD
This commit is contained in:
commit
3fe7106668
365 changed files with 76943 additions and 1704 deletions
|
@ -15,11 +15,11 @@ Debian:
|
||||||
- apt-get update -yq
|
- apt-get update -yq
|
||||||
- apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libsdl2-dev libqt4-dev libopenal-dev libopenscenegraph-3.4-dev libunshield-dev libtinyxml-dev
|
- apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libsdl2-dev libqt4-dev libopenal-dev libopenscenegraph-3.4-dev libunshield-dev libtinyxml-dev
|
||||||
# - apt-get install -y libmygui-dev libbullet-dev # to be updated to latest below because stretch is too old
|
# - apt-get install -y libmygui-dev libbullet-dev # to be updated to latest below because stretch is too old
|
||||||
- curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet-dev_2.87+dfsg-2_amd64.deb -o libbullet-dev_2.87+dfsg-2_amd64.deb
|
- curl -L http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet-dev_2.87+dfsg-2_amd64.deb -o libbullet-dev_2.87+dfsg-2_amd64.deb
|
||||||
- curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet2.87_2.87+dfsg-2_amd64.deb -o libbullet2.87_2.87+dfsg-2_amd64.deb
|
- curl -L http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet2.87_2.87+dfsg-2_amd64.deb -o libbullet2.87_2.87+dfsg-2_amd64.deb
|
||||||
- curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb -o libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb
|
- curl -L https://http.kali.org/pool/main/m/mygui/libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb -o libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb
|
||||||
- curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb -o libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb
|
- curl -L https://http.kali.org/pool/main/m/mygui/libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb -o libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb
|
||||||
- curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui-dev_3.2.2+dfsg-1_amd64.deb -o libmygui-dev_3.2.2+dfsg-1_amd64.deb
|
- curl -L https://http.kali.org/pool/main/m/mygui/libmygui-dev_3.2.2+dfsg-1_amd64.deb -o libmygui-dev_3.2.2+dfsg-1_amd64.deb
|
||||||
- dpkg --ignore-depends=libmygui.ogreplatform0debian1v5 -i *.deb
|
- dpkg --ignore-depends=libmygui.ogreplatform0debian1v5 -i *.deb
|
||||||
stage: build
|
stage: build
|
||||||
script:
|
script:
|
||||||
|
|
54
.travis.yml
54
.travis.yml
|
@ -1,10 +1,4 @@
|
||||||
os:
|
|
||||||
- linux
|
|
||||||
- osx
|
|
||||||
osx_image: xcode9.4
|
|
||||||
language: cpp
|
language: cpp
|
||||||
sudo: required
|
|
||||||
dist: trusty
|
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
- master
|
- master
|
||||||
|
@ -20,16 +14,16 @@ addons:
|
||||||
sources:
|
sources:
|
||||||
- sourceline: 'ppa:openmw/openmw'
|
- sourceline: 'ppa:openmw/openmw'
|
||||||
- ubuntu-toolchain-r-test
|
- ubuntu-toolchain-r-test
|
||||||
- llvm-toolchain-precise-3.6
|
- llvm-toolchain-trusty-7
|
||||||
packages: [
|
packages: [
|
||||||
# Dev
|
# Dev
|
||||||
cmake, clang-3.6, libunshield-dev, libtinyxml-dev,
|
cmake, clang-7, clang-tools-7, gcc-8, g++-8,
|
||||||
# Boost
|
# Boost
|
||||||
libboost-filesystem-dev, libboost-program-options-dev, libboost-system-dev,
|
libboost-filesystem-dev, libboost-program-options-dev, libboost-system-dev,
|
||||||
# FFmpeg
|
# FFmpeg
|
||||||
libavcodec-dev, libavformat-dev, libavutil-dev, libswscale-dev,
|
libavcodec-dev, libavformat-dev, libavutil-dev, libswscale-dev,
|
||||||
# Audio & Video
|
# Audio, Video and Misc. deps
|
||||||
libsdl2-dev, libqt4-dev, libopenal-dev,
|
libsdl2-dev, libqt4-dev, libopenal-dev, libunshield-dev, libtinyxml-dev,
|
||||||
# The other ones from OpenMW ppa
|
# The other ones from OpenMW ppa
|
||||||
libbullet-dev, libswresample-dev, libopenscenegraph-3.4-dev, libmygui-dev
|
libbullet-dev, libswresample-dev, libopenscenegraph-3.4-dev, libmygui-dev
|
||||||
]
|
]
|
||||||
|
@ -44,18 +38,44 @@ addons:
|
||||||
branch_pattern: coverity_scan
|
branch_pattern: coverity_scan
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: linux
|
- name: OpenMW (all) on MacOS xcode9.4
|
||||||
|
os: osx
|
||||||
|
osx_image: xcode9.4
|
||||||
|
- name: OpenMW (all) on Ubuntu Trusty GCC-8
|
||||||
|
os: linux
|
||||||
|
dist: trusty
|
||||||
|
sudo: required
|
||||||
env:
|
env:
|
||||||
ANALYZE="scan-build-3.6 --use-cc clang-3.6 --use-c++ clang++-3.6 "
|
- MATRIX_EVAL="CC=gcc-8 && CXX=g++-8"
|
||||||
|
- name: OpenMW (openmw) on Ubuntu Trusty Clang-7 with Static Analysis
|
||||||
|
os: linux
|
||||||
|
dist: trusty
|
||||||
|
sudo: required
|
||||||
|
env:
|
||||||
|
- MATRIX_EVAL="CC=clang-7 && CXX=clang++-7"
|
||||||
|
- ANALYZE="scan-build-7 --use-cc clang-7 --use-c++ clang++-7"
|
||||||
|
- BUILD_OPENMW_CS="OFF"
|
||||||
compiler: clang
|
compiler: clang
|
||||||
allow_failures:
|
- name: OpenMW (openmw-cs) on Ubuntu Trusty Clang-7 with Static Analysis
|
||||||
- env: ANALYZE="scan-build-3.6 --use-cc clang-3.6 --use-c++ clang++-3.6 "
|
os: linux
|
||||||
|
dist: trusty
|
||||||
|
sudo: required
|
||||||
|
env:
|
||||||
|
- MATRIX_EVAL="CC=clang-7 && CXX=clang++-7"
|
||||||
|
- ANALYZE="scan-build-7 --use-cc clang-7 --use-c++ clang++-7"
|
||||||
|
- BUILD_OPENMW="OFF"
|
||||||
|
compiler: clang
|
||||||
|
# allow_failures:
|
||||||
|
# - name: OpenMW (openmw) on Ubuntu Trusty Clang-7 with Static Analysis
|
||||||
|
|
||||||
before_install: ./CI/before_install.${TRAVIS_OS_NAME}.sh
|
before_install:
|
||||||
before_script: ./CI/before_script.${TRAVIS_OS_NAME}.sh
|
- if [ "${TRAVIS_OS_NAME}" = "linux" ]; then eval "${MATRIX_EVAL}"; fi
|
||||||
|
- ./CI/before_install.${TRAVIS_OS_NAME}.sh
|
||||||
|
before_script:
|
||||||
|
- ./CI/before_script.${TRAVIS_OS_NAME}.sh
|
||||||
script:
|
script:
|
||||||
- cd ./build
|
- cd ./build
|
||||||
- if [ "$COVERITY_SCAN_BRANCH" != 1 ]; then ${ANALYZE}make -j3; fi
|
- if [ "$COVERITY_SCAN_BRANCH" != 1 ]; then ${ANALYZE} make -j3; fi
|
||||||
- if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi
|
- if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi
|
||||||
- if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./openmw_test_suite; fi
|
- if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./openmw_test_suite; fi
|
||||||
- if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi
|
- if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi
|
||||||
|
|
25
CHANGELOG.md
25
CHANGELOG.md
|
@ -1,3 +1,14 @@
|
||||||
|
0.46.0
|
||||||
|
------
|
||||||
|
|
||||||
|
Bug #3623: Fix HiDPI on Windows
|
||||||
|
Bug #4540: Rain delay when exiting water
|
||||||
|
Bug #4701: PrisonMarker record is not hardcoded like other markers
|
||||||
|
Feature #2229: Improve pathfinding AI
|
||||||
|
Feature #3442: Default values for fallbacks from ini file
|
||||||
|
Feature #4673: Weapon sheathing
|
||||||
|
Task #4686: Upgrade media decoder to a more current FFmpeg API
|
||||||
|
|
||||||
0.45.0
|
0.45.0
|
||||||
------
|
------
|
||||||
|
|
||||||
|
@ -8,6 +19,7 @@
|
||||||
Bug #2256: Landing sound not playing when jumping immediately after landing
|
Bug #2256: Landing sound not playing when jumping immediately after landing
|
||||||
Bug #2274: Thin platform clips through player character instead of lifting
|
Bug #2274: Thin platform clips through player character instead of lifting
|
||||||
Bug #2326: After a bound item expires the last equipped item of that type is not automatically re-equipped
|
Bug #2326: After a bound item expires the last equipped item of that type is not automatically re-equipped
|
||||||
|
Bug #2446: Restore Attribute/Skill should allow restoring drained attributes
|
||||||
Bug #2455: Creatures attacks degrade armor
|
Bug #2455: Creatures attacks degrade armor
|
||||||
Bug #2562: Forcing AI to activate a teleport door sometimes causes a crash
|
Bug #2562: Forcing AI to activate a teleport door sometimes causes a crash
|
||||||
Bug #2626: Resurrecting the player does not resume the game
|
Bug #2626: Resurrecting the player does not resume the game
|
||||||
|
@ -33,6 +45,7 @@
|
||||||
Bug #3788: GetPCInJail and GetPCTraveling do not work as in vanilla
|
Bug #3788: GetPCInJail and GetPCTraveling do not work as in vanilla
|
||||||
Bug #3836: Script fails to compile when command argument contains "\n"
|
Bug #3836: Script fails to compile when command argument contains "\n"
|
||||||
Bug #3876: Landscape texture painting is misaligned
|
Bug #3876: Landscape texture painting is misaligned
|
||||||
|
Bug #3890: Magic light source attenuation is inaccurate
|
||||||
Bug #3897: Have Goodbye give all choices the effects of Goodbye
|
Bug #3897: Have Goodbye give all choices the effects of Goodbye
|
||||||
Bug #3911: [macOS] Typing in the "Content List name" dialog box produces double characters
|
Bug #3911: [macOS] Typing in the "Content List name" dialog box produces double characters
|
||||||
Bug #3920: RemoveSpellEffects doesn't remove constant effects
|
Bug #3920: RemoveSpellEffects doesn't remove constant effects
|
||||||
|
@ -45,6 +58,7 @@
|
||||||
Bug #4110: Fixed undo / redo menu text losing the assigned shortcuts
|
Bug #4110: Fixed undo / redo menu text losing the assigned shortcuts
|
||||||
Bug #4125: OpenMW logo cropped on bugtracker
|
Bug #4125: OpenMW logo cropped on bugtracker
|
||||||
Bug #4215: OpenMW shows book text after last EOL tag
|
Bug #4215: OpenMW shows book text after last EOL tag
|
||||||
|
Bug #4217: Fixme implementation differs from Morrowind's
|
||||||
Bug #4221: Characters get stuck in V-shaped terrain
|
Bug #4221: Characters get stuck in V-shaped terrain
|
||||||
Bug #4230: AiTravel package issues break some Tribunal quests
|
Bug #4230: AiTravel package issues break some Tribunal quests
|
||||||
Bug #4231: Infected rats from the "Crimson Plague" quest rendered unconscious by change in Drain Fatigue functionality
|
Bug #4231: Infected rats from the "Crimson Plague" quest rendered unconscious by change in Drain Fatigue functionality
|
||||||
|
@ -54,6 +68,7 @@
|
||||||
Bug #4274: Pre-0.43 death animations are not forward-compatible with 0.43+
|
Bug #4274: Pre-0.43 death animations are not forward-compatible with 0.43+
|
||||||
Bug #4286: Scripted animations can be interrupted
|
Bug #4286: Scripted animations can be interrupted
|
||||||
Bug #4291: Non-persistent actors that started the game as dead do not play death animations
|
Bug #4291: Non-persistent actors that started the game as dead do not play death animations
|
||||||
|
Bug #4292: CenterOnCell implementation differs from vanilla
|
||||||
Bug #4293: Faction members are not aware of faction ownerships in barter
|
Bug #4293: Faction members are not aware of faction ownerships in barter
|
||||||
Bug #4304: "Follow" not working as a second AI package
|
Bug #4304: "Follow" not working as a second AI package
|
||||||
Bug #4307: World cleanup should remove dead bodies only if death animation is finished
|
Bug #4307: World cleanup should remove dead bodies only if death animation is finished
|
||||||
|
@ -64,7 +79,7 @@
|
||||||
Bug #4368: Settings window ok button doesn't have key focus by default
|
Bug #4368: Settings window ok button doesn't have key focus by default
|
||||||
Bug #4378: On-self absorb spells restore stats
|
Bug #4378: On-self absorb spells restore stats
|
||||||
Bug #4393: NPCs walk back to where they were after using ResetActors
|
Bug #4393: NPCs walk back to where they were after using ResetActors
|
||||||
Bug #4416: Handle exception if we try to play non-music file
|
Bug #4416: Non-music files crash the game when they are tried to be played
|
||||||
Bug #4419: MRK NiStringExtraData is handled incorrectly
|
Bug #4419: MRK NiStringExtraData is handled incorrectly
|
||||||
Bug #4426: RotateWorld behavior is incorrect
|
Bug #4426: RotateWorld behavior is incorrect
|
||||||
Bug #4429: [Windows] Error on build INSTALL.vcxproj project (debug) with cmake 3.7.2
|
Bug #4429: [Windows] Error on build INSTALL.vcxproj project (debug) with cmake 3.7.2
|
||||||
|
@ -80,6 +95,7 @@
|
||||||
Bug #4459: NotCell dialogue condition doesn't support partial matches
|
Bug #4459: NotCell dialogue condition doesn't support partial matches
|
||||||
Bug #4460: Script function "Equip" doesn't bypass beast restrictions
|
Bug #4460: Script function "Equip" doesn't bypass beast restrictions
|
||||||
Bug #4461: "Open" spell from non-player caster isn't a crime
|
Bug #4461: "Open" spell from non-player caster isn't a crime
|
||||||
|
Bug #4463: %g format doesn't return more digits
|
||||||
Bug #4464: OpenMW keeps AiState cached storages even after we cancel AI packages
|
Bug #4464: OpenMW keeps AiState cached storages even after we cancel AI packages
|
||||||
Bug #4467: Content selector: cyrillic characters are decoded incorrectly in plugin descriptions
|
Bug #4467: Content selector: cyrillic characters are decoded incorrectly in plugin descriptions
|
||||||
Bug #4469: Abot Silt Striders – Model turn 90 degrees on horizontal
|
Bug #4469: Abot Silt Striders – Model turn 90 degrees on horizontal
|
||||||
|
@ -143,12 +159,17 @@
|
||||||
Bug #4674: Journal can be opened when settings window is open
|
Bug #4674: Journal can be opened when settings window is open
|
||||||
Bug #4677: Crash in ESM reader when NPC record has DNAM record without DODT one
|
Bug #4677: Crash in ESM reader when NPC record has DNAM record without DODT one
|
||||||
Bug #4678: Crash in ESP parser when SCVR has no variable names
|
Bug #4678: Crash in ESP parser when SCVR has no variable names
|
||||||
|
Bug #4684: Spell Absorption is additive
|
||||||
Bug #4685: Missing sound causes an exception inside Say command
|
Bug #4685: Missing sound causes an exception inside Say command
|
||||||
|
Bug #4689: Default creature soundgen entries are not used
|
||||||
|
Bug #4691: Loading bar for cell should be moved up when text is still active at bottom of screen
|
||||||
Feature #912: Editor: Add missing icons to UniversalId tables
|
Feature #912: Editor: Add missing icons to UniversalId tables
|
||||||
Feature #1221: Editor: Creature/NPC rendering
|
Feature #1221: Editor: Creature/NPC rendering
|
||||||
Feature #1617: Editor: Enchantment effect record verifier
|
Feature #1617: Editor: Enchantment effect record verifier
|
||||||
Feature #1645: Casting effects from objects
|
Feature #1645: Casting effects from objects
|
||||||
Feature #2606: Editor: Implemented (optional) case sensitive global search
|
Feature #2606: Editor: Implemented (optional) case sensitive global search
|
||||||
|
Feature #2787: Use the autogenerated collision box, if the creature mesh has no predefined one
|
||||||
|
Feature #2845: Editor: add record view and preview default keybindings
|
||||||
Feature #2847: Content selector: allow to copy the path to a file by using the context menu
|
Feature #2847: Content selector: allow to copy the path to a file by using the context menu
|
||||||
Feature #3083: Play animation when NPC is casting spell via script
|
Feature #3083: Play animation when NPC is casting spell via script
|
||||||
Feature #3103: Provide option for disposition to get increased by successful trade
|
Feature #3103: Provide option for disposition to get increased by successful trade
|
||||||
|
@ -178,7 +199,9 @@
|
||||||
Feature #4632: AI priority: utilize vanilla AI GMSTs for priority rating
|
Feature #4632: AI priority: utilize vanilla AI GMSTs for priority rating
|
||||||
Feature #4636: Use sTo GMST in spellmaking menu
|
Feature #4636: Use sTo GMST in spellmaking menu
|
||||||
Feature #4642: Batching potion creation
|
Feature #4642: Batching potion creation
|
||||||
|
Feature #4647: Cull actors outside of AI processing range
|
||||||
Feature #4682: Use the collision box from basic creature mesh if the X one have no collisions
|
Feature #4682: Use the collision box from basic creature mesh if the X one have no collisions
|
||||||
|
Feature #4697: Use the real thrown weapon damage in tooltips and AI
|
||||||
Task #2490: Don't open command prompt window on Release-mode builds automatically
|
Task #2490: Don't open command prompt window on Release-mode builds automatically
|
||||||
Task #4545: Enable is_pod string test
|
Task #4545: Enable is_pod string test
|
||||||
Task #4605: Optimize skinning
|
Task #4605: Optimize skinning
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
#!/bin/sh
|
#!/bin/bash -ex
|
||||||
sudo ln -s /usr/bin/clang-3.6 /usr/local/bin/clang
|
|
||||||
sudo ln -s /usr/bin/clang++-3.6 /usr/local/bin/clang++
|
sudo ln -sf /usr/bin/clang-7 /usr/local/bin/clang
|
||||||
|
sudo ln -sf /usr/bin/clang++-7 /usr/local/bin/clang++
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#!/bin/sh
|
#!/bin/sh -e
|
||||||
|
|
||||||
brew update
|
brew update
|
||||||
|
|
||||||
|
@ -6,5 +6,5 @@ brew outdated cmake || brew upgrade cmake
|
||||||
brew outdated pkgconfig || brew upgrade pkgconfig
|
brew outdated pkgconfig || brew upgrade pkgconfig
|
||||||
brew install qt
|
brew install qt
|
||||||
|
|
||||||
curl -fSL -R -J https://downloads.openmw.org/osx/dependencies/openmw-deps-100d2e0.zip -o ~/openmw-deps.zip
|
curl -fSL -R -J https://downloads.openmw.org/osx/dependencies/openmw-deps-7cf2789.zip -o ~/openmw-deps.zip
|
||||||
unzip -o ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null
|
unzip -o ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#!/bin/sh -e
|
#!/bin/bash -ex
|
||||||
|
|
||||||
free -m
|
free -m
|
||||||
|
|
||||||
|
@ -8,8 +8,22 @@ GOOGLETEST_DIR="$(pwd)/googletest/build"
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
export CODE_COVERAGE=1
|
export CODE_COVERAGE=1
|
||||||
if [ "${CC}" = "clang" ]; then export CODE_COVERAGE=0; fi
|
|
||||||
${ANALYZE}cmake \
|
if [[ "${CC}" =~ "clang" ]]; then export CODE_COVERAGE=0; fi
|
||||||
|
if [[ -z "${BUILD_OPENMW}" ]]; then export BUILD_OPENMW=ON; fi
|
||||||
|
if [[ -z "${BUILD_OPENMW_CS}" ]]; then export BUILD_OPENMW_CS=ON; fi
|
||||||
|
|
||||||
|
${ANALYZE} cmake \
|
||||||
|
-DBUILD_OPENMW=${BUILD_OPENMW} \
|
||||||
|
-DBUILD_OPENCS=${BUILD_OPENMW_CS} \
|
||||||
|
-DBUILD_LAUNCHER=${BUILD_OPENMW_CS} \
|
||||||
|
-DBUILD_BSATOOL=${BUILD_OPENMW_CS} \
|
||||||
|
-DBUILD_ESMTOOL=${BUILD_OPENMW_CS} \
|
||||||
|
-DBUILD_MWINIIMPORTER=${BUILD_OPENMW_CS} \
|
||||||
|
-DBUILD_ESSIMPORTER=${BUILD_OPENMW_CS} \
|
||||||
|
-DBUILD_WIZARD=${BUILD_OPENMW_CS} \
|
||||||
|
-DBUILD_NIFTEST=${BUILD_OPENMW_CS} \
|
||||||
|
-DBUILD_MYGUI_PLUGIN=${BUILD_OPENMW_CS} \
|
||||||
-DBUILD_WITH_CODE_COVERAGE=${CODE_COVERAGE} \
|
-DBUILD_WITH_CODE_COVERAGE=${CODE_COVERAGE} \
|
||||||
-DBUILD_UNITTESTS=1 \
|
-DBUILD_UNITTESTS=1 \
|
||||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||||
|
|
|
@ -194,7 +194,11 @@ download() {
|
||||||
}
|
}
|
||||||
|
|
||||||
real_pwd() {
|
real_pwd() {
|
||||||
pwd | sed "s,/\(.\),\1:,"
|
if type cygpath >/dev/null 2>&1; then
|
||||||
|
cygpath -am "$PWD"
|
||||||
|
else
|
||||||
|
pwd # not git bash, Cygwin or the like
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
CMAKE_OPTS=""
|
CMAKE_OPTS=""
|
||||||
|
@ -304,6 +308,10 @@ if ! [ -z $UNITY_BUILD ]; then
|
||||||
add_cmake_opts "-DOPENMW_UNITY_BUILD=True"
|
add_cmake_opts "-DOPENMW_UNITY_BUILD=True"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ ${BITS} -eq 64 ]; then
|
||||||
|
GENERATOR="${GENERATOR} Win64"
|
||||||
|
fi
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "==================================="
|
echo "==================================="
|
||||||
echo "Starting prebuild on MSVC${MSVC_DISPLAY_YEAR} WIN${BITS}"
|
echo "Starting prebuild on MSVC${MSVC_DISPLAY_YEAR} WIN${BITS}"
|
||||||
|
@ -324,7 +332,7 @@ if [ -z $SKIP_DOWNLOAD ]; then
|
||||||
if [ -z $APPVEYOR ]; then
|
if [ -z $APPVEYOR ]; then
|
||||||
download "Boost 1.67.0" \
|
download "Boost 1.67.0" \
|
||||||
"https://sourceforge.net/projects/boost/files/boost-binaries/1.67.0/boost_1_67_0-msvc-${MSVC_VER}-${BITS}.exe" \
|
"https://sourceforge.net/projects/boost/files/boost-binaries/1.67.0/boost_1_67_0-msvc-${MSVC_VER}-${BITS}.exe" \
|
||||||
"boost-1.67.0-msvc${MSVC_YEAR}-win${BITS}.exe"
|
"boost-1.67.0-msvc${MSVC_VER}-win${BITS}.exe"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Bullet
|
# Bullet
|
||||||
|
@ -430,7 +438,7 @@ fi
|
||||||
rm -rf Boost
|
rm -rf Boost
|
||||||
CI_EXTRA_INNO_OPTIONS=""
|
CI_EXTRA_INNO_OPTIONS=""
|
||||||
[ -n "$CI" ] && CI_EXTRA_INNO_OPTIONS="//SUPPRESSMSGBOXES //LOG='boost_install.log'"
|
[ -n "$CI" ] && CI_EXTRA_INNO_OPTIONS="//SUPPRESSMSGBOXES //LOG='boost_install.log'"
|
||||||
"${DEPS}/boost-1.67.0-msvc${MSVC_YEAR}-win${BITS}.exe" //DIR="${CWD_DRIVE_ROOT}" //VERYSILENT //NORESTART ${CI_EXTRA_INNO_OPTIONS}
|
"${DEPS}/boost-1.67.0-msvc${MSVC_VER}-win${BITS}.exe" //DIR="${CWD_DRIVE_ROOT}" //VERYSILENT //NORESTART ${CI_EXTRA_INNO_OPTIONS}
|
||||||
mv "${CWD_DRIVE_ROOT_BASH}" "${BOOST_SDK}"
|
mv "${CWD_DRIVE_ROOT_BASH}" "${BOOST_SDK}"
|
||||||
fi
|
fi
|
||||||
add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \
|
add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \
|
||||||
|
@ -449,7 +457,7 @@ fi
|
||||||
else
|
else
|
||||||
LIB_SUFFIX="0"
|
LIB_SUFFIX="0"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \
|
add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \
|
||||||
-DBOOST_LIBRARYDIR="${BOOST_SDK}/lib${BITS}-msvc-${MSVC_VER}.${LIB_SUFFIX}"
|
-DBOOST_LIBRARYDIR="${BOOST_SDK}/lib${BITS}-msvc-${MSVC_VER}.${LIB_SUFFIX}"
|
||||||
add_cmake_opts -DBoost_COMPILER="-${TOOLSET}"
|
add_cmake_opts -DBoost_COMPILER="-${TOOLSET}"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#!/bin/sh
|
#!/bin/sh -e
|
||||||
|
|
||||||
export CXX=clang++
|
export CXX=clang++
|
||||||
export CC=clang
|
export CC=clang
|
||||||
|
|
|
@ -153,7 +153,52 @@ if (USE_QT)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Sound setup
|
# Sound setup
|
||||||
|
|
||||||
|
# Require at least ffmpeg 3.2 for now
|
||||||
|
SET(FFVER_OK FALSE)
|
||||||
|
|
||||||
find_package(FFmpeg REQUIRED COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE SWRESAMPLE)
|
find_package(FFmpeg REQUIRED COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE SWRESAMPLE)
|
||||||
|
|
||||||
|
if(FFmpeg_FOUND)
|
||||||
|
SET(FFVER_OK TRUE)
|
||||||
|
|
||||||
|
# Can not detect FFmpeg version on Windows for now
|
||||||
|
if (NOT WIN32)
|
||||||
|
if(FFmpeg_AVFORMAT_VERSION VERSION_LESS "57.56.100")
|
||||||
|
message(STATUS "libavformat is too old! (${FFmpeg_AVFORMAT_VERSION}, wanted 57.56.100)")
|
||||||
|
set(FFVER_OK FALSE)
|
||||||
|
endif()
|
||||||
|
if(FFmpeg_AVCODEC_VERSION VERSION_LESS "57.64.100")
|
||||||
|
message(STATUS "libavcodec is too old! (${FFmpeg_AVCODEC_VERSION}, wanted 57.64.100)")
|
||||||
|
set(FFVER_OK FALSE)
|
||||||
|
endif()
|
||||||
|
if(FFmpeg_AVUTIL_VERSION VERSION_LESS "55.34.100")
|
||||||
|
message(STATUS "libavutil is too old! (${FFmpeg_AVUTIL_VERSION}, wanted 55.34.100)")
|
||||||
|
set(FFVER_OK FALSE)
|
||||||
|
endif()
|
||||||
|
if(FFmpeg_SWSCALE_VERSION VERSION_LESS "4.2.100")
|
||||||
|
message(STATUS "libswscale is too old! (${FFmpeg_SWSCALE_VERSION}, wanted 4.2.100)")
|
||||||
|
set(FFVER_OK FALSE)
|
||||||
|
endif()
|
||||||
|
if(FFmpeg_SWRESAMPLE_VERSION VERSION_LESS "2.3.100")
|
||||||
|
message(STATUS "libswresample is too old! (${FFmpeg_SWRESAMPLE_VERSION}, wanted 2.3.100)")
|
||||||
|
set(FFVER_OK FALSE)
|
||||||
|
endif()
|
||||||
|
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()
|
||||||
|
|
||||||
# Required for building the FFmpeg headers
|
# Required for building the FFmpeg headers
|
||||||
add_definitions(-D__STDC_CONSTANT_MACROS)
|
add_definitions(-D__STDC_CONSTANT_MACROS)
|
||||||
|
|
||||||
|
@ -275,9 +320,6 @@ if (APPLE)
|
||||||
"${APP_BUNDLE_DIR}/Contents/Resources/OpenMW.icns" COPYONLY)
|
"${APP_BUNDLE_DIR}/Contents/Resources/OpenMW.icns" COPYONLY)
|
||||||
endif (APPLE)
|
endif (APPLE)
|
||||||
|
|
||||||
# Set up DEBUG define
|
|
||||||
set_directory_properties(PROPERTIES COMPILE_DEFINITIONS_DEBUG DEBUG=1)
|
|
||||||
|
|
||||||
if (NOT APPLE)
|
if (NOT APPLE)
|
||||||
set(OPENMW_MYGUI_FILES_ROOT ${OpenMW_BINARY_DIR})
|
set(OPENMW_MYGUI_FILES_ROOT ${OpenMW_BINARY_DIR})
|
||||||
set(OPENMW_SHADERS_ROOT ${OpenMW_BINARY_DIR})
|
set(OPENMW_SHADERS_ROOT ${OpenMW_BINARY_DIR})
|
||||||
|
@ -469,7 +511,7 @@ if(WIN32)
|
||||||
|
|
||||||
INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Debug/resources" DESTINATION "." CONFIGURATIONS Debug)
|
INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Debug/resources" DESTINATION "." CONFIGURATIONS Debug)
|
||||||
INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Release/resources" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
|
INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Release/resources" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
|
||||||
|
|
||||||
FILE(GLOB plugin_dir_debug "${OpenMW_BINARY_DIR}/Debug/osgPlugins-*")
|
FILE(GLOB plugin_dir_debug "${OpenMW_BINARY_DIR}/Debug/osgPlugins-*")
|
||||||
FILE(GLOB plugin_dir_release "${OpenMW_BINARY_DIR}/Release/osgPlugins-*")
|
FILE(GLOB plugin_dir_release "${OpenMW_BINARY_DIR}/Release/osgPlugins-*")
|
||||||
INSTALL(DIRECTORY ${plugin_dir_debug} DESTINATION "." CONFIGURATIONS Debug)
|
INSTALL(DIRECTORY ${plugin_dir_debug} DESTINATION "." CONFIGURATIONS Debug)
|
||||||
|
@ -536,6 +578,11 @@ if(WIN32)
|
||||||
endif(WIN32)
|
endif(WIN32)
|
||||||
|
|
||||||
# Extern
|
# Extern
|
||||||
|
set(RECASTNAVIGATION_DEMO OFF CACHE BOOL "Do not build RecastDemo")
|
||||||
|
set(RECASTNAVIGATION_STATIC ON CACHE BOOL "Build recastnavigation static libraries")
|
||||||
|
set(RECASTNAVIGATION_TESTS OFF CACHE BOOL "Do not build recastnavigation tests")
|
||||||
|
|
||||||
|
add_subdirectory (extern/recastnavigation)
|
||||||
add_subdirectory (extern/osg-ffmpeg-videoplayer)
|
add_subdirectory (extern/osg-ffmpeg-videoplayer)
|
||||||
add_subdirectory (extern/oics)
|
add_subdirectory (extern/oics)
|
||||||
if (BUILD_OPENCS)
|
if (BUILD_OPENCS)
|
||||||
|
@ -602,7 +649,7 @@ if (WIN32)
|
||||||
if (USE_DEBUG_CONSOLE AND BUILD_OPENMW)
|
if (USE_DEBUG_CONSOLE AND BUILD_OPENMW)
|
||||||
set_target_properties(openmw PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE")
|
set_target_properties(openmw PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE")
|
||||||
set_target_properties(openmw PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:CONSOLE")
|
set_target_properties(openmw PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:CONSOLE")
|
||||||
set_target_properties(openmw PROPERTIES COMPILE_DEFINITIONS_DEBUG "_CONSOLE")
|
set_target_properties(openmw PROPERTIES COMPILE_DEFINITIONS $<$<CONFIG:Debug>:_CONSOLE>)
|
||||||
elseif (BUILD_OPENMW)
|
elseif (BUILD_OPENMW)
|
||||||
# Turn off debug console, debug output will be written to visual studio output instead
|
# Turn off debug console, debug output will be written to visual studio output instead
|
||||||
set_target_properties(openmw PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:WINDOWS")
|
set_target_properties(openmw PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:WINDOWS")
|
||||||
|
@ -612,7 +659,6 @@ if (WIN32)
|
||||||
if (BUILD_OPENMW)
|
if (BUILD_OPENMW)
|
||||||
# Release builds don't use the debug console
|
# Release builds don't use the debug console
|
||||||
set_target_properties(openmw PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS")
|
set_target_properties(openmw PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS")
|
||||||
set_target_properties(openmw PROPERTIES COMPILE_DEFINITIONS_RELEASE "_WINDOWS")
|
|
||||||
set_target_properties(openmw PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:WINDOWS")
|
set_target_properties(openmw PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:WINDOWS")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#include <components/debug/debuglog.hpp>
|
#include <components/debug/debuglog.hpp>
|
||||||
#include <components/fallback/validate.hpp>
|
#include <components/fallback/validate.hpp>
|
||||||
|
#include <components/misc/rng.hpp>
|
||||||
#include <components/nifosg/nifloader.hpp>
|
#include <components/nifosg/nifloader.hpp>
|
||||||
|
|
||||||
#include "model/doc/document.hpp"
|
#include "model/doc/document.hpp"
|
||||||
|
@ -355,6 +356,8 @@ int CS::Editor::run()
|
||||||
if (mLocal.empty())
|
if (mLocal.empty())
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
|
Misc::Rng::init();
|
||||||
|
|
||||||
mStartup.show();
|
mStartup.show();
|
||||||
|
|
||||||
QApplication::setQuitOnLastWindowClosed (true);
|
QApplication::setQuitOnLastWindowClosed (true);
|
||||||
|
|
|
@ -317,8 +317,8 @@ void CSMPrefs::State::declare()
|
||||||
declareShortcut ("table-remove", "Remove Row/Record", QKeySequence(Qt::Key_Delete));
|
declareShortcut ("table-remove", "Remove Row/Record", QKeySequence(Qt::Key_Delete));
|
||||||
declareShortcut ("table-moveup", "Move Record Up", QKeySequence());
|
declareShortcut ("table-moveup", "Move Record Up", QKeySequence());
|
||||||
declareShortcut ("table-movedown", "Move Record Down", QKeySequence());
|
declareShortcut ("table-movedown", "Move Record Down", QKeySequence());
|
||||||
declareShortcut ("table-view", "View Record", QKeySequence());
|
declareShortcut ("table-view", "View Record", QKeySequence(Qt::ShiftModifier | Qt::Key_C));
|
||||||
declareShortcut ("table-preview", "Preview Record", QKeySequence());
|
declareShortcut ("table-preview", "Preview Record", QKeySequence(Qt::ShiftModifier | Qt::Key_V));
|
||||||
declareShortcut ("table-extendeddelete", "Extended Record Deletion", QKeySequence());
|
declareShortcut ("table-extendeddelete", "Extended Record Deletion", QKeySequence());
|
||||||
declareShortcut ("table-extendedrevert", "Extended Record Revertion", QKeySequence());
|
declareShortcut ("table-extendedrevert", "Extended Record Revertion", QKeySequence());
|
||||||
|
|
||||||
|
|
|
@ -977,15 +977,19 @@ int CSMWorld::Data::startLoading (const boost::filesystem::path& path, bool base
|
||||||
void CSMWorld::Data::loadFallbackEntries()
|
void CSMWorld::Data::loadFallbackEntries()
|
||||||
{
|
{
|
||||||
// Load default marker definitions, if game files do not have them for some reason
|
// Load default marker definitions, if game files do not have them for some reason
|
||||||
std::pair<std::string, std::string> markers[] = {
|
std::pair<std::string, std::string> staticMarkers[] = {
|
||||||
std::make_pair("divinemarker", "marker_divine.nif"),
|
std::make_pair("DivineMarker", "marker_divine.nif"),
|
||||||
std::make_pair("doormarker", "marker_arrow.nif"),
|
std::make_pair("DoorMarker", "marker_arrow.nif"),
|
||||||
std::make_pair("northmarker", "marker_north.nif"),
|
std::make_pair("NorthMarker", "marker_north.nif"),
|
||||||
std::make_pair("templemarker", "marker_temple.nif"),
|
std::make_pair("TempleMarker", "marker_temple.nif"),
|
||||||
std::make_pair("travelmarker", "marker_travel.nif")
|
std::make_pair("TravelMarker", "marker_travel.nif")
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const std::pair<std::string, std::string> marker : markers)
|
std::pair<std::string, std::string> doorMarkers[] = {
|
||||||
|
std::make_pair("PrisonMarker", "marker_prison.nif")
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const std::pair<std::string, std::string> marker : staticMarkers)
|
||||||
{
|
{
|
||||||
if (mReferenceables.searchId (marker.first)==-1)
|
if (mReferenceables.searchId (marker.first)==-1)
|
||||||
{
|
{
|
||||||
|
@ -995,6 +999,17 @@ void CSMWorld::Data::loadFallbackEntries()
|
||||||
mReferenceables.appendRecord (record, CSMWorld::UniversalId::Type_Static);
|
mReferenceables.appendRecord (record, CSMWorld::UniversalId::Type_Static);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const std::pair<std::string, std::string> marker : doorMarkers)
|
||||||
|
{
|
||||||
|
if (mReferenceables.searchId (marker.first)==-1)
|
||||||
|
{
|
||||||
|
CSMWorld::Record<ESM::Door> record;
|
||||||
|
record.mBase = ESM::Door(marker.first, std::string(), marker.second, std::string(), std::string(), std::string());
|
||||||
|
record.mState = CSMWorld::RecordBase::State_BaseOnly;
|
||||||
|
mReferenceables.appendRecord (record, CSMWorld::UniversalId::Type_Door);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages)
|
bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages)
|
||||||
|
|
|
@ -520,7 +520,6 @@ std::pair<float, float> CSMWorld::ConstInfoSelectWrapper::getConditionFloatRange
|
||||||
const float FloatMax = std::numeric_limits<float>::infinity();
|
const float FloatMax = std::numeric_limits<float>::infinity();
|
||||||
const float FloatMin = -std::numeric_limits<float>::infinity();
|
const float FloatMin = -std::numeric_limits<float>::infinity();
|
||||||
const float Epsilon = std::numeric_limits<float>::epsilon();
|
const float Epsilon = std::numeric_limits<float>::epsilon();
|
||||||
const std::pair<float, float> InvalidRange(FloatMax, FloatMin);
|
|
||||||
|
|
||||||
float value = mConstSelect.mValue.getFloat();
|
float value = mConstSelect.mValue.getFloat();
|
||||||
|
|
||||||
|
|
|
@ -292,6 +292,7 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id,
|
||||||
|
|
||||||
mEditAction = new QAction (tr ("Edit Record"), this);
|
mEditAction = new QAction (tr ("Edit Record"), this);
|
||||||
connect (mEditAction, SIGNAL (triggered()), this, SLOT (editRecord()));
|
connect (mEditAction, SIGNAL (triggered()), this, SLOT (editRecord()));
|
||||||
|
mEditAction->setIcon(QIcon(":edit-edit"));
|
||||||
addAction (mEditAction);
|
addAction (mEditAction);
|
||||||
CSMPrefs::Shortcut* editShortcut = new CSMPrefs::Shortcut("table-edit", this);
|
CSMPrefs::Shortcut* editShortcut = new CSMPrefs::Shortcut("table-edit", this);
|
||||||
editShortcut->associateAction(mEditAction);
|
editShortcut->associateAction(mEditAction);
|
||||||
|
@ -300,12 +301,14 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id,
|
||||||
{
|
{
|
||||||
mCreateAction = new QAction (tr ("Add Record"), this);
|
mCreateAction = new QAction (tr ("Add Record"), this);
|
||||||
connect (mCreateAction, SIGNAL (triggered()), this, SIGNAL (createRequest()));
|
connect (mCreateAction, SIGNAL (triggered()), this, SIGNAL (createRequest()));
|
||||||
|
mCreateAction->setIcon(QIcon(":edit-add"));
|
||||||
addAction (mCreateAction);
|
addAction (mCreateAction);
|
||||||
CSMPrefs::Shortcut* createShortcut = new CSMPrefs::Shortcut("table-add", this);
|
CSMPrefs::Shortcut* createShortcut = new CSMPrefs::Shortcut("table-add", this);
|
||||||
createShortcut->associateAction(mCreateAction);
|
createShortcut->associateAction(mCreateAction);
|
||||||
|
|
||||||
mCloneAction = new QAction (tr ("Clone Record"), this);
|
mCloneAction = new QAction (tr ("Clone Record"), this);
|
||||||
connect(mCloneAction, SIGNAL (triggered()), this, SLOT (cloneRecord()));
|
connect(mCloneAction, SIGNAL (triggered()), this, SLOT (cloneRecord()));
|
||||||
|
mCloneAction->setIcon(QIcon(":edit-clone"));
|
||||||
addAction(mCloneAction);
|
addAction(mCloneAction);
|
||||||
CSMPrefs::Shortcut* cloneShortcut = new CSMPrefs::Shortcut("table-clone", this);
|
CSMPrefs::Shortcut* cloneShortcut = new CSMPrefs::Shortcut("table-clone", this);
|
||||||
cloneShortcut->associateAction(mCloneAction);
|
cloneShortcut->associateAction(mCloneAction);
|
||||||
|
@ -315,6 +318,7 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id,
|
||||||
{
|
{
|
||||||
mTouchAction = new QAction(tr("Touch Record"), this);
|
mTouchAction = new QAction(tr("Touch Record"), this);
|
||||||
connect(mTouchAction, SIGNAL(triggered()), this, SLOT(touchRecord()));
|
connect(mTouchAction, SIGNAL(triggered()), this, SLOT(touchRecord()));
|
||||||
|
mTouchAction->setIcon(QIcon(":edit-touch"));
|
||||||
addAction(mTouchAction);
|
addAction(mTouchAction);
|
||||||
CSMPrefs::Shortcut* touchShortcut = new CSMPrefs::Shortcut("table-touch", this);
|
CSMPrefs::Shortcut* touchShortcut = new CSMPrefs::Shortcut("table-touch", this);
|
||||||
touchShortcut->associateAction(mTouchAction);
|
touchShortcut->associateAction(mTouchAction);
|
||||||
|
@ -322,49 +326,56 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id,
|
||||||
|
|
||||||
mRevertAction = new QAction (tr ("Revert Record"), this);
|
mRevertAction = new QAction (tr ("Revert Record"), this);
|
||||||
connect (mRevertAction, SIGNAL (triggered()), mDispatcher, SLOT (executeRevert()));
|
connect (mRevertAction, SIGNAL (triggered()), mDispatcher, SLOT (executeRevert()));
|
||||||
|
mRevertAction->setIcon(QIcon(":edit-undo"));
|
||||||
addAction (mRevertAction);
|
addAction (mRevertAction);
|
||||||
CSMPrefs::Shortcut* revertShortcut = new CSMPrefs::Shortcut("table-revert", this);
|
CSMPrefs::Shortcut* revertShortcut = new CSMPrefs::Shortcut("table-revert", this);
|
||||||
revertShortcut->associateAction(mRevertAction);
|
revertShortcut->associateAction(mRevertAction);
|
||||||
|
|
||||||
mDeleteAction = new QAction (tr ("Delete Record"), this);
|
mDeleteAction = new QAction (tr ("Delete Record"), this);
|
||||||
connect (mDeleteAction, SIGNAL (triggered()), mDispatcher, SLOT (executeDelete()));
|
connect (mDeleteAction, SIGNAL (triggered()), mDispatcher, SLOT (executeDelete()));
|
||||||
|
mDeleteAction->setIcon(QIcon(":edit-delete"));
|
||||||
addAction (mDeleteAction);
|
addAction (mDeleteAction);
|
||||||
CSMPrefs::Shortcut* deleteShortcut = new CSMPrefs::Shortcut("table-remove", this);
|
CSMPrefs::Shortcut* deleteShortcut = new CSMPrefs::Shortcut("table-remove", this);
|
||||||
deleteShortcut->associateAction(mDeleteAction);
|
deleteShortcut->associateAction(mDeleteAction);
|
||||||
|
|
||||||
|
|
||||||
mMoveUpAction = new QAction (tr ("Move Up"), this);
|
mMoveUpAction = new QAction (tr ("Move Up"), this);
|
||||||
connect (mMoveUpAction, SIGNAL (triggered()), this, SLOT (moveUpRecord()));
|
connect (mMoveUpAction, SIGNAL (triggered()), this, SLOT (moveUpRecord()));
|
||||||
|
mMoveUpAction->setIcon(QIcon(":record-up"));
|
||||||
addAction (mMoveUpAction);
|
addAction (mMoveUpAction);
|
||||||
CSMPrefs::Shortcut* moveUpShortcut = new CSMPrefs::Shortcut("table-moveup", this);
|
CSMPrefs::Shortcut* moveUpShortcut = new CSMPrefs::Shortcut("table-moveup", this);
|
||||||
moveUpShortcut->associateAction(mMoveUpAction);
|
moveUpShortcut->associateAction(mMoveUpAction);
|
||||||
|
|
||||||
mMoveDownAction = new QAction (tr ("Move Down"), this);
|
mMoveDownAction = new QAction (tr ("Move Down"), this);
|
||||||
connect (mMoveDownAction, SIGNAL (triggered()), this, SLOT (moveDownRecord()));
|
connect (mMoveDownAction, SIGNAL (triggered()), this, SLOT (moveDownRecord()));
|
||||||
|
mMoveDownAction->setIcon(QIcon(":record-down"));
|
||||||
addAction (mMoveDownAction);
|
addAction (mMoveDownAction);
|
||||||
CSMPrefs::Shortcut* moveDownShortcut = new CSMPrefs::Shortcut("table-movedown", this);
|
CSMPrefs::Shortcut* moveDownShortcut = new CSMPrefs::Shortcut("table-movedown", this);
|
||||||
moveDownShortcut->associateAction(mMoveDownAction);
|
moveDownShortcut->associateAction(mMoveDownAction);
|
||||||
|
|
||||||
mViewAction = new QAction (tr ("View"), this);
|
mViewAction = new QAction (tr ("View"), this);
|
||||||
connect (mViewAction, SIGNAL (triggered()), this, SLOT (viewRecord()));
|
connect (mViewAction, SIGNAL (triggered()), this, SLOT (viewRecord()));
|
||||||
|
mViewAction->setIcon(QIcon(":/cell.png"));
|
||||||
addAction (mViewAction);
|
addAction (mViewAction);
|
||||||
CSMPrefs::Shortcut* viewShortcut = new CSMPrefs::Shortcut("table-view", this);
|
CSMPrefs::Shortcut* viewShortcut = new CSMPrefs::Shortcut("table-view", this);
|
||||||
viewShortcut->associateAction(mViewAction);
|
viewShortcut->associateAction(mViewAction);
|
||||||
|
|
||||||
mPreviewAction = new QAction (tr ("Preview"), this);
|
mPreviewAction = new QAction (tr ("Preview"), this);
|
||||||
connect (mPreviewAction, SIGNAL (triggered()), this, SLOT (previewRecord()));
|
connect (mPreviewAction, SIGNAL (triggered()), this, SLOT (previewRecord()));
|
||||||
|
mPreviewAction->setIcon(QIcon(":edit-preview"));
|
||||||
addAction (mPreviewAction);
|
addAction (mPreviewAction);
|
||||||
CSMPrefs::Shortcut* previewShortcut = new CSMPrefs::Shortcut("table-preview", this);
|
CSMPrefs::Shortcut* previewShortcut = new CSMPrefs::Shortcut("table-preview", this);
|
||||||
previewShortcut->associateAction(mPreviewAction);
|
previewShortcut->associateAction(mPreviewAction);
|
||||||
|
|
||||||
mExtendedDeleteAction = new QAction (tr ("Extended Delete Record"), this);
|
mExtendedDeleteAction = new QAction (tr ("Extended Delete Record"), this);
|
||||||
connect (mExtendedDeleteAction, SIGNAL (triggered()), this, SLOT (executeExtendedDelete()));
|
connect (mExtendedDeleteAction, SIGNAL (triggered()), this, SLOT (executeExtendedDelete()));
|
||||||
|
mExtendedDeleteAction->setIcon(QIcon(":edit-delete"));
|
||||||
addAction (mExtendedDeleteAction);
|
addAction (mExtendedDeleteAction);
|
||||||
CSMPrefs::Shortcut* extendedDeleteShortcut = new CSMPrefs::Shortcut("table-extendeddelete", this);
|
CSMPrefs::Shortcut* extendedDeleteShortcut = new CSMPrefs::Shortcut("table-extendeddelete", this);
|
||||||
extendedDeleteShortcut->associateAction(mExtendedDeleteAction);
|
extendedDeleteShortcut->associateAction(mExtendedDeleteAction);
|
||||||
|
|
||||||
mExtendedRevertAction = new QAction (tr ("Extended Revert Record"), this);
|
mExtendedRevertAction = new QAction (tr ("Extended Revert Record"), this);
|
||||||
connect (mExtendedRevertAction, SIGNAL (triggered()), this, SLOT (executeExtendedRevert()));
|
connect (mExtendedRevertAction, SIGNAL (triggered()), this, SLOT (executeExtendedRevert()));
|
||||||
|
mExtendedRevertAction->setIcon(QIcon(":edit-undo"));
|
||||||
addAction (mExtendedRevertAction);
|
addAction (mExtendedRevertAction);
|
||||||
CSMPrefs::Shortcut* extendedRevertShortcut = new CSMPrefs::Shortcut("table-extendedrevert", this);
|
CSMPrefs::Shortcut* extendedRevertShortcut = new CSMPrefs::Shortcut("table-extendedrevert", this);
|
||||||
extendedRevertShortcut->associateAction(mExtendedRevertAction);
|
extendedRevertShortcut->associateAction(mExtendedRevertAction);
|
||||||
|
|
|
@ -4,6 +4,7 @@ set(GAME
|
||||||
engine.cpp
|
engine.cpp
|
||||||
|
|
||||||
${CMAKE_SOURCE_DIR}/files/windows/openmw.rc
|
${CMAKE_SOURCE_DIR}/files/windows/openmw.rc
|
||||||
|
${CMAKE_SOURCE_DIR}/files/windows/openmw.exe.manifest
|
||||||
)
|
)
|
||||||
|
|
||||||
if (ANDROID)
|
if (ANDROID)
|
||||||
|
@ -21,7 +22,7 @@ add_openmw_dir (mwrender
|
||||||
actors objects renderingmanager animation rotatecontroller sky npcanimation vismask
|
actors objects renderingmanager animation rotatecontroller sky npcanimation vismask
|
||||||
creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation
|
creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation
|
||||||
bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation
|
bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation
|
||||||
renderbin actoranimation landmanager
|
renderbin actoranimation landmanager navmesh actorspaths
|
||||||
)
|
)
|
||||||
|
|
||||||
add_openmw_dir (mwinput
|
add_openmw_dir (mwinput
|
||||||
|
@ -70,7 +71,7 @@ add_openmw_dir (mwworld
|
||||||
)
|
)
|
||||||
|
|
||||||
add_openmw_dir (mwphysics
|
add_openmw_dir (mwphysics
|
||||||
physicssystem trace collisiontype actor convert
|
physicssystem trace collisiontype actor convert object heightfield
|
||||||
)
|
)
|
||||||
|
|
||||||
add_openmw_dir (mwclass
|
add_openmw_dir (mwclass
|
||||||
|
@ -133,6 +134,7 @@ target_link_libraries(openmw
|
||||||
${FFmpeg_LIBRARIES}
|
${FFmpeg_LIBRARIES}
|
||||||
${MyGUI_LIBRARIES}
|
${MyGUI_LIBRARIES}
|
||||||
${SDL2_LIBRARY}
|
${SDL2_LIBRARY}
|
||||||
|
${RecastNavigation_LIBRARIES}
|
||||||
"osg-ffmpeg-videoplayer"
|
"osg-ffmpeg-videoplayer"
|
||||||
"oics"
|
"oics"
|
||||||
components
|
components
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
#include "engine.hpp"
|
#include "engine.hpp"
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
// For OutputDebugString
|
|
||||||
#ifndef WIN32_LEAN_AND_MEAN
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
#define WIN32_LEAN_AND_MEAN
|
#define WIN32_LEAN_AND_MEAN
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -233,6 +233,10 @@ namespace MWBase
|
||||||
|
|
||||||
virtual void castSpell(const MWWorld::Ptr& ptr, const std::string spellId, bool manualSpell) = 0;
|
virtual void castSpell(const MWWorld::Ptr& ptr, const std::string spellId, bool manualSpell) = 0;
|
||||||
|
|
||||||
|
virtual void processChangedSettings (const std::set< std::pair<std::string, std::string> >& settings) = 0;
|
||||||
|
|
||||||
|
virtual float getActorsProcessingRange() const = 0;
|
||||||
|
|
||||||
/// Check if the target actor was detected by an observer
|
/// Check if the target actor was detected by an observer
|
||||||
/// If the observer is a non-NPC, check all actors in AI processing distance as observers
|
/// If the observer is a non-NPC, check all actors in AI processing distance as observers
|
||||||
virtual bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) = 0;
|
virtual bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) = 0;
|
||||||
|
|
|
@ -285,6 +285,8 @@ namespace MWBase
|
||||||
|
|
||||||
virtual void setEnemy (const MWWorld::Ptr& enemy) = 0;
|
virtual void setEnemy (const MWWorld::Ptr& enemy) = 0;
|
||||||
|
|
||||||
|
virtual int getMessagesCount() const = 0;
|
||||||
|
|
||||||
virtual const Translation::Storage& getTranslationDataStorage() const = 0;
|
virtual const Translation::Storage& getTranslationDataStorage() const = 0;
|
||||||
|
|
||||||
/// Warning: do not use MyGUI::InputManager::setKeyFocusWidget directly. Instead use this.
|
/// Warning: do not use MyGUI::InputManager::setKeyFocusWidget directly. Instead use this.
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
#include <deque>
|
||||||
|
|
||||||
#include <components/esm/cellid.hpp>
|
#include <components/esm/cellid.hpp>
|
||||||
|
|
||||||
|
@ -54,6 +55,11 @@ namespace MWMechanics
|
||||||
struct Movement;
|
struct Movement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace DetourNavigator
|
||||||
|
{
|
||||||
|
class Navigator;
|
||||||
|
}
|
||||||
|
|
||||||
namespace MWWorld
|
namespace MWWorld
|
||||||
{
|
{
|
||||||
class CellStore;
|
class CellStore;
|
||||||
|
@ -262,8 +268,8 @@ namespace MWBase
|
||||||
///< Adjust position after load to be on ground. Must be called after model load.
|
///< Adjust position after load to be on ground. Must be called after model load.
|
||||||
/// @param force do this even if the ptr is flying
|
/// @param force do this even if the ptr is flying
|
||||||
|
|
||||||
virtual void fixPosition (const MWWorld::Ptr& actor) = 0;
|
virtual void fixPosition () = 0;
|
||||||
///< Attempt to fix position so that the Ptr is no longer inside collision geometry.
|
///< Attempt to fix position so that the player is not stuck inside the geometry.
|
||||||
|
|
||||||
/// @note No-op for items in containers. Use ContainerStore::removeItem instead.
|
/// @note No-op for items in containers. Use ContainerStore::removeItem instead.
|
||||||
virtual void deleteObject (const MWWorld::Ptr& ptr) = 0;
|
virtual void deleteObject (const MWWorld::Ptr& ptr) = 0;
|
||||||
|
@ -302,6 +308,9 @@ namespace MWBase
|
||||||
|
|
||||||
virtual bool castRay (float x1, float y1, float z1, float x2, float y2, float z2) = 0;
|
virtual bool castRay (float x1, float y1, float z1, float x2, float y2, float z2) = 0;
|
||||||
|
|
||||||
|
virtual void setActorCollisionMode(const MWWorld::Ptr& ptr, bool enabled) = 0;
|
||||||
|
virtual bool isActorCollisionEnabled(const MWWorld::Ptr& ptr) = 0;
|
||||||
|
|
||||||
virtual bool toggleCollisionMode() = 0;
|
virtual bool toggleCollisionMode() = 0;
|
||||||
///< Toggle collision mode for player. If disabled player object should ignore
|
///< Toggle collision mode for player. If disabled player object should ignore
|
||||||
/// collisions and gravity.
|
/// collisions and gravity.
|
||||||
|
@ -592,6 +601,15 @@ namespace MWBase
|
||||||
|
|
||||||
/// Preload VFX associated with this effect list
|
/// Preload VFX associated with this effect list
|
||||||
virtual void preloadEffects(const ESM::EffectList* effectList) = 0;
|
virtual void preloadEffects(const ESM::EffectList* effectList) = 0;
|
||||||
|
|
||||||
|
virtual DetourNavigator::Navigator* getNavigator() const = 0;
|
||||||
|
|
||||||
|
virtual void updateActorPath(const MWWorld::ConstPtr& actor, const std::deque<osg::Vec3f>& path,
|
||||||
|
const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end) const = 0;
|
||||||
|
|
||||||
|
virtual void removeActorPath(const MWWorld::ConstPtr& actor) const = 0;
|
||||||
|
|
||||||
|
virtual void setNavMeshNumberToRender(const std::size_t value) = 0;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -138,7 +138,7 @@ namespace MWClass
|
||||||
|
|
||||||
std::string Activator::getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const
|
std::string Activator::getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const
|
||||||
{
|
{
|
||||||
std::string model = getModel(ptr); // Assume it's not empty, since we wouldn't have gotten the soundgen otherwise
|
const std::string model = getModel(ptr); // Assume it's not empty, since we wouldn't have gotten the soundgen otherwise
|
||||||
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
|
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
|
||||||
std::string creatureId;
|
std::string creatureId;
|
||||||
|
|
||||||
|
@ -151,21 +151,35 @@ namespace MWClass
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (creatureId.empty())
|
|
||||||
return std::string();
|
|
||||||
|
|
||||||
int type = getSndGenTypeFromName(name);
|
int type = getSndGenTypeFromName(name);
|
||||||
std::vector<const ESM::SoundGenerator*> sounds;
|
|
||||||
|
|
||||||
for (auto sound = store.get<ESM::SoundGenerator>().begin(); sound != store.get<ESM::SoundGenerator>().end(); ++sound)
|
std::vector<const ESM::SoundGenerator*> fallbacksounds;
|
||||||
if (type == sound->mType && !sound->mCreature.empty() && (Misc::StringUtils::ciEqual(creatureId, sound->mCreature)))
|
if (!creatureId.empty())
|
||||||
sounds.push_back(&*sound);
|
{
|
||||||
|
std::vector<const ESM::SoundGenerator*> sounds;
|
||||||
|
for (auto sound = store.get<ESM::SoundGenerator>().begin(); sound != store.get<ESM::SoundGenerator>().end(); ++sound)
|
||||||
|
{
|
||||||
|
if (type == sound->mType && !sound->mCreature.empty() && (Misc::StringUtils::ciEqual(creatureId, sound->mCreature)))
|
||||||
|
sounds.push_back(&*sound);
|
||||||
|
if (type == sound->mType && sound->mCreature.empty())
|
||||||
|
fallbacksounds.push_back(&*sound);
|
||||||
|
}
|
||||||
|
|
||||||
if (!sounds.empty())
|
if (!sounds.empty())
|
||||||
return sounds[Misc::Rng::rollDice(sounds.size())]->mSound;
|
return sounds[Misc::Rng::rollDice(sounds.size())]->mSound;
|
||||||
|
if (!fallbacksounds.empty())
|
||||||
|
return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size())]->mSound;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// The activator doesn't have a corresponding creature ID, but we can try to use the defaults
|
||||||
|
for (auto sound = store.get<ESM::SoundGenerator>().begin(); sound != store.get<ESM::SoundGenerator>().end(); ++sound)
|
||||||
|
if (type == sound->mType && sound->mCreature.empty())
|
||||||
|
fallbacksounds.push_back(&*sound);
|
||||||
|
|
||||||
if (type == ESM::SoundGenerator::Land)
|
if (!fallbacksounds.empty())
|
||||||
return "Body Fall Large";
|
return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size())]->mSound;
|
||||||
|
}
|
||||||
|
|
||||||
return std::string();
|
return std::string();
|
||||||
}
|
}
|
||||||
|
|
|
@ -297,11 +297,11 @@ namespace MWClass
|
||||||
{
|
{
|
||||||
const MWWorld::InventoryStore& invStore = npc.getClass().getInventoryStore(npc);
|
const MWWorld::InventoryStore& invStore = npc.getClass().getInventoryStore(npc);
|
||||||
|
|
||||||
if (ptr.getCellRef().getCharge() == 0)
|
if (getItemHealth(ptr) == 0)
|
||||||
return std::make_pair(0, "#{sInventoryMessage1}");
|
return std::make_pair(0, "#{sInventoryMessage1}");
|
||||||
|
|
||||||
// slots that this item can be equipped in
|
// slots that this item can be equipped in
|
||||||
std::pair<std::vector<int>, bool> slots_ = ptr.getClass().getEquipmentSlots(ptr);
|
std::pair<std::vector<int>, bool> slots_ = getEquipmentSlots(ptr);
|
||||||
|
|
||||||
if (slots_.first.empty())
|
if (slots_.first.empty())
|
||||||
return std::make_pair(0, "");
|
return std::make_pair(0, "");
|
||||||
|
|
|
@ -215,7 +215,7 @@ namespace MWClass
|
||||||
std::pair<int, std::string> Clothing::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const
|
std::pair<int, std::string> Clothing::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const
|
||||||
{
|
{
|
||||||
// slots that this item can be equipped in
|
// slots that this item can be equipped in
|
||||||
std::pair<std::vector<int>, bool> slots_ = ptr.getClass().getEquipmentSlots(ptr);
|
std::pair<std::vector<int>, bool> slots_ = getEquipmentSlots(ptr);
|
||||||
|
|
||||||
if (slots_.first.empty())
|
if (slots_.first.empty())
|
||||||
return std::make_pair(0, "");
|
return std::make_pair(0, "");
|
||||||
|
|
|
@ -137,7 +137,7 @@ namespace MWClass
|
||||||
|
|
||||||
// Persistent actors with 0 health do not play death animation
|
// Persistent actors with 0 health do not play death animation
|
||||||
if (data->mCreatureStats.isDead())
|
if (data->mCreatureStats.isDead())
|
||||||
data->mCreatureStats.setDeathAnimationFinished(ptr.getClass().isPersistent(ptr));
|
data->mCreatureStats.setDeathAnimationFinished(isPersistent(ptr));
|
||||||
|
|
||||||
// spells
|
// spells
|
||||||
for (std::vector<std::string>::const_iterator iter (ref->mBase->mSpells.mList.begin());
|
for (std::vector<std::string>::const_iterator iter (ref->mBase->mSpells.mList.begin());
|
||||||
|
@ -166,7 +166,7 @@ namespace MWClass
|
||||||
getContainerStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId());
|
getContainerStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId());
|
||||||
|
|
||||||
if (hasInventory)
|
if (hasInventory)
|
||||||
getInventoryStore(ptr).autoEquipShield(ptr);
|
getInventoryStore(ptr).autoEquip(ptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,9 +194,9 @@ namespace MWClass
|
||||||
models.push_back(model);
|
models.push_back(model);
|
||||||
|
|
||||||
// FIXME: use const version of InventoryStore functions once they are available
|
// FIXME: use const version of InventoryStore functions once they are available
|
||||||
if (ptr.getClass().hasInventoryStore(ptr))
|
if (hasInventoryStore(ptr))
|
||||||
{
|
{
|
||||||
const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr);
|
const MWWorld::InventoryStore& invStore = getInventoryStore(ptr);
|
||||||
for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
|
for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
|
||||||
{
|
{
|
||||||
MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot);
|
MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot);
|
||||||
|
@ -237,7 +237,7 @@ namespace MWClass
|
||||||
|
|
||||||
// Get the weapon used (if hand-to-hand, weapon = inv.end())
|
// Get the weapon used (if hand-to-hand, weapon = inv.end())
|
||||||
MWWorld::Ptr weapon;
|
MWWorld::Ptr weapon;
|
||||||
if (ptr.getClass().hasInventoryStore(ptr))
|
if (hasInventoryStore(ptr))
|
||||||
{
|
{
|
||||||
MWWorld::InventoryStore &inv = getInventoryStore(ptr);
|
MWWorld::InventoryStore &inv = getInventoryStore(ptr);
|
||||||
MWWorld::ContainerStoreIterator weaponslot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
MWWorld::ContainerStoreIterator weaponslot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||||
|
@ -253,8 +253,7 @@ namespace MWClass
|
||||||
|
|
||||||
// For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result.
|
// For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result.
|
||||||
std::vector<MWWorld::Ptr> targetActors;
|
std::vector<MWWorld::Ptr> targetActors;
|
||||||
if (!ptr.isEmpty() && ptr.getClass().isActor())
|
stats.getAiSequence().getCombatTargets(targetActors);
|
||||||
ptr.getClass().getCreatureStats(ptr).getAiSequence().getCombatTargets(targetActors);
|
|
||||||
|
|
||||||
std::pair<MWWorld::Ptr, osg::Vec3f> result = MWBase::Environment::get().getWorld()->getHitContact(ptr, dist, targetActors);
|
std::pair<MWWorld::Ptr, osg::Vec3f> result = MWBase::Environment::get().getWorld()->getHitContact(ptr, dist, targetActors);
|
||||||
if (result.first.isEmpty())
|
if (result.first.isEmpty())
|
||||||
|
@ -332,7 +331,7 @@ namespace MWClass
|
||||||
|
|
||||||
void Creature::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const
|
void Creature::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const
|
||||||
{
|
{
|
||||||
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
|
MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
|
||||||
|
|
||||||
// NOTE: 'object' and/or 'attacker' may be empty.
|
// NOTE: 'object' and/or 'attacker' may be empty.
|
||||||
if (!attacker.isEmpty() && attacker.getClass().isActor() && !stats.getAiSequence().isInCombat(attacker))
|
if (!attacker.isEmpty() && attacker.getClass().isActor() && !stats.getAiSequence().isInCombat(attacker))
|
||||||
|
@ -346,7 +345,7 @@ namespace MWClass
|
||||||
setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker);
|
setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker);
|
||||||
|
|
||||||
// Attacker and target store each other as hitattemptactor if they have no one stored yet
|
// Attacker and target store each other as hitattemptactor if they have no one stored yet
|
||||||
if (!attacker.isEmpty() && attacker.getClass().isActor() && !ptr.isEmpty() && ptr.getClass().isActor())
|
if (!attacker.isEmpty() && attacker.getClass().isActor())
|
||||||
{
|
{
|
||||||
MWMechanics::CreatureStats& statsAttacker = attacker.getClass().getCreatureStats(attacker);
|
MWMechanics::CreatureStats& statsAttacker = attacker.getClass().getCreatureStats(attacker);
|
||||||
// First handle the attacked actor
|
// First handle the attacked actor
|
||||||
|
@ -522,7 +521,7 @@ namespace MWClass
|
||||||
const MWBase::World *world = MWBase::Environment::get().getWorld();
|
const MWBase::World *world = MWBase::Environment::get().getWorld();
|
||||||
const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects();
|
const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects();
|
||||||
|
|
||||||
bool running = ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run);
|
bool running = stats.getStance(MWMechanics::CreatureStats::Stance_Run);
|
||||||
|
|
||||||
// The Run speed difference for creatures comes from the animation speed difference (see runStateToWalkState in character.cpp)
|
// The Run speed difference for creatures comes from the animation speed difference (see runStateToWalkState in character.cpp)
|
||||||
float runSpeed = walkSpeed;
|
float runSpeed = walkSpeed;
|
||||||
|
@ -632,25 +631,27 @@ namespace MWClass
|
||||||
if(type >= 0)
|
if(type >= 0)
|
||||||
{
|
{
|
||||||
std::vector<const ESM::SoundGenerator*> sounds;
|
std::vector<const ESM::SoundGenerator*> sounds;
|
||||||
|
std::vector<const ESM::SoundGenerator*> fallbacksounds;
|
||||||
|
|
||||||
MWWorld::LiveCellRef<ESM::Creature>* ref = ptr.get<ESM::Creature>();
|
MWWorld::LiveCellRef<ESM::Creature>* ref = ptr.get<ESM::Creature>();
|
||||||
|
|
||||||
const std::string& ourId = (ref->mBase->mOriginal.empty()) ? ptr.getCellRef().getRefId() : ref->mBase->mOriginal;
|
const std::string& ourId = (ref->mBase->mOriginal.empty()) ? ptr.getCellRef().getRefId() : ref->mBase->mOriginal;
|
||||||
|
|
||||||
MWWorld::Store<ESM::SoundGenerator>::iterator sound = store.begin();
|
MWWorld::Store<ESM::SoundGenerator>::iterator sound = store.begin();
|
||||||
while(sound != store.end())
|
while (sound != store.end())
|
||||||
{
|
{
|
||||||
if (type == sound->mType && !sound->mCreature.empty() && (Misc::StringUtils::ciEqual(ourId, sound->mCreature)))
|
if (type == sound->mType && !sound->mCreature.empty() && (Misc::StringUtils::ciEqual(ourId, sound->mCreature)))
|
||||||
sounds.push_back(&*sound);
|
sounds.push_back(&*sound);
|
||||||
|
if (type == sound->mType && sound->mCreature.empty())
|
||||||
|
fallbacksounds.push_back(&*sound);
|
||||||
++sound;
|
++sound;
|
||||||
}
|
}
|
||||||
if(!sounds.empty())
|
if (!sounds.empty())
|
||||||
return sounds[Misc::Rng::rollDice(sounds.size())]->mSound;
|
return sounds[Misc::Rng::rollDice(sounds.size())]->mSound;
|
||||||
|
if (!fallbacksounds.empty())
|
||||||
|
return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size())]->mSound;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == ESM::SoundGenerator::Land)
|
|
||||||
return "Body Fall Large";
|
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -806,7 +807,7 @@ namespace MWClass
|
||||||
|
|
||||||
void Creature::respawn(const MWWorld::Ptr &ptr) const
|
void Creature::respawn(const MWWorld::Ptr &ptr) const
|
||||||
{
|
{
|
||||||
const MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr);
|
const MWMechanics::CreatureStats& creatureStats = getCreatureStats(ptr);
|
||||||
if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead())
|
if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
@ -357,7 +357,7 @@ namespace MWClass
|
||||||
|
|
||||||
// Persistent actors with 0 health do not play death animation
|
// Persistent actors with 0 health do not play death animation
|
||||||
if (data->mNpcStats.isDead())
|
if (data->mNpcStats.isDead())
|
||||||
data->mNpcStats.setDeathAnimationFinished(ptr.getClass().isPersistent(ptr));
|
data->mNpcStats.setDeathAnimationFinished(isPersistent(ptr));
|
||||||
|
|
||||||
// race powers
|
// race powers
|
||||||
const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace);
|
const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace);
|
||||||
|
@ -469,41 +469,38 @@ namespace MWClass
|
||||||
|
|
||||||
// FIXME: use const version of InventoryStore functions once they are available
|
// FIXME: use const version of InventoryStore functions once they are available
|
||||||
// preload equipped items
|
// preload equipped items
|
||||||
if (ptr.getClass().hasInventoryStore(ptr))
|
const MWWorld::InventoryStore& invStore = getInventoryStore(ptr);
|
||||||
|
for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
|
||||||
{
|
{
|
||||||
const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr);
|
MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot);
|
||||||
for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
|
if (equipped != invStore.end())
|
||||||
{
|
{
|
||||||
MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot);
|
std::vector<ESM::PartReference> parts;
|
||||||
if (equipped != invStore.end())
|
if(equipped->getTypeName() == typeid(ESM::Clothing).name())
|
||||||
{
|
{
|
||||||
std::vector<ESM::PartReference> parts;
|
const ESM::Clothing *clothes = equipped->get<ESM::Clothing>()->mBase;
|
||||||
if(equipped->getTypeName() == typeid(ESM::Clothing).name())
|
parts = clothes->mParts.mParts;
|
||||||
{
|
}
|
||||||
const ESM::Clothing *clothes = equipped->get<ESM::Clothing>()->mBase;
|
else if(equipped->getTypeName() == typeid(ESM::Armor).name())
|
||||||
parts = clothes->mParts.mParts;
|
{
|
||||||
}
|
const ESM::Armor *armor = equipped->get<ESM::Armor>()->mBase;
|
||||||
else if(equipped->getTypeName() == typeid(ESM::Armor).name())
|
parts = armor->mParts.mParts;
|
||||||
{
|
}
|
||||||
const ESM::Armor *armor = equipped->get<ESM::Armor>()->mBase;
|
else
|
||||||
parts = armor->mParts.mParts;
|
{
|
||||||
}
|
std::string model = equipped->getClass().getModel(*equipped);
|
||||||
else
|
if (!model.empty())
|
||||||
{
|
models.push_back(model);
|
||||||
std::string model = equipped->getClass().getModel(*equipped);
|
}
|
||||||
if (!model.empty())
|
|
||||||
models.push_back(model);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (std::vector<ESM::PartReference>::const_iterator it = parts.begin(); it != parts.end(); ++it)
|
for (std::vector<ESM::PartReference>::const_iterator it = parts.begin(); it != parts.end(); ++it)
|
||||||
{
|
{
|
||||||
std::string partname = female ? it->mFemale : it->mMale;
|
std::string partname = female ? it->mFemale : it->mMale;
|
||||||
if (partname.empty())
|
if (partname.empty())
|
||||||
partname = female ? it->mMale : it->mFemale;
|
partname = female ? it->mMale : it->mFemale;
|
||||||
const ESM::BodyPart* part = MWBase::Environment::get().getWorld()->getStore().get<ESM::BodyPart>().search(partname);
|
const ESM::BodyPart* part = MWBase::Environment::get().getWorld()->getStore().get<ESM::BodyPart>().search(partname);
|
||||||
if (part && !part->mModel.empty())
|
if (part && !part->mModel.empty())
|
||||||
models.push_back("meshes/"+part->mModel);
|
models.push_back("meshes/"+part->mModel);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -573,8 +570,8 @@ namespace MWClass
|
||||||
|
|
||||||
// For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result.
|
// For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result.
|
||||||
std::vector<MWWorld::Ptr> targetActors;
|
std::vector<MWWorld::Ptr> targetActors;
|
||||||
if (!ptr.isEmpty() && ptr.getClass().isActor() && ptr != MWMechanics::getPlayer())
|
if (ptr != MWMechanics::getPlayer())
|
||||||
ptr.getClass().getCreatureStats(ptr).getAiSequence().getCombatTargets(targetActors);
|
getCreatureStats(ptr).getAiSequence().getCombatTargets(targetActors);
|
||||||
|
|
||||||
// TODO: Use second to work out the hit angle
|
// TODO: Use second to work out the hit angle
|
||||||
std::pair<MWWorld::Ptr, osg::Vec3f> result = world->getHitContact(ptr, dist, targetActors);
|
std::pair<MWWorld::Ptr, osg::Vec3f> result = world->getHitContact(ptr, dist, targetActors);
|
||||||
|
@ -597,7 +594,7 @@ namespace MWClass
|
||||||
if(!weapon.isEmpty())
|
if(!weapon.isEmpty())
|
||||||
weapskill = weapon.getClass().getEquipmentSkill(weapon);
|
weapskill = weapon.getClass().getEquipmentSkill(weapon);
|
||||||
|
|
||||||
float hitchance = MWMechanics::getHitChance(ptr, victim, ptr.getClass().getSkill(ptr, weapskill));
|
float hitchance = MWMechanics::getHitChance(ptr, victim, getSkill(ptr, weapskill));
|
||||||
|
|
||||||
if (Misc::Rng::roll0to99() >= hitchance)
|
if (Misc::Rng::roll0to99() >= hitchance)
|
||||||
{
|
{
|
||||||
|
@ -667,7 +664,7 @@ namespace MWClass
|
||||||
void Npc::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const
|
void Npc::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const
|
||||||
{
|
{
|
||||||
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
||||||
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
|
MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
|
||||||
bool wasDead = stats.isDead();
|
bool wasDead = stats.isDead();
|
||||||
|
|
||||||
// Note OnPcHitMe is not set for friendly hits.
|
// Note OnPcHitMe is not set for friendly hits.
|
||||||
|
@ -681,7 +678,7 @@ namespace MWClass
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attacker and target store each other as hitattemptactor if they have no one stored yet
|
// Attacker and target store each other as hitattemptactor if they have no one stored yet
|
||||||
if (!attacker.isEmpty() && attacker.getClass().isActor() && !ptr.isEmpty() && ptr.getClass().isActor())
|
if (!attacker.isEmpty() && attacker.getClass().isActor())
|
||||||
{
|
{
|
||||||
MWMechanics::CreatureStats& statsAttacker = attacker.getClass().getCreatureStats(attacker);
|
MWMechanics::CreatureStats& statsAttacker = attacker.getClass().getCreatureStats(attacker);
|
||||||
// First handle the attacked actor
|
// First handle the attacked actor
|
||||||
|
@ -702,7 +699,7 @@ namespace MWClass
|
||||||
|
|
||||||
if (setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer())
|
if (setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer())
|
||||||
{
|
{
|
||||||
const std::string &script = ptr.getClass().getScript(ptr);
|
const std::string &script = getScript(ptr);
|
||||||
/* Set the OnPCHitMe script variable. The script is responsible for clearing it. */
|
/* Set the OnPCHitMe script variable. The script is responsible for clearing it. */
|
||||||
if(!script.empty())
|
if(!script.empty())
|
||||||
ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1);
|
ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1);
|
||||||
|
@ -891,12 +888,11 @@ namespace MWClass
|
||||||
if(stats.getAiSequence().isInCombat())
|
if(stats.getAiSequence().isInCombat())
|
||||||
return std::shared_ptr<MWWorld::Action>(new MWWorld::FailedAction(""));
|
return std::shared_ptr<MWWorld::Action>(new MWWorld::FailedAction(""));
|
||||||
|
|
||||||
if(getCreatureStats(actor).getStance(MWMechanics::CreatureStats::Stance_Sneak)
|
if(getCreatureStats(actor).getStance(MWMechanics::CreatureStats::Stance_Sneak) || stats.getKnockedDown())
|
||||||
|| ptr.getClass().getCreatureStats(ptr).getKnockedDown())
|
|
||||||
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr)); // stealing
|
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr)); // stealing
|
||||||
|
|
||||||
// Can't talk to werewolfs
|
// Can't talk to werewolfs
|
||||||
if(ptr.getClass().isNpc() && ptr.getClass().getNpcStats(ptr).isWerewolf())
|
if(getNpcStats(ptr).isWerewolf())
|
||||||
return std::shared_ptr<MWWorld::Action> (new MWWorld::FailedAction(""));
|
return std::shared_ptr<MWWorld::Action> (new MWWorld::FailedAction(""));
|
||||||
|
|
||||||
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(ptr));
|
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(ptr));
|
||||||
|
@ -927,7 +923,7 @@ namespace MWClass
|
||||||
|
|
||||||
float Npc::getSpeed(const MWWorld::Ptr& ptr) const
|
float Npc::getSpeed(const MWWorld::Ptr& ptr) const
|
||||||
{
|
{
|
||||||
const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
|
const MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
|
||||||
if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead())
|
if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead())
|
||||||
return 0.f;
|
return 0.f;
|
||||||
|
|
||||||
|
@ -993,7 +989,7 @@ namespace MWClass
|
||||||
if(getEncumbrance(ptr) > getCapacity(ptr))
|
if(getEncumbrance(ptr) > getCapacity(ptr))
|
||||||
return 0.f;
|
return 0.f;
|
||||||
|
|
||||||
const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
|
const MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
|
||||||
if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead())
|
if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead())
|
||||||
return 0.f;
|
return 0.f;
|
||||||
|
|
||||||
|
@ -1148,9 +1144,7 @@ namespace MWClass
|
||||||
const bool hasHealth = it->getClass().hasItemHealth(*it);
|
const bool hasHealth = it->getClass().hasItemHealth(*it);
|
||||||
if (hasHealth)
|
if (hasHealth)
|
||||||
{
|
{
|
||||||
int armorHealth = it->getClass().getItemHealth(*it);
|
ratings[i] *= it->getClass().getItemNormalizedHealth(*it);
|
||||||
int armorMaxHealth = it->getClass().getItemMaxHealth(*it);
|
|
||||||
ratings[i] *= (float(armorHealth) / armorMaxHealth);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1215,13 +1209,13 @@ namespace MWClass
|
||||||
return (name == "left") ? "FootWaterLeft" : "FootWaterRight";
|
return (name == "left") ? "FootWaterLeft" : "FootWaterRight";
|
||||||
if(world->isOnGround(ptr))
|
if(world->isOnGround(ptr))
|
||||||
{
|
{
|
||||||
if (ptr.getClass().getNpcStats(ptr).isWerewolf()
|
if (getNpcStats(ptr).isWerewolf()
|
||||||
&& ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run))
|
&& getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run))
|
||||||
{
|
{
|
||||||
MWMechanics::WeaponType weaponType = MWMechanics::WeapType_None;
|
MWMechanics::WeaponType weaponType = MWMechanics::WeapType_None;
|
||||||
MWMechanics::getActiveWeapon(ptr.getClass().getCreatureStats(ptr), ptr.getClass().getInventoryStore(ptr), &weaponType);
|
MWMechanics::getActiveWeapon(getCreatureStats(ptr), getInventoryStore(ptr), &weaponType);
|
||||||
if (weaponType == MWMechanics::WeapType_None)
|
if (weaponType == MWMechanics::WeapType_None)
|
||||||
return "";
|
return std::string();
|
||||||
}
|
}
|
||||||
|
|
||||||
const MWWorld::InventoryStore &inv = Npc::getInventoryStore(ptr);
|
const MWWorld::InventoryStore &inv = Npc::getInventoryStore(ptr);
|
||||||
|
@ -1239,12 +1233,12 @@ namespace MWClass
|
||||||
return (name == "left") ? "FootHeavyLeft" : "FootHeavyRight";
|
return (name == "left") ? "FootHeavyLeft" : "FootHeavyRight";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "";
|
return std::string();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Morrowind ignores land soundgen for NPCs
|
// Morrowind ignores land soundgen for NPCs
|
||||||
if(name == "land")
|
if(name == "land")
|
||||||
return "";
|
return std::string();
|
||||||
if(name == "swimleft")
|
if(name == "swimleft")
|
||||||
return "Swim Left";
|
return "Swim Left";
|
||||||
if(name == "swimright")
|
if(name == "swimright")
|
||||||
|
@ -1254,11 +1248,11 @@ namespace MWClass
|
||||||
// only for biped creatures?
|
// only for biped creatures?
|
||||||
|
|
||||||
if(name == "moan")
|
if(name == "moan")
|
||||||
return "";
|
return std::string();
|
||||||
if(name == "roar")
|
if(name == "roar")
|
||||||
return "";
|
return std::string();
|
||||||
if(name == "scream")
|
if(name == "scream")
|
||||||
return "";
|
return std::string();
|
||||||
|
|
||||||
throw std::runtime_error(std::string("Unexpected soundgen type: ")+name);
|
throw std::runtime_error(std::string("Unexpected soundgen type: ")+name);
|
||||||
}
|
}
|
||||||
|
@ -1272,7 +1266,7 @@ namespace MWClass
|
||||||
|
|
||||||
int Npc::getSkill(const MWWorld::Ptr& ptr, int skill) const
|
int Npc::getSkill(const MWWorld::Ptr& ptr, int skill) const
|
||||||
{
|
{
|
||||||
return ptr.getClass().getNpcStats(ptr).getSkill(skill).getModified();
|
return getNpcStats(ptr).getSkill(skill).getModified();
|
||||||
}
|
}
|
||||||
|
|
||||||
int Npc::getBloodTexture(const MWWorld::ConstPtr &ptr) const
|
int Npc::getBloodTexture(const MWWorld::ConstPtr &ptr) const
|
||||||
|
@ -1354,7 +1348,7 @@ namespace MWClass
|
||||||
|
|
||||||
void Npc::respawn(const MWWorld::Ptr &ptr) const
|
void Npc::respawn(const MWWorld::Ptr &ptr) const
|
||||||
{
|
{
|
||||||
const MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr);
|
const MWMechanics::CreatureStats& creatureStats = getCreatureStats(ptr);
|
||||||
if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead())
|
if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
@ -265,7 +265,7 @@ namespace MWClass
|
||||||
std::string text;
|
std::string text;
|
||||||
|
|
||||||
// weapon type & damage
|
// weapon type & damage
|
||||||
if ((ref->mBase->mData.mType < 12 || Settings::Manager::getBool("show projectile damage", "Game")) && ref->mBase->mData.mType < 14)
|
if ((ref->mBase->mData.mType < ESM::Weapon::Arrow || Settings::Manager::getBool("show projectile damage", "Game")) && ref->mBase->mData.mType <= ESM::Weapon::Bolt)
|
||||||
{
|
{
|
||||||
text += "\n#{sType} ";
|
text += "\n#{sType} ";
|
||||||
|
|
||||||
|
@ -295,7 +295,15 @@ namespace MWClass
|
||||||
((oneOrTwoHanded != "") ? ", " + store.get<ESM::GameSetting>().find(oneOrTwoHanded)->mValue.getString() : "");
|
((oneOrTwoHanded != "") ? ", " + store.get<ESM::GameSetting>().find(oneOrTwoHanded)->mValue.getString() : "");
|
||||||
|
|
||||||
// weapon damage
|
// weapon damage
|
||||||
if (ref->mBase->mData.mType >= 9)
|
if (ref->mBase->mData.mType == ESM::Weapon::MarksmanThrown)
|
||||||
|
{
|
||||||
|
// Thrown weapons have 2x real damage applied
|
||||||
|
// as they're both the weapon and the ammo
|
||||||
|
text += "\n#{sAttack}: "
|
||||||
|
+ MWGui::ToolTips::toString(static_cast<int>(ref->mBase->mData.mChop[0] * 2))
|
||||||
|
+ " - " + MWGui::ToolTips::toString(static_cast<int>(ref->mBase->mData.mChop[1] * 2));
|
||||||
|
}
|
||||||
|
else if (ref->mBase->mData.mType >= ESM::Weapon::MarksmanBow)
|
||||||
{
|
{
|
||||||
// marksman
|
// marksman
|
||||||
text += "\n#{sAttack}: "
|
text += "\n#{sAttack}: "
|
||||||
|
@ -383,7 +391,7 @@ namespace MWClass
|
||||||
|
|
||||||
std::pair<int, std::string> Weapon::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const
|
std::pair<int, std::string> Weapon::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const
|
||||||
{
|
{
|
||||||
if (hasItemHealth(ptr) && ptr.getCellRef().getCharge() == 0)
|
if (hasItemHealth(ptr) && getItemHealth(ptr) == 0)
|
||||||
return std::make_pair(0, "#{sInventoryMessage1}");
|
return std::make_pair(0, "#{sInventoryMessage1}");
|
||||||
|
|
||||||
// Do not allow equip weapons from inventory during attack
|
// Do not allow equip weapons from inventory during attack
|
||||||
|
@ -391,7 +399,7 @@ namespace MWClass
|
||||||
&& MWBase::Environment::get().getWindowManager()->isGuiMode())
|
&& MWBase::Environment::get().getWindowManager()->isGuiMode())
|
||||||
return std::make_pair(0, "#{sCantEquipWeapWarning}");
|
return std::make_pair(0, "#{sCantEquipWeapWarning}");
|
||||||
|
|
||||||
std::pair<std::vector<int>, bool> slots_ = ptr.getClass().getEquipmentSlots(ptr);
|
std::pair<std::vector<int>, bool> slots_ = getEquipmentSlots(ptr);
|
||||||
|
|
||||||
if (slots_.first.empty())
|
if (slots_.first.empty())
|
||||||
return std::make_pair (0, "");
|
return std::make_pair (0, "");
|
||||||
|
|
|
@ -96,7 +96,7 @@ namespace MWGui
|
||||||
Log(Debug::Warning) << "Warning: no splash screens found!";
|
Log(Debug::Warning) << "Warning: no splash screens found!";
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoadingScreen::setLabel(const std::string &label, bool important)
|
void LoadingScreen::setLabel(const std::string &label, bool important, bool center)
|
||||||
{
|
{
|
||||||
mImportantLabel = important;
|
mImportantLabel = important;
|
||||||
|
|
||||||
|
@ -105,7 +105,11 @@ namespace MWGui
|
||||||
MyGUI::IntSize size(mLoadingText->getTextSize().width+padding, mLoadingBox->getHeight());
|
MyGUI::IntSize size(mLoadingText->getTextSize().width+padding, mLoadingBox->getHeight());
|
||||||
size.width = std::max(300, size.width);
|
size.width = std::max(300, size.width);
|
||||||
mLoadingBox->setSize(size);
|
mLoadingBox->setSize(size);
|
||||||
mLoadingBox->setPosition(mMainWidget->getWidth()/2 - mLoadingBox->getWidth()/2, mLoadingBox->getTop());
|
|
||||||
|
if (center)
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoadingScreen::setVisible(bool visible)
|
void LoadingScreen::setVisible(bool visible)
|
||||||
|
|
|
@ -34,7 +34,7 @@ namespace MWGui
|
||||||
virtual ~LoadingScreen();
|
virtual ~LoadingScreen();
|
||||||
|
|
||||||
/// Overridden from Loading::Listener, see the Loading::Listener documentation for usage details
|
/// Overridden from Loading::Listener, see the Loading::Listener documentation for usage details
|
||||||
virtual void setLabel (const std::string& label, bool important);
|
virtual void setLabel (const std::string& label, bool important, bool center);
|
||||||
virtual void loadingOn(bool visible=true);
|
virtual void loadingOn(bool visible=true);
|
||||||
virtual void loadingOff();
|
virtual void loadingOff();
|
||||||
virtual void setProgressRange (size_t range);
|
virtual void setProgressRange (size_t range);
|
||||||
|
|
|
@ -51,7 +51,7 @@ void MerchantRepair::setPtr(const MWWorld::Ptr &actor)
|
||||||
{
|
{
|
||||||
int maxDurability = iter->getClass().getItemMaxHealth(*iter);
|
int maxDurability = iter->getClass().getItemMaxHealth(*iter);
|
||||||
int durability = iter->getClass().getItemHealth(*iter);
|
int durability = iter->getClass().getItemHealth(*iter);
|
||||||
if (maxDurability == durability)
|
if (maxDurability == durability || maxDurability == 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
int basePrice = iter->getClass().getValue(*iter);
|
int basePrice = iter->getClass().getValue(*iter);
|
||||||
|
|
|
@ -35,6 +35,11 @@ namespace MWGui
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int MessageBoxManager::getMessagesCount()
|
||||||
|
{
|
||||||
|
return mMessageBoxes.size();
|
||||||
|
}
|
||||||
|
|
||||||
void MessageBoxManager::clear()
|
void MessageBoxManager::clear()
|
||||||
{
|
{
|
||||||
if (mInterMessageBoxe)
|
if (mInterMessageBoxe)
|
||||||
|
|
|
@ -28,6 +28,8 @@ namespace MWGui
|
||||||
bool createInteractiveMessageBox (const std::string& message, const std::vector<std::string>& buttons);
|
bool createInteractiveMessageBox (const std::string& message, const std::vector<std::string>& buttons);
|
||||||
bool isInteractiveMessageBox ();
|
bool isInteractiveMessageBox ();
|
||||||
|
|
||||||
|
int getMessagesCount();
|
||||||
|
|
||||||
const InteractiveMessageBox* getInteractiveMessageBox() const { return mInterMessageBoxe; }
|
const InteractiveMessageBox* getInteractiveMessageBox() const { return mInterMessageBoxe; }
|
||||||
|
|
||||||
/// Remove all message boxes
|
/// Remove all message boxes
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
#include "../mwbase/soundmanager.hpp"
|
#include "../mwbase/soundmanager.hpp"
|
||||||
#include "../mwbase/inputmanager.hpp"
|
#include "../mwbase/inputmanager.hpp"
|
||||||
|
#include "../mwbase/mechanicsmanager.hpp"
|
||||||
#include "../mwbase/windowmanager.hpp"
|
#include "../mwbase/windowmanager.hpp"
|
||||||
|
|
||||||
#include "confirmationdialog.hpp"
|
#include "confirmationdialog.hpp"
|
||||||
|
@ -437,6 +438,7 @@ namespace MWGui
|
||||||
MWBase::Environment::get().getSoundManager()->processChangedSettings(changed);
|
MWBase::Environment::get().getSoundManager()->processChangedSettings(changed);
|
||||||
MWBase::Environment::get().getWindowManager()->processChangedSettings(changed);
|
MWBase::Environment::get().getWindowManager()->processChangedSettings(changed);
|
||||||
MWBase::Environment::get().getInputManager()->processChangedSettings(changed);
|
MWBase::Environment::get().getInputManager()->processChangedSettings(changed);
|
||||||
|
MWBase::Environment::get().getMechanicsManager()->processChangedSettings(changed);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SettingsWindow::onKeyboardSwitchClicked(MyGUI::Widget* _sender)
|
void SettingsWindow::onKeyboardSwitchClicked(MyGUI::Widget* _sender)
|
||||||
|
|
|
@ -91,8 +91,7 @@ namespace
|
||||||
if (ench->mData.mType == ESM::Enchantment::ConstantEffect)
|
if (ench->mData.mType == ESM::Enchantment::ConstantEffect)
|
||||||
leftChargePercent = 101;
|
leftChargePercent = 101;
|
||||||
else
|
else
|
||||||
leftChargePercent = (left.mBase.getCellRef().getEnchantmentCharge() == -1) ? 100
|
leftChargePercent = static_cast<int>(left.mBase.getCellRef().getNormalizedEnchantmentCharge(ench->mData.mCharge) * 100);
|
||||||
: static_cast<int>(left.mBase.getCellRef().getEnchantmentCharge() / static_cast<float>(ench->mData.mCharge) * 100);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,8 +103,7 @@ namespace
|
||||||
if (ench->mData.mType == ESM::Enchantment::ConstantEffect)
|
if (ench->mData.mType == ESM::Enchantment::ConstantEffect)
|
||||||
rightChargePercent = 101;
|
rightChargePercent = 101;
|
||||||
else
|
else
|
||||||
rightChargePercent = (right.mBase.getCellRef().getEnchantmentCharge() == -1) ? 100
|
rightChargePercent = static_cast<int>(right.mBase.getCellRef().getNormalizedEnchantmentCharge(ench->mData.mCharge) * 100);
|
||||||
: static_cast<int>(right.mBase.getCellRef().getEnchantmentCharge() / static_cast<float>(ench->mData.mCharge) * 100);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,8 +35,7 @@ namespace
|
||||||
float price = static_cast<float>(item.getClass().getValue(item));
|
float price = static_cast<float>(item.getClass().getValue(item));
|
||||||
if (item.getClass().hasItemHealth(item))
|
if (item.getClass().hasItemHealth(item))
|
||||||
{
|
{
|
||||||
price *= item.getClass().getItemHealth(item);
|
price *= item.getClass().getItemNormalizedHealth(item);
|
||||||
price /= item.getClass().getItemMaxHealth(item);
|
|
||||||
}
|
}
|
||||||
return static_cast<int>(price * count);
|
return static_cast<int>(price * count);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1369,8 +1369,7 @@ namespace MWGui
|
||||||
const ESM::Enchantment* ench = mStore->get<ESM::Enchantment>()
|
const ESM::Enchantment* ench = mStore->get<ESM::Enchantment>()
|
||||||
.find(item.getClass().getEnchantment(item));
|
.find(item.getClass().getEnchantment(item));
|
||||||
|
|
||||||
int chargePercent = (item.getCellRef().getEnchantmentCharge() == -1) ? 100
|
int chargePercent = static_cast<int>(item.getCellRef().getNormalizedEnchantmentCharge(ench->mData.mCharge) * 100);
|
||||||
: static_cast<int>(item.getCellRef().getEnchantmentCharge() / static_cast<float>(ench->mData.mCharge) * 100);
|
|
||||||
mHud->setSelectedEnchantItem(item, chargePercent);
|
mHud->setSelectedEnchantItem(item, chargePercent);
|
||||||
mSpellWindow->setTitle(item.getClass().getName(item));
|
mSpellWindow->setTitle(item.getClass().getName(item));
|
||||||
}
|
}
|
||||||
|
@ -1386,7 +1385,7 @@ namespace MWGui
|
||||||
int durabilityPercent = 100;
|
int durabilityPercent = 100;
|
||||||
if (item.getClass().hasItemHealth(item))
|
if (item.getClass().hasItemHealth(item))
|
||||||
{
|
{
|
||||||
durabilityPercent = static_cast<int>(item.getClass().getItemHealth(item) / static_cast<float>(item.getClass().getItemMaxHealth(item)) * 100);
|
durabilityPercent = static_cast<int>(item.getClass().getItemNormalizedHealth(item) * 100);
|
||||||
}
|
}
|
||||||
mHud->setSelectedWeapon(item, durabilityPercent);
|
mHud->setSelectedWeapon(item, durabilityPercent);
|
||||||
mInventoryWindow->setTitle(item.getClass().getName(item));
|
mInventoryWindow->setTitle(item.getClass().getName(item));
|
||||||
|
@ -1671,6 +1670,15 @@ namespace MWGui
|
||||||
mHud->setEnemy(enemy);
|
mHud->setEnemy(enemy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int WindowManager::getMessagesCount() const
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
if (mMessageBoxManager)
|
||||||
|
count = mMessageBoxManager->getMessagesCount();
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
Loading::Listener* WindowManager::getLoadingScreen()
|
Loading::Listener* WindowManager::getLoadingScreen()
|
||||||
{
|
{
|
||||||
return mLoadingScreen;
|
return mLoadingScreen;
|
||||||
|
|
|
@ -316,6 +316,8 @@ namespace MWGui
|
||||||
|
|
||||||
virtual void setEnemy (const MWWorld::Ptr& enemy);
|
virtual void setEnemy (const MWWorld::Ptr& enemy);
|
||||||
|
|
||||||
|
virtual int getMessagesCount() const;
|
||||||
|
|
||||||
virtual const Translation::Storage& getTranslationDataStorage() const;
|
virtual const Translation::Storage& getTranslationDataStorage() const;
|
||||||
|
|
||||||
void onSoulgemDialogButtonPressed (int button);
|
void onSoulgemDialogButtonPressed (int button);
|
||||||
|
|
|
@ -147,9 +147,6 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
const float aiProcessingDistance = 7168;
|
|
||||||
const float sqrAiProcessingDistance = aiProcessingDistance*aiProcessingDistance;
|
|
||||||
|
|
||||||
class SoulTrap : public MWMechanics::EffectSourceVisitor
|
class SoulTrap : public MWMechanics::EffectSourceVisitor
|
||||||
{
|
{
|
||||||
MWWorld::Ptr mCreature;
|
MWWorld::Ptr mCreature;
|
||||||
|
@ -364,7 +361,8 @@ namespace MWMechanics
|
||||||
const ESM::Position& actor1Pos = actor1.getRefData().getPosition();
|
const ESM::Position& actor1Pos = actor1.getRefData().getPosition();
|
||||||
const ESM::Position& actor2Pos = actor2.getRefData().getPosition();
|
const ESM::Position& actor2Pos = actor2.getRefData().getPosition();
|
||||||
float sqrDist = (actor1Pos.asVec3() - actor2Pos.asVec3()).length2();
|
float sqrDist = (actor1Pos.asVec3() - actor2Pos.asVec3()).length2();
|
||||||
if (sqrDist > sqrAiProcessingDistance)
|
|
||||||
|
if (sqrDist > mActorsProcessingRange*mActorsProcessingRange)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// No combat for totally static creatures
|
// No combat for totally static creatures
|
||||||
|
@ -1130,8 +1128,11 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Actors::Actors() {
|
Actors::Actors()
|
||||||
|
{
|
||||||
mTimerDisposeSummonsCorpses = 0.2f; // We should add a delay between summoned creature death and its corpse despawning
|
mTimerDisposeSummonsCorpses = 0.2f; // We should add a delay between summoned creature death and its corpse despawning
|
||||||
|
|
||||||
|
updateProcessingRange();
|
||||||
}
|
}
|
||||||
|
|
||||||
Actors::~Actors()
|
Actors::~Actors()
|
||||||
|
@ -1139,6 +1140,23 @@ namespace MWMechanics
|
||||||
clear();
|
clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float Actors::getProcessingRange() const
|
||||||
|
{
|
||||||
|
return mActorsProcessingRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Actors::updateProcessingRange()
|
||||||
|
{
|
||||||
|
// We have to cap it since using high values (larger than 7168) will make some quests harder or impossible to complete (bug #1876)
|
||||||
|
static const float maxProcessingRange = 7168.f;
|
||||||
|
static const float minProcessingRange = maxProcessingRange / 2.f;
|
||||||
|
|
||||||
|
float actorsProcessingRange = Settings::Manager::getFloat("actors processing range", "Game");
|
||||||
|
actorsProcessingRange = std::min(actorsProcessingRange, maxProcessingRange);
|
||||||
|
actorsProcessingRange = std::max(actorsProcessingRange, minProcessingRange);
|
||||||
|
mActorsProcessingRange = actorsProcessingRange;
|
||||||
|
}
|
||||||
|
|
||||||
void Actors::addActor (const MWWorld::Ptr& ptr, bool updateImmediately)
|
void Actors::addActor (const MWWorld::Ptr& ptr, bool updateImmediately)
|
||||||
{
|
{
|
||||||
removeActor(ptr);
|
removeActor(ptr);
|
||||||
|
@ -1184,7 +1202,7 @@ namespace MWMechanics
|
||||||
// Otherwise check if any actor in AI processing range sees the target actor
|
// Otherwise check if any actor in AI processing range sees the target actor
|
||||||
std::vector<MWWorld::Ptr> actors;
|
std::vector<MWWorld::Ptr> actors;
|
||||||
osg::Vec3f position (actor.getRefData().getPosition().asVec3());
|
osg::Vec3f position (actor.getRefData().getPosition().asVec3());
|
||||||
getObjectsInRange(position, aiProcessingDistance, actors);
|
getObjectsInRange(position, mActorsProcessingRange, actors);
|
||||||
for(std::vector<MWWorld::Ptr>::iterator it = actors.begin(); it != actors.end(); ++it)
|
for(std::vector<MWWorld::Ptr>::iterator it = actors.begin(); it != actors.end(); ++it)
|
||||||
{
|
{
|
||||||
if (*it == actor)
|
if (*it == actor)
|
||||||
|
@ -1242,7 +1260,7 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
if (iter->first == player) continue;
|
if (iter->first == player) continue;
|
||||||
|
|
||||||
bool inProcessingRange = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2() <= sqrAiProcessingDistance;
|
bool inProcessingRange = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2() <= mActorsProcessingRange*mActorsProcessingRange;
|
||||||
if (inProcessingRange)
|
if (inProcessingRange)
|
||||||
{
|
{
|
||||||
MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first);
|
MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first);
|
||||||
|
@ -1288,7 +1306,8 @@ namespace MWMechanics
|
||||||
if (timerUpdateEquippedLight >= updateEquippedLightInterval) timerUpdateEquippedLight = 0;
|
if (timerUpdateEquippedLight >= updateEquippedLightInterval) timerUpdateEquippedLight = 0;
|
||||||
|
|
||||||
// show torches only when there are darkness and no precipitations
|
// show torches only when there are darkness and no precipitations
|
||||||
bool showTorches = MWBase::Environment::get().getWorld()->useTorches();
|
MWBase::World* world = MWBase::Environment::get().getWorld();
|
||||||
|
bool showTorches = world->useTorches();
|
||||||
|
|
||||||
MWWorld::Ptr player = getPlayer();
|
MWWorld::Ptr player = getPlayer();
|
||||||
const osg::Vec3f playerPos = player.getRefData().getPosition().asVec3();
|
const osg::Vec3f playerPos = player.getRefData().getPosition().asVec3();
|
||||||
|
@ -1301,7 +1320,7 @@ namespace MWMechanics
|
||||||
int attackedByPlayerId = player.getClass().getCreatureStats(player).getHitAttemptActorId();
|
int attackedByPlayerId = player.getClass().getCreatureStats(player).getHitAttemptActorId();
|
||||||
if (attackedByPlayerId != -1)
|
if (attackedByPlayerId != -1)
|
||||||
{
|
{
|
||||||
const MWWorld::Ptr playerHitAttemptActor = MWBase::Environment::get().getWorld()->searchPtrViaActorId(attackedByPlayerId);
|
const MWWorld::Ptr playerHitAttemptActor = world->searchPtrViaActorId(attackedByPlayerId);
|
||||||
|
|
||||||
if (!playerHitAttemptActor.isInCell())
|
if (!playerHitAttemptActor.isInCell())
|
||||||
player.getClass().getCreatureStats(player).setHitAttemptActorId(-1);
|
player.getClass().getCreatureStats(player).setHitAttemptActorId(-1);
|
||||||
|
@ -1314,14 +1333,11 @@ namespace MWMechanics
|
||||||
CharacterController* ctrl = iter->second->getCharacterController();
|
CharacterController* ctrl = iter->second->getCharacterController();
|
||||||
|
|
||||||
float distSqr = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2();
|
float distSqr = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2();
|
||||||
// AI processing is only done within distance of 7168 units to the player. Note the "AI distance" slider doesn't affect this
|
// AI processing is only done within given distance to the player.
|
||||||
// (it only does some throttling for targets beyond the "AI distance", so doesn't give any guarantees as to whether AI will be enabled or not)
|
bool inProcessingRange = distSqr <= mActorsProcessingRange*mActorsProcessingRange;
|
||||||
// This distance could be made configurable later, but the setting must be marked with a big warning:
|
|
||||||
// using higher values will make a quest in Bloodmoon harder or impossible to complete (bug #1876)
|
|
||||||
bool inProcessingRange = distSqr <= sqrAiProcessingDistance;
|
|
||||||
|
|
||||||
if (isPlayer)
|
if (isPlayer)
|
||||||
ctrl->setAttackingOrSpell(MWBase::Environment::get().getWorld()->getPlayer().getAttackingOrSpell());
|
ctrl->setAttackingOrSpell(world->getPlayer().getAttackingOrSpell());
|
||||||
|
|
||||||
// If dead or no longer in combat, no longer store any actors who attempted to hit us. Also remove for the player.
|
// If dead or no longer in combat, no longer store any actors who attempted to hit us. Also remove for the player.
|
||||||
if (iter->first != player && (iter->first.getClass().getCreatureStats(iter->first).isDead()
|
if (iter->first != player && (iter->first.getClass().getCreatureStats(iter->first).isDead()
|
||||||
|
@ -1335,10 +1351,10 @@ namespace MWMechanics
|
||||||
|
|
||||||
if (!iter->first.getClass().getCreatureStats(iter->first).isDead())
|
if (!iter->first.getClass().getCreatureStats(iter->first).isDead())
|
||||||
{
|
{
|
||||||
bool cellChanged = MWBase::Environment::get().getWorld()->hasCellChanged();
|
bool cellChanged = world->hasCellChanged();
|
||||||
MWWorld::Ptr actor = iter->first; // make a copy of the map key to avoid it being invalidated when the player teleports
|
MWWorld::Ptr actor = iter->first; // make a copy of the map key to avoid it being invalidated when the player teleports
|
||||||
updateActor(actor, duration);
|
updateActor(actor, duration);
|
||||||
if (!cellChanged && MWBase::Environment::get().getWorld()->hasCellChanged())
|
if (!cellChanged && world->hasCellChanged())
|
||||||
{
|
{
|
||||||
return; // for now abort update of the old cell when cell changes by teleportation magic effect
|
return; // for now abort update of the old cell when cell changes by teleportation magic effect
|
||||||
// a better solution might be to apply cell changes at the end of the frame
|
// a better solution might be to apply cell changes at the end of the frame
|
||||||
|
@ -1363,7 +1379,7 @@ namespace MWMechanics
|
||||||
MWWorld::Ptr headTrackTarget;
|
MWWorld::Ptr headTrackTarget;
|
||||||
|
|
||||||
MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first);
|
MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first);
|
||||||
bool firstPersonPlayer = isPlayer && MWBase::Environment::get().getWorld()->isFirstPerson();
|
bool firstPersonPlayer = isPlayer && world->isFirstPerson();
|
||||||
|
|
||||||
// 1. Unconsious actor can not track target
|
// 1. Unconsious actor can not track target
|
||||||
// 2. Actors in combat and pursue mode do not bother to headtrack
|
// 2. Actors in combat and pursue mode do not bother to headtrack
|
||||||
|
@ -1423,27 +1439,25 @@ namespace MWMechanics
|
||||||
CharacterController* playerCharacter = nullptr;
|
CharacterController* playerCharacter = nullptr;
|
||||||
for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
|
for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
|
||||||
{
|
{
|
||||||
const float animationDistance = aiProcessingDistance + 400; // Slightly larger than AI distance so there is time to switch back to the idle animation.
|
const float dist = (playerPos - iter->first.getRefData().getPosition().asVec3()).length();
|
||||||
const float distSqr = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2();
|
|
||||||
bool isPlayer = iter->first == player;
|
bool isPlayer = iter->first == player;
|
||||||
bool inAnimationRange = isPlayer || (animationDistance == 0 || distSqr <= animationDistance*animationDistance);
|
bool inRange = isPlayer || dist <= mActorsProcessingRange;
|
||||||
int activeFlag = 1; // Can be changed back to '2' to keep updating bounding boxes off screen (more accurate, but slower)
|
int activeFlag = 1; // Can be changed back to '2' to keep updating bounding boxes off screen (more accurate, but slower)
|
||||||
if (isPlayer)
|
if (isPlayer)
|
||||||
activeFlag = 2;
|
activeFlag = 2;
|
||||||
int active = inAnimationRange ? activeFlag : 0;
|
int active = inRange ? activeFlag : 0;
|
||||||
bool canFly = iter->first.getClass().canFly(iter->first);
|
|
||||||
if (canFly)
|
|
||||||
{
|
|
||||||
// Keep animating flying creatures so they don't just hover in-air
|
|
||||||
inAnimationRange = true;
|
|
||||||
active = std::max(1, active);
|
|
||||||
}
|
|
||||||
|
|
||||||
CharacterController* ctrl = iter->second->getCharacterController();
|
CharacterController* ctrl = iter->second->getCharacterController();
|
||||||
ctrl->setActive(active);
|
ctrl->setActive(active);
|
||||||
|
|
||||||
if (!inAnimationRange)
|
if (!inRange)
|
||||||
|
{
|
||||||
|
iter->first.getRefData().getBaseNode()->setNodeMask(0);
|
||||||
|
world->setActorCollisionMode(iter->first, false);
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
else if (!isPlayer)
|
||||||
|
iter->first.getRefData().getBaseNode()->setNodeMask(1<<3);
|
||||||
|
|
||||||
if (iter->first.getClass().getCreatureStats(iter->first).isParalyzed())
|
if (iter->first.getClass().getCreatureStats(iter->first).isParalyzed())
|
||||||
ctrl->skipAnim();
|
ctrl->skipAnim();
|
||||||
|
@ -1455,11 +1469,28 @@ namespace MWMechanics
|
||||||
playerCharacter = ctrl;
|
playerCharacter = ctrl;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
world->setActorCollisionMode(iter->first, true);
|
||||||
ctrl->update(duration);
|
ctrl->update(duration);
|
||||||
|
|
||||||
|
// Fade away actors on large distance (>90% of actor's processing distance)
|
||||||
|
float visibilityRatio = 1.0;
|
||||||
|
float fadeStartDistance = mActorsProcessingRange*0.9f;
|
||||||
|
float fadeEndDistance = mActorsProcessingRange;
|
||||||
|
float fadeRatio = (dist - fadeStartDistance)/(fadeEndDistance - fadeStartDistance);
|
||||||
|
if (fadeRatio > 0)
|
||||||
|
visibilityRatio -= std::max(0.f, fadeRatio);
|
||||||
|
|
||||||
|
visibilityRatio = std::min(1.f, visibilityRatio);
|
||||||
|
|
||||||
|
ctrl->setVisibility(visibilityRatio);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (playerCharacter)
|
if (playerCharacter)
|
||||||
|
{
|
||||||
playerCharacter->update(duration);
|
playerCharacter->update(duration);
|
||||||
|
playerCharacter->setVisibility(1.f);
|
||||||
|
}
|
||||||
|
|
||||||
for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
|
for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
|
||||||
{
|
{
|
||||||
|
@ -1572,6 +1603,7 @@ namespace MWMechanics
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MWBase::Environment::get().getWorld()->removeActorPath(iter->first);
|
||||||
CharacterController::KillResult killResult = iter->second->getCharacterController()->kill();
|
CharacterController::KillResult killResult = iter->second->getCharacterController()->kill();
|
||||||
if (killResult == CharacterController::Result_DeathAnimStarted)
|
if (killResult == CharacterController::Result_DeathAnimStarted)
|
||||||
{
|
{
|
||||||
|
@ -1671,7 +1703,7 @@ namespace MWMechanics
|
||||||
restoreDynamicStats(iter->first, sleep);
|
restoreDynamicStats(iter->first, sleep);
|
||||||
|
|
||||||
if ((!iter->first.getRefData().getBaseNode()) ||
|
if ((!iter->first.getRefData().getBaseNode()) ||
|
||||||
(playerPos - iter->first.getRefData().getPosition().asVec3()).length2() > sqrAiProcessingDistance)
|
(playerPos - iter->first.getRefData().getPosition().asVec3()).length2() > mActorsProcessingRange*mActorsProcessingRange)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
adjustMagicEffects (iter->first);
|
adjustMagicEffects (iter->first);
|
||||||
|
@ -1915,7 +1947,7 @@ namespace MWMechanics
|
||||||
std::list<MWWorld::Ptr> list;
|
std::list<MWWorld::Ptr> list;
|
||||||
std::vector<MWWorld::Ptr> neighbors;
|
std::vector<MWWorld::Ptr> neighbors;
|
||||||
osg::Vec3f position (actor.getRefData().getPosition().asVec3());
|
osg::Vec3f position (actor.getRefData().getPosition().asVec3());
|
||||||
getObjectsInRange(position, aiProcessingDistance, neighbors);
|
getObjectsInRange(position, mActorsProcessingRange, neighbors);
|
||||||
for(auto neighbor = neighbors.begin(); neighbor != neighbors.end(); ++neighbor)
|
for(auto neighbor = neighbors.begin(); neighbor != neighbors.end(); ++neighbor)
|
||||||
{
|
{
|
||||||
if (*neighbor == actor)
|
if (*neighbor == actor)
|
||||||
|
@ -1936,7 +1968,7 @@ namespace MWMechanics
|
||||||
std::list<MWWorld::Ptr> list;
|
std::list<MWWorld::Ptr> list;
|
||||||
std::vector<MWWorld::Ptr> neighbors;
|
std::vector<MWWorld::Ptr> neighbors;
|
||||||
osg::Vec3f position (actor.getRefData().getPosition().asVec3());
|
osg::Vec3f position (actor.getRefData().getPosition().asVec3());
|
||||||
getObjectsInRange(position, aiProcessingDistance, neighbors);
|
getObjectsInRange(position, mActorsProcessingRange, neighbors);
|
||||||
|
|
||||||
std::set<MWWorld::Ptr> followers;
|
std::set<MWWorld::Ptr> followers;
|
||||||
getActorsFollowing(actor, followers);
|
getActorsFollowing(actor, followers);
|
||||||
|
|
|
@ -65,6 +65,9 @@ namespace MWMechanics
|
||||||
/// paused we may want to do it manually (after equipping permanent enchantment)
|
/// paused we may want to do it manually (after equipping permanent enchantment)
|
||||||
void updateMagicEffects (const MWWorld::Ptr& ptr);
|
void updateMagicEffects (const MWWorld::Ptr& ptr);
|
||||||
|
|
||||||
|
void updateProcessingRange();
|
||||||
|
float getProcessingRange() const;
|
||||||
|
|
||||||
void addActor (const MWWorld::Ptr& ptr, bool updateImmediately=false);
|
void addActor (const MWWorld::Ptr& ptr, bool updateImmediately=false);
|
||||||
///< Register an actor for stats management
|
///< Register an actor for stats management
|
||||||
///
|
///
|
||||||
|
@ -168,6 +171,7 @@ namespace MWMechanics
|
||||||
private:
|
private:
|
||||||
PtrActorMap mActors;
|
PtrActorMap mActors;
|
||||||
float mTimerDisposeSummonsCorpses;
|
float mTimerDisposeSummonsCorpses;
|
||||||
|
float mActorsProcessingRange;
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,14 +29,13 @@ namespace MWMechanics
|
||||||
|
|
||||||
actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing);
|
actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing);
|
||||||
|
|
||||||
if (target == MWWorld::Ptr() ||
|
// Stop if the target doesn't exist
|
||||||
!target.getRefData().getCount() || !target.getRefData().isEnabled() // Really we should check whether the target is currently registered
|
// Really we should be checking whether the target is currently registered with the MechanicsManager
|
||||||
// with the MechanicsManager
|
if (target == MWWorld::Ptr() || !target.getRefData().getCount() || !target.getRefData().isEnabled())
|
||||||
)
|
return true;
|
||||||
return true; //Target doesn't exist
|
|
||||||
|
|
||||||
//Set the target destination for the actor
|
//Set the target destination for the actor
|
||||||
ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos;
|
const osg::Vec3f dest = target.getRefData().getPosition().asVec3();
|
||||||
|
|
||||||
if (pathTo(actor, dest, duration, MWBase::Environment::get().getWorld()->getMaxActivationDistance())) //Stop when you get in activation range
|
if (pathTo(actor, dest, duration, MWBase::Environment::get().getWorld()->getMaxActivationDistance())) //Stop when you get in activation range
|
||||||
{
|
{
|
||||||
|
|
|
@ -26,9 +26,9 @@ bool MWMechanics::AiBreathe::execute (const MWWorld::Ptr& actor, CharacterContro
|
||||||
{
|
{
|
||||||
if (actorClass.getNpcStats(actor).getTimeToStartDrowning() < fHoldBreathTime / 2)
|
if (actorClass.getNpcStats(actor).getTimeToStartDrowning() < fHoldBreathTime / 2)
|
||||||
{
|
{
|
||||||
actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
|
actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
|
||||||
|
|
||||||
actor.getClass().getMovementSettings(actor).mPosition[1] = 1;
|
actorClass.getMovementSettings(actor).mPosition[1] = 1;
|
||||||
smoothTurn(actor, -180, 0);
|
smoothTurn(actor, -180, 0);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -37,7 +37,7 @@ bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::Charac
|
||||||
if (!target)
|
if (!target)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (!mManual && !pathTo(actor, target.getRefData().getPosition().pos, duration, mDistance))
|
if (!mManual && !pathTo(actor, target.getRefData().getPosition().asVec3(), duration, mDistance))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -128,7 +128,7 @@ namespace MWMechanics
|
||||||
float targetReachedTolerance = 0.0f;
|
float targetReachedTolerance = 0.0f;
|
||||||
if (storage.mLOS)
|
if (storage.mLOS)
|
||||||
targetReachedTolerance = storage.mAttackRange;
|
targetReachedTolerance = storage.mAttackRange;
|
||||||
bool is_target_reached = pathTo(actor, target.getRefData().getPosition().pos, duration, targetReachedTolerance);
|
const bool is_target_reached = pathTo(actor, target.getRefData().getPosition().asVec3(), duration, targetReachedTolerance);
|
||||||
if (is_target_reached) storage.mReadyToAttack = true;
|
if (is_target_reached) storage.mReadyToAttack = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,7 +307,7 @@ namespace MWMechanics
|
||||||
osg::Vec3f localPos = actor.getRefData().getPosition().asVec3();
|
osg::Vec3f localPos = actor.getRefData().getPosition().asVec3();
|
||||||
coords.toLocal(localPos);
|
coords.toLocal(localPos);
|
||||||
|
|
||||||
int closestPointIndex = PathFinder::GetClosestPoint(pathgrid, localPos);
|
int closestPointIndex = PathFinder::getClosestPoint(pathgrid, localPos);
|
||||||
for (int i = 0; i < static_cast<int>(pathgrid->mPoints.size()); i++)
|
for (int i = 0; i < static_cast<int>(pathgrid->mPoints.size()); i++)
|
||||||
{
|
{
|
||||||
if (i != closestPointIndex && getPathGridGraph(storage.mCell).isPointConnected(closestPointIndex, i))
|
if (i != closestPointIndex && getPathGridGraph(storage.mCell).isPointConnected(closestPointIndex, i))
|
||||||
|
@ -359,7 +359,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
float dist = (actor.getRefData().getPosition().asVec3() - target.getRefData().getPosition().asVec3()).length();
|
float dist = (actor.getRefData().getPosition().asVec3() - target.getRefData().getPosition().asVec3()).length();
|
||||||
if ((dist > fFleeDistance && !storage.mLOS)
|
if ((dist > fFleeDistance && !storage.mLOS)
|
||||||
|| pathTo(actor, storage.mFleeDest, duration))
|
|| pathTo(actor, PathFinder::makeOsgVec3(storage.mFleeDest), duration))
|
||||||
{
|
{
|
||||||
state = AiCombatStorage::FleeState_Idle;
|
state = AiCombatStorage::FleeState_Idle;
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,24 +83,13 @@ namespace MWMechanics
|
||||||
actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false);
|
actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false);
|
||||||
|
|
||||||
const MWWorld::Ptr follower = MWBase::Environment::get().getWorld()->getPtr(mTargetActorRefId, false);
|
const MWWorld::Ptr follower = MWBase::Environment::get().getWorld()->getPtr(mTargetActorRefId, false);
|
||||||
const float* const leaderPos = actor.getRefData().getPosition().pos;
|
const osg::Vec3f leaderPos = actor.getRefData().getPosition().asVec3();
|
||||||
const float* const followerPos = follower.getRefData().getPosition().pos;
|
const osg::Vec3f followerPos = follower.getRefData().getPosition().asVec3();
|
||||||
double differenceBetween[3];
|
|
||||||
|
|
||||||
for (short counter = 0; counter < 3; counter++)
|
if ((leaderPos - followerPos).length2() <= mMaxDist * mMaxDist)
|
||||||
differenceBetween[counter] = (leaderPos[counter] - followerPos[counter]);
|
|
||||||
|
|
||||||
double distanceBetweenResult =
|
|
||||||
(differenceBetween[0] * differenceBetween[0]) + (differenceBetween[1] * differenceBetween[1]) + (differenceBetween[2] *
|
|
||||||
differenceBetween[2]);
|
|
||||||
|
|
||||||
if (distanceBetweenResult <= mMaxDist * mMaxDist)
|
|
||||||
{
|
{
|
||||||
ESM::Pathgrid::Point point(static_cast<int>(mX), static_cast<int>(mY), static_cast<int>(mZ));
|
const osg::Vec3f dest(mX, mY, mZ);
|
||||||
point.mAutogenerated = 0;
|
if (pathTo(actor, dest, duration)) //Returns true on path complete
|
||||||
point.mConnectionNum = 0;
|
|
||||||
point.mUnknown = 0;
|
|
||||||
if (pathTo(actor,point,duration)) //Returns true on path complete
|
|
||||||
{
|
{
|
||||||
mRemainingDuration = mDuration;
|
mRemainingDuration = mDuration;
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -74,12 +74,12 @@ AiFollow::AiFollow(const ESM::AiSequence::AiFollow *follow)
|
||||||
|
|
||||||
bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration)
|
bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration)
|
||||||
{
|
{
|
||||||
MWWorld::Ptr target = getTarget();
|
const MWWorld::Ptr target = getTarget();
|
||||||
|
|
||||||
if (target.isEmpty() || !target.getRefData().getCount() || !target.getRefData().isEnabled() // Really we should be checking whether the target is currently registered
|
// Target is not here right now, wait for it to return
|
||||||
// with the MechanicsManager
|
// Really we should be checking whether the target is currently registered with the MechanicsManager
|
||||||
)
|
if (target == MWWorld::Ptr() || !target.getRefData().getCount() || !target.getRefData().isEnabled())
|
||||||
return false; // Target is not here right now, wait for it to return
|
return false;
|
||||||
|
|
||||||
actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing);
|
actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing);
|
||||||
|
|
||||||
|
@ -94,6 +94,10 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
|
||||||
|
const osg::Vec3f targetPos(target.getRefData().getPosition().asVec3());
|
||||||
|
const osg::Vec3f targetDir = targetPos - actorPos;
|
||||||
|
|
||||||
// AiFollow requires the target to be in range and within sight for the initial activation
|
// AiFollow requires the target to be in range and within sight for the initial activation
|
||||||
if (!mActive)
|
if (!mActive)
|
||||||
{
|
{
|
||||||
|
@ -101,9 +105,7 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte
|
||||||
|
|
||||||
if (storage.mTimer < 0)
|
if (storage.mTimer < 0)
|
||||||
{
|
{
|
||||||
if ((actor.getRefData().getPosition().asVec3() - target.getRefData().getPosition().asVec3()).length2()
|
if (targetDir.length2() < 500*500 && MWBase::Environment::get().getWorld()->getLOS(actor, target))
|
||||||
< 500*500
|
|
||||||
&& MWBase::Environment::get().getWorld()->getLOS(actor, target))
|
|
||||||
mActive = true;
|
mActive = true;
|
||||||
storage.mTimer = 0.5f;
|
storage.mTimer = 0.5f;
|
||||||
}
|
}
|
||||||
|
@ -111,8 +113,6 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte
|
||||||
if (!mActive)
|
if (!mActive)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
ESM::Position pos = actor.getRefData().getPosition(); //position of the actor
|
|
||||||
|
|
||||||
// The distances below are approximations based on observations of the original engine.
|
// The distances below are approximations based on observations of the original engine.
|
||||||
// If only one actor is following the target, it uses 186.
|
// If only one actor is following the target, it uses 186.
|
||||||
// If there are multiple actors following the same target, they form a group with each group member at 313 + (130 * i) distance to the target.
|
// If there are multiple actors following the same target, they form a group with each group member at 313 + (130 * i) distance to the target.
|
||||||
|
@ -145,9 +145,8 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((pos.pos[0]-mX)*(pos.pos[0]-mX) +
|
osg::Vec3f finalPos(mX, mY, mZ);
|
||||||
(pos.pos[1]-mY)*(pos.pos[1]-mY) +
|
if ((actorPos-finalPos).length2() < followDistance*followDistance) //Close-ish to final position
|
||||||
(pos.pos[2]-mZ)*(pos.pos[2]-mZ) < followDistance*followDistance) //Close-ish to final position
|
|
||||||
{
|
{
|
||||||
if (actor.getCell()->isExterior()) //Outside?
|
if (actor.getCell()->isExterior()) //Outside?
|
||||||
{
|
{
|
||||||
|
@ -162,8 +161,6 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Set the target destination from the actor
|
|
||||||
ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos;
|
|
||||||
|
|
||||||
short baseFollowDistance = followDistance;
|
short baseFollowDistance = followDistance;
|
||||||
short threshold = 30; // to avoid constant switching between moving/stopping
|
short threshold = 30; // to avoid constant switching between moving/stopping
|
||||||
|
@ -172,15 +169,9 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte
|
||||||
else
|
else
|
||||||
followDistance += threshold;
|
followDistance += threshold;
|
||||||
|
|
||||||
osg::Vec3f targetPos(target.getRefData().getPosition().asVec3());
|
if (targetDir.length2() <= followDistance * followDistance)
|
||||||
osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
|
|
||||||
|
|
||||||
osg::Vec3f dir = targetPos - actorPos;
|
|
||||||
float targetDistSqr = dir.length2();
|
|
||||||
|
|
||||||
if (targetDistSqr <= followDistance * followDistance)
|
|
||||||
{
|
{
|
||||||
float faceAngleRadians = std::atan2(dir.x(), dir.y());
|
float faceAngleRadians = std::atan2(targetDir.x(), targetDir.y());
|
||||||
|
|
||||||
if (!zTurn(actor, faceAngleRadians, osg::DegreesToRadians(45.f)))
|
if (!zTurn(actor, faceAngleRadians, osg::DegreesToRadians(45.f)))
|
||||||
{
|
{
|
||||||
|
@ -191,16 +182,14 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
storage.mMoving = !pathTo(actor, dest, duration, baseFollowDistance); // Go to the destination
|
storage.mMoving = !pathTo(actor, targetPos, duration, baseFollowDistance); // Go to the destination
|
||||||
|
|
||||||
if (storage.mMoving)
|
if (storage.mMoving)
|
||||||
{
|
{
|
||||||
//Check if you're far away
|
//Check if you're far away
|
||||||
float dist = distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]);
|
if (targetDir.length2() > 450 * 450)
|
||||||
|
|
||||||
if (dist > 450)
|
|
||||||
actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run
|
actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run
|
||||||
else if (dist < 325) //Have a bit of a dead zone, otherwise npc will constantly flip between running and not when right on the edge of the running threshold
|
else if (targetDir.length2() < 325 * 325) //Have a bit of a dead zone, otherwise npc will constantly flip between running and not when right on the edge of the running threshold
|
||||||
actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, false); //make NPC walk
|
actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, false); //make NPC walk
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include <components/esm/loadcell.hpp>
|
#include <components/esm/loadcell.hpp>
|
||||||
#include <components/esm/loadland.hpp>
|
#include <components/esm/loadland.hpp>
|
||||||
#include <components/esm/loadmgef.hpp>
|
#include <components/esm/loadmgef.hpp>
|
||||||
|
#include <components/detournavigator/navigator.hpp>
|
||||||
|
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
|
@ -25,7 +26,7 @@
|
||||||
|
|
||||||
MWMechanics::AiPackage::~AiPackage() {}
|
MWMechanics::AiPackage::~AiPackage() {}
|
||||||
|
|
||||||
MWMechanics::AiPackage::AiPackage() :
|
MWMechanics::AiPackage::AiPackage() :
|
||||||
mTimer(AI_REACTION_TIME + 1.0f), // to force initial pathbuild
|
mTimer(AI_REACTION_TIME + 1.0f), // to force initial pathbuild
|
||||||
mTargetActorRefId(""),
|
mTargetActorRefId(""),
|
||||||
mTargetActorId(-1),
|
mTargetActorId(-1),
|
||||||
|
@ -90,70 +91,65 @@ void MWMechanics::AiPackage::reset()
|
||||||
mTimer = AI_REACTION_TIME + 1.0f;
|
mTimer = AI_REACTION_TIME + 1.0f;
|
||||||
mIsShortcutting = false;
|
mIsShortcutting = false;
|
||||||
mShortcutProhibited = false;
|
mShortcutProhibited = false;
|
||||||
mShortcutFailPos = ESM::Pathgrid::Point();
|
mShortcutFailPos = osg::Vec3f();
|
||||||
|
|
||||||
mPathFinder.clearPath();
|
mPathFinder.clearPath();
|
||||||
mObstacleCheck.clear();
|
mObstacleCheck.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest, float duration, float destTolerance)
|
bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& dest, float duration, float destTolerance)
|
||||||
{
|
{
|
||||||
mTimer += duration; //Update timer
|
mTimer += duration; //Update timer
|
||||||
|
|
||||||
ESM::Position pos = actor.getRefData().getPosition(); //position of the actor
|
const osg::Vec3f position = actor.getRefData().getPosition().asVec3(); //position of the actor
|
||||||
|
MWBase::World* world = MWBase::Environment::get().getWorld();
|
||||||
|
|
||||||
|
const osg::Vec3f halfExtents = world->getHalfExtents(actor);
|
||||||
|
|
||||||
/// Stops the actor when it gets too close to a unloaded cell
|
/// Stops the actor when it gets too close to a unloaded cell
|
||||||
//... At current time, this test is unnecessary. AI shuts down when actor is more than 7168
|
//... At current time, this test is unnecessary. AI shuts down when actor is more than "actors processing range" setting value
|
||||||
//... units from player, and exterior cells are 8192 units long and wide.
|
//... units from player, and exterior cells are 8192 units long and wide.
|
||||||
//... But AI processing distance may increase in the future.
|
//... But AI processing distance may increase in the future.
|
||||||
if (isNearInactiveCell(pos))
|
if (isNearInactiveCell(position))
|
||||||
{
|
{
|
||||||
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
|
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
|
||||||
|
world->updateActorPath(actor, mPathFinder.getPath(), halfExtents, position, dest);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle path building and shortcutting
|
const float distToTarget = distance(position, dest);
|
||||||
ESM::Pathgrid::Point start = pos.pos;
|
const bool isDestReached = (distToTarget <= destTolerance);
|
||||||
|
|
||||||
float distToTarget = distance(start, dest);
|
|
||||||
bool isDestReached = (distToTarget <= destTolerance);
|
|
||||||
|
|
||||||
if (!isDestReached && mTimer > AI_REACTION_TIME)
|
if (!isDestReached && mTimer > AI_REACTION_TIME)
|
||||||
{
|
{
|
||||||
if (actor.getClass().isBipedal(actor))
|
if (actor.getClass().isBipedal(actor))
|
||||||
openDoors(actor);
|
openDoors(actor);
|
||||||
|
|
||||||
bool wasShortcutting = mIsShortcutting;
|
const bool wasShortcutting = mIsShortcutting;
|
||||||
bool destInLOS = false;
|
bool destInLOS = false;
|
||||||
|
const bool actorCanMoveByZ = canActorMoveByZAxis(actor);
|
||||||
const MWWorld::Class& actorClass = actor.getClass();
|
|
||||||
MWBase::World* world = MWBase::Environment::get().getWorld();
|
|
||||||
|
|
||||||
// check if actor can move along z-axis
|
|
||||||
bool actorCanMoveByZ = (actorClass.canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor))
|
|
||||||
|| world->isFlying(actor);
|
|
||||||
|
|
||||||
// Prohibit shortcuts for AiWander, if the actor can not move in 3 dimensions.
|
// Prohibit shortcuts for AiWander, if the actor can not move in 3 dimensions.
|
||||||
if (actorCanMoveByZ || getTypeId() != TypeIdWander)
|
mIsShortcutting = actorCanMoveByZ
|
||||||
mIsShortcutting = shortcutPath(start, dest, actor, &destInLOS, actorCanMoveByZ); // try to shortcut first
|
&& shortcutPath(position, dest, actor, &destInLOS, actorCanMoveByZ); // try to shortcut first
|
||||||
|
|
||||||
if (!mIsShortcutting)
|
if (!mIsShortcutting)
|
||||||
{
|
{
|
||||||
if (wasShortcutting || doesPathNeedRecalc(dest, actor.getCell())) // if need to rebuild path
|
if (wasShortcutting || doesPathNeedRecalc(dest, actor.getCell())) // if need to rebuild path
|
||||||
{
|
{
|
||||||
mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell()));
|
const osg::Vec3f playerHalfExtents = world->getHalfExtents(getPlayer()); // Using player half extents for better performance
|
||||||
|
mPathFinder.buildPath(actor, position, dest, actor.getCell(), getPathGridGraph(actor.getCell()),
|
||||||
|
playerHalfExtents, getNavigatorFlags(actor));
|
||||||
mRotateOnTheRunChecks = 3;
|
mRotateOnTheRunChecks = 3;
|
||||||
|
|
||||||
// give priority to go directly on target if there is minimal opportunity
|
// give priority to go directly on target if there is minimal opportunity
|
||||||
if (destInLOS && mPathFinder.getPath().size() > 1)
|
if (destInLOS && mPathFinder.getPath().size() > 1)
|
||||||
{
|
{
|
||||||
// get point just before dest
|
// get point just before dest
|
||||||
std::list<ESM::Pathgrid::Point>::const_iterator pPointBeforeDest = mPathFinder.getPath().end();
|
auto pPointBeforeDest = mPathFinder.getPath().rbegin() + 1;
|
||||||
--pPointBeforeDest;
|
|
||||||
--pPointBeforeDest;
|
|
||||||
|
|
||||||
// if start point is closer to the target then last point of path (excluding target itself) then go straight on the target
|
// if start point is closer to the target then last point of path (excluding target itself) then go straight on the target
|
||||||
if (distance(start, dest) <= distance(dest, *pPointBeforeDest))
|
if (distance(position, dest) <= distance(dest, *pPointBeforeDest))
|
||||||
{
|
{
|
||||||
mPathFinder.clearPath();
|
mPathFinder.clearPath();
|
||||||
mPathFinder.addPointToPath(dest);
|
mPathFinder.addPointToPath(dest);
|
||||||
|
@ -163,7 +159,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr
|
||||||
|
|
||||||
if (!mPathFinder.getPath().empty()) //Path has points in it
|
if (!mPathFinder.getPath().empty()) //Path has points in it
|
||||||
{
|
{
|
||||||
ESM::Pathgrid::Point lastPos = mPathFinder.getPath().back(); //Get the end of the proposed path
|
const osg::Vec3f& lastPos = mPathFinder.getPath().back(); //Get the end of the proposed path
|
||||||
|
|
||||||
if(distance(dest, lastPos) > 100) //End of the path is far from the destination
|
if(distance(dest, lastPos) > 100) //End of the path is far from the destination
|
||||||
mPathFinder.addPointToPath(dest); //Adds the final destination to the path, to try to get to where you want to go
|
mPathFinder.addPointToPath(dest); //Adds the final destination to the path, to try to get to where you want to go
|
||||||
|
@ -173,41 +169,44 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr
|
||||||
mTimer = 0;
|
mTimer = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDestReached || mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1])) // if path is finished
|
const float pointTolerance = std::min(actor.getClass().getSpeed(actor), DEFAULT_TOLERANCE);
|
||||||
|
|
||||||
|
mPathFinder.update(position, pointTolerance, DEFAULT_TOLERANCE);
|
||||||
|
|
||||||
|
if (isDestReached || mPathFinder.checkPathCompleted()) // if path is finished
|
||||||
{
|
{
|
||||||
// turn to destination point
|
// turn to destination point
|
||||||
zTurn(actor, getZAngleToPoint(start, dest));
|
zTurn(actor, getZAngleToPoint(position, dest));
|
||||||
smoothTurn(actor, getXAngleToPoint(start, dest), 0);
|
smoothTurn(actor, getXAngleToPoint(position, dest), 0);
|
||||||
|
world->removeActorPath(actor);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
if (mRotateOnTheRunChecks == 0
|
|
||||||
|| isReachableRotatingOnTheRun(actor, *mPathFinder.getPath().begin())) // to prevent circling around a path point
|
|
||||||
{
|
|
||||||
actor.getClass().getMovementSettings(actor).mPosition[1] = 1; // move to the target
|
|
||||||
if (mRotateOnTheRunChecks > 0) mRotateOnTheRunChecks--;
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle obstacles on the way
|
world->updateActorPath(actor, mPathFinder.getPath(), halfExtents, position, dest);
|
||||||
evadeObstacles(actor, duration, pos);
|
|
||||||
|
if (mRotateOnTheRunChecks == 0
|
||||||
|
|| isReachableRotatingOnTheRun(actor, *mPathFinder.getPath().begin())) // to prevent circling around a path point
|
||||||
|
{
|
||||||
|
actor.getClass().getMovementSettings(actor).mPosition[1] = 1; // move to the target
|
||||||
|
if (mRotateOnTheRunChecks > 0) mRotateOnTheRunChecks--;
|
||||||
}
|
}
|
||||||
|
|
||||||
// turn to next path point by X,Z axes
|
// turn to next path point by X,Z axes
|
||||||
zTurn(actor, mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]));
|
zTurn(actor, mPathFinder.getZAngleToNext(position.x(), position.y()));
|
||||||
smoothTurn(actor, mPathFinder.getXAngleToNext(pos.pos[0], pos.pos[1], pos.pos[2]), 0);
|
smoothTurn(actor, mPathFinder.getXAngleToNext(position.x(), position.y(), position.z()), 0);
|
||||||
|
|
||||||
|
mObstacleCheck.update(actor, duration);
|
||||||
|
|
||||||
|
// handle obstacles on the way
|
||||||
|
evadeObstacles(actor);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor, float duration, const ESM::Position& pos)
|
void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor)
|
||||||
{
|
{
|
||||||
zTurn(actor, mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]));
|
|
||||||
|
|
||||||
MWMechanics::Movement& movement = actor.getClass().getMovementSettings(actor);
|
|
||||||
|
|
||||||
// check if stuck due to obstacles
|
// check if stuck due to obstacles
|
||||||
if (!mObstacleCheck.check(actor, duration)) return;
|
if (!mObstacleCheck.isEvading()) return;
|
||||||
|
|
||||||
// first check if obstacle is a door
|
// first check if obstacle is a door
|
||||||
static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance();
|
static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance();
|
||||||
|
@ -219,13 +218,14 @@ void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor, float dur
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mObstacleCheck.takeEvasiveAction(movement);
|
mObstacleCheck.takeEvasiveAction(actor.getClass().getMovementSettings(actor));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MWMechanics::AiPackage::openDoors(const MWWorld::Ptr& actor)
|
void MWMechanics::AiPackage::openDoors(const MWWorld::Ptr& actor)
|
||||||
{
|
{
|
||||||
static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance();
|
MWBase::World* world = MWBase::Environment::get().getWorld();
|
||||||
|
static float distance = world->getMaxActivationDistance();
|
||||||
|
|
||||||
const MWWorld::Ptr door = getNearbyDoor(actor, distance);
|
const MWWorld::Ptr door = getNearbyDoor(actor, distance);
|
||||||
if (door == MWWorld::Ptr())
|
if (door == MWWorld::Ptr())
|
||||||
|
@ -236,7 +236,7 @@ void MWMechanics::AiPackage::openDoors(const MWWorld::Ptr& actor)
|
||||||
{
|
{
|
||||||
if ((door.getCellRef().getTrap().empty() && door.getCellRef().getLockLevel() <= 0 ))
|
if ((door.getCellRef().getTrap().empty() && door.getCellRef().getLockLevel() <= 0 ))
|
||||||
{
|
{
|
||||||
MWBase::Environment::get().getWorld()->activate(door, actor);
|
world->activate(door, actor);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,7 +248,7 @@ void MWMechanics::AiPackage::openDoors(const MWWorld::Ptr& actor)
|
||||||
MWWorld::Ptr keyPtr = invStore.search(keyId);
|
MWWorld::Ptr keyPtr = invStore.search(keyId);
|
||||||
|
|
||||||
if (!keyPtr.isEmpty())
|
if (!keyPtr.isEmpty())
|
||||||
MWBase::Environment::get().getWorld()->activate(door, actor);
|
world->activate(door, actor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,14 +266,15 @@ const MWMechanics::PathgridGraph& MWMechanics::AiPackage::getPathGridGraph(const
|
||||||
return *cache[id].get();
|
return *cache[id].get();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MWMechanics::AiPackage::shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS, bool isPathClear)
|
bool MWMechanics::AiPackage::shortcutPath(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
||||||
|
const MWWorld::Ptr& actor, bool *destInLOS, bool isPathClear)
|
||||||
{
|
{
|
||||||
if (!mShortcutProhibited || (PathFinder::MakeOsgVec3(mShortcutFailPos) - PathFinder::MakeOsgVec3(startPoint)).length() >= PATHFIND_SHORTCUT_RETRY_DIST)
|
if (!mShortcutProhibited || (mShortcutFailPos - startPoint).length() >= PATHFIND_SHORTCUT_RETRY_DIST)
|
||||||
{
|
{
|
||||||
// check if target is clearly visible
|
// check if target is clearly visible
|
||||||
isPathClear = !MWBase::Environment::get().getWorld()->castRay(
|
isPathClear = !MWBase::Environment::get().getWorld()->castRay(
|
||||||
static_cast<float>(startPoint.mX), static_cast<float>(startPoint.mY), static_cast<float>(startPoint.mZ),
|
startPoint.x(), startPoint.y(), startPoint.z(),
|
||||||
static_cast<float>(endPoint.mX), static_cast<float>(endPoint.mY), static_cast<float>(endPoint.mZ));
|
endPoint.x(), endPoint.y(), endPoint.z());
|
||||||
|
|
||||||
if (destInLOS != nullptr) *destInLOS = isPathClear;
|
if (destInLOS != nullptr) *destInLOS = isPathClear;
|
||||||
|
|
||||||
|
@ -294,46 +295,44 @@ bool MWMechanics::AiPackage::shortcutPath(const ESM::Pathgrid::Point& startPoint
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MWMechanics::AiPackage::checkWayIsClearForActor(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor)
|
bool MWMechanics::AiPackage::checkWayIsClearForActor(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::Ptr& actor)
|
||||||
{
|
{
|
||||||
bool actorCanMoveByZ = (actor.getClass().canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor))
|
if (canActorMoveByZAxis(actor))
|
||||||
|| MWBase::Environment::get().getWorld()->isFlying(actor);
|
|
||||||
|
|
||||||
if (actorCanMoveByZ)
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
float actorSpeed = actor.getClass().getSpeed(actor);
|
const float actorSpeed = actor.getClass().getSpeed(actor);
|
||||||
float maxAvoidDist = AI_REACTION_TIME * actorSpeed + actorSpeed / MAX_VEL_ANGULAR_RADIANS * 2; // *2 - for reliability
|
const float maxAvoidDist = AI_REACTION_TIME * actorSpeed + actorSpeed / MAX_VEL_ANGULAR_RADIANS * 2; // *2 - for reliability
|
||||||
osg::Vec3f::value_type distToTarget = osg::Vec3f(static_cast<float>(endPoint.mX), static_cast<float>(endPoint.mY), 0).length();
|
const float distToTarget = osg::Vec2f(endPoint.x(), endPoint.y()).length();
|
||||||
|
|
||||||
float offsetXY = distToTarget > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2;
|
const float offsetXY = distToTarget > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2;
|
||||||
|
|
||||||
bool isClear = checkWayIsClear(PathFinder::MakeOsgVec3(startPoint), PathFinder::MakeOsgVec3(endPoint), offsetXY);
|
|
||||||
|
|
||||||
// update shortcut prohibit state
|
// update shortcut prohibit state
|
||||||
if (isClear)
|
if (checkWayIsClear(startPoint, endPoint, offsetXY))
|
||||||
{
|
{
|
||||||
if (mShortcutProhibited)
|
if (mShortcutProhibited)
|
||||||
{
|
{
|
||||||
mShortcutProhibited = false;
|
mShortcutProhibited = false;
|
||||||
mShortcutFailPos = ESM::Pathgrid::Point();
|
mShortcutFailPos = osg::Vec3f();
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
if (!isClear)
|
else
|
||||||
{
|
{
|
||||||
if (mShortcutFailPos.mX == 0 && mShortcutFailPos.mY == 0 && mShortcutFailPos.mZ == 0)
|
if (mShortcutFailPos == osg::Vec3f())
|
||||||
{
|
{
|
||||||
mShortcutProhibited = true;
|
mShortcutProhibited = true;
|
||||||
mShortcutFailPos = startPoint;
|
mShortcutFailPos = startPoint;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return isClear;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MWMechanics::AiPackage::doesPathNeedRecalc(const ESM::Pathgrid::Point& newDest, const MWWorld::CellStore* currentCell)
|
bool MWMechanics::AiPackage::doesPathNeedRecalc(const osg::Vec3f& newDest, const MWWorld::CellStore* currentCell)
|
||||||
{
|
{
|
||||||
return mPathFinder.getPath().empty() || (distance(mPathFinder.getPath().back(), newDest) > 10) || mPathFinder.getPathCell() != currentCell;
|
return mPathFinder.getPath().empty()
|
||||||
|
|| (distance(mPathFinder.getPath().back(), newDest) > 10)
|
||||||
|
|| mPathFinder.getPathCell() != currentCell;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MWMechanics::AiPackage::isTargetMagicallyHidden(const MWWorld::Ptr& target)
|
bool MWMechanics::AiPackage::isTargetMagicallyHidden(const MWWorld::Ptr& target)
|
||||||
|
@ -343,23 +342,22 @@ bool MWMechanics::AiPackage::isTargetMagicallyHidden(const MWWorld::Ptr& target)
|
||||||
|| (magicEffects.get(ESM::MagicEffect::Chameleon).getMagnitude() > 75);
|
|| (magicEffects.get(ESM::MagicEffect::Chameleon).getMagnitude() > 75);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MWMechanics::AiPackage::isNearInactiveCell(const ESM::Position& actorPos)
|
bool MWMechanics::AiPackage::isNearInactiveCell(osg::Vec3f position)
|
||||||
{
|
{
|
||||||
const ESM::Cell* playerCell(getPlayer().getCell()->getCell());
|
const ESM::Cell* playerCell(getPlayer().getCell()->getCell());
|
||||||
if (playerCell->isExterior())
|
if (playerCell->isExterior())
|
||||||
{
|
{
|
||||||
// get actor's distance from origin of center cell
|
// get actor's distance from origin of center cell
|
||||||
osg::Vec3f actorOffset(actorPos.asVec3());
|
CoordinateConverter(playerCell).toLocal(position);
|
||||||
CoordinateConverter(playerCell).toLocal(actorOffset);
|
|
||||||
|
|
||||||
// currently assumes 3 x 3 grid for exterior cells, with player at center cell.
|
// currently assumes 3 x 3 grid for exterior cells, with player at center cell.
|
||||||
// ToDo: (Maybe) use "exterior cell load distance" setting to get count of actual active cells
|
// ToDo: (Maybe) use "exterior cell load distance" setting to get count of actual active cells
|
||||||
// While AI Process distance is 7168, AI shuts down actors before they reach edges of 3 x 3 grid.
|
// AI shuts down actors before they reach edges of 3 x 3 grid.
|
||||||
const float distanceFromEdge = 200.0;
|
const float distanceFromEdge = 200.0;
|
||||||
float minThreshold = (-1.0f * ESM::Land::REAL_SIZE) + distanceFromEdge;
|
float minThreshold = (-1.0f * ESM::Land::REAL_SIZE) + distanceFromEdge;
|
||||||
float maxThreshold = (2.0f * ESM::Land::REAL_SIZE) - distanceFromEdge;
|
float maxThreshold = (2.0f * ESM::Land::REAL_SIZE) - distanceFromEdge;
|
||||||
return (actorOffset[0] < minThreshold) || (maxThreshold < actorOffset[0])
|
return (position.x() < minThreshold) || (maxThreshold < position.x())
|
||||||
|| (actorOffset[1] < minThreshold) || (maxThreshold < actorOffset[1]);
|
|| (position.y() < minThreshold) || (maxThreshold < position.y());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -367,7 +365,7 @@ bool MWMechanics::AiPackage::isNearInactiveCell(const ESM::Position& actorPos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MWMechanics::AiPackage::isReachableRotatingOnTheRun(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest)
|
bool MWMechanics::AiPackage::isReachableRotatingOnTheRun(const MWWorld::Ptr& actor, const osg::Vec3f& dest)
|
||||||
{
|
{
|
||||||
// get actor's shortest radius for moving in circle
|
// get actor's shortest radius for moving in circle
|
||||||
float speed = actor.getClass().getSpeed(actor);
|
float speed = actor.getClass().getSpeed(actor);
|
||||||
|
@ -381,17 +379,39 @@ bool MWMechanics::AiPackage::isReachableRotatingOnTheRun(const MWWorld::Ptr& act
|
||||||
osg::Vec3f radiusDir = dir ^ osg::Z_AXIS; // radius is perpendicular to a tangent
|
osg::Vec3f radiusDir = dir ^ osg::Z_AXIS; // radius is perpendicular to a tangent
|
||||||
radiusDir.normalize();
|
radiusDir.normalize();
|
||||||
radiusDir *= radius;
|
radiusDir *= radius;
|
||||||
|
|
||||||
// pick up the nearest center candidate
|
// pick up the nearest center candidate
|
||||||
osg::Vec3f dest_ = PathFinder::MakeOsgVec3(dest);
|
|
||||||
osg::Vec3f pos = actor.getRefData().getPosition().asVec3();
|
osg::Vec3f pos = actor.getRefData().getPosition().asVec3();
|
||||||
osg::Vec3f center1 = pos - radiusDir;
|
osg::Vec3f center1 = pos - radiusDir;
|
||||||
osg::Vec3f center2 = pos + radiusDir;
|
osg::Vec3f center2 = pos + radiusDir;
|
||||||
osg::Vec3f center = (center1 - dest_).length2() < (center2 - dest_).length2() ? center1 : center2;
|
osg::Vec3f center = (center1 - dest).length2() < (center2 - dest).length2() ? center1 : center2;
|
||||||
|
|
||||||
float distToDest = (center - dest_).length();
|
float distToDest = (center - dest).length();
|
||||||
|
|
||||||
// if pathpoint is reachable for the actor rotating on the run:
|
// if pathpoint is reachable for the actor rotating on the run:
|
||||||
// no points of actor's circle should be farther from the center than destination point
|
// no points of actor's circle should be farther from the center than destination point
|
||||||
return (radius <= distToDest);
|
return (radius <= distToDest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DetourNavigator::Flags MWMechanics::AiPackage::getNavigatorFlags(const MWWorld::Ptr& actor) const
|
||||||
|
{
|
||||||
|
const MWWorld::Class& actorClass = actor.getClass();
|
||||||
|
DetourNavigator::Flags result = DetourNavigator::Flag_none;
|
||||||
|
|
||||||
|
if (actorClass.isPureWaterCreature(actor) || (getTypeId() != TypeIdWander && actorClass.canSwim(actor)))
|
||||||
|
result |= DetourNavigator::Flag_swim;
|
||||||
|
|
||||||
|
if (actorClass.canWalk(actor))
|
||||||
|
result |= DetourNavigator::Flag_walk;
|
||||||
|
|
||||||
|
if (actorClass.isBipedal(actor) && getTypeId() != TypeIdWander)
|
||||||
|
result |= DetourNavigator::Flag_openDoor;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MWMechanics::AiPackage::canActorMoveByZAxis(const MWWorld::Ptr& actor) const
|
||||||
|
{
|
||||||
|
MWBase::World* world = MWBase::Environment::get().getWorld();
|
||||||
|
return (actor.getClass().canSwim(actor) && world->isSwimming(actor)) || world->isFlying(actor);
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
#include "obstacle.hpp"
|
#include "obstacle.hpp"
|
||||||
#include "aistate.hpp"
|
#include "aistate.hpp"
|
||||||
|
|
||||||
|
#include <boost/optional.hpp>
|
||||||
|
|
||||||
namespace MWWorld
|
namespace MWWorld
|
||||||
{
|
{
|
||||||
class Ptr;
|
class Ptr;
|
||||||
|
@ -105,29 +107,35 @@ namespace MWMechanics
|
||||||
bool isTargetMagicallyHidden(const MWWorld::Ptr& target);
|
bool isTargetMagicallyHidden(const MWWorld::Ptr& target);
|
||||||
|
|
||||||
/// Return if actor's rotation speed is sufficient to rotate to the destination pathpoint on the run. Otherwise actor should rotate while standing.
|
/// 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 ESM::Pathgrid::Point& dest);
|
static bool isReachableRotatingOnTheRun(const MWWorld::Ptr& actor, const osg::Vec3f& dest);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// Handles path building and shortcutting with obstacles avoiding
|
/// Handles path building and shortcutting with obstacles avoiding
|
||||||
/** \return If the actor has arrived at his destination **/
|
/** \return If the actor has arrived at his destination **/
|
||||||
bool pathTo(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest, float duration, float destTolerance = 0.0f);
|
bool pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& dest, float duration, float destTolerance = 0.0f);
|
||||||
|
|
||||||
/// Check if there aren't any obstacles along the path to make shortcut possible
|
/// Check if there aren't any obstacles along the path to make shortcut possible
|
||||||
/// If a shortcut is possible then path will be cleared and filled with the destination point.
|
/// If a shortcut is possible then path will be cleared and filled with the destination point.
|
||||||
/// \param destInLOS If not nullptr function will return ray cast check result
|
/// \param destInLOS If not nullptr function will return ray cast check result
|
||||||
/// \return If can shortcut the path
|
/// \return If can shortcut the path
|
||||||
bool shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS, bool isPathClear);
|
bool shortcutPath(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::Ptr& actor,
|
||||||
|
bool *destInLOS, bool isPathClear);
|
||||||
|
|
||||||
/// Check if the way to the destination is clear, taking into account actor speed
|
/// Check if the way to the destination is clear, taking into account actor speed
|
||||||
bool checkWayIsClearForActor(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor);
|
bool checkWayIsClearForActor(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::Ptr& actor);
|
||||||
|
|
||||||
virtual bool doesPathNeedRecalc(const ESM::Pathgrid::Point& newDest, const MWWorld::CellStore* currentCell);
|
bool doesPathNeedRecalc(const osg::Vec3f& newDest, const MWWorld::CellStore* currentCell);
|
||||||
|
|
||||||
|
void evadeObstacles(const MWWorld::Ptr& actor);
|
||||||
|
|
||||||
void evadeObstacles(const MWWorld::Ptr& actor, float duration, const ESM::Position& pos);
|
|
||||||
void openDoors(const MWWorld::Ptr& actor);
|
void openDoors(const MWWorld::Ptr& actor);
|
||||||
|
|
||||||
const PathgridGraph& getPathGridGraph(const MWWorld::CellStore* cell);
|
const PathgridGraph& getPathGridGraph(const MWWorld::CellStore* cell);
|
||||||
|
|
||||||
|
DetourNavigator::Flags getNavigatorFlags(const MWWorld::Ptr& actor) const;
|
||||||
|
|
||||||
|
bool canActorMoveByZAxis(const MWWorld::Ptr& actor) const;
|
||||||
|
|
||||||
// TODO: all this does not belong here, move into temporary storage
|
// TODO: all this does not belong here, move into temporary storage
|
||||||
PathFinder mPathFinder;
|
PathFinder mPathFinder;
|
||||||
ObstacleCheck mObstacleCheck;
|
ObstacleCheck mObstacleCheck;
|
||||||
|
@ -137,16 +145,14 @@ namespace MWMechanics
|
||||||
std::string mTargetActorRefId;
|
std::string mTargetActorRefId;
|
||||||
mutable int mTargetActorId;
|
mutable int mTargetActorId;
|
||||||
|
|
||||||
osg::Vec3f mLastActorPos;
|
|
||||||
|
|
||||||
short mRotateOnTheRunChecks; // attempts to check rotation to the pathpoint on the run possibility
|
short mRotateOnTheRunChecks; // attempts to check rotation to the pathpoint on the run possibility
|
||||||
|
|
||||||
bool mIsShortcutting; // if shortcutting at the moment
|
bool mIsShortcutting; // if shortcutting at the moment
|
||||||
bool mShortcutProhibited; // shortcutting may be prohibited after unsuccessful attempt
|
bool mShortcutProhibited; // shortcutting may be prohibited after unsuccessful attempt
|
||||||
ESM::Pathgrid::Point mShortcutFailPos; // position of last shortcut fail
|
osg::Vec3f mShortcutFailPos; // position of last shortcut fail
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool isNearInactiveCell(const ESM::Position& actorPos);
|
bool isNearInactiveCell(osg::Vec3f position);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,27 +35,27 @@ bool AiPursue::execute (const MWWorld::Ptr& actor, CharacterController& characte
|
||||||
|
|
||||||
const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); //The target to follow
|
const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); //The target to follow
|
||||||
|
|
||||||
if(target == MWWorld::Ptr() || !target.getRefData().getCount() || !target.getRefData().isEnabled() // Really we should be checking whether the target is currently registered
|
// Stop if the target doesn't exist
|
||||||
// with the MechanicsManager
|
// Really we should be checking whether the target is currently registered with the MechanicsManager
|
||||||
)
|
if (target == MWWorld::Ptr() || !target.getRefData().getCount() || !target.getRefData().isEnabled())
|
||||||
return true; //Target doesn't exist
|
return true;
|
||||||
|
|
||||||
if (isTargetMagicallyHidden(target))
|
if (isTargetMagicallyHidden(target))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if(target.getClass().getCreatureStats(target).isDead())
|
if (target.getClass().getCreatureStats(target).isDead())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing);
|
actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing);
|
||||||
|
|
||||||
//Set the target desition from the actor
|
//Set the target destination
|
||||||
ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos;
|
const osg::Vec3f dest = target.getRefData().getPosition().asVec3();
|
||||||
ESM::Position aPos = actor.getRefData().getPosition();
|
const osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3();
|
||||||
|
|
||||||
float pathTolerance = 100.0;
|
const float pathTolerance = 100.f;
|
||||||
|
|
||||||
if (pathTo(actor, dest, duration, pathTolerance) &&
|
if (pathTo(actor, dest, duration, pathTolerance) &&
|
||||||
std::abs(dest.mZ - aPos.pos[2]) < pathTolerance) // check the true distance in case the target is far away in Z-direction
|
std::abs(dest.z() - actorPos.z()) < pathTolerance) // check the true distance in case the target is far away in Z-direction
|
||||||
{
|
{
|
||||||
target.getClass().activate(target,actor).get()->execute(actor); //Arrest player when reached
|
target.getClass().activate(target,actor).get()->execute(actor); //Arrest player when reached
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -3,8 +3,9 @@
|
||||||
#include <components/esm/aisequence.hpp>
|
#include <components/esm/aisequence.hpp>
|
||||||
#include <components/esm/loadcell.hpp>
|
#include <components/esm/loadcell.hpp>
|
||||||
|
|
||||||
#include "../mwbase/world.hpp"
|
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
|
#include "../mwbase/mechanicsmanager.hpp"
|
||||||
|
#include "../mwbase/world.hpp"
|
||||||
|
|
||||||
#include "../mwworld/class.hpp"
|
#include "../mwworld/class.hpp"
|
||||||
#include "../mwworld/cellstore.hpp"
|
#include "../mwworld/cellstore.hpp"
|
||||||
|
@ -20,8 +21,9 @@ bool isWithinMaxRange(const osg::Vec3f& pos1, const osg::Vec3f& pos2)
|
||||||
{
|
{
|
||||||
// Maximum travel distance for vanilla compatibility.
|
// Maximum travel distance for vanilla compatibility.
|
||||||
// Was likely meant to prevent NPCs walking into non-loaded exterior cells, but for some reason is used in interior cells as well.
|
// Was likely meant to prevent NPCs walking into non-loaded exterior cells, but for some reason is used in interior cells as well.
|
||||||
// We can make this configurable at some point, but the default *must* be the below value. Anything else will break shoddily-written content (*cough* MW *cough*) in bizarre ways.
|
// The specific range below is configurable, but its limit is currently 7168 units. Anything greater will break shoddily-written content (*cough* MW *cough*) in bizarre ways.
|
||||||
return (pos1 - pos2).length2() <= 7168*7168;
|
float aiDistance = MWBase::Environment::get().getMechanicsManager()->getActorsProcessingRange();
|
||||||
|
return (pos1 - pos2).length2() <= aiDistance*aiDistance;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -45,19 +47,19 @@ namespace MWMechanics
|
||||||
|
|
||||||
bool AiTravel::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration)
|
bool AiTravel::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration)
|
||||||
{
|
{
|
||||||
ESM::Position pos = actor.getRefData().getPosition();
|
const osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
|
||||||
|
const osg::Vec3f targetPos(mX, mY, mZ);
|
||||||
|
|
||||||
actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false);
|
actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false);
|
||||||
actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing);
|
actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing);
|
||||||
|
|
||||||
if (!isWithinMaxRange(osg::Vec3f(mX, mY, mZ), pos.asVec3()))
|
if (!isWithinMaxRange(targetPos, actorPos))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Unfortunately, with vanilla assets destination is sometimes blocked by other actor.
|
// Unfortunately, with vanilla assets destination is sometimes blocked by other actor.
|
||||||
// If we got close to target, check for actors nearby. If they are, finish AI package.
|
// If we got close to target, check for actors nearby. If they are, finish AI package.
|
||||||
int destinationTolerance = 64;
|
int destinationTolerance = 64;
|
||||||
ESM::Pathgrid::Point dest(static_cast<int>(mX), static_cast<int>(mY), static_cast<int>(mZ));
|
if (distance(actorPos, targetPos) <= destinationTolerance)
|
||||||
if (distance(pos.pos, dest) <= destinationTolerance)
|
|
||||||
{
|
{
|
||||||
std::vector<MWWorld::Ptr> targetActors;
|
std::vector<MWWorld::Ptr> targetActors;
|
||||||
std::pair<MWWorld::Ptr, osg::Vec3f> result = MWBase::Environment::get().getWorld()->getHitContact(actor, destinationTolerance, targetActors);
|
std::pair<MWWorld::Ptr, osg::Vec3f> result = MWBase::Environment::get().getWorld()->getHitContact(actor, destinationTolerance, targetActors);
|
||||||
|
@ -69,7 +71,7 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pathTo(actor, dest, duration))
|
if (pathTo(actor, targetPos, duration))
|
||||||
{
|
{
|
||||||
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
|
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -50,7 +50,8 @@ namespace MWMechanics
|
||||||
|
|
||||||
AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector<unsigned char>& idle, bool repeat):
|
AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector<unsigned char>& idle, bool repeat):
|
||||||
mDistance(distance), mDuration(duration), mRemainingDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle),
|
mDistance(distance), mDuration(duration), mRemainingDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle),
|
||||||
mRepeat(repeat), mStoredInitialActorPosition(false), mInitialActorPosition(osg::Vec3f(0, 0, 0)), mHasDestination(false), mDestination(osg::Vec3f(0, 0, 0))
|
mRepeat(repeat), mStoredInitialActorPosition(false), mInitialActorPosition(osg::Vec3f(0, 0, 0)),
|
||||||
|
mHasDestination(false), mDestination(osg::Vec3f(0, 0, 0)), mUsePathgrid(false)
|
||||||
{
|
{
|
||||||
mIdle.resize(8, 0);
|
mIdle.resize(8, 0);
|
||||||
init();
|
init();
|
||||||
|
@ -123,14 +124,13 @@ namespace MWMechanics
|
||||||
*/
|
*/
|
||||||
bool AiWander::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration)
|
bool AiWander::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration)
|
||||||
{
|
{
|
||||||
// get or create temporary storage
|
|
||||||
AiWanderStorage& storage = state.get<AiWanderStorage>();
|
|
||||||
|
|
||||||
const MWWorld::CellStore*& currentCell = storage.mCell;
|
|
||||||
MWMechanics::CreatureStats& cStats = actor.getClass().getCreatureStats(actor);
|
MWMechanics::CreatureStats& cStats = actor.getClass().getCreatureStats(actor);
|
||||||
if(cStats.isDead() || cStats.getHealth().getCurrent() <= 0)
|
if (cStats.isDead() || cStats.getHealth().getCurrent() <= 0)
|
||||||
return true; // Don't bother with dead actors
|
return true; // Don't bother with dead actors
|
||||||
|
|
||||||
|
// get or create temporary storage
|
||||||
|
AiWanderStorage& storage = state.get<AiWanderStorage>();
|
||||||
|
const MWWorld::CellStore*& currentCell = storage.mCell;
|
||||||
bool cellChange = currentCell && (actor.getCell() != currentCell);
|
bool cellChange = currentCell && (actor.getCell() != currentCell);
|
||||||
if(!currentCell || cellChange)
|
if(!currentCell || cellChange)
|
||||||
{
|
{
|
||||||
|
@ -151,16 +151,23 @@ namespace MWMechanics
|
||||||
// rebuild a path to it
|
// rebuild a path to it
|
||||||
if (!mPathFinder.isPathConstructed() && mHasDestination)
|
if (!mPathFinder.isPathConstructed() && mHasDestination)
|
||||||
{
|
{
|
||||||
ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(mDestination));
|
if (mUsePathgrid)
|
||||||
ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos));
|
{
|
||||||
|
mPathFinder.buildPathByPathgrid(pos.asVec3(), mDestination, actor.getCell(),
|
||||||
mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell()));
|
getPathGridGraph(actor.getCell()));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const osg::Vec3f playerHalfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(getPlayer()); // Using player half extents for better performance
|
||||||
|
mPathFinder.buildPath(actor, pos.asVec3(), mDestination, actor.getCell(),
|
||||||
|
getPathGridGraph(actor.getCell()), playerHalfExtents, getNavigatorFlags(actor));
|
||||||
|
}
|
||||||
|
|
||||||
if (mPathFinder.isPathConstructed())
|
if (mPathFinder.isPathConstructed())
|
||||||
storage.setState(AiWanderStorage::Wander_Walking);
|
storage.setState(AiWanderStorage::Wander_Walking);
|
||||||
}
|
}
|
||||||
|
|
||||||
doPerFrameActionsForState(actor, duration, storage, pos);
|
doPerFrameActionsForState(actor, duration, storage);
|
||||||
|
|
||||||
playIdleDialogueRandomly(actor);
|
playIdleDialogueRandomly(actor);
|
||||||
|
|
||||||
|
@ -169,14 +176,14 @@ namespace MWMechanics
|
||||||
if (AI_REACTION_TIME <= lastReaction)
|
if (AI_REACTION_TIME <= lastReaction)
|
||||||
{
|
{
|
||||||
lastReaction = 0;
|
lastReaction = 0;
|
||||||
return reactionTimeActions(actor, storage, currentCell, cellChange, pos, duration);
|
return reactionTimeActions(actor, storage, currentCell, cellChange, pos);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AiWander::reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage,
|
bool AiWander::reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage,
|
||||||
const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos, float duration)
|
const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos)
|
||||||
{
|
{
|
||||||
if (mDistance <= 0)
|
if (mDistance <= 0)
|
||||||
storage.mCanWanderAlongPathGrid = false;
|
storage.mCanWanderAlongPathGrid = false;
|
||||||
|
@ -226,7 +233,7 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
|
|
||||||
// If Wandering manually and hit an obstacle, stop
|
// If Wandering manually and hit an obstacle, stop
|
||||||
if (storage.mIsWanderingManually && mObstacleCheck.check(actor, duration, 2.0f)) {
|
if (storage.mIsWanderingManually && mObstacleCheck.isEvading()) {
|
||||||
completeManualWalking(actor, storage);
|
completeManualWalking(actor, storage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,7 +258,9 @@ namespace MWMechanics
|
||||||
setPathToAnAllowedNode(actor, storage, pos);
|
setPathToAnAllowedNode(actor, storage, pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (storage.mIsWanderingManually && mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DESTINATION_TOLERANCE)) {
|
}
|
||||||
|
else if (storage.mIsWanderingManually && mPathFinder.checkPathCompleted())
|
||||||
|
{
|
||||||
completeManualWalking(actor, storage);
|
completeManualWalking(actor, storage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,9 +277,7 @@ namespace MWMechanics
|
||||||
if (mHasDestination)
|
if (mHasDestination)
|
||||||
return mDestination;
|
return mDestination;
|
||||||
|
|
||||||
const ESM::Pathgrid::Point currentPosition = actor.getRefData().getPosition().pos;
|
return actor.getRefData().getPosition().asVec3();
|
||||||
const osg::Vec3f currentPositionVec3f = osg::Vec3f(currentPosition.mX, currentPosition.mY, currentPosition.mZ);
|
|
||||||
return currentPositionVec3f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AiWander::isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage)
|
bool AiWander::isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage)
|
||||||
|
@ -292,11 +299,9 @@ namespace MWMechanics
|
||||||
* Commands actor to walk to a random location near original spawn location.
|
* Commands actor to walk to a random location near original spawn location.
|
||||||
*/
|
*/
|
||||||
void AiWander::wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance) {
|
void AiWander::wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance) {
|
||||||
const ESM::Pathgrid::Point currentPosition = actor.getRefData().getPosition().pos;
|
const auto currentPosition = actor.getRefData().getPosition().asVec3();
|
||||||
const osg::Vec3f currentPositionVec3f = osg::Vec3f(currentPosition.mX, currentPosition.mY, currentPosition.mZ);
|
|
||||||
|
|
||||||
std::size_t attempts = 10; // If a unit can't wander out of water, don't want to hang here
|
std::size_t attempts = 10; // If a unit can't wander out of water, don't want to hang here
|
||||||
ESM::Pathgrid::Point destinationPosition;
|
|
||||||
bool isWaterCreature = actor.getClass().isPureWaterCreature(actor);
|
bool isWaterCreature = actor.getClass().isPureWaterCreature(actor);
|
||||||
do {
|
do {
|
||||||
// Determine a random location within radius of original position
|
// Determine a random location within radius of original position
|
||||||
|
@ -305,19 +310,23 @@ namespace MWMechanics
|
||||||
const float destinationX = mInitialActorPosition.x() + wanderRadius * std::cos(randomDirection);
|
const float destinationX = mInitialActorPosition.x() + wanderRadius * std::cos(randomDirection);
|
||||||
const float destinationY = mInitialActorPosition.y() + wanderRadius * std::sin(randomDirection);
|
const float destinationY = mInitialActorPosition.y() + wanderRadius * std::sin(randomDirection);
|
||||||
const float destinationZ = mInitialActorPosition.z();
|
const float destinationZ = mInitialActorPosition.z();
|
||||||
destinationPosition = ESM::Pathgrid::Point(destinationX, destinationY, destinationZ);
|
|
||||||
mDestination = osg::Vec3f(destinationX, destinationY, destinationZ);
|
mDestination = osg::Vec3f(destinationX, destinationY, destinationZ);
|
||||||
|
|
||||||
// Check if land creature will walk onto water or if water creature will swim onto land
|
// Check if land creature will walk onto water or if water creature will swim onto land
|
||||||
if ((!isWaterCreature && !destinationIsAtWater(actor, mDestination)) ||
|
if ((!isWaterCreature && !destinationIsAtWater(actor, mDestination)) ||
|
||||||
(isWaterCreature && !destinationThroughGround(currentPositionVec3f, mDestination))) {
|
(isWaterCreature && !destinationThroughGround(currentPosition, mDestination)))
|
||||||
mPathFinder.buildSyncedPath(currentPosition, destinationPosition, actor.getCell(), getPathGridGraph(actor.getCell()));
|
{
|
||||||
mPathFinder.addPointToPath(destinationPosition);
|
// Using player half extents for better performance
|
||||||
|
const osg::Vec3f playerHalfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(getPlayer());
|
||||||
|
mPathFinder.buildPath(actor, currentPosition, mDestination, actor.getCell(),
|
||||||
|
getPathGridGraph(actor.getCell()), playerHalfExtents, getNavigatorFlags(actor));
|
||||||
|
mPathFinder.addPointToPath(mDestination);
|
||||||
|
|
||||||
if (mPathFinder.isPathConstructed())
|
if (mPathFinder.isPathConstructed())
|
||||||
{
|
{
|
||||||
storage.setState(AiWanderStorage::Wander_Walking, true);
|
storage.setState(AiWanderStorage::Wander_Walking, true);
|
||||||
mHasDestination = true;
|
mHasDestination = true;
|
||||||
|
mUsePathgrid = false;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -348,7 +357,7 @@ namespace MWMechanics
|
||||||
storage.setState(AiWanderStorage::Wander_IdleNow);
|
storage.setState(AiWanderStorage::Wander_IdleNow);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AiWander::doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage, ESM::Position& pos)
|
void AiWander::doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage)
|
||||||
{
|
{
|
||||||
switch (storage.mState)
|
switch (storage.mState)
|
||||||
{
|
{
|
||||||
|
@ -357,7 +366,7 @@ namespace MWMechanics
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AiWanderStorage::Wander_Walking:
|
case AiWanderStorage::Wander_Walking:
|
||||||
onWalkingStatePerFrameActions(actor, duration, storage, pos);
|
onWalkingStatePerFrameActions(actor, duration, storage);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AiWanderStorage::Wander_ChooseAction:
|
case AiWanderStorage::Wander_ChooseAction:
|
||||||
|
@ -413,11 +422,10 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor,
|
void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage)
|
||||||
float duration, AiWanderStorage& storage, ESM::Position& pos)
|
|
||||||
{
|
{
|
||||||
// Is there no destination or are we there yet?
|
// Is there no destination or are we there yet?
|
||||||
if ((!mPathFinder.isPathConstructed()) || pathTo(actor, ESM::Pathgrid::Point(mPathFinder.getPath().back()), duration, DESTINATION_TOLERANCE))
|
if ((!mPathFinder.isPathConstructed()) || pathTo(actor, osg::Vec3f(mPathFinder.getPath().back()), duration, DESTINATION_TOLERANCE))
|
||||||
{
|
{
|
||||||
stopWalking(actor, storage);
|
stopWalking(actor, storage);
|
||||||
storage.setState(AiWanderStorage::Wander_ChooseAction);
|
storage.setState(AiWanderStorage::Wander_ChooseAction);
|
||||||
|
@ -425,7 +433,7 @@ namespace MWMechanics
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// have not yet reached the destination
|
// have not yet reached the destination
|
||||||
evadeObstacles(actor, storage, duration, pos);
|
evadeObstacles(actor, storage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -456,7 +464,7 @@ namespace MWMechanics
|
||||||
storage.setState(AiWanderStorage::Wander_IdleNow);
|
storage.setState(AiWanderStorage::Wander_IdleNow);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AiWander::evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage, float duration, ESM::Position& pos)
|
void AiWander::evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage)
|
||||||
{
|
{
|
||||||
if (mObstacleCheck.isEvading())
|
if (mObstacleCheck.isEvading())
|
||||||
{
|
{
|
||||||
|
@ -507,9 +515,9 @@ namespace MWMechanics
|
||||||
// Only say Idle voices when player is in LOS
|
// Only say Idle voices when player is in LOS
|
||||||
// A bit counterintuitive, likely vanilla did this to reduce the appearance of
|
// A bit counterintuitive, likely vanilla did this to reduce the appearance of
|
||||||
// voices going through walls?
|
// voices going through walls?
|
||||||
const ESM::Position& pos = actor.getRefData().getPosition();
|
const osg::Vec3f playerPos(player.getRefData().getPosition().asVec3());
|
||||||
if (roll < x && (player.getRefData().getPosition().asVec3() - pos.asVec3()).length2()
|
const osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
|
||||||
< 3000 * 3000 // maybe should be fAudioVoiceDefaultMaxDistance*fAudioMaxDistanceMult instead
|
if (roll < x && (playerPos - actorPos).length2() < 3000 * 3000 // maybe should be fAudioVoiceDefaultMaxDistance*fAudioMaxDistanceMult instead
|
||||||
&& MWBase::Environment::get().getWorld()->getLOS(player, actor))
|
&& MWBase::Environment::get().getWorld()->getLOS(player, actor))
|
||||||
MWBase::Environment::get().getDialogueManager()->say(actor, "idle");
|
MWBase::Environment::get().getDialogueManager()->say(actor, "idle");
|
||||||
}
|
}
|
||||||
|
@ -528,13 +536,12 @@ namespace MWMechanics
|
||||||
MWWorld::Ptr player = getPlayer();
|
MWWorld::Ptr player = getPlayer();
|
||||||
osg::Vec3f playerPos(player.getRefData().getPosition().asVec3());
|
osg::Vec3f playerPos(player.getRefData().getPosition().asVec3());
|
||||||
osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
|
osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
|
||||||
float playerDistSqr = (playerPos - actorPos).length2();
|
|
||||||
|
|
||||||
int& greetingTimer = storage.mGreetingTimer;
|
int& greetingTimer = storage.mGreetingTimer;
|
||||||
AiWanderStorage::GreetingState& greetingState = storage.mSaidGreeting;
|
AiWanderStorage::GreetingState& greetingState = storage.mSaidGreeting;
|
||||||
if (greetingState == AiWanderStorage::Greet_None)
|
if (greetingState == AiWanderStorage::Greet_None)
|
||||||
{
|
{
|
||||||
if ((playerDistSqr <= helloDistance*helloDistance) &&
|
if ((playerPos - actorPos).length2() <= helloDistance*helloDistance &&
|
||||||
!player.getClass().getCreatureStats(player).isDead() && MWBase::Environment::get().getWorld()->getLOS(player, actor)
|
!player.getClass().getCreatureStats(player).isDead() && MWBase::Environment::get().getWorld()->getLOS(player, actor)
|
||||||
&& MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, actor))
|
&& MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, actor))
|
||||||
greetingTimer++;
|
greetingTimer++;
|
||||||
|
@ -570,7 +577,7 @@ namespace MWMechanics
|
||||||
if (greetingState == AiWanderStorage::Greet_Done)
|
if (greetingState == AiWanderStorage::Greet_Done)
|
||||||
{
|
{
|
||||||
float resetDist = 2 * helloDistance;
|
float resetDist = 2 * helloDistance;
|
||||||
if (playerDistSqr >= resetDist*resetDist)
|
if ((playerPos - actorPos).length2() >= resetDist*resetDist)
|
||||||
greetingState = AiWanderStorage::Greet_None;
|
greetingState = AiWanderStorage::Greet_None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -592,15 +599,17 @@ namespace MWMechanics
|
||||||
ToWorldCoordinates(dest, storage.mCell->getCell());
|
ToWorldCoordinates(dest, storage.mCell->getCell());
|
||||||
|
|
||||||
// actor position is already in world coordinates
|
// actor position is already in world coordinates
|
||||||
ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(actorPos));
|
const osg::Vec3f start = actorPos.asVec3();
|
||||||
|
|
||||||
// don't take shortcuts for wandering
|
// don't take shortcuts for wandering
|
||||||
mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell()));
|
const osg::Vec3f destVec3f = PathFinder::makeOsgVec3(dest);
|
||||||
|
mPathFinder.buildPathByPathgrid(start, destVec3f, actor.getCell(), getPathGridGraph(actor.getCell()));
|
||||||
|
|
||||||
if (mPathFinder.isPathConstructed())
|
if (mPathFinder.isPathConstructed())
|
||||||
{
|
{
|
||||||
mDestination = osg::Vec3f(dest.mX, dest.mY, dest.mZ);
|
mDestination = destVec3f;
|
||||||
mHasDestination = true;
|
mHasDestination = true;
|
||||||
|
mUsePathgrid = true;
|
||||||
// Remove this node as an option and add back the previously used node (stops NPC from picking the same node):
|
// Remove this node as an option and add back the previously used node (stops NPC from picking the same node):
|
||||||
ESM::Pathgrid::Point temp = storage.mAllowedNodes[randNode];
|
ESM::Pathgrid::Point temp = storage.mAllowedNodes[randNode];
|
||||||
storage.mAllowedNodes.erase(storage.mAllowedNodes.begin() + randNode);
|
storage.mAllowedNodes.erase(storage.mAllowedNodes.begin() + randNode);
|
||||||
|
@ -631,15 +640,15 @@ namespace MWMechanics
|
||||||
// Every now and then check whether one of the doors is opened. (maybe
|
// Every now and then check whether one of the doors is opened. (maybe
|
||||||
// at the end of playing idle?) If the door is opened then re-calculate
|
// at the end of playing idle?) If the door is opened then re-calculate
|
||||||
// allowed nodes starting from the spawn point.
|
// allowed nodes starting from the spawn point.
|
||||||
std::list<ESM::Pathgrid::Point> paths = pathfinder.getPath();
|
auto paths = pathfinder.getPath();
|
||||||
while(paths.size() >= 2)
|
while(paths.size() >= 2)
|
||||||
{
|
{
|
||||||
ESM::Pathgrid::Point pt = paths.back();
|
const auto pt = paths.back();
|
||||||
for(unsigned int j = 0; j < nodes.size(); j++)
|
for(unsigned int j = 0; j < nodes.size(); j++)
|
||||||
{
|
{
|
||||||
// FIXME: doesn't handle a door with the same X/Y
|
// FIXME: doesn't handle a door with the same X/Y
|
||||||
// coordinates but with a different Z
|
// coordinates but with a different Z
|
||||||
if(nodes[j].mX == pt.mX && nodes[j].mY == pt.mY)
|
if (std::abs(nodes[j].mX - pt.x()) <= 0.5 && std::abs(nodes[j].mY - pt.y()) <= 0.5)
|
||||||
{
|
{
|
||||||
nodes.erase(nodes.begin() + j);
|
nodes.erase(nodes.begin() + j);
|
||||||
break;
|
break;
|
||||||
|
@ -731,7 +740,7 @@ namespace MWMechanics
|
||||||
ESM::Pathgrid::Point worldDest = dest;
|
ESM::Pathgrid::Point worldDest = dest;
|
||||||
ToWorldCoordinates(worldDest, actor.getCell()->getCell());
|
ToWorldCoordinates(worldDest, actor.getCell()->getCell());
|
||||||
|
|
||||||
bool isPathGridOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(PathFinder::MakeOsgVec3(worldDest), 60);
|
bool isPathGridOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(PathFinder::makeOsgVec3(worldDest), 60);
|
||||||
|
|
||||||
// add offset only if the selected pathgrid is occupied by another actor
|
// add offset only if the selected pathgrid is occupied by another actor
|
||||||
if (isPathGridOccupied)
|
if (isPathGridOccupied)
|
||||||
|
@ -752,18 +761,18 @@ namespace MWMechanics
|
||||||
ESM::Pathgrid::Point connDest = points[randomIndex];
|
ESM::Pathgrid::Point connDest = points[randomIndex];
|
||||||
|
|
||||||
// add an offset towards random neighboring node
|
// add an offset towards random neighboring node
|
||||||
osg::Vec3f dir = PathFinder::MakeOsgVec3(connDest) - PathFinder::MakeOsgVec3(dest);
|
osg::Vec3f dir = PathFinder::makeOsgVec3(connDest) - PathFinder::makeOsgVec3(dest);
|
||||||
float length = dir.length();
|
float length = dir.length();
|
||||||
dir.normalize();
|
dir.normalize();
|
||||||
|
|
||||||
for (int j = 1; j <= 3; j++)
|
for (int j = 1; j <= 3; j++)
|
||||||
{
|
{
|
||||||
// move for 5-15% towards random neighboring node
|
// move for 5-15% towards random neighboring node
|
||||||
dest = PathFinder::MakePathgridPoint(PathFinder::MakeOsgVec3(dest) + dir * (j * 5 * length / 100.f));
|
dest = PathFinder::makePathgridPoint(PathFinder::makeOsgVec3(dest) + dir * (j * 5 * length / 100.f));
|
||||||
worldDest = dest;
|
worldDest = dest;
|
||||||
ToWorldCoordinates(worldDest, actor.getCell()->getCell());
|
ToWorldCoordinates(worldDest, actor.getCell()->getCell());
|
||||||
|
|
||||||
isOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(PathFinder::MakeOsgVec3(worldDest), 60);
|
isOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(PathFinder::makeOsgVec3(worldDest), 60);
|
||||||
|
|
||||||
if (!isOccupied)
|
if (!isOccupied)
|
||||||
break;
|
break;
|
||||||
|
@ -799,7 +808,7 @@ namespace MWMechanics
|
||||||
const ESM::Pathgrid *pathgrid =
|
const ESM::Pathgrid *pathgrid =
|
||||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Pathgrid>().search(*currentCell->getCell());
|
MWBase::Environment::get().getWorld()->getStore().get<ESM::Pathgrid>().search(*currentCell->getCell());
|
||||||
|
|
||||||
int index = PathFinder::GetClosestPoint(pathgrid, PathFinder::MakeOsgVec3(dest));
|
int index = PathFinder::getClosestPoint(pathgrid, PathFinder::makeOsgVec3(dest));
|
||||||
|
|
||||||
getPathGridGraph(currentCell).getNeighbouringPoints(index, points);
|
getPathGridGraph(currentCell).getNeighbouringPoints(index, points);
|
||||||
}
|
}
|
||||||
|
@ -832,7 +841,7 @@ namespace MWMechanics
|
||||||
CoordinateConverter(cell).toLocal(npcPos);
|
CoordinateConverter(cell).toLocal(npcPos);
|
||||||
|
|
||||||
// Find closest pathgrid point
|
// Find closest pathgrid point
|
||||||
int closestPointIndex = PathFinder::GetClosestPoint(pathgrid, npcPos);
|
int closestPointIndex = PathFinder::getClosestPoint(pathgrid, npcPos);
|
||||||
|
|
||||||
// mAllowedNodes for this actor with pathgrid point indexes based on mDistance
|
// mAllowedNodes for this actor with pathgrid point indexes based on mDistance
|
||||||
// and if the point is connected to the closest current point
|
// and if the point is connected to the closest current point
|
||||||
|
@ -840,7 +849,7 @@ namespace MWMechanics
|
||||||
int pointIndex = 0;
|
int pointIndex = 0;
|
||||||
for(unsigned int counter = 0; counter < pathgrid->mPoints.size(); counter++)
|
for(unsigned int counter = 0; counter < pathgrid->mPoints.size(); counter++)
|
||||||
{
|
{
|
||||||
osg::Vec3f nodePos(PathFinder::MakeOsgVec3(pathgrid->mPoints[counter]));
|
osg::Vec3f nodePos(PathFinder::makeOsgVec3(pathgrid->mPoints[counter]));
|
||||||
if((npcPos - nodePos).length2() <= mDistance * mDistance &&
|
if((npcPos - nodePos).length2() <= mDistance * mDistance &&
|
||||||
getPathGridGraph(cellStore).isPointConnected(closestPointIndex, counter))
|
getPathGridGraph(cellStore).isPointConnected(closestPointIndex, counter))
|
||||||
{
|
{
|
||||||
|
@ -867,7 +876,7 @@ namespace MWMechanics
|
||||||
// 2. Partway along the path between the point and its connected points.
|
// 2. Partway along the path between the point and its connected points.
|
||||||
void AiWander::AddNonPathGridAllowedPoints(osg::Vec3f npcPos, const ESM::Pathgrid * pathGrid, int pointIndex, AiWanderStorage& storage)
|
void AiWander::AddNonPathGridAllowedPoints(osg::Vec3f npcPos, const ESM::Pathgrid * pathGrid, int pointIndex, AiWanderStorage& storage)
|
||||||
{
|
{
|
||||||
storage.mAllowedNodes.push_back(PathFinder::MakePathgridPoint(npcPos));
|
storage.mAllowedNodes.push_back(PathFinder::makePathgridPoint(npcPos));
|
||||||
for (std::vector<ESM::Pathgrid::Edge>::const_iterator it = pathGrid->mEdges.begin(); it != pathGrid->mEdges.end(); ++it)
|
for (std::vector<ESM::Pathgrid::Edge>::const_iterator it = pathGrid->mEdges.begin(); it != pathGrid->mEdges.end(); ++it)
|
||||||
{
|
{
|
||||||
if (it->mV0 == pointIndex)
|
if (it->mV0 == pointIndex)
|
||||||
|
@ -879,8 +888,8 @@ namespace MWMechanics
|
||||||
|
|
||||||
void AiWander::AddPointBetweenPathGridPoints(const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end, AiWanderStorage& storage)
|
void AiWander::AddPointBetweenPathGridPoints(const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end, AiWanderStorage& storage)
|
||||||
{
|
{
|
||||||
osg::Vec3f vectorStart = PathFinder::MakeOsgVec3(start);
|
osg::Vec3f vectorStart = PathFinder::makeOsgVec3(start);
|
||||||
osg::Vec3f delta = PathFinder::MakeOsgVec3(end) - vectorStart;
|
osg::Vec3f delta = PathFinder::makeOsgVec3(end) - vectorStart;
|
||||||
float length = delta.length();
|
float length = delta.length();
|
||||||
delta.normalize();
|
delta.normalize();
|
||||||
|
|
||||||
|
@ -889,7 +898,7 @@ namespace MWMechanics
|
||||||
// must not travel longer than distance between waypoints or NPC goes past waypoint
|
// must not travel longer than distance between waypoints or NPC goes past waypoint
|
||||||
distance = std::min(distance, static_cast<int>(length));
|
distance = std::min(distance, static_cast<int>(length));
|
||||||
delta *= distance;
|
delta *= distance;
|
||||||
storage.mAllowedNodes.push_back(PathFinder::MakePathgridPoint(vectorStart + delta));
|
storage.mAllowedNodes.push_back(PathFinder::makePathgridPoint(vectorStart + delta));
|
||||||
}
|
}
|
||||||
|
|
||||||
void AiWander::SetCurrentNodeToClosestAllowedNode(const osg::Vec3f& npcPos, AiWanderStorage& storage)
|
void AiWander::SetCurrentNodeToClosestAllowedNode(const osg::Vec3f& npcPos, AiWanderStorage& storage)
|
||||||
|
@ -898,7 +907,7 @@ namespace MWMechanics
|
||||||
unsigned int index = 0;
|
unsigned int index = 0;
|
||||||
for (unsigned int counterThree = 0; counterThree < storage.mAllowedNodes.size(); counterThree++)
|
for (unsigned int counterThree = 0; counterThree < storage.mAllowedNodes.size(); counterThree++)
|
||||||
{
|
{
|
||||||
osg::Vec3f nodePos(PathFinder::MakeOsgVec3(storage.mAllowedNodes[counterThree]));
|
osg::Vec3f nodePos(PathFinder::makeOsgVec3(storage.mAllowedNodes[counterThree]));
|
||||||
float tempDist = (npcPos - nodePos).length2();
|
float tempDist = (npcPos - nodePos).length2();
|
||||||
if (tempDist < distanceToClosestNode)
|
if (tempDist < distanceToClosestNode)
|
||||||
{
|
{
|
||||||
|
|
|
@ -137,15 +137,15 @@ namespace MWMechanics
|
||||||
short unsigned getRandomIdle();
|
short unsigned getRandomIdle();
|
||||||
void setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos);
|
void setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos);
|
||||||
void playGreetingIfPlayerGetsTooClose(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
void playGreetingIfPlayerGetsTooClose(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
||||||
void evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage, float duration, ESM::Position& pos);
|
void evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
||||||
void playIdleDialogueRandomly(const MWWorld::Ptr& actor);
|
void playIdleDialogueRandomly(const MWWorld::Ptr& actor);
|
||||||
void turnActorToFacePlayer(const osg::Vec3f& actorPosition, const osg::Vec3f& playerPosition, AiWanderStorage& storage);
|
void turnActorToFacePlayer(const osg::Vec3f& actorPosition, const osg::Vec3f& playerPosition, AiWanderStorage& storage);
|
||||||
void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage, ESM::Position& pos);
|
void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage);
|
||||||
void onIdleStatePerFrameActions(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, ESM::Position& pos);
|
void onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage);
|
||||||
void onChooseActionStatePerFrameActions(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
void onChooseActionStatePerFrameActions(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
||||||
bool reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage,
|
bool reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage,
|
||||||
const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos, float duration);
|
const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos);
|
||||||
bool isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
bool isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
||||||
void wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance);
|
void wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance);
|
||||||
bool destinationIsAtWater(const MWWorld::Ptr &actor, const osg::Vec3f& destination);
|
bool destinationIsAtWater(const MWWorld::Ptr &actor, const osg::Vec3f& destination);
|
||||||
|
@ -164,6 +164,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
bool mHasDestination;
|
bool mHasDestination;
|
||||||
osg::Vec3f mDestination;
|
osg::Vec3f mDestination;
|
||||||
|
bool mUsePathgrid;
|
||||||
|
|
||||||
void getNeighbouringNodes(ESM::Pathgrid::Point dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points);
|
void getNeighbouringNodes(ESM::Pathgrid::Point dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points);
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
#include "../mwrender/animation.hpp"
|
#include "../mwrender/animation.hpp"
|
||||||
|
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
|
#include "../mwbase/mechanicsmanager.hpp"
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
#include "../mwbase/soundmanager.hpp"
|
#include "../mwbase/soundmanager.hpp"
|
||||||
#include "../mwbase/windowmanager.hpp"
|
#include "../mwbase/windowmanager.hpp"
|
||||||
|
@ -1849,7 +1850,7 @@ void CharacterController::updateAnimQueue()
|
||||||
mAnimation->setLoopingEnabled(mAnimQueue.front().mGroup, mAnimQueue.size() <= 1);
|
mAnimation->setLoopingEnabled(mAnimQueue.front().mGroup, mAnimQueue.size() <= 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterController::update(float duration)
|
void CharacterController::update(float duration, bool animationOnly)
|
||||||
{
|
{
|
||||||
MWBase::World *world = MWBase::Environment::get().getWorld();
|
MWBase::World *world = MWBase::Environment::get().getWorld();
|
||||||
const MWWorld::Class &cls = mPtr.getClass();
|
const MWWorld::Class &cls = mPtr.getClass();
|
||||||
|
@ -2235,10 +2236,10 @@ void CharacterController::update(float duration)
|
||||||
world->rotateObject(mPtr, rot.x(), rot.y(), 0.0f, true);
|
world->rotateObject(mPtr, rot.x(), rot.y(), 0.0f, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mMovementAnimationControlled)
|
if (!animationOnly && !mMovementAnimationControlled)
|
||||||
world->queueMovement(mPtr, vec);
|
world->queueMovement(mPtr, vec);
|
||||||
}
|
}
|
||||||
else
|
else if (!animationOnly)
|
||||||
// We must always queue movement, even if there is none, to apply gravity.
|
// We must always queue movement, even if there is none, to apply gravity.
|
||||||
world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f));
|
world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f));
|
||||||
|
|
||||||
|
@ -2261,7 +2262,8 @@ void CharacterController::update(float duration)
|
||||||
playDeath(1.f, mDeathState);
|
playDeath(1.f, mDeathState);
|
||||||
}
|
}
|
||||||
// We must always queue movement, even if there is none, to apply gravity.
|
// We must always queue movement, even if there is none, to apply gravity.
|
||||||
world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f));
|
if (!animationOnly)
|
||||||
|
world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isPersist = isPersistentAnimPlaying();
|
bool isPersist = isPersistentAnimPlaying();
|
||||||
|
@ -2295,7 +2297,7 @@ void CharacterController::update(float duration)
|
||||||
moved.z() = 1.0;
|
moved.z() = 1.0;
|
||||||
|
|
||||||
// Update movement
|
// Update movement
|
||||||
if(mMovementAnimationControlled && mPtr.getClass().isActor())
|
if(!animationOnly && mMovementAnimationControlled && mPtr.getClass().isActor())
|
||||||
world->queueMovement(mPtr, moved);
|
world->queueMovement(mPtr, moved);
|
||||||
|
|
||||||
mSkipAnim = false;
|
mSkipAnim = false;
|
||||||
|
@ -2521,6 +2523,7 @@ void CharacterController::resurrect()
|
||||||
mAnimation->disable(mCurrentDeath);
|
mAnimation->disable(mCurrentDeath);
|
||||||
mCurrentDeath.clear();
|
mCurrentDeath.clear();
|
||||||
mDeathState = CharState_None;
|
mDeathState = CharState_None;
|
||||||
|
mWeaponType = WeapType_None;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterController::updateContinuousVfx()
|
void CharacterController::updateContinuousVfx()
|
||||||
|
@ -2544,20 +2547,6 @@ void CharacterController::updateMagicEffects()
|
||||||
{
|
{
|
||||||
if (!mPtr.getClass().isActor())
|
if (!mPtr.getClass().isActor())
|
||||||
return;
|
return;
|
||||||
float alpha = 1.f;
|
|
||||||
if (mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Invisibility).getModifier()) // Ignore base magnitude (see bug #3555).
|
|
||||||
{
|
|
||||||
if (mPtr == getPlayer())
|
|
||||||
alpha = 0.4f;
|
|
||||||
else
|
|
||||||
alpha = 0.f;
|
|
||||||
}
|
|
||||||
float chameleon = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude();
|
|
||||||
if (chameleon)
|
|
||||||
{
|
|
||||||
alpha *= std::max(0.2f, (100.f - chameleon)/100.f);
|
|
||||||
}
|
|
||||||
mAnimation->setAlpha(alpha);
|
|
||||||
|
|
||||||
bool vampire = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0.0f;
|
bool vampire = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0.0f;
|
||||||
mAnimation->setVampire(vampire);
|
mAnimation->setVampire(vampire);
|
||||||
|
@ -2566,6 +2555,32 @@ void CharacterController::updateMagicEffects()
|
||||||
mAnimation->setLightEffect(light);
|
mAnimation->setLightEffect(light);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CharacterController::setVisibility(float visibility)
|
||||||
|
{
|
||||||
|
// We should take actor's invisibility in account
|
||||||
|
if (mPtr.getClass().isActor())
|
||||||
|
{
|
||||||
|
float alpha = 1.f;
|
||||||
|
if (mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Invisibility).getModifier()) // Ignore base magnitude (see bug #3555).
|
||||||
|
{
|
||||||
|
if (mPtr == getPlayer())
|
||||||
|
alpha = 0.4f;
|
||||||
|
else
|
||||||
|
alpha = 0.f;
|
||||||
|
}
|
||||||
|
float chameleon = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude();
|
||||||
|
if (chameleon)
|
||||||
|
{
|
||||||
|
alpha *= std::max(0.2f, (100.f - chameleon)/100.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
visibility = std::min(visibility, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: implement a dithering shader rather than just change object transparency.
|
||||||
|
mAnimation->setAlpha(visibility);
|
||||||
|
}
|
||||||
|
|
||||||
void CharacterController::setAttackTypeBasedOnMovement()
|
void CharacterController::setAttackTypeBasedOnMovement()
|
||||||
{
|
{
|
||||||
float *move = mPtr.getClass().getMovementSettings(mPtr).mPosition;
|
float *move = mPtr.getClass().getMovementSettings(mPtr).mPosition;
|
||||||
|
|
|
@ -257,7 +257,7 @@ public:
|
||||||
|
|
||||||
void updatePtr(const MWWorld::Ptr &ptr);
|
void updatePtr(const MWWorld::Ptr &ptr);
|
||||||
|
|
||||||
void update(float duration);
|
void update(float duration, bool animationOnly=false);
|
||||||
|
|
||||||
void persistAnimationState();
|
void persistAnimationState();
|
||||||
void unpersistAnimationState();
|
void unpersistAnimationState();
|
||||||
|
@ -292,6 +292,7 @@ public:
|
||||||
bool isTurning() const;
|
bool isTurning() const;
|
||||||
bool isAttackingOrSpell() const;
|
bool isAttackingOrSpell() const;
|
||||||
|
|
||||||
|
void setVisibility(float visibility);
|
||||||
void setAttackingOrSpell(bool attackingOrSpell);
|
void setAttackingOrSpell(bool attackingOrSpell);
|
||||||
void castSpell(const std::string spellId, bool manualSpell=false);
|
void castSpell(const std::string spellId, bool manualSpell=false);
|
||||||
void setAIAttackType(const std::string& attackType);
|
void setAIAttackType(const std::string& attackType);
|
||||||
|
|
|
@ -371,11 +371,9 @@ namespace MWMechanics
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const bool weaphashealth = weapon.getClass().hasItemHealth(weapon);
|
const bool weaphashealth = weapon.getClass().hasItemHealth(weapon);
|
||||||
if(weaphashealth)
|
if (weaphashealth)
|
||||||
{
|
{
|
||||||
int weaphealth = weapon.getClass().getItemHealth(weapon);
|
damage *= weapon.getClass().getItemNormalizedHealth(weapon);
|
||||||
int weapmaxhealth = weapon.getClass().getItemMaxHealth(weapon);
|
|
||||||
damage *= (float(weaphealth) / weapmaxhealth);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const float fDamageStrengthBase = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
|
static const float fDamageStrengthBase = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
|
||||||
|
|
|
@ -33,11 +33,12 @@ namespace MWMechanics
|
||||||
point.y() -= static_cast<float>(mCellY);
|
point.y() -= static_cast<float>(mCellY);
|
||||||
}
|
}
|
||||||
|
|
||||||
osg::Vec3f CoordinateConverter::toLocalVec3(const ESM::Pathgrid::Point& point)
|
osg::Vec3f CoordinateConverter::toLocalVec3(const osg::Vec3f& point)
|
||||||
{
|
{
|
||||||
return osg::Vec3f(
|
return osg::Vec3f(
|
||||||
static_cast<float>(point.mX - mCellX),
|
point.x() - static_cast<float>(mCellX),
|
||||||
static_cast<float>(point.mY - mCellY),
|
point.y() - static_cast<float>(mCellY),
|
||||||
static_cast<float>(point.mZ));
|
point.z()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ namespace MWMechanics
|
||||||
/// in-place conversion from world to local
|
/// in-place conversion from world to local
|
||||||
void toLocal(osg::Vec3f& point);
|
void toLocal(osg::Vec3f& point);
|
||||||
|
|
||||||
osg::Vec3f toLocalVec3(const ESM::Pathgrid::Point& point);
|
osg::Vec3f toLocalVec3(const osg::Vec3f& point);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int mCellX;
|
int mCellX;
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include "../mwworld/ptr.hpp"
|
#include "../mwworld/ptr.hpp"
|
||||||
|
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
|
#include "../mwbase/statemanager.hpp"
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
#include "../mwbase/windowmanager.hpp"
|
#include "../mwbase/windowmanager.hpp"
|
||||||
#include "../mwbase/dialoguemanager.hpp"
|
#include "../mwbase/dialoguemanager.hpp"
|
||||||
|
@ -431,6 +432,29 @@ namespace MWMechanics
|
||||||
mObjects.update(duration, paused);
|
mObjects.update(duration, paused);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MechanicsManager::processChangedSettings(const Settings::CategorySettingVector &changed)
|
||||||
|
{
|
||||||
|
for (Settings::CategorySettingVector::const_iterator it = changed.begin(); it != changed.end(); ++it)
|
||||||
|
{
|
||||||
|
if (it->first == "Game" && it->second == "actors processing range")
|
||||||
|
{
|
||||||
|
int state = MWBase::Environment::get().getStateManager()->getState();
|
||||||
|
if (state != MWBase::StateManager::State_Running)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
mActors.updateProcessingRange();
|
||||||
|
|
||||||
|
// Update mechanics for new processing range immediately
|
||||||
|
update(0.f, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float MechanicsManager::getActorsProcessingRange() const
|
||||||
|
{
|
||||||
|
return mActors.getProcessingRange();
|
||||||
|
}
|
||||||
|
|
||||||
bool MechanicsManager::isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer)
|
bool MechanicsManager::isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer)
|
||||||
{
|
{
|
||||||
return mActors.isActorDetected(actor, observer);
|
return mActors.isActorDetected(actor, observer);
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#ifndef GAME_MWMECHANICS_MECHANICSMANAGERIMP_H
|
#ifndef GAME_MWMECHANICS_MECHANICSMANAGERIMP_H
|
||||||
#define GAME_MWMECHANICS_MECHANICSMANAGERIMP_H
|
#define GAME_MWMECHANICS_MECHANICSMANAGERIMP_H
|
||||||
|
|
||||||
|
#include <components/settings/settings.hpp>
|
||||||
|
|
||||||
#include "../mwbase/mechanicsmanager.hpp"
|
#include "../mwbase/mechanicsmanager.hpp"
|
||||||
|
|
||||||
#include "../mwworld/ptr.hpp"
|
#include "../mwworld/ptr.hpp"
|
||||||
|
@ -52,71 +54,71 @@ namespace MWMechanics
|
||||||
|
|
||||||
MechanicsManager();
|
MechanicsManager();
|
||||||
|
|
||||||
virtual void add (const MWWorld::Ptr& ptr);
|
virtual void add (const MWWorld::Ptr& ptr) override;
|
||||||
///< Register an object for management
|
///< Register an object for management
|
||||||
|
|
||||||
virtual void remove (const MWWorld::Ptr& ptr);
|
virtual void remove (const MWWorld::Ptr& ptr) override;
|
||||||
///< Deregister an object for management
|
///< Deregister an object for management
|
||||||
|
|
||||||
virtual void updateCell(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr);
|
virtual void updateCell(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr) override;
|
||||||
///< Moves an object to a new cell
|
///< Moves an object to a new cell
|
||||||
|
|
||||||
virtual void drop(const MWWorld::CellStore *cellStore);
|
virtual void drop(const MWWorld::CellStore *cellStore) override;
|
||||||
///< Deregister all objects in the given cell.
|
///< Deregister all objects in the given cell.
|
||||||
|
|
||||||
virtual void watchActor(const MWWorld::Ptr& ptr);
|
virtual void watchActor(const MWWorld::Ptr& ptr) override;
|
||||||
///< On each update look for changes in a previously registered actor and update the
|
///< On each update look for changes in a previously registered actor and update the
|
||||||
/// GUI accordingly.
|
/// GUI accordingly.
|
||||||
|
|
||||||
virtual void update (float duration, bool paused);
|
virtual void update (float duration, bool paused) override;
|
||||||
///< Update objects
|
///< Update objects
|
||||||
///
|
///
|
||||||
/// \param paused In game type does not currently advance (this usually means some GUI
|
/// \param paused In game type does not currently advance (this usually means some GUI
|
||||||
/// component is up).
|
/// component is up).
|
||||||
|
|
||||||
virtual void advanceTime (float duration);
|
virtual void advanceTime (float duration) override;
|
||||||
|
|
||||||
virtual void setPlayerName (const std::string& name);
|
virtual void setPlayerName (const std::string& name) override;
|
||||||
///< Set player name.
|
///< Set player name.
|
||||||
|
|
||||||
virtual void setPlayerRace (const std::string& id, bool male, const std::string &head, const std::string &hair);
|
virtual void setPlayerRace (const std::string& id, bool male, const std::string &head, const std::string &hair) override;
|
||||||
///< Set player race.
|
///< Set player race.
|
||||||
|
|
||||||
virtual void setPlayerBirthsign (const std::string& id);
|
virtual void setPlayerBirthsign (const std::string& id) override;
|
||||||
///< Set player birthsign.
|
///< Set player birthsign.
|
||||||
|
|
||||||
virtual void setPlayerClass (const std::string& id);
|
virtual void setPlayerClass (const std::string& id) override;
|
||||||
///< Set player class to stock class.
|
///< Set player class to stock class.
|
||||||
|
|
||||||
virtual void setPlayerClass (const ESM::Class& class_);
|
virtual void setPlayerClass (const ESM::Class& class_) override;
|
||||||
///< Set player class to custom class.
|
///< Set player class to custom class.
|
||||||
|
|
||||||
virtual void restoreDynamicStats(MWWorld::Ptr actor, bool sleep);
|
virtual void restoreDynamicStats(MWWorld::Ptr actor, bool sleep) override;
|
||||||
|
|
||||||
virtual void rest(bool sleep);
|
virtual void rest(bool sleep) override;
|
||||||
///< If the player is sleeping or waiting, this should be called every hour.
|
///< If the player is sleeping or waiting, this should be called every hour.
|
||||||
/// @param sleep is the player sleeping or waiting?
|
/// @param sleep is the player sleeping or waiting?
|
||||||
|
|
||||||
virtual int getHoursToRest() const;
|
virtual int getHoursToRest() const override;
|
||||||
///< Calculate how many hours the player needs to rest in order to be fully healed
|
///< Calculate how many hours the player needs to rest in order to be fully healed
|
||||||
|
|
||||||
virtual int getBarterOffer(const MWWorld::Ptr& ptr,int basePrice, bool buying);
|
virtual int getBarterOffer(const MWWorld::Ptr& ptr,int basePrice, bool buying) override;
|
||||||
///< This is used by every service to determine the price of objects given the trading skills of the player and NPC.
|
///< This is used by every service to determine the price of objects given the trading skills of the player and NPC.
|
||||||
|
|
||||||
virtual int getDerivedDisposition(const MWWorld::Ptr& ptr, bool addTemporaryDispositionChange = true);
|
virtual int getDerivedDisposition(const MWWorld::Ptr& ptr, bool addTemporaryDispositionChange = true) override;
|
||||||
///< Calculate the diposition of an NPC toward the player.
|
///< Calculate the diposition of an NPC toward the player.
|
||||||
|
|
||||||
virtual int countDeaths (const std::string& id) const;
|
virtual int countDeaths (const std::string& id) const override;
|
||||||
///< Return the number of deaths for actors with the given ID.
|
///< Return the number of deaths for actors with the given ID.
|
||||||
|
|
||||||
virtual void getPersuasionDispositionChange (const MWWorld::Ptr& npc, PersuasionType type, bool& success, float& tempChange, float& permChange);
|
virtual void getPersuasionDispositionChange (const MWWorld::Ptr& npc, PersuasionType type, bool& success, float& tempChange, float& permChange) override;
|
||||||
///< Perform a persuasion action on NPC
|
///< Perform a persuasion action on NPC
|
||||||
|
|
||||||
/// Check if \a observer is potentially aware of \a ptr. Does not do a line of sight check!
|
/// Check if \a observer is potentially aware of \a ptr. Does not do a line of sight check!
|
||||||
virtual bool awarenessCheck (const MWWorld::Ptr& ptr, const MWWorld::Ptr& observer);
|
virtual bool awarenessCheck (const MWWorld::Ptr& ptr, const MWWorld::Ptr& observer) override;
|
||||||
|
|
||||||
/// Makes \a ptr fight \a target. Also shouts a combat taunt.
|
/// Makes \a ptr fight \a target. Also shouts a combat taunt.
|
||||||
virtual void startCombat (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target);
|
virtual void startCombat (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @note victim may be empty
|
* @note victim may be empty
|
||||||
|
@ -126,122 +128,119 @@ namespace MWMechanics
|
||||||
* @return was the crime seen?
|
* @return was the crime seen?
|
||||||
*/
|
*/
|
||||||
virtual bool commitCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim,
|
virtual bool commitCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim,
|
||||||
OffenseType type, int arg=0, bool victimAware=false);
|
OffenseType type, int arg=0, bool victimAware=false) override;
|
||||||
/// @return false if the attack was considered a "friendly hit" and forgiven
|
/// @return false if the attack was considered a "friendly hit" and forgiven
|
||||||
virtual bool actorAttacked (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker);
|
virtual bool actorAttacked (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker) override;
|
||||||
|
|
||||||
/// Notify that actor was killed, add a murder bounty if applicable
|
/// Notify that actor was killed, add a murder bounty if applicable
|
||||||
/// @note No-op for non-player attackers
|
/// @note No-op for non-player attackers
|
||||||
virtual void actorKilled (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker);
|
virtual void actorKilled (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker) override;
|
||||||
|
|
||||||
/// Checks if commiting a crime is currently valid
|
|
||||||
/// @param victim The actor being attacked
|
|
||||||
/// @param attacker The actor commiting the crime
|
|
||||||
/// @return true if the victim is a valid target for crime
|
|
||||||
virtual bool canCommitCrimeAgainst(const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker);
|
|
||||||
|
|
||||||
/// Utility to check if taking this item is illegal and calling commitCrime if so
|
/// Utility to check if taking this item is illegal and calling commitCrime if so
|
||||||
/// @param container The container the item is in; may be empty for an item in the world
|
/// @param container The container the item is in; may be empty for an item in the world
|
||||||
virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, const MWWorld::Ptr& container,
|
virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, const MWWorld::Ptr& container,
|
||||||
int count, bool alarm = true);
|
int count, bool alarm = true) override;
|
||||||
/// Utility to check if opening (i.e. unlocking) this object is illegal and calling commitCrime if so
|
/// Utility to check if opening (i.e. unlocking) this object is illegal and calling commitCrime if so
|
||||||
virtual void objectOpened (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item);
|
virtual void objectOpened (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item) override;
|
||||||
/// Attempt sleeping in a bed. If this is illegal, call commitCrime.
|
/// Attempt sleeping in a bed. If this is illegal, call commitCrime.
|
||||||
/// @return was it illegal, and someone saw you doing it? Also returns fail when enemies are nearby
|
/// @return was it illegal, and someone saw you doing it? Also returns fail when enemies are nearby
|
||||||
virtual bool sleepInBed (const MWWorld::Ptr& ptr, const MWWorld::Ptr& bed);
|
virtual bool sleepInBed (const MWWorld::Ptr& ptr, const MWWorld::Ptr& bed) override;
|
||||||
|
|
||||||
virtual void forceStateUpdate(const MWWorld::Ptr &ptr);
|
virtual void forceStateUpdate(const MWWorld::Ptr &ptr) override;
|
||||||
|
|
||||||
/// Attempt to play an animation group
|
/// Attempt to play an animation group
|
||||||
/// @return Success or error
|
/// @return Success or error
|
||||||
virtual bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist=false);
|
virtual bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist=false) override;
|
||||||
virtual void skipAnimation(const MWWorld::Ptr& ptr);
|
virtual void skipAnimation(const MWWorld::Ptr& ptr) override;
|
||||||
virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string &groupName);
|
virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string &groupName) override;
|
||||||
virtual void persistAnimationStates();
|
virtual void persistAnimationStates() override;
|
||||||
|
|
||||||
/// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently
|
/// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently
|
||||||
/// paused we may want to do it manually (after equipping permanent enchantment)
|
/// paused we may want to do it manually (after equipping permanent enchantment)
|
||||||
virtual void updateMagicEffects (const MWWorld::Ptr& ptr);
|
virtual void updateMagicEffects (const MWWorld::Ptr& ptr) override;
|
||||||
|
|
||||||
virtual void getObjectsInRange (const osg::Vec3f& position, float radius, std::vector<MWWorld::Ptr>& objects);
|
virtual void getObjectsInRange (const osg::Vec3f& position, float radius, std::vector<MWWorld::Ptr>& objects) override;
|
||||||
virtual void getActorsInRange(const osg::Vec3f &position, float radius, std::vector<MWWorld::Ptr> &objects);
|
virtual void getActorsInRange(const osg::Vec3f &position, float radius, std::vector<MWWorld::Ptr> &objects) override;
|
||||||
|
|
||||||
/// Check if there are actors in selected range
|
/// Check if there are actors in selected range
|
||||||
virtual bool isAnyActorInRange(const osg::Vec3f &position, float radius);
|
virtual bool isAnyActorInRange(const osg::Vec3f &position, float radius) override;
|
||||||
|
|
||||||
virtual std::list<MWWorld::Ptr> getActorsSidingWith(const MWWorld::Ptr& actor);
|
virtual std::list<MWWorld::Ptr> getActorsSidingWith(const MWWorld::Ptr& actor) override;
|
||||||
virtual std::list<MWWorld::Ptr> getActorsFollowing(const MWWorld::Ptr& actor);
|
virtual std::list<MWWorld::Ptr> getActorsFollowing(const MWWorld::Ptr& actor) override;
|
||||||
virtual std::list<int> getActorsFollowingIndices(const MWWorld::Ptr& actor);
|
virtual std::list<int> getActorsFollowingIndices(const MWWorld::Ptr& actor) override;
|
||||||
|
|
||||||
virtual std::list<MWWorld::Ptr> getActorsFighting(const MWWorld::Ptr& actor);
|
virtual std::list<MWWorld::Ptr> getActorsFighting(const MWWorld::Ptr& actor) override;
|
||||||
virtual std::list<MWWorld::Ptr> getEnemiesNearby(const MWWorld::Ptr& actor);
|
virtual std::list<MWWorld::Ptr> getEnemiesNearby(const MWWorld::Ptr& actor) override;
|
||||||
|
|
||||||
/// Recursive version of getActorsFollowing
|
/// Recursive version of getActorsFollowing
|
||||||
virtual void getActorsFollowing(const MWWorld::Ptr& actor, std::set<MWWorld::Ptr>& out);
|
virtual void getActorsFollowing(const MWWorld::Ptr& actor, std::set<MWWorld::Ptr>& out) override;
|
||||||
/// Recursive version of getActorsSidingWith
|
/// Recursive version of getActorsSidingWith
|
||||||
virtual void getActorsSidingWith(const MWWorld::Ptr& actor, std::set<MWWorld::Ptr>& out);
|
virtual void getActorsSidingWith(const MWWorld::Ptr& actor, std::set<MWWorld::Ptr>& out) override;
|
||||||
|
|
||||||
virtual bool toggleAI();
|
virtual bool toggleAI() override;
|
||||||
virtual bool isAIActive();
|
virtual bool isAIActive() override;
|
||||||
|
|
||||||
virtual void playerLoaded();
|
virtual void playerLoaded() override;
|
||||||
|
|
||||||
virtual int countSavedGameRecords() const;
|
virtual int countSavedGameRecords() const override;
|
||||||
|
|
||||||
virtual void write (ESM::ESMWriter& writer, Loading::Listener& listener) const;
|
virtual void write (ESM::ESMWriter& writer, Loading::Listener& listener) const override;
|
||||||
|
|
||||||
virtual void readRecord (ESM::ESMReader& reader, uint32_t type);
|
virtual void readRecord (ESM::ESMReader& reader, uint32_t type) override;
|
||||||
|
|
||||||
virtual void clear();
|
virtual void clear() override;
|
||||||
|
|
||||||
virtual bool isAggressive (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target);
|
virtual bool isAggressive (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) override;
|
||||||
|
|
||||||
virtual void keepPlayerAlive();
|
virtual void keepPlayerAlive() override;
|
||||||
|
|
||||||
virtual bool isCastingSpell (const MWWorld::Ptr& ptr) const;
|
virtual bool isCastingSpell (const MWWorld::Ptr& ptr) const override;
|
||||||
|
|
||||||
virtual bool isReadyToBlock (const MWWorld::Ptr& ptr) const;
|
virtual bool isReadyToBlock (const MWWorld::Ptr& ptr) const override;
|
||||||
/// Is \a ptr casting spell or using weapon now?
|
/// Is \a ptr casting spell or using weapon now?
|
||||||
virtual bool isAttackingOrSpell(const MWWorld::Ptr &ptr) const;
|
virtual bool isAttackingOrSpell(const MWWorld::Ptr &ptr) const override;
|
||||||
|
|
||||||
virtual void castSpell(const MWWorld::Ptr& ptr, const std::string spellId, bool manualSpell=false);
|
virtual void castSpell(const MWWorld::Ptr& ptr, const std::string spellId, bool manualSpell=false) override;
|
||||||
|
|
||||||
|
void processChangedSettings(const Settings::CategorySettingVector& settings) override;
|
||||||
|
|
||||||
|
virtual float getActorsProcessingRange() const override;
|
||||||
|
|
||||||
/// Check if the target actor was detected by an observer
|
/// Check if the target actor was detected by an observer
|
||||||
/// If the observer is a non-NPC, check all actors in AI processing distance as observers
|
/// If the observer is a non-NPC, check all actors in AI processing distance as observers
|
||||||
virtual bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer);
|
virtual bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) override;
|
||||||
|
|
||||||
virtual void confiscateStolenItems (const MWWorld::Ptr& player, const MWWorld::Ptr& targetContainer);
|
virtual void confiscateStolenItems (const MWWorld::Ptr& player, const MWWorld::Ptr& targetContainer) override;
|
||||||
|
|
||||||
/// List the owners that the player has stolen this item from (the owner can be an NPC or a faction).
|
/// List the owners that the player has stolen this item from (the owner can be an NPC or a faction).
|
||||||
/// <Owner, item count>
|
/// <Owner, item count>
|
||||||
virtual std::vector<std::pair<std::string, int> > getStolenItemOwners(const std::string& itemid);
|
virtual std::vector<std::pair<std::string, int> > getStolenItemOwners(const std::string& itemid) override;
|
||||||
|
|
||||||
/// Has the player stolen this item from the given owner?
|
/// Has the player stolen this item from the given owner?
|
||||||
virtual bool isItemStolenFrom(const std::string& itemid, const MWWorld::Ptr& ptr);
|
virtual bool isItemStolenFrom(const std::string& itemid, const MWWorld::Ptr& ptr) override;
|
||||||
|
|
||||||
virtual bool isBoundItem(const MWWorld::Ptr& item);
|
virtual bool isBoundItem(const MWWorld::Ptr& item) override;
|
||||||
|
|
||||||
/// @return is \a ptr allowed to take/use \a target or is it a crime?
|
/// @return is \a ptr allowed to take/use \a target or is it a crime?
|
||||||
virtual bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, MWWorld::Ptr& victim);
|
virtual bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, MWWorld::Ptr& victim) override;
|
||||||
|
|
||||||
virtual void setWerewolf(const MWWorld::Ptr& actor, bool werewolf);
|
virtual void setWerewolf(const MWWorld::Ptr& actor, bool werewolf) override;
|
||||||
virtual void applyWerewolfAcrobatics(const MWWorld::Ptr& actor);
|
virtual void applyWerewolfAcrobatics(const MWWorld::Ptr& actor) override;
|
||||||
|
|
||||||
virtual void cleanupSummonedCreature(const MWWorld::Ptr& caster, int creatureActorId);
|
virtual void cleanupSummonedCreature(const MWWorld::Ptr& caster, int creatureActorId) override;
|
||||||
|
|
||||||
virtual void confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count);
|
virtual void confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count) override;
|
||||||
|
|
||||||
virtual bool isAttackPreparing(const MWWorld::Ptr& ptr);
|
virtual bool isAttackPreparing(const MWWorld::Ptr& ptr) override;
|
||||||
virtual bool isRunning(const MWWorld::Ptr& ptr);
|
virtual bool isRunning(const MWWorld::Ptr& ptr) override;
|
||||||
virtual bool isSneaking(const MWWorld::Ptr& ptr);
|
virtual bool isSneaking(const MWWorld::Ptr& ptr) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool canCommitCrimeAgainst(const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker);
|
||||||
bool canReportCrime(const MWWorld::Ptr &actor, const MWWorld::Ptr &victim, std::set<MWWorld::Ptr> &playerFollowers);
|
bool canReportCrime(const MWWorld::Ptr &actor, const MWWorld::Ptr &victim, std::set<MWWorld::Ptr> &playerFollowers);
|
||||||
|
|
||||||
bool reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim,
|
bool reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim,
|
||||||
OffenseType type, int arg=0);
|
OffenseType type, int arg=0);
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -96,11 +96,6 @@ namespace MWMechanics
|
||||||
mEvadeDuration = 0;
|
mEvadeDuration = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ObstacleCheck::isNormalState() const
|
|
||||||
{
|
|
||||||
return mWalkState == State_Norm;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ObstacleCheck::isEvading() const
|
bool ObstacleCheck::isEvading() const
|
||||||
{
|
{
|
||||||
return mWalkState == State_Evade;
|
return mWalkState == State_Evade;
|
||||||
|
@ -128,7 +123,7 @@ namespace MWMechanics
|
||||||
* u = how long to move sideways
|
* u = how long to move sideways
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
bool ObstacleCheck::check(const MWWorld::Ptr& actor, float duration, float scaleMinimumDistance)
|
void ObstacleCheck::update(const MWWorld::Ptr& actor, float duration, float scaleMinimumDistance)
|
||||||
{
|
{
|
||||||
const MWWorld::Class& cls = actor.getClass();
|
const MWWorld::Class& cls = actor.getClass();
|
||||||
ESM::Position pos = actor.getRefData().getPosition();
|
ESM::Position pos = actor.getRefData().getPosition();
|
||||||
|
@ -180,9 +175,7 @@ namespace MWMechanics
|
||||||
case State_Evade:
|
case State_Evade:
|
||||||
{
|
{
|
||||||
mEvadeDuration += duration;
|
mEvadeDuration += duration;
|
||||||
if(mEvadeDuration < DURATION_TO_EVADE)
|
if(mEvadeDuration >= DURATION_TO_EVADE)
|
||||||
return true;
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
// tried to evade, assume all is ok and start again
|
// tried to evade, assume all is ok and start again
|
||||||
mWalkState = State_Norm;
|
mWalkState = State_Norm;
|
||||||
|
@ -191,10 +184,9 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
/* NO DEFAULT CASE */
|
/* NO DEFAULT CASE */
|
||||||
}
|
}
|
||||||
return false; // no obstacles to evade (yet)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ObstacleCheck::takeEvasiveAction(MWMechanics::Movement& actorMovement)
|
void ObstacleCheck::takeEvasiveAction(MWMechanics::Movement& actorMovement) const
|
||||||
{
|
{
|
||||||
actorMovement.mPosition[0] = evadeDirections[mEvadeDirectionIndex][0];
|
actorMovement.mPosition[0] = evadeDirections[mEvadeDirectionIndex][0];
|
||||||
actorMovement.mPosition[1] = evadeDirections[mEvadeDirectionIndex][1];
|
actorMovement.mPosition[1] = evadeDirections[mEvadeDirectionIndex][1];
|
||||||
|
|
|
@ -27,15 +27,13 @@ namespace MWMechanics
|
||||||
// Clear the timers and set the state machine to default
|
// Clear the timers and set the state machine to default
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
bool isNormalState() const;
|
|
||||||
bool isEvading() const;
|
bool isEvading() const;
|
||||||
|
|
||||||
// Returns true if there is an obstacle and an evasive action
|
// Updates internal state, call each frame for moving actor
|
||||||
// should be taken
|
void update(const MWWorld::Ptr& actor, float duration, float scaleMinimumDistance = 1.0f);
|
||||||
bool check(const MWWorld::Ptr& actor, float duration, float scaleMinimumDistance = 1.0f);
|
|
||||||
|
|
||||||
// change direction to try to fix "stuck" actor
|
// change direction to try to fix "stuck" actor
|
||||||
void takeEvasiveAction(MWMechanics::Movement& actorMovement);
|
void takeEvasiveAction(MWMechanics::Movement& actorMovement) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,20 @@
|
||||||
#include "pathfinding.hpp"
|
#include "pathfinding.hpp"
|
||||||
|
|
||||||
|
#include <iterator>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
|
||||||
|
#include <components/detournavigator/exceptions.hpp>
|
||||||
|
#include <components/detournavigator/debug.hpp>
|
||||||
|
#include <components/detournavigator/navigator.hpp>
|
||||||
|
#include <components/debug/debuglog.hpp>
|
||||||
|
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
|
|
||||||
#include "../mwphysics/collisiontype.hpp"
|
#include "../mwphysics/collisiontype.hpp"
|
||||||
|
|
||||||
#include "../mwworld/cellstore.hpp"
|
#include "../mwworld/cellstore.hpp"
|
||||||
|
#include "../mwworld/class.hpp"
|
||||||
|
|
||||||
#include "pathgrid.hpp"
|
#include "pathgrid.hpp"
|
||||||
#include "coordinateconverter.hpp"
|
#include "coordinateconverter.hpp"
|
||||||
|
@ -29,7 +36,7 @@ namespace
|
||||||
// points to a quadtree may help
|
// points to a quadtree may help
|
||||||
for(unsigned int counter = 0; counter < grid->mPoints.size(); counter++)
|
for(unsigned int counter = 0; counter < grid->mPoints.size(); counter++)
|
||||||
{
|
{
|
||||||
float potentialDistBetween = MWMechanics::PathFinder::DistanceSquared(grid->mPoints[counter], pos);
|
float potentialDistBetween = MWMechanics::PathFinder::distanceSquared(grid->mPoints[counter], pos);
|
||||||
if (potentialDistBetween < closestDistanceReachable)
|
if (potentialDistBetween < closestDistanceReachable)
|
||||||
{
|
{
|
||||||
// found a closer one
|
// found a closer one
|
||||||
|
@ -57,56 +64,19 @@ namespace
|
||||||
(closestReachableIndex, closestReachableIndex == closestIndex);
|
(closestReachableIndex, closestReachableIndex == closestIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float sqrDistance(const osg::Vec2f& lhs, const osg::Vec2f& rhs)
|
||||||
|
{
|
||||||
|
return (lhs - rhs).length2();
|
||||||
|
}
|
||||||
|
|
||||||
|
float sqrDistanceIgnoreZ(const osg::Vec3f& lhs, const osg::Vec3f& rhs)
|
||||||
|
{
|
||||||
|
return sqrDistance(osg::Vec2f(lhs.x(), lhs.y()), osg::Vec2f(rhs.x(), rhs.y()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
float sqrDistanceIgnoreZ(const ESM::Pathgrid::Point& point, float x, float y)
|
|
||||||
{
|
|
||||||
x -= point.mX;
|
|
||||||
y -= point.mY;
|
|
||||||
return (x * x + y * y);
|
|
||||||
}
|
|
||||||
|
|
||||||
float distance(const ESM::Pathgrid::Point& point, float x, float y, float z)
|
|
||||||
{
|
|
||||||
x -= point.mX;
|
|
||||||
y -= point.mY;
|
|
||||||
z -= point.mZ;
|
|
||||||
return sqrt(x * x + y * y + z * z);
|
|
||||||
}
|
|
||||||
|
|
||||||
float distance(const ESM::Pathgrid::Point& a, const ESM::Pathgrid::Point& b)
|
|
||||||
{
|
|
||||||
float x = static_cast<float>(a.mX - b.mX);
|
|
||||||
float y = static_cast<float>(a.mY - b.mY);
|
|
||||||
float z = static_cast<float>(a.mZ - b.mZ);
|
|
||||||
return sqrt(x * x + y * y + z * z);
|
|
||||||
}
|
|
||||||
|
|
||||||
float getZAngleToDir(const osg::Vec3f& dir)
|
|
||||||
{
|
|
||||||
return std::atan2(dir.x(), dir.y());
|
|
||||||
}
|
|
||||||
|
|
||||||
float getXAngleToDir(const osg::Vec3f& dir)
|
|
||||||
{
|
|
||||||
float dirLen = dir.length();
|
|
||||||
return (dirLen != 0) ? -std::asin(dir.z() / dirLen) : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
float getZAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest)
|
|
||||||
{
|
|
||||||
osg::Vec3f dir = PathFinder::MakeOsgVec3(dest) - PathFinder::MakeOsgVec3(origin);
|
|
||||||
return getZAngleToDir(dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
float getXAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest)
|
|
||||||
{
|
|
||||||
osg::Vec3f dir = PathFinder::MakeOsgVec3(dest) - PathFinder::MakeOsgVec3(origin);
|
|
||||||
return getXAngleToDir(dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool checkWayIsClear(const osg::Vec3f& from, const osg::Vec3f& to, float offsetXY)
|
bool checkWayIsClear(const osg::Vec3f& from, const osg::Vec3f& to, float offsetXY)
|
||||||
{
|
{
|
||||||
osg::Vec3f dir = to - from;
|
osg::Vec3f dir = to - from;
|
||||||
|
@ -121,18 +91,6 @@ namespace MWMechanics
|
||||||
return (std::abs(from.z() - h) <= PATHFIND_Z_REACH);
|
return (std::abs(from.z() - h) <= PATHFIND_Z_REACH);
|
||||||
}
|
}
|
||||||
|
|
||||||
PathFinder::PathFinder()
|
|
||||||
: mPathgrid(nullptr)
|
|
||||||
, mCell(nullptr)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void PathFinder::clearPath()
|
|
||||||
{
|
|
||||||
if(!mPath.empty())
|
|
||||||
mPath.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* NOTE: This method may fail to find a path. The caller must check the
|
* NOTE: This method may fail to find a path. The caller must check the
|
||||||
* result before using it. If there is no path the AI routies need to
|
* result before using it. If there is no path the AI routies need to
|
||||||
|
@ -150,7 +108,7 @@ namespace MWMechanics
|
||||||
* pathgrid point (e.g. wander) then it may be worth while to call
|
* pathgrid point (e.g. wander) then it may be worth while to call
|
||||||
* pop_back() to remove the redundant entry.
|
* pop_back() to remove the redundant entry.
|
||||||
*
|
*
|
||||||
* NOTE: coordinates must be converted prior to calling GetClosestPoint()
|
* NOTE: coordinates must be converted prior to calling getClosestPoint()
|
||||||
*
|
*
|
||||||
* |
|
* |
|
||||||
* | cell
|
* | cell
|
||||||
|
@ -169,52 +127,44 @@ namespace MWMechanics
|
||||||
* j = @.x in local coordinates (i.e. within the cell)
|
* j = @.x in local coordinates (i.e. within the cell)
|
||||||
* k = @.x in world coordinates
|
* k = @.x in world coordinates
|
||||||
*/
|
*/
|
||||||
void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint,
|
void PathFinder::buildPathByPathgridImpl(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
||||||
const ESM::Pathgrid::Point &endPoint,
|
const PathgridGraph& pathgridGraph, std::back_insert_iterator<std::deque<osg::Vec3f>> out)
|
||||||
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph)
|
|
||||||
{
|
{
|
||||||
mPath.clear();
|
const auto pathgrid = pathgridGraph.getPathgrid();
|
||||||
|
|
||||||
// TODO: consider removing mCell / mPathgrid in favor of mPathgridGraph
|
|
||||||
if(mCell != cell || !mPathgrid)
|
|
||||||
{
|
|
||||||
mCell = cell;
|
|
||||||
mPathgrid = pathgridGraph.getPathgrid();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refer to AiWander reseach topic on openmw forums for some background.
|
// Refer to AiWander reseach topic on openmw forums for some background.
|
||||||
// Maybe there is no pathgrid for this cell. Just go to destination and let
|
// Maybe there is no pathgrid for this cell. Just go to destination and let
|
||||||
// physics take care of any blockages.
|
// physics take care of any blockages.
|
||||||
if(!mPathgrid || mPathgrid->mPoints.empty())
|
if(!pathgrid || pathgrid->mPoints.empty())
|
||||||
{
|
{
|
||||||
mPath.push_back(endPoint);
|
*out++ = endPoint;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: GetClosestPoint expects local coordinates
|
// NOTE: getClosestPoint expects local coordinates
|
||||||
CoordinateConverter converter(mCell->getCell());
|
CoordinateConverter converter(mCell->getCell());
|
||||||
|
|
||||||
// NOTE: It is possible that GetClosestPoint returns a pathgrind point index
|
// NOTE: It is possible that getClosestPoint returns a pathgrind point index
|
||||||
// that is unreachable in some situations. e.g. actor is standing
|
// that is unreachable in some situations. e.g. actor is standing
|
||||||
// outside an area enclosed by walls, but there is a pathgrid
|
// outside an area enclosed by walls, but there is a pathgrid
|
||||||
// point right behind the wall that is closer than any pathgrid
|
// point right behind the wall that is closer than any pathgrid
|
||||||
// point outside the wall
|
// point outside the wall
|
||||||
osg::Vec3f startPointInLocalCoords(converter.toLocalVec3(startPoint));
|
osg::Vec3f startPointInLocalCoords(converter.toLocalVec3(startPoint));
|
||||||
int startNode = GetClosestPoint(mPathgrid, startPointInLocalCoords);
|
int startNode = getClosestPoint(pathgrid, startPointInLocalCoords);
|
||||||
|
|
||||||
osg::Vec3f endPointInLocalCoords(converter.toLocalVec3(endPoint));
|
osg::Vec3f endPointInLocalCoords(converter.toLocalVec3(endPoint));
|
||||||
std::pair<int, bool> endNode = getClosestReachablePoint(mPathgrid, &pathgridGraph,
|
std::pair<int, bool> endNode = getClosestReachablePoint(pathgrid, &pathgridGraph,
|
||||||
endPointInLocalCoords,
|
endPointInLocalCoords,
|
||||||
startNode);
|
startNode);
|
||||||
|
|
||||||
// if it's shorter for actor to travel from start to end, than to travel from either
|
// 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.
|
// start or end to nearest pathgrid point, just travel from start to end.
|
||||||
float startToEndLength2 = (endPointInLocalCoords - startPointInLocalCoords).length2();
|
float startToEndLength2 = (endPointInLocalCoords - startPointInLocalCoords).length2();
|
||||||
float endTolastNodeLength2 = DistanceSquared(mPathgrid->mPoints[endNode.first], endPointInLocalCoords);
|
float endTolastNodeLength2 = distanceSquared(pathgrid->mPoints[endNode.first], endPointInLocalCoords);
|
||||||
float startTo1stNodeLength2 = DistanceSquared(mPathgrid->mPoints[startNode], startPointInLocalCoords);
|
float startTo1stNodeLength2 = distanceSquared(pathgrid->mPoints[startNode], startPointInLocalCoords);
|
||||||
if ((startToEndLength2 < startTo1stNodeLength2) || (startToEndLength2 < endTolastNodeLength2))
|
if ((startToEndLength2 < startTo1stNodeLength2) || (startToEndLength2 < endTolastNodeLength2))
|
||||||
{
|
{
|
||||||
mPath.push_back(endPoint);
|
*out++ = endPoint;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,21 +175,21 @@ namespace MWMechanics
|
||||||
// nodes are the same
|
// nodes are the same
|
||||||
if(startNode == endNode.first)
|
if(startNode == endNode.first)
|
||||||
{
|
{
|
||||||
ESM::Pathgrid::Point temp(mPathgrid->mPoints[startNode]);
|
ESM::Pathgrid::Point temp(pathgrid->mPoints[startNode]);
|
||||||
converter.toWorld(temp);
|
converter.toWorld(temp);
|
||||||
mPath.push_back(temp);
|
*out++ = makeOsgVec3(temp);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mPath = pathgridGraph.aStarSearch(startNode, endNode.first);
|
auto path = pathgridGraph.aStarSearch(startNode, endNode.first);
|
||||||
|
|
||||||
// If nearest path node is in opposite direction from second, remove it from path.
|
// If nearest path node is in opposite direction from second, remove it from path.
|
||||||
// Especially useful for wandering actors, if the nearest node is blocked for some reason.
|
// Especially useful for wandering actors, if the nearest node is blocked for some reason.
|
||||||
if (mPath.size() > 1)
|
if (path.size() > 1)
|
||||||
{
|
{
|
||||||
ESM::Pathgrid::Point secondNode = *(++mPath.begin());
|
ESM::Pathgrid::Point secondNode = *(++path.begin());
|
||||||
osg::Vec3f firstNodeVec3f = MakeOsgVec3(mPathgrid->mPoints[startNode]);
|
osg::Vec3f firstNodeVec3f = makeOsgVec3(pathgrid->mPoints[startNode]);
|
||||||
osg::Vec3f secondNodeVec3f = MakeOsgVec3(secondNode);
|
osg::Vec3f secondNodeVec3f = makeOsgVec3(secondNode);
|
||||||
osg::Vec3f toSecondNodeVec3f = secondNodeVec3f - firstNodeVec3f;
|
osg::Vec3f toSecondNodeVec3f = secondNodeVec3f - firstNodeVec3f;
|
||||||
osg::Vec3f toStartPointVec3f = startPointInLocalCoords - firstNodeVec3f;
|
osg::Vec3f toStartPointVec3f = startPointInLocalCoords - firstNodeVec3f;
|
||||||
if (toSecondNodeVec3f * toStartPointVec3f > 0)
|
if (toSecondNodeVec3f * toStartPointVec3f > 0)
|
||||||
|
@ -248,23 +198,25 @@ namespace MWMechanics
|
||||||
converter.toWorld(temp);
|
converter.toWorld(temp);
|
||||||
// Add Z offset since path node can overlap with other objects.
|
// Add Z offset since path node can overlap with other objects.
|
||||||
// Also ignore doors in raytesting.
|
// Also ignore doors in raytesting.
|
||||||
int mask = MWPhysics::CollisionType_World;
|
const int mask = MWPhysics::CollisionType_World;
|
||||||
bool isPathClear = !MWBase::Environment::get().getWorld()->castRay(
|
bool isPathClear = !MWBase::Environment::get().getWorld()->castRay(
|
||||||
startPoint.mX, startPoint.mY, startPoint.mZ+16, temp.mX, temp.mY, temp.mZ+16, mask);
|
startPoint.x(), startPoint.y(), startPoint.z() + 16, temp.mX, temp.mY, temp.mZ + 16, mask);
|
||||||
if (isPathClear)
|
if (isPathClear)
|
||||||
mPath.pop_front();
|
path.pop_front();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert supplied path to world coordinates
|
// convert supplied path to world coordinates
|
||||||
for (std::list<ESM::Pathgrid::Point>::iterator iter(mPath.begin()); iter != mPath.end(); ++iter)
|
std::transform(path.begin(), path.end(), out,
|
||||||
{
|
[&] (ESM::Pathgrid::Point& point)
|
||||||
converter.toWorld(*iter);
|
{
|
||||||
}
|
converter.toWorld(point);
|
||||||
|
return makeOsgVec3(point);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// If endNode found is NOT the closest PathGrid point to the endPoint,
|
// If endNode found is NOT the closest PathGrid point to the endPoint,
|
||||||
// assume endPoint is not reachable from endNode. In which case,
|
// assume endPoint is not reachable from endNode. In which case,
|
||||||
// path ends at endNode.
|
// path ends at endNode.
|
||||||
//
|
//
|
||||||
// So only add the destination (which may be different to the closest
|
// So only add the destination (which may be different to the closest
|
||||||
|
@ -276,7 +228,7 @@ namespace MWMechanics
|
||||||
//
|
//
|
||||||
// The AI routines will have to deal with such situations.
|
// The AI routines will have to deal with such situations.
|
||||||
if(endNode.second)
|
if(endNode.second)
|
||||||
mPath.push_back(endPoint);
|
*out++ = endPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
float PathFinder::getZAngleToNext(float x, float y) const
|
float PathFinder::getZAngleToNext(float x, float y) const
|
||||||
|
@ -286,9 +238,9 @@ namespace MWMechanics
|
||||||
if(mPath.empty())
|
if(mPath.empty())
|
||||||
return 0.;
|
return 0.;
|
||||||
|
|
||||||
const ESM::Pathgrid::Point &nextPoint = *mPath.begin();
|
const auto& nextPoint = mPath.front();
|
||||||
float directionX = nextPoint.mX - x;
|
const float directionX = nextPoint.x() - x;
|
||||||
float directionY = nextPoint.mY - y;
|
const float directionY = nextPoint.y() - y;
|
||||||
|
|
||||||
return std::atan2(directionX, directionY);
|
return std::atan2(directionX, directionY);
|
||||||
}
|
}
|
||||||
|
@ -300,62 +252,64 @@ namespace MWMechanics
|
||||||
if(mPath.empty())
|
if(mPath.empty())
|
||||||
return 0.;
|
return 0.;
|
||||||
|
|
||||||
const ESM::Pathgrid::Point &nextPoint = *mPath.begin();
|
const osg::Vec3f dir = mPath.front() - osg::Vec3f(x, y, z);
|
||||||
osg::Vec3f dir = MakeOsgVec3(nextPoint) - osg::Vec3f(x,y,z);
|
|
||||||
|
|
||||||
return getXAngleToDir(dir);
|
return getXAngleToDir(dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PathFinder::checkPathCompleted(float x, float y, float tolerance)
|
void PathFinder::update(const osg::Vec3f& position, const float pointTolerance, const float destinationTolerance)
|
||||||
{
|
{
|
||||||
if(mPath.empty())
|
if (mPath.empty())
|
||||||
return true;
|
return;
|
||||||
|
|
||||||
const ESM::Pathgrid::Point& nextPoint = *mPath.begin();
|
const auto tolerance = mPath.size() > 1 ? pointTolerance : destinationTolerance;
|
||||||
if (sqrDistanceIgnoreZ(nextPoint, x, y) < tolerance*tolerance)
|
|
||||||
{
|
if (sqrDistanceIgnoreZ(mPath.front(), position) < tolerance * tolerance)
|
||||||
mPath.pop_front();
|
mPath.pop_front();
|
||||||
if(mPath.empty())
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// see header for the rationale
|
void PathFinder::buildPathByPathgrid(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
||||||
void PathFinder::buildSyncedPath(const ESM::Pathgrid::Point &startPoint,
|
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph)
|
||||||
const ESM::Pathgrid::Point &endPoint,
|
|
||||||
const MWWorld::CellStore* cell, const MWMechanics::PathgridGraph& pathgridGraph)
|
|
||||||
{
|
{
|
||||||
if (mPath.size() < 2)
|
mPath.clear();
|
||||||
{
|
mCell = cell;
|
||||||
// if path has one point, then it's the destination.
|
|
||||||
// don't need to worry about bad path for this case
|
buildPathByPathgridImpl(startPoint, endPoint, pathgridGraph, std::back_inserter(mPath));
|
||||||
buildPath(startPoint, endPoint, cell, pathgridGraph);
|
|
||||||
}
|
mConstructed = true;
|
||||||
else
|
|
||||||
{
|
|
||||||
const ESM::Pathgrid::Point oldStart(*getPath().begin());
|
|
||||||
buildPath(startPoint, endPoint, cell, pathgridGraph);
|
|
||||||
if (mPath.size() >= 2)
|
|
||||||
{
|
|
||||||
// if 2nd waypoint of new path == 1st waypoint of old,
|
|
||||||
// delete 1st waypoint of new path.
|
|
||||||
std::list<ESM::Pathgrid::Point>::iterator iter = ++mPath.begin();
|
|
||||||
if (iter->mX == oldStart.mX
|
|
||||||
&& iter->mY == oldStart.mY
|
|
||||||
&& iter->mZ == oldStart.mZ)
|
|
||||||
{
|
|
||||||
mPath.pop_front();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const MWWorld::CellStore* PathFinder::getPathCell() const
|
void PathFinder::buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
||||||
|
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents,
|
||||||
|
const DetourNavigator::Flags flags)
|
||||||
{
|
{
|
||||||
return mCell;
|
mPath.clear();
|
||||||
|
mCell = cell;
|
||||||
|
|
||||||
|
buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags, std::back_inserter(mPath));
|
||||||
|
|
||||||
|
if (mPath.empty())
|
||||||
|
buildPathByPathgridImpl(startPoint, endPoint, pathgridGraph, std::back_inserter(mPath));
|
||||||
|
|
||||||
|
mConstructed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PathFinder::buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
|
||||||
|
const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags,
|
||||||
|
std::back_insert_iterator<std::deque<osg::Vec3f>> out)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const auto navigator = MWBase::Environment::get().getWorld()->getNavigator();
|
||||||
|
navigator->findPath(halfExtents, startPoint, endPoint, flags, out);
|
||||||
|
}
|
||||||
|
catch (const DetourNavigator::NavigatorException& exception)
|
||||||
|
{
|
||||||
|
DetourNavigator::log("PathFinder::buildPathByNavigator navigator exception: ", exception.what());
|
||||||
|
Log(Debug::Verbose) << "Build path by navigator exception: \"" << exception.what()
|
||||||
|
<< "\" for \"" << actor.getClass().getName(actor) << "\" (" << actor.getBase()
|
||||||
|
<< ") from " << startPoint << " to " << endPoint << " with flags ("
|
||||||
|
<< DetourNavigator::WriteFlags {flags} << ")";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,33 +1,57 @@
|
||||||
#ifndef GAME_MWMECHANICS_PATHFINDING_H
|
#ifndef GAME_MWMECHANICS_PATHFINDING_H
|
||||||
#define GAME_MWMECHANICS_PATHFINDING_H
|
#define GAME_MWMECHANICS_PATHFINDING_H
|
||||||
|
|
||||||
#include <list>
|
#include <deque>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <iterator>
|
||||||
|
|
||||||
|
#include <components/detournavigator/flags.hpp>
|
||||||
#include <components/esm/defs.hpp>
|
#include <components/esm/defs.hpp>
|
||||||
#include <components/esm/loadpgrd.hpp>
|
#include <components/esm/loadpgrd.hpp>
|
||||||
|
|
||||||
namespace MWWorld
|
namespace MWWorld
|
||||||
{
|
{
|
||||||
class CellStore;
|
class CellStore;
|
||||||
|
class ConstPtr;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
class PathgridGraph;
|
class PathgridGraph;
|
||||||
|
|
||||||
float distance(const ESM::Pathgrid::Point& point, float x, float y, float);
|
inline float distance(const osg::Vec3f& lhs, const osg::Vec3f& rhs)
|
||||||
float distance(const ESM::Pathgrid::Point& a, const ESM::Pathgrid::Point& b);
|
{
|
||||||
float getZAngleToDir(const osg::Vec3f& dir);
|
return (lhs - rhs).length();
|
||||||
float getXAngleToDir(const osg::Vec3f& dir);
|
}
|
||||||
float getZAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest);
|
|
||||||
float getXAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest);
|
|
||||||
|
|
||||||
const float PATHFIND_Z_REACH = 50.0f;
|
inline float getZAngleToDir(const osg::Vec3f& dir)
|
||||||
|
{
|
||||||
|
return std::atan2(dir.x(), dir.y());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float getXAngleToDir(const osg::Vec3f& dir)
|
||||||
|
{
|
||||||
|
float dirLen = dir.length();
|
||||||
|
return (dirLen != 0) ? -std::asin(dir.z() / dirLen) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float getZAngleToPoint(const osg::Vec3f& origin, const osg::Vec3f& dest)
|
||||||
|
{
|
||||||
|
return getZAngleToDir(dest - origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float getXAngleToPoint(const osg::Vec3f& origin, const osg::Vec3f& dest)
|
||||||
|
{
|
||||||
|
return getXAngleToDir(dest - origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
const float PATHFIND_Z_REACH = 50.0f;
|
||||||
//static const float sMaxSlope = 49.0f; // duplicate as in physicssystem
|
//static const float sMaxSlope = 49.0f; // duplicate as in physicssystem
|
||||||
// distance after which actor (failed previously to shortcut) will try again
|
// distance after which actor (failed previously to shortcut) will try again
|
||||||
const float PATHFIND_SHORTCUT_RETRY_DIST = 300.0f;
|
const float PATHFIND_SHORTCUT_RETRY_DIST = 300.0f;
|
||||||
|
|
||||||
|
const float DEFAULT_TOLERANCE = 32.0f;
|
||||||
|
|
||||||
// cast up-down ray with some offset from actor position to check for pits/obstacles on the way to target;
|
// cast up-down ray with some offset from actor position to check for pits/obstacles on the way to target;
|
||||||
// magnitude of pits/obstacles is defined by PATHFIND_Z_REACH
|
// magnitude of pits/obstacles is defined by PATHFIND_Z_REACH
|
||||||
bool checkWayIsClear(const osg::Vec3f& from, const osg::Vec3f& to, float offsetXY);
|
bool checkWayIsClear(const osg::Vec3f& from, const osg::Vec3f& to, float offsetXY);
|
||||||
|
@ -35,31 +59,33 @@ namespace MWMechanics
|
||||||
class PathFinder
|
class PathFinder
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
PathFinder();
|
PathFinder()
|
||||||
|
: mConstructed(false)
|
||||||
static const int PathTolerance = 32;
|
, mCell(nullptr)
|
||||||
|
|
||||||
static float sgn(float val)
|
|
||||||
{
|
{
|
||||||
if(val > 0)
|
|
||||||
return 1.0;
|
|
||||||
return -1.0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int sgn(int a)
|
void clearPath()
|
||||||
{
|
{
|
||||||
if(a > 0)
|
mConstructed = false;
|
||||||
return 1;
|
mPath.clear();
|
||||||
return -1;
|
mCell = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearPath();
|
void buildPathByPathgrid(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
||||||
|
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph);
|
||||||
|
|
||||||
void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint,
|
void buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
||||||
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph);
|
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents,
|
||||||
|
const DetourNavigator::Flags flags);
|
||||||
|
|
||||||
bool checkPathCompleted(float x, float y, float tolerance = PathTolerance);
|
/// Remove front point if exist and within tolerance
|
||||||
///< \Returns true if we are within \a tolerance units of the last path point.
|
void update(const osg::Vec3f& position, const float pointTolerance, const float destinationTolerance);
|
||||||
|
|
||||||
|
bool checkPathCompleted() const
|
||||||
|
{
|
||||||
|
return mConstructed && mPath.empty();
|
||||||
|
}
|
||||||
|
|
||||||
/// In radians
|
/// In radians
|
||||||
float getZAngleToNext(float x, float y) const;
|
float getZAngleToNext(float x, float y) const;
|
||||||
|
@ -68,60 +94,54 @@ namespace MWMechanics
|
||||||
|
|
||||||
bool isPathConstructed() const
|
bool isPathConstructed() const
|
||||||
{
|
{
|
||||||
return !mPath.empty();
|
return mConstructed && !mPath.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
int getPathSize() const
|
std::size_t getPathSize() const
|
||||||
{
|
{
|
||||||
return mPath.size();
|
return mPath.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::list<ESM::Pathgrid::Point>& getPath() const
|
const std::deque<osg::Vec3f>& getPath() const
|
||||||
{
|
{
|
||||||
return mPath;
|
return mPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MWWorld::CellStore* getPathCell() const;
|
const MWWorld::CellStore* getPathCell() const
|
||||||
|
|
||||||
/** Synchronize new path with old one to avoid visiting 1 waypoint 2 times
|
|
||||||
@note
|
|
||||||
BuildPath() takes closest PathGrid point to NPC as first point of path.
|
|
||||||
This is undesirable if NPC has just passed a Pathgrid point, as this
|
|
||||||
makes the 2nd point of the new path == the 1st point of old path.
|
|
||||||
Which results in NPC "running in a circle" back to the just passed waypoint.
|
|
||||||
*/
|
|
||||||
void buildSyncedPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint,
|
|
||||||
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph);
|
|
||||||
|
|
||||||
void addPointToPath(const ESM::Pathgrid::Point &point)
|
|
||||||
{
|
{
|
||||||
|
return mCell;
|
||||||
|
}
|
||||||
|
|
||||||
|
void addPointToPath(const osg::Vec3f& point)
|
||||||
|
{
|
||||||
|
mConstructed = true;
|
||||||
mPath.push_back(point);
|
mPath.push_back(point);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// utility function to convert a osg::Vec3f to a Pathgrid::Point
|
/// utility function to convert a osg::Vec3f to a Pathgrid::Point
|
||||||
static ESM::Pathgrid::Point MakePathgridPoint(const osg::Vec3f& v)
|
static ESM::Pathgrid::Point makePathgridPoint(const osg::Vec3f& v)
|
||||||
{
|
{
|
||||||
return ESM::Pathgrid::Point(static_cast<int>(v[0]), static_cast<int>(v[1]), static_cast<int>(v[2]));
|
return ESM::Pathgrid::Point(static_cast<int>(v[0]), static_cast<int>(v[1]), static_cast<int>(v[2]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// utility function to convert an ESM::Position to a Pathgrid::Point
|
/// utility function to convert an ESM::Position to a Pathgrid::Point
|
||||||
static ESM::Pathgrid::Point MakePathgridPoint(const ESM::Position& p)
|
static ESM::Pathgrid::Point makePathgridPoint(const ESM::Position& p)
|
||||||
{
|
{
|
||||||
return ESM::Pathgrid::Point(static_cast<int>(p.pos[0]), static_cast<int>(p.pos[1]), static_cast<int>(p.pos[2]));
|
return ESM::Pathgrid::Point(static_cast<int>(p.pos[0]), static_cast<int>(p.pos[1]), static_cast<int>(p.pos[2]));
|
||||||
}
|
}
|
||||||
|
|
||||||
static osg::Vec3f MakeOsgVec3(const ESM::Pathgrid::Point& p)
|
static osg::Vec3f makeOsgVec3(const ESM::Pathgrid::Point& p)
|
||||||
{
|
{
|
||||||
return osg::Vec3f(static_cast<float>(p.mX), static_cast<float>(p.mY), static_cast<float>(p.mZ));
|
return osg::Vec3f(static_cast<float>(p.mX), static_cast<float>(p.mY), static_cast<float>(p.mZ));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slightly cheaper version for comparisons.
|
// Slightly cheaper version for comparisons.
|
||||||
// Caller needs to be careful for very short distances (i.e. less than 1)
|
// Caller needs to be careful for very short distances (i.e. less than 1)
|
||||||
// or when accumuating the results i.e. (a + b)^2 != a^2 + b^2
|
// or when accumuating the results i.e. (a + b)^2 != a^2 + b^2
|
||||||
//
|
//
|
||||||
static float DistanceSquared(ESM::Pathgrid::Point point, const osg::Vec3f& pos)
|
static float distanceSquared(ESM::Pathgrid::Point point, const osg::Vec3f& pos)
|
||||||
{
|
{
|
||||||
return (MWMechanics::PathFinder::MakeOsgVec3(point) - pos).length2();
|
return (MWMechanics::PathFinder::makeOsgVec3(point) - pos).length2();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the closest pathgrid point index from the specified position
|
// Return the closest pathgrid point index from the specified position
|
||||||
|
@ -130,18 +150,18 @@ namespace MWMechanics
|
||||||
//
|
//
|
||||||
// NOTE: pos is expected to be in local coordinates, as is grid->mPoints
|
// NOTE: pos is expected to be in local coordinates, as is grid->mPoints
|
||||||
//
|
//
|
||||||
static int GetClosestPoint(const ESM::Pathgrid* grid, const osg::Vec3f& pos)
|
static int getClosestPoint(const ESM::Pathgrid* grid, const osg::Vec3f& pos)
|
||||||
{
|
{
|
||||||
assert(grid && !grid->mPoints.empty());
|
assert(grid && !grid->mPoints.empty());
|
||||||
|
|
||||||
float distanceBetween = DistanceSquared(grid->mPoints[0], pos);
|
float distanceBetween = distanceSquared(grid->mPoints[0], pos);
|
||||||
int closestIndex = 0;
|
int closestIndex = 0;
|
||||||
|
|
||||||
// TODO: if this full scan causes performance problems mapping pathgrid
|
// TODO: if this full scan causes performance problems mapping pathgrid
|
||||||
// points to a quadtree may help
|
// points to a quadtree may help
|
||||||
for(unsigned int counter = 1; counter < grid->mPoints.size(); counter++)
|
for(unsigned int counter = 1; counter < grid->mPoints.size(); counter++)
|
||||||
{
|
{
|
||||||
float potentialDistBetween = DistanceSquared(grid->mPoints[counter], pos);
|
float potentialDistBetween = distanceSquared(grid->mPoints[counter], pos);
|
||||||
if(potentialDistBetween < distanceBetween)
|
if(potentialDistBetween < distanceBetween)
|
||||||
{
|
{
|
||||||
distanceBetween = potentialDistBetween;
|
distanceBetween = potentialDistBetween;
|
||||||
|
@ -153,10 +173,17 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::list<ESM::Pathgrid::Point> mPath;
|
bool mConstructed;
|
||||||
|
std::deque<osg::Vec3f> mPath;
|
||||||
|
|
||||||
const ESM::Pathgrid *mPathgrid;
|
|
||||||
const MWWorld::CellStore* mCell;
|
const MWWorld::CellStore* mCell;
|
||||||
|
|
||||||
|
void buildPathByPathgridImpl(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
||||||
|
const PathgridGraph& pathgridGraph, std::back_insert_iterator<std::deque<osg::Vec3f>> out);
|
||||||
|
|
||||||
|
void buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
|
||||||
|
const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags,
|
||||||
|
std::back_insert_iterator<std::deque<osg::Vec3f>> out);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -257,10 +257,9 @@ namespace MWMechanics
|
||||||
* pathgrid points form (currently they are converted to world
|
* pathgrid points form (currently they are converted to world
|
||||||
* coordinates). Essentially trading speed w/ memory.
|
* coordinates). Essentially trading speed w/ memory.
|
||||||
*/
|
*/
|
||||||
std::list<ESM::Pathgrid::Point> PathgridGraph::aStarSearch(const int start,
|
std::deque<ESM::Pathgrid::Point> PathgridGraph::aStarSearch(const int start, const int goal) const
|
||||||
const int goal) const
|
|
||||||
{
|
{
|
||||||
std::list<ESM::Pathgrid::Point> path;
|
std::deque<ESM::Pathgrid::Point> path;
|
||||||
if(!isPointConnected(start, goal))
|
if(!isPointConnected(start, goal))
|
||||||
{
|
{
|
||||||
return path; // there is no path, return an empty path
|
return path; // there is no path, return an empty path
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#ifndef GAME_MWMECHANICS_PATHGRID_H
|
#ifndef GAME_MWMECHANICS_PATHGRID_H
|
||||||
#define GAME_MWMECHANICS_PATHGRID_H
|
#define GAME_MWMECHANICS_PATHGRID_H
|
||||||
|
|
||||||
#include <list>
|
#include <deque>
|
||||||
|
|
||||||
#include <components/esm/loadpgrd.hpp>
|
#include <components/esm/loadpgrd.hpp>
|
||||||
|
|
||||||
|
@ -38,8 +38,8 @@ namespace MWMechanics
|
||||||
// cells) coordinates
|
// cells) coordinates
|
||||||
//
|
//
|
||||||
// NOTE: if start equals end an empty path is returned
|
// NOTE: if start equals end an empty path is returned
|
||||||
std::list<ESM::Pathgrid::Point> aStarSearch(const int start,
|
std::deque<ESM::Pathgrid::Point> aStarSearch(const int start, const int end) const;
|
||||||
const int end) const;
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
const ESM::Cell *mCell;
|
const ESM::Cell *mCell;
|
||||||
|
|
|
@ -324,6 +324,34 @@ namespace MWMechanics
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class GetAbsorptionProbability : public MWMechanics::EffectSourceVisitor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
float mProbability;
|
||||||
|
|
||||||
|
GetAbsorptionProbability(const MWWorld::Ptr& actor)
|
||||||
|
: mProbability(0.f){}
|
||||||
|
|
||||||
|
virtual void visit (MWMechanics::EffectKey key,
|
||||||
|
const std::string& sourceName, const std::string& sourceId, int casterActorId,
|
||||||
|
float magnitude, float remainingTime = -1, float totalTime = -1)
|
||||||
|
{
|
||||||
|
if (key.mId == ESM::MagicEffect::SpellAbsorption)
|
||||||
|
{
|
||||||
|
if (mProbability == 0.f)
|
||||||
|
mProbability = magnitude / 100;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If there are different sources of SpellAbsorption effect, multiply failing probability for all effects.
|
||||||
|
// Real absorption probability will be the (1 - total fail chance) in this case.
|
||||||
|
float failProbability = 1.f - mProbability;
|
||||||
|
failProbability *= 1.f - magnitude / 100;
|
||||||
|
mProbability = 1.f - failProbability;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target, const bool fromProjectile, const bool manualSpell)
|
CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target, const bool fromProjectile, const bool manualSpell)
|
||||||
: mCaster(caster)
|
: mCaster(caster)
|
||||||
, mTarget(target)
|
, mTarget(target)
|
||||||
|
@ -444,22 +472,31 @@ namespace MWMechanics
|
||||||
MWBase::Environment::get().getWindowManager()->setEnemy(target);
|
MWBase::Environment::get().getWindowManager()->setEnemy(target);
|
||||||
|
|
||||||
// Try absorbing if it's a spell
|
// Try absorbing if it's a spell
|
||||||
// NOTE: Vanilla does this once per spell absorption effect source instead of adding the % from all sources together, not sure
|
// Unlike Reflect, this is done once per spell absorption effect source
|
||||||
// if that is worth replicating.
|
|
||||||
bool absorbed = false;
|
bool absorbed = false;
|
||||||
if (spell && caster != target && target.getClass().isActor())
|
if (spell && caster != target && target.getClass().isActor())
|
||||||
{
|
{
|
||||||
float absorb = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude();
|
CreatureStats& stats = target.getClass().getCreatureStats(target);
|
||||||
absorbed = (Misc::Rng::roll0to99() < absorb);
|
if (stats.getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude() > 0.f)
|
||||||
if (absorbed)
|
|
||||||
{
|
{
|
||||||
const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_Absorb");
|
GetAbsorptionProbability check(target);
|
||||||
MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect(
|
stats.getActiveSpells().visitEffectSources(check);
|
||||||
"meshes\\" + absorbStatic->mModel, ESM::MagicEffect::SpellAbsorption, false, "");
|
stats.getSpells().visitEffectSources(check);
|
||||||
// Magicka is increased by cost of spell
|
if (target.getClass().hasInventoryStore(target))
|
||||||
DynamicStat<float> magicka = target.getClass().getCreatureStats(target).getMagicka();
|
target.getClass().getInventoryStore(target).visitEffectSources(check);
|
||||||
magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost);
|
|
||||||
target.getClass().getCreatureStats(target).setMagicka(magicka);
|
int absorb = check.mProbability * 100;
|
||||||
|
absorbed = (Misc::Rng::roll0to99() < absorb);
|
||||||
|
if (absorbed)
|
||||||
|
{
|
||||||
|
const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_Absorb");
|
||||||
|
MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect(
|
||||||
|
"meshes\\" + absorbStatic->mModel, ESM::MagicEffect::SpellAbsorption, false, "");
|
||||||
|
// Magicka is increased by cost of spell
|
||||||
|
DynamicStat<float> magicka = stats.getMagicka();
|
||||||
|
magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost);
|
||||||
|
stats.setMagicka(magicka);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -58,8 +58,16 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
|
|
||||||
const float chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1]) / 2.f;
|
const float chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1]) / 2.f;
|
||||||
if (weapon->mData.mType >= ESM::Weapon::MarksmanBow)
|
// We need to account for the fact that thrown weapons have 2x real damage applied to the target
|
||||||
|
// as they're both the weapon and the ammo of the hit
|
||||||
|
if (weapon->mData.mType == ESM::Weapon::MarksmanThrown)
|
||||||
|
{
|
||||||
|
rating = chop * 2;
|
||||||
|
}
|
||||||
|
else if (weapon->mData.mType >= ESM::Weapon::MarksmanBow)
|
||||||
|
{
|
||||||
rating = chop;
|
rating = chop;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
const float slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1]) / 2.f;
|
const float slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1]) / 2.f;
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#include <components/sceneutil/positionattitudetransform.hpp>
|
#include <components/sceneutil/positionattitudetransform.hpp>
|
||||||
#include <components/resource/bulletshape.hpp>
|
#include <components/resource/bulletshape.hpp>
|
||||||
|
#include <components/debug/debuglog.hpp>
|
||||||
|
|
||||||
#include "../mwworld/class.hpp"
|
#include "../mwworld/class.hpp"
|
||||||
|
|
||||||
|
@ -28,6 +29,31 @@ Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr<const Resource::BulletShape>
|
||||||
mHalfExtents = shape->mCollisionBoxHalfExtents;
|
mHalfExtents = shape->mCollisionBoxHalfExtents;
|
||||||
mMeshTranslation = shape->mCollisionBoxTranslate;
|
mMeshTranslation = shape->mCollisionBoxTranslate;
|
||||||
|
|
||||||
|
// We can not create actor without collisions - he will fall through the ground.
|
||||||
|
// In this case we should autogenerate collision box based on mesh shape
|
||||||
|
// (NPCs have bodyparts and use a different approach)
|
||||||
|
if (!ptr.getClass().isNpc() && mHalfExtents.length2() == 0.f)
|
||||||
|
{
|
||||||
|
const Resource::BulletShape* collisionShape = shape.get();
|
||||||
|
if (collisionShape && collisionShape->mCollisionShape)
|
||||||
|
{
|
||||||
|
btTransform transform;
|
||||||
|
transform.setIdentity();
|
||||||
|
btVector3 min;
|
||||||
|
btVector3 max;
|
||||||
|
|
||||||
|
collisionShape->mCollisionShape->getAabb(transform, min, max);
|
||||||
|
mHalfExtents.x() = (max[0] - min[0])/2.f;
|
||||||
|
mHalfExtents.y() = (max[1] - min[1])/2.f;
|
||||||
|
mHalfExtents.z() = (max[2] - min[2])/2.f;
|
||||||
|
|
||||||
|
mMeshTranslation = osg::Vec3f(0.f, 0.f, mHalfExtents.z());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mHalfExtents.length2() == 0.f)
|
||||||
|
Log(Debug::Error) << "Error: Failed to calculate bounding box for actor \"" << ptr.getCellRef().getRefId() << "\".";
|
||||||
|
}
|
||||||
|
|
||||||
// Use capsule shape only if base is square (nonuniform scaling apparently doesn't work on it)
|
// Use capsule shape only if base is square (nonuniform scaling apparently doesn't work on it)
|
||||||
if (std::abs(mHalfExtents.x()-mHalfExtents.y())<mHalfExtents.x()*0.05 && mHalfExtents.z() >= mHalfExtents.x())
|
if (std::abs(mHalfExtents.x()-mHalfExtents.y())<mHalfExtents.x()*0.05 && mHalfExtents.z() >= mHalfExtents.x())
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "../mwworld/ptr.hpp"
|
#include "ptrholder.hpp"
|
||||||
|
|
||||||
#include <osg/Vec3f>
|
#include <osg/Vec3f>
|
||||||
#include <osg/Quat>
|
#include <osg/Quat>
|
||||||
|
@ -22,30 +22,6 @@ namespace Resource
|
||||||
namespace MWPhysics
|
namespace MWPhysics
|
||||||
{
|
{
|
||||||
|
|
||||||
class PtrHolder
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual ~PtrHolder() {}
|
|
||||||
|
|
||||||
void updatePtr(const MWWorld::Ptr& updated)
|
|
||||||
{
|
|
||||||
mPtr = updated;
|
|
||||||
}
|
|
||||||
|
|
||||||
MWWorld::Ptr getPtr()
|
|
||||||
{
|
|
||||||
return mPtr;
|
|
||||||
}
|
|
||||||
|
|
||||||
MWWorld::ConstPtr getPtr() const
|
|
||||||
{
|
|
||||||
return mPtr;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
MWWorld::Ptr mPtr;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Actor : public PtrHolder
|
class Actor : public PtrHolder
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
54
apps/openmw/mwphysics/heightfield.cpp
Normal file
54
apps/openmw/mwphysics/heightfield.cpp
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
#include "heightfield.hpp"
|
||||||
|
|
||||||
|
#include <osg/Object>
|
||||||
|
|
||||||
|
#include <BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h>
|
||||||
|
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
|
||||||
|
|
||||||
|
#include <LinearMath/btTransform.h>
|
||||||
|
|
||||||
|
namespace MWPhysics
|
||||||
|
{
|
||||||
|
HeightField::HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject)
|
||||||
|
{
|
||||||
|
mShape = new btHeightfieldTerrainShape(
|
||||||
|
sqrtVerts, sqrtVerts, heights, 1,
|
||||||
|
minH, maxH, 2,
|
||||||
|
PHY_FLOAT, false
|
||||||
|
);
|
||||||
|
mShape->setUseDiamondSubdivision(true);
|
||||||
|
mShape->setLocalScaling(btVector3(triSize, triSize, 1));
|
||||||
|
|
||||||
|
btTransform transform(btQuaternion::getIdentity(),
|
||||||
|
btVector3((x+0.5f) * triSize * (sqrtVerts-1),
|
||||||
|
(y+0.5f) * triSize * (sqrtVerts-1),
|
||||||
|
(maxH+minH)*0.5f));
|
||||||
|
|
||||||
|
mCollisionObject = new btCollisionObject;
|
||||||
|
mCollisionObject->setCollisionShape(mShape);
|
||||||
|
mCollisionObject->setWorldTransform(transform);
|
||||||
|
|
||||||
|
mHoldObject = holdObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
HeightField::~HeightField()
|
||||||
|
{
|
||||||
|
delete mCollisionObject;
|
||||||
|
delete mShape;
|
||||||
|
}
|
||||||
|
|
||||||
|
btCollisionObject* HeightField::getCollisionObject()
|
||||||
|
{
|
||||||
|
return mCollisionObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
const btCollisionObject* HeightField::getCollisionObject() const
|
||||||
|
{
|
||||||
|
return mCollisionObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
const btHeightfieldTerrainShape* HeightField::getShape() const
|
||||||
|
{
|
||||||
|
return mShape;
|
||||||
|
}
|
||||||
|
}
|
36
apps/openmw/mwphysics/heightfield.hpp
Normal file
36
apps/openmw/mwphysics/heightfield.hpp
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
#ifndef OPENMW_MWPHYSICS_HEIGHTFIELD_H
|
||||||
|
#define OPENMW_MWPHYSICS_HEIGHTFIELD_H
|
||||||
|
|
||||||
|
#include <osg/ref_ptr>
|
||||||
|
|
||||||
|
class btCollisionObject;
|
||||||
|
class btHeightfieldTerrainShape;
|
||||||
|
|
||||||
|
namespace osg
|
||||||
|
{
|
||||||
|
class Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace MWPhysics
|
||||||
|
{
|
||||||
|
class HeightField
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject);
|
||||||
|
~HeightField();
|
||||||
|
|
||||||
|
btCollisionObject* getCollisionObject();
|
||||||
|
const btCollisionObject* getCollisionObject() const;
|
||||||
|
const btHeightfieldTerrainShape* getShape() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
btHeightfieldTerrainShape* mShape;
|
||||||
|
btCollisionObject* mCollisionObject;
|
||||||
|
osg::ref_ptr<const osg::Object> mHoldObject;
|
||||||
|
|
||||||
|
void operator=(const HeightField&);
|
||||||
|
HeightField(const HeightField&);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
130
apps/openmw/mwphysics/object.cpp
Normal file
130
apps/openmw/mwphysics/object.cpp
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
#include "object.hpp"
|
||||||
|
#include "convert.hpp"
|
||||||
|
|
||||||
|
#include <components/debug/debuglog.hpp>
|
||||||
|
#include <components/nifosg/particle.hpp>
|
||||||
|
#include <components/resource/bulletshape.hpp>
|
||||||
|
#include <components/sceneutil/positionattitudetransform.hpp>
|
||||||
|
|
||||||
|
#include <osg/Object>
|
||||||
|
|
||||||
|
#include <BulletCollision/CollisionShapes/btCompoundShape.h>
|
||||||
|
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
|
||||||
|
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
||||||
|
|
||||||
|
#include <LinearMath/btTransform.h>
|
||||||
|
|
||||||
|
namespace MWPhysics
|
||||||
|
{
|
||||||
|
Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance)
|
||||||
|
: mShapeInstance(shapeInstance)
|
||||||
|
, mSolid(true)
|
||||||
|
{
|
||||||
|
mPtr = ptr;
|
||||||
|
|
||||||
|
mCollisionObject.reset(new btCollisionObject);
|
||||||
|
mCollisionObject->setCollisionShape(shapeInstance->getCollisionShape());
|
||||||
|
|
||||||
|
mCollisionObject->setUserPointer(static_cast<PtrHolder*>(this));
|
||||||
|
|
||||||
|
setScale(ptr.getCellRef().getScale());
|
||||||
|
setRotation(toBullet(ptr.getRefData().getBaseNode()->getAttitude()));
|
||||||
|
const float* pos = ptr.getRefData().getPosition().pos;
|
||||||
|
setOrigin(btVector3(pos[0], pos[1], pos[2]));
|
||||||
|
}
|
||||||
|
|
||||||
|
const Resource::BulletShapeInstance* Object::getShapeInstance() const
|
||||||
|
{
|
||||||
|
return mShapeInstance.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Object::setScale(float scale)
|
||||||
|
{
|
||||||
|
mShapeInstance->setLocalScaling(btVector3(scale, scale, scale));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Object::setRotation(const btQuaternion& quat)
|
||||||
|
{
|
||||||
|
mCollisionObject->getWorldTransform().setRotation(quat);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Object::setOrigin(const btVector3& vec)
|
||||||
|
{
|
||||||
|
mCollisionObject->getWorldTransform().setOrigin(vec);
|
||||||
|
}
|
||||||
|
|
||||||
|
btCollisionObject* Object::getCollisionObject()
|
||||||
|
{
|
||||||
|
return mCollisionObject.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
const btCollisionObject* Object::getCollisionObject() const
|
||||||
|
{
|
||||||
|
return mCollisionObject.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Object::isSolid() const
|
||||||
|
{
|
||||||
|
return mSolid;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Object::setSolid(bool solid)
|
||||||
|
{
|
||||||
|
mSolid = solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Object::isAnimated() const
|
||||||
|
{
|
||||||
|
return !mShapeInstance->mAnimatedShapes.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Object::animateCollisionShapes(btCollisionWorld* collisionWorld)
|
||||||
|
{
|
||||||
|
if (mShapeInstance->mAnimatedShapes.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
assert (mShapeInstance->getCollisionShape()->isCompound());
|
||||||
|
|
||||||
|
btCompoundShape* compound = static_cast<btCompoundShape*>(mShapeInstance->getCollisionShape());
|
||||||
|
for (std::map<int, int>::const_iterator it = mShapeInstance->mAnimatedShapes.begin(); it != mShapeInstance->mAnimatedShapes.end(); ++it)
|
||||||
|
{
|
||||||
|
int recIndex = it->first;
|
||||||
|
int shapeIndex = it->second;
|
||||||
|
|
||||||
|
std::map<int, osg::NodePath>::iterator nodePathFound = mRecIndexToNodePath.find(recIndex);
|
||||||
|
if (nodePathFound == mRecIndexToNodePath.end())
|
||||||
|
{
|
||||||
|
NifOsg::FindGroupByRecIndex visitor(recIndex);
|
||||||
|
mPtr.getRefData().getBaseNode()->accept(visitor);
|
||||||
|
if (!visitor.mFound)
|
||||||
|
{
|
||||||
|
Log(Debug::Warning) << "Warning: animateCollisionShapes can't find node " << recIndex << " for " << mPtr.getCellRef().getRefId();
|
||||||
|
|
||||||
|
// Remove nonexistent nodes from animated shapes map and early out
|
||||||
|
mShapeInstance->mAnimatedShapes.erase(recIndex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
osg::NodePath nodePath = visitor.mFoundPath;
|
||||||
|
nodePath.erase(nodePath.begin());
|
||||||
|
nodePathFound = mRecIndexToNodePath.insert(std::make_pair(recIndex, nodePath)).first;
|
||||||
|
}
|
||||||
|
|
||||||
|
osg::NodePath& nodePath = nodePathFound->second;
|
||||||
|
osg::Matrixf matrix = osg::computeLocalToWorld(nodePath);
|
||||||
|
matrix.orthoNormalize(matrix);
|
||||||
|
|
||||||
|
btTransform transform;
|
||||||
|
transform.setOrigin(toBullet(matrix.getTrans()) * compound->getLocalScaling());
|
||||||
|
for (int i=0; i<3; ++i)
|
||||||
|
for (int j=0; j<3; ++j)
|
||||||
|
transform.getBasis()[i][j] = matrix(j,i); // NB column/row major difference
|
||||||
|
|
||||||
|
// Note: we can not apply scaling here for now since we treat scaled shapes
|
||||||
|
// as new shapes (btScaledBvhTriangleMeshShape) with 1.0 scale for now
|
||||||
|
if (!(transform == compound->getChildTransform(shapeIndex)))
|
||||||
|
compound->updateChildTransform(shapeIndex, transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
collisionWorld->updateSingleAabb(mCollisionObject.get());
|
||||||
|
}
|
||||||
|
}
|
48
apps/openmw/mwphysics/object.hpp
Normal file
48
apps/openmw/mwphysics/object.hpp
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
#ifndef OPENMW_MWPHYSICS_OBJECT_H
|
||||||
|
#define OPENMW_MWPHYSICS_OBJECT_H
|
||||||
|
|
||||||
|
#include "ptrholder.hpp"
|
||||||
|
|
||||||
|
#include <osg/Node>
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace Resource
|
||||||
|
{
|
||||||
|
class BulletShapeInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
class btCollisionObject;
|
||||||
|
class btCollisionWorld;
|
||||||
|
class btQuaternion;
|
||||||
|
class btVector3;
|
||||||
|
|
||||||
|
namespace MWPhysics
|
||||||
|
{
|
||||||
|
class Object : public PtrHolder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Object(const MWWorld::Ptr& ptr, osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance);
|
||||||
|
|
||||||
|
const Resource::BulletShapeInstance* getShapeInstance() const;
|
||||||
|
void setScale(float scale);
|
||||||
|
void setRotation(const btQuaternion& quat);
|
||||||
|
void setOrigin(const btVector3& vec);
|
||||||
|
btCollisionObject* getCollisionObject();
|
||||||
|
const btCollisionObject* getCollisionObject() const;
|
||||||
|
/// Return solid flag. Not used by the object itself, true by default.
|
||||||
|
bool isSolid() const;
|
||||||
|
void setSolid(bool solid);
|
||||||
|
bool isAnimated() const;
|
||||||
|
void animateCollisionShapes(btCollisionWorld* collisionWorld);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<btCollisionObject> mCollisionObject;
|
||||||
|
osg::ref_ptr<Resource::BulletShapeInstance> mShapeInstance;
|
||||||
|
std::map<int, osg::NodePath> mRecIndexToNodePath;
|
||||||
|
bool mSolid;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -1,6 +1,11 @@
|
||||||
#include "physicssystem.hpp"
|
#include "physicssystem.hpp"
|
||||||
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <fstream>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
#include <boost/optional.hpp>
|
||||||
|
|
||||||
#include <osg/Group>
|
#include <osg/Group>
|
||||||
|
|
||||||
|
@ -16,6 +21,12 @@
|
||||||
|
|
||||||
#include <LinearMath/btQuickprof.h>
|
#include <LinearMath/btQuickprof.h>
|
||||||
|
|
||||||
|
#include <DetourCommon.h>
|
||||||
|
#include <DetourNavMesh.h>
|
||||||
|
#include <DetourNavMeshBuilder.h>
|
||||||
|
#include <DetourNavMeshQuery.h>
|
||||||
|
#include <Recast.h>
|
||||||
|
|
||||||
#include <components/nifbullet/bulletnifloader.hpp>
|
#include <components/nifbullet/bulletnifloader.hpp>
|
||||||
#include <components/resource/resourcesystem.hpp>
|
#include <components/resource/resourcesystem.hpp>
|
||||||
#include <components/resource/bulletshapemanager.hpp>
|
#include <components/resource/bulletshapemanager.hpp>
|
||||||
|
@ -48,12 +59,12 @@
|
||||||
#include "actor.hpp"
|
#include "actor.hpp"
|
||||||
#include "convert.hpp"
|
#include "convert.hpp"
|
||||||
#include "trace.h"
|
#include "trace.h"
|
||||||
|
#include "object.hpp"
|
||||||
|
#include "heightfield.hpp"
|
||||||
|
|
||||||
namespace MWPhysics
|
namespace MWPhysics
|
||||||
{
|
{
|
||||||
|
|
||||||
static const float sMaxSlope = 49.0f;
|
|
||||||
static const float sStepSizeUp = 34.0f;
|
|
||||||
static const float sStepSizeDown = 62.0f;
|
static const float sStepSizeDown = 62.0f;
|
||||||
static const float sMinStep = 10.f;
|
static const float sMinStep = 10.f;
|
||||||
static const float sGroundOffset = 1.0f;
|
static const float sGroundOffset = 1.0f;
|
||||||
|
@ -507,175 +518,6 @@ namespace MWPhysics
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
|
|
||||||
class HeightField
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject)
|
|
||||||
{
|
|
||||||
mShape = new btHeightfieldTerrainShape(
|
|
||||||
sqrtVerts, sqrtVerts, heights, 1,
|
|
||||||
minH, maxH, 2,
|
|
||||||
PHY_FLOAT, false
|
|
||||||
);
|
|
||||||
mShape->setUseDiamondSubdivision(true);
|
|
||||||
mShape->setLocalScaling(btVector3(triSize, triSize, 1));
|
|
||||||
|
|
||||||
btTransform transform(btQuaternion::getIdentity(),
|
|
||||||
btVector3((x+0.5f) * triSize * (sqrtVerts-1),
|
|
||||||
(y+0.5f) * triSize * (sqrtVerts-1),
|
|
||||||
(maxH+minH)*0.5f));
|
|
||||||
|
|
||||||
mCollisionObject = new btCollisionObject;
|
|
||||||
mCollisionObject->setCollisionShape(mShape);
|
|
||||||
mCollisionObject->setWorldTransform(transform);
|
|
||||||
|
|
||||||
mHoldObject = holdObject;
|
|
||||||
}
|
|
||||||
~HeightField()
|
|
||||||
{
|
|
||||||
delete mCollisionObject;
|
|
||||||
delete mShape;
|
|
||||||
}
|
|
||||||
btCollisionObject* getCollisionObject()
|
|
||||||
{
|
|
||||||
return mCollisionObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
btHeightfieldTerrainShape* mShape;
|
|
||||||
btCollisionObject* mCollisionObject;
|
|
||||||
osg::ref_ptr<const osg::Object> mHoldObject;
|
|
||||||
|
|
||||||
void operator=(const HeightField&);
|
|
||||||
HeightField(const HeightField&);
|
|
||||||
};
|
|
||||||
|
|
||||||
// --------------------------------------------------------------
|
|
||||||
|
|
||||||
class Object : public PtrHolder
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Object(const MWWorld::Ptr& ptr, osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance)
|
|
||||||
: mShapeInstance(shapeInstance)
|
|
||||||
, mSolid(true)
|
|
||||||
{
|
|
||||||
mPtr = ptr;
|
|
||||||
|
|
||||||
mCollisionObject.reset(new btCollisionObject);
|
|
||||||
mCollisionObject->setCollisionShape(shapeInstance->getCollisionShape());
|
|
||||||
|
|
||||||
mCollisionObject->setUserPointer(static_cast<PtrHolder*>(this));
|
|
||||||
|
|
||||||
setScale(ptr.getCellRef().getScale());
|
|
||||||
setRotation(toBullet(ptr.getRefData().getBaseNode()->getAttitude()));
|
|
||||||
const float* pos = ptr.getRefData().getPosition().pos;
|
|
||||||
setOrigin(btVector3(pos[0], pos[1], pos[2]));
|
|
||||||
}
|
|
||||||
|
|
||||||
const Resource::BulletShapeInstance* getShapeInstance() const
|
|
||||||
{
|
|
||||||
return mShapeInstance.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
void setScale(float scale)
|
|
||||||
{
|
|
||||||
mShapeInstance->getCollisionShape()->setLocalScaling(btVector3(scale,scale,scale));
|
|
||||||
}
|
|
||||||
|
|
||||||
void setRotation(const btQuaternion& quat)
|
|
||||||
{
|
|
||||||
mCollisionObject->getWorldTransform().setRotation(quat);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setOrigin(const btVector3& vec)
|
|
||||||
{
|
|
||||||
mCollisionObject->getWorldTransform().setOrigin(vec);
|
|
||||||
}
|
|
||||||
|
|
||||||
btCollisionObject* getCollisionObject()
|
|
||||||
{
|
|
||||||
return mCollisionObject.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
const btCollisionObject* getCollisionObject() const
|
|
||||||
{
|
|
||||||
return mCollisionObject.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return solid flag. Not used by the object itself, true by default.
|
|
||||||
bool isSolid() const
|
|
||||||
{
|
|
||||||
return mSolid;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setSolid(bool solid)
|
|
||||||
{
|
|
||||||
mSolid = solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isAnimated() const
|
|
||||||
{
|
|
||||||
return !mShapeInstance->mAnimatedShapes.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
void animateCollisionShapes(btCollisionWorld* collisionWorld)
|
|
||||||
{
|
|
||||||
if (mShapeInstance->mAnimatedShapes.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
assert (mShapeInstance->getCollisionShape()->isCompound());
|
|
||||||
|
|
||||||
btCompoundShape* compound = static_cast<btCompoundShape*>(mShapeInstance->getCollisionShape());
|
|
||||||
for (std::map<int, int>::const_iterator it = mShapeInstance->mAnimatedShapes.begin(); it != mShapeInstance->mAnimatedShapes.end(); ++it)
|
|
||||||
{
|
|
||||||
int recIndex = it->first;
|
|
||||||
int shapeIndex = it->second;
|
|
||||||
|
|
||||||
std::map<int, osg::NodePath>::iterator nodePathFound = mRecIndexToNodePath.find(recIndex);
|
|
||||||
if (nodePathFound == mRecIndexToNodePath.end())
|
|
||||||
{
|
|
||||||
NifOsg::FindGroupByRecIndex visitor(recIndex);
|
|
||||||
mPtr.getRefData().getBaseNode()->accept(visitor);
|
|
||||||
if (!visitor.mFound)
|
|
||||||
{
|
|
||||||
Log(Debug::Warning) << "Warning: animateCollisionShapes can't find node " << recIndex << " for " << mPtr.getCellRef().getRefId();
|
|
||||||
|
|
||||||
// Remove nonexistent nodes from animated shapes map and early out
|
|
||||||
mShapeInstance->mAnimatedShapes.erase(recIndex);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
osg::NodePath nodePath = visitor.mFoundPath;
|
|
||||||
nodePath.erase(nodePath.begin());
|
|
||||||
nodePathFound = mRecIndexToNodePath.insert(std::make_pair(recIndex, nodePath)).first;
|
|
||||||
}
|
|
||||||
|
|
||||||
osg::NodePath& nodePath = nodePathFound->second;
|
|
||||||
osg::Matrixf matrix = osg::computeLocalToWorld(nodePath);
|
|
||||||
matrix.orthoNormalize(matrix);
|
|
||||||
|
|
||||||
btTransform transform;
|
|
||||||
transform.setOrigin(toBullet(matrix.getTrans()) * compound->getLocalScaling());
|
|
||||||
for (int i=0; i<3; ++i)
|
|
||||||
for (int j=0; j<3; ++j)
|
|
||||||
transform.getBasis()[i][j] = matrix(j,i); // NB column/row major difference
|
|
||||||
|
|
||||||
// Note: we can not apply scaling here for now since we treat scaled shapes
|
|
||||||
// as new shapes (btScaledBvhTriangleMeshShape) with 1.0 scale for now
|
|
||||||
if (!(transform == compound->getChildTransform(shapeIndex)))
|
|
||||||
compound->updateChildTransform(shapeIndex, transform);
|
|
||||||
}
|
|
||||||
|
|
||||||
collisionWorld->updateSingleAabb(mCollisionObject.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::unique_ptr<btCollisionObject> mCollisionObject;
|
|
||||||
osg::ref_ptr<Resource::BulletShapeInstance> mShapeInstance;
|
|
||||||
std::map<int, osg::NodePath> mRecIndexToNodePath;
|
|
||||||
bool mSolid;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
|
||||||
|
|
||||||
PhysicsSystem::PhysicsSystem(Resource::ResourceSystem* resourceSystem, osg::ref_ptr<osg::Group> parentNode)
|
PhysicsSystem::PhysicsSystem(Resource::ResourceSystem* resourceSystem, osg::ref_ptr<osg::Group> parentNode)
|
||||||
: mShapeManager(new Resource::BulletShapeManager(resourceSystem->getVFS(), resourceSystem->getSceneManager(), resourceSystem->getNifFileManager()))
|
: mShapeManager(new Resource::BulletShapeManager(resourceSystem->getVFS(), resourceSystem->getSceneManager(), resourceSystem->getNifFileManager()))
|
||||||
|
@ -1171,6 +1013,14 @@ namespace MWPhysics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const HeightField* PhysicsSystem::getHeightField(int x, int y) const
|
||||||
|
{
|
||||||
|
const auto heightField = mHeightFields.find(std::make_pair(x, y));
|
||||||
|
if (heightField == mHeightFields.end())
|
||||||
|
return nullptr;
|
||||||
|
return heightField->second;
|
||||||
|
}
|
||||||
|
|
||||||
void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType)
|
void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType)
|
||||||
{
|
{
|
||||||
osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance = mShapeManager->getInstance(mesh);
|
osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance = mShapeManager->getInstance(mesh);
|
||||||
|
@ -1366,6 +1216,33 @@ namespace MWPhysics
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PhysicsSystem::setActorCollisionMode(const MWWorld::Ptr& ptr, bool enabled)
|
||||||
|
{
|
||||||
|
ActorMap::iterator found = mActors.find(ptr);
|
||||||
|
if (found != mActors.end())
|
||||||
|
{
|
||||||
|
bool cmode = found->second->getCollisionMode();
|
||||||
|
if (cmode == enabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
cmode = enabled;
|
||||||
|
found->second->enableCollisionMode(cmode);
|
||||||
|
found->second->enableCollisionBody(cmode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PhysicsSystem::isActorCollisionEnabled(const MWWorld::Ptr& ptr)
|
||||||
|
{
|
||||||
|
ActorMap::iterator found = mActors.find(ptr);
|
||||||
|
if (found != mActors.end())
|
||||||
|
{
|
||||||
|
bool cmode = found->second->getCollisionMode();
|
||||||
|
return cmode;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void PhysicsSystem::queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &movement)
|
void PhysicsSystem::queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &movement)
|
||||||
{
|
{
|
||||||
PtrVelocityList::iterator iter = mMovementQueue.begin();
|
PtrVelocityList::iterator iter = mMovementQueue.begin();
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include <osg/Quat>
|
#include <osg/Quat>
|
||||||
#include <osg/ref_ptr>
|
#include <osg/ref_ptr>
|
||||||
|
@ -49,6 +50,9 @@ namespace MWPhysics
|
||||||
class Object;
|
class Object;
|
||||||
class Actor;
|
class Actor;
|
||||||
|
|
||||||
|
static const float sMaxSlope = 49.0f;
|
||||||
|
static const float sStepSizeUp = 34.0f;
|
||||||
|
|
||||||
class PhysicsSystem
|
class PhysicsSystem
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -85,7 +89,11 @@ namespace MWPhysics
|
||||||
|
|
||||||
void removeHeightField (int x, int y);
|
void removeHeightField (int x, int y);
|
||||||
|
|
||||||
|
const HeightField* getHeightField(int x, int y) const;
|
||||||
|
|
||||||
bool toggleCollisionMode();
|
bool toggleCollisionMode();
|
||||||
|
bool isActorCollisionEnabled(const MWWorld::Ptr& ptr);
|
||||||
|
void setActorCollisionMode(const MWWorld::Ptr& ptr, bool enabled);
|
||||||
|
|
||||||
void stepSimulation(float dt);
|
void stepSimulation(float dt);
|
||||||
void debugDraw();
|
void debugDraw();
|
||||||
|
@ -170,6 +178,12 @@ namespace MWPhysics
|
||||||
|
|
||||||
bool isOnSolidGround (const MWWorld::Ptr& actor) const;
|
bool isOnSolidGround (const MWWorld::Ptr& actor) const;
|
||||||
|
|
||||||
|
template <class Function>
|
||||||
|
void forEachAnimatedObject(Function&& function) const
|
||||||
|
{
|
||||||
|
std::for_each(mAnimatedObjects.begin(), mAnimatedObjects.end(), function);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
void updateWater();
|
void updateWater();
|
||||||
|
|
33
apps/openmw/mwphysics/ptrholder.hpp
Normal file
33
apps/openmw/mwphysics/ptrholder.hpp
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
#ifndef OPENMW_MWPHYSICS_PTRHOLDER_H
|
||||||
|
#define OPENMW_MWPHYSICS_PTRHOLDER_H
|
||||||
|
|
||||||
|
#include "../mwworld/ptr.hpp"
|
||||||
|
|
||||||
|
namespace MWPhysics
|
||||||
|
{
|
||||||
|
class PtrHolder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~PtrHolder() {}
|
||||||
|
|
||||||
|
void updatePtr(const MWWorld::Ptr& updated)
|
||||||
|
{
|
||||||
|
mPtr = updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
MWWorld::Ptr getPtr()
|
||||||
|
{
|
||||||
|
return mPtr;
|
||||||
|
}
|
||||||
|
|
||||||
|
MWWorld::ConstPtr getPtr() const
|
||||||
|
{
|
||||||
|
return mPtr;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
MWWorld::Ptr mPtr;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -1,5 +1,4 @@
|
||||||
#include "actoranimation.hpp"
|
#include "actoranimation.hpp"
|
||||||
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include <osg/Node>
|
#include <osg/Node>
|
||||||
|
@ -9,11 +8,22 @@
|
||||||
#include <components/esm/loadligh.hpp>
|
#include <components/esm/loadligh.hpp>
|
||||||
#include <components/esm/loadcell.hpp>
|
#include <components/esm/loadcell.hpp>
|
||||||
|
|
||||||
|
#include <components/resource/resourcesystem.hpp>
|
||||||
|
#include <components/resource/scenemanager.hpp>
|
||||||
|
|
||||||
|
#include <components/sceneutil/attach.hpp>
|
||||||
#include <components/sceneutil/lightmanager.hpp>
|
#include <components/sceneutil/lightmanager.hpp>
|
||||||
#include <components/sceneutil/lightutil.hpp>
|
#include <components/sceneutil/lightutil.hpp>
|
||||||
|
#include <components/sceneutil/visitor.hpp>
|
||||||
|
|
||||||
#include <components/fallback/fallback.hpp>
|
#include <components/fallback/fallback.hpp>
|
||||||
|
|
||||||
|
#include <components/misc/stringops.hpp>
|
||||||
|
|
||||||
|
#include <components/settings/settings.hpp>
|
||||||
|
|
||||||
|
#include <components/vfs/manager.hpp>
|
||||||
|
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
#include "../mwworld/ptr.hpp"
|
#include "../mwworld/ptr.hpp"
|
||||||
|
@ -43,6 +53,8 @@ ActorAnimation::ActorAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr<osg::Group>
|
||||||
|
|
||||||
// Make sure we cleaned object from effects, just in cast if we re-use node
|
// Make sure we cleaned object from effects, just in cast if we re-use node
|
||||||
removeEffects();
|
removeEffects();
|
||||||
|
|
||||||
|
mWeaponSheathing = Settings::Manager::getBool("weapon sheathing", "Game");
|
||||||
}
|
}
|
||||||
|
|
||||||
ActorAnimation::~ActorAnimation()
|
ActorAnimation::~ActorAnimation()
|
||||||
|
@ -51,6 +63,302 @@ ActorAnimation::~ActorAnimation()
|
||||||
{
|
{
|
||||||
mInsert->removeChild(iter->second);
|
mInsert->removeChild(iter->second);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mScabbard.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
PartHolderPtr ActorAnimation::getWeaponPart(const std::string& model, const std::string& bonename, bool enchantedGlow, osg::Vec4f* glowColor)
|
||||||
|
{
|
||||||
|
osg::Group* parent = getBoneByName(bonename);
|
||||||
|
if (!parent)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Node> instance = mResourceSystem->getSceneManager()->getInstance(model, parent);
|
||||||
|
|
||||||
|
const NodeMap& nodeMap = getNodeMap();
|
||||||
|
NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename));
|
||||||
|
if (found == nodeMap.end())
|
||||||
|
return PartHolderPtr();
|
||||||
|
|
||||||
|
if (enchantedGlow)
|
||||||
|
addGlow(instance, *glowColor);
|
||||||
|
|
||||||
|
return PartHolderPtr(new PartHolder(instance));
|
||||||
|
}
|
||||||
|
|
||||||
|
osg::Group* ActorAnimation::getBoneByName(std::string boneName)
|
||||||
|
{
|
||||||
|
if (!mObjectRoot)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
SceneUtil::FindByNameVisitor findVisitor (boneName);
|
||||||
|
mObjectRoot->accept(findVisitor);
|
||||||
|
|
||||||
|
return findVisitor.mFoundNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ActorAnimation::getHolsteredWeaponBoneName(const MWWorld::ConstPtr& weapon)
|
||||||
|
{
|
||||||
|
std::string boneName;
|
||||||
|
if(weapon.isEmpty())
|
||||||
|
return boneName;
|
||||||
|
|
||||||
|
const std::string &type = weapon.getClass().getTypeName();
|
||||||
|
if(type == typeid(ESM::Weapon).name())
|
||||||
|
{
|
||||||
|
const MWWorld::LiveCellRef<ESM::Weapon> *ref = weapon.get<ESM::Weapon>();
|
||||||
|
ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType;
|
||||||
|
return getHolsteredWeaponBoneName(weaponType);
|
||||||
|
}
|
||||||
|
|
||||||
|
return boneName;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ActorAnimation::getHolsteredWeaponBoneName(const unsigned int weaponType)
|
||||||
|
{
|
||||||
|
std::string boneName;
|
||||||
|
|
||||||
|
switch(weaponType)
|
||||||
|
{
|
||||||
|
case ESM::Weapon::ShortBladeOneHand:
|
||||||
|
boneName = "Bip01 ShortBladeOneHand";
|
||||||
|
break;
|
||||||
|
case ESM::Weapon::LongBladeOneHand:
|
||||||
|
boneName = "Bip01 LongBladeOneHand";
|
||||||
|
break;
|
||||||
|
case ESM::Weapon::BluntOneHand:
|
||||||
|
boneName = "Bip01 BluntOneHand";
|
||||||
|
break;
|
||||||
|
case ESM::Weapon::AxeOneHand:
|
||||||
|
boneName = "Bip01 LongBladeOneHand";
|
||||||
|
break;
|
||||||
|
case ESM::Weapon::LongBladeTwoHand:
|
||||||
|
boneName = "Bip01 LongBladeTwoClose";
|
||||||
|
break;
|
||||||
|
case ESM::Weapon::BluntTwoClose:
|
||||||
|
boneName = "Bip01 BluntTwoClose";
|
||||||
|
break;
|
||||||
|
case ESM::Weapon::AxeTwoHand:
|
||||||
|
boneName = "Bip01 AxeTwoClose";
|
||||||
|
break;
|
||||||
|
case ESM::Weapon::BluntTwoWide:
|
||||||
|
boneName = "Bip01 BluntTwoWide";
|
||||||
|
break;
|
||||||
|
case ESM::Weapon::SpearTwoWide:
|
||||||
|
boneName = "Bip01 SpearTwoWide";
|
||||||
|
break;
|
||||||
|
case ESM::Weapon::MarksmanBow:
|
||||||
|
boneName = "Bip01 MarksmanBow";
|
||||||
|
break;
|
||||||
|
case ESM::Weapon::MarksmanCrossbow:
|
||||||
|
boneName = "Bip01 MarksmanCrossbow";
|
||||||
|
break;
|
||||||
|
case ESM::Weapon::MarksmanThrown:
|
||||||
|
boneName = "Bip01 MarksmanThrown";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return boneName;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActorAnimation::injectWeaponBones()
|
||||||
|
{
|
||||||
|
if (!mResourceSystem->getVFS()->exists("meshes\\xbase_anim_sh.nif"))
|
||||||
|
{
|
||||||
|
mWeaponSheathing = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Node> sheathSkeleton = mResourceSystem->getSceneManager()->getInstance("meshes\\xbase_anim_sh.nif");
|
||||||
|
|
||||||
|
for (unsigned int type=0; type<=ESM::Weapon::MarksmanThrown; ++type)
|
||||||
|
{
|
||||||
|
const std::string holsteredBoneName = getHolsteredWeaponBoneName(type);
|
||||||
|
|
||||||
|
SceneUtil::FindByNameVisitor findVisitor (holsteredBoneName);
|
||||||
|
sheathSkeleton->accept(findVisitor);
|
||||||
|
osg::ref_ptr<osg::Node> sheathNode = findVisitor.mFoundNode;
|
||||||
|
|
||||||
|
if (sheathNode && sheathNode.get()->getNumParents())
|
||||||
|
{
|
||||||
|
osg::Group* sheathParent = getBoneByName(sheathNode.get()->getParent(0)->getName());
|
||||||
|
|
||||||
|
if (sheathParent)
|
||||||
|
{
|
||||||
|
sheathNode.get()->getParent(0)->removeChild(sheathNode);
|
||||||
|
sheathParent->addChild(sheathNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// To make sure we do not run morph controllers for weapons, i.e. bows
|
||||||
|
class EmptyCallback : public osg::NodeCallback
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons)
|
||||||
|
{
|
||||||
|
if (!mWeaponSheathing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!mPtr.getClass().hasInventoryStore(mPtr))
|
||||||
|
return;
|
||||||
|
|
||||||
|
mScabbard.reset();
|
||||||
|
|
||||||
|
const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
|
||||||
|
MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||||
|
if (weapon == inv.end() || weapon->getTypeName() != typeid(ESM::Weapon).name())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Since throwing weapons stack themselves, do not show such weapon itself
|
||||||
|
if (weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanThrown)
|
||||||
|
showHolsteredWeapons = false;
|
||||||
|
|
||||||
|
std::string mesh = weapon->getClass().getModel(*weapon);
|
||||||
|
std::string scabbardName = mesh;
|
||||||
|
|
||||||
|
std::string boneName = getHolsteredWeaponBoneName(*weapon);
|
||||||
|
if (mesh.empty() || boneName.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If the scabbard is not found, use a weapon mesh as fallback
|
||||||
|
scabbardName = scabbardName.replace(scabbardName.size()-4, 4, "_sh.nif");
|
||||||
|
bool isEnchanted = !weapon->getClass().getEnchantment(*weapon).empty();
|
||||||
|
if(!mResourceSystem->getVFS()->exists(scabbardName))
|
||||||
|
{
|
||||||
|
if (showHolsteredWeapons)
|
||||||
|
{
|
||||||
|
osg::Vec4f glowColor = getEnchantmentColor(*weapon);
|
||||||
|
mScabbard = getWeaponPart(mesh, boneName, isEnchanted, &glowColor);
|
||||||
|
if (mScabbard)
|
||||||
|
mScabbard->getNode()->setUpdateCallback(new EmptyCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mScabbard = getWeaponPart(scabbardName, boneName);
|
||||||
|
|
||||||
|
osg::Group* weaponNode = getBoneByName("Bip01 Weapon");
|
||||||
|
if (!weaponNode)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// When we draw weapon, hide the Weapon node from sheath model.
|
||||||
|
// Otherwise add the enchanted glow to it.
|
||||||
|
if (!showHolsteredWeapons)
|
||||||
|
{
|
||||||
|
weaponNode->setNodeMask(0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If mesh author declared empty weapon node, use transformation from this node, but use the common weapon mesh.
|
||||||
|
// This approach allows to tweak weapon position without need to store the whole weapon mesh in the _sh file.
|
||||||
|
if (!weaponNode->getNumChildren())
|
||||||
|
{
|
||||||
|
osg::ref_ptr<osg::Node> fallbackNode = mResourceSystem->getSceneManager()->getInstance(mesh, weaponNode);
|
||||||
|
fallbackNode->setUpdateCallback(new EmptyCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEnchanted)
|
||||||
|
{
|
||||||
|
osg::Vec4f glowColor = getEnchantmentColor(*weapon);
|
||||||
|
addGlow(weaponNode, glowColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActorAnimation::updateQuiver()
|
||||||
|
{
|
||||||
|
if (!mWeaponSheathing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!mPtr.getClass().hasInventoryStore(mPtr))
|
||||||
|
return;
|
||||||
|
|
||||||
|
const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
|
||||||
|
MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||||
|
if(weapon == inv.end() || weapon->getTypeName() != typeid(ESM::Weapon).name())
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::string mesh = weapon->getClass().getModel(*weapon);
|
||||||
|
std::string boneName = getHolsteredWeaponBoneName(*weapon);
|
||||||
|
if (mesh.empty() || boneName.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
osg::Group* ammoNode = getBoneByName("Bip01 Ammo");
|
||||||
|
if (!ammoNode)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Special case for throwing weapons - they do not use ammo, but they stack themselves
|
||||||
|
bool suitableAmmo = false;
|
||||||
|
MWWorld::ConstContainerStoreIterator ammo = weapon;
|
||||||
|
unsigned int ammoCount = 0;
|
||||||
|
if (weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanThrown)
|
||||||
|
{
|
||||||
|
ammoCount = ammo->getRefData().getCount();
|
||||||
|
osg::Group* throwingWeaponNode = getBoneByName("Weapon Bone");
|
||||||
|
if (throwingWeaponNode && throwingWeaponNode->getNumChildren())
|
||||||
|
ammoCount--;
|
||||||
|
|
||||||
|
suitableAmmo = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
|
||||||
|
if (ammo == inv.end())
|
||||||
|
return;
|
||||||
|
|
||||||
|
ammoCount = ammo->getRefData().getCount();
|
||||||
|
bool arrowAttached = isArrowAttached();
|
||||||
|
if (arrowAttached)
|
||||||
|
ammoCount--;
|
||||||
|
|
||||||
|
if (weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow)
|
||||||
|
suitableAmmo = ammo->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::Bolt;
|
||||||
|
else if (weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanBow)
|
||||||
|
suitableAmmo = ammo->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::Arrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ammoNode && suitableAmmo)
|
||||||
|
{
|
||||||
|
// We should not show more ammo than equipped and more than quiver mesh has
|
||||||
|
ammoCount = std::min(ammoCount, ammoNode->getNumChildren());
|
||||||
|
|
||||||
|
// Remove existing ammo nodes
|
||||||
|
for (unsigned int i=0; i<ammoNode->getNumChildren(); ++i)
|
||||||
|
{
|
||||||
|
osg::ref_ptr<osg::Group> arrowNode = ammoNode->getChild(i)->asGroup();
|
||||||
|
if (!arrowNode->getNumChildren())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Node> arrowChildNode = arrowNode->getChild(0);
|
||||||
|
arrowNode->removeChild(arrowChildNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new ones
|
||||||
|
osg::Vec4f glowColor = getEnchantmentColor(*ammo);
|
||||||
|
std::string model = ammo->getClass().getModel(*ammo);
|
||||||
|
for (unsigned int i=0; i<ammoCount; ++i)
|
||||||
|
{
|
||||||
|
osg::ref_ptr<osg::Group> arrowNode = ammoNode->getChild(i)->asGroup();
|
||||||
|
osg::ref_ptr<osg::Node> arrow = mResourceSystem->getSceneManager()->getInstance(model, arrowNode);
|
||||||
|
if (!ammo->getClass().getEnchantment(*ammo).empty())
|
||||||
|
addGlow(arrow, glowColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// recreate shaders for invisible actors, otherwise new nodes will be visible
|
||||||
|
if (mAlpha != 1.f)
|
||||||
|
mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActorAnimation::itemAdded(const MWWorld::ConstPtr& item, int /*count*/)
|
void ActorAnimation::itemAdded(const MWWorld::ConstPtr& item, int /*count*/)
|
||||||
|
@ -63,6 +371,24 @@ void ActorAnimation::itemAdded(const MWWorld::ConstPtr& item, int /*count*/)
|
||||||
addHiddenItemLight(item, light);
|
addHiddenItemLight(item, light);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!mPtr.getClass().hasInventoryStore(mPtr))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If the count of equipped ammo or throwing weapon was changed, we should update quiver
|
||||||
|
const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
|
||||||
|
MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||||
|
if(weapon == inv.end() || weapon->getTypeName() != typeid(ESM::Weapon).name())
|
||||||
|
return;
|
||||||
|
|
||||||
|
MWWorld::ConstContainerStoreIterator ammo = inv.end();
|
||||||
|
if (weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanThrown)
|
||||||
|
ammo = weapon;
|
||||||
|
else
|
||||||
|
ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
|
||||||
|
|
||||||
|
if(ammo != inv.end() && item.getCellRef().getRefId() == ammo->getCellRef().getRefId())
|
||||||
|
updateQuiver();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActorAnimation::itemRemoved(const MWWorld::ConstPtr& item, int /*count*/)
|
void ActorAnimation::itemRemoved(const MWWorld::ConstPtr& item, int /*count*/)
|
||||||
|
@ -78,6 +404,24 @@ void ActorAnimation::itemRemoved(const MWWorld::ConstPtr& item, int /*count*/)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!mPtr.getClass().hasInventoryStore(mPtr))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If the count of equipped ammo or throwing weapon was changed, we should update quiver
|
||||||
|
const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
|
||||||
|
MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||||
|
if(weapon == inv.end() || weapon->getTypeName() != typeid(ESM::Weapon).name())
|
||||||
|
return;
|
||||||
|
|
||||||
|
MWWorld::ConstContainerStoreIterator ammo = inv.end();
|
||||||
|
if (weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanThrown)
|
||||||
|
ammo = weapon;
|
||||||
|
else
|
||||||
|
ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
|
||||||
|
|
||||||
|
if(ammo != inv.end() && item.getCellRef().getRefId() == ammo->getCellRef().getRefId())
|
||||||
|
updateQuiver();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActorAnimation::addHiddenItemLight(const MWWorld::ConstPtr& item, const ESM::Light* esmLight)
|
void ActorAnimation::addHiddenItemLight(const MWWorld::ConstPtr& item, const ESM::Light* esmLight)
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include <osg/ref_ptr>
|
#include <osg/ref_ptr>
|
||||||
|
|
||||||
#include "../mwworld/containerstore.hpp"
|
#include "../mwworld/containerstore.hpp"
|
||||||
|
#include "../mwworld/inventorystore.hpp"
|
||||||
|
|
||||||
#include "animation.hpp"
|
#include "animation.hpp"
|
||||||
|
|
||||||
|
@ -36,6 +37,24 @@ class ActorAnimation : public Animation, public MWWorld::ContainerStoreListener
|
||||||
|
|
||||||
virtual void itemAdded(const MWWorld::ConstPtr& item, int count);
|
virtual void itemAdded(const MWWorld::ConstPtr& item, int count);
|
||||||
virtual void itemRemoved(const MWWorld::ConstPtr& item, int count);
|
virtual void itemRemoved(const MWWorld::ConstPtr& item, int count);
|
||||||
|
virtual bool isArrowAttached() const { return false; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool mWeaponSheathing;
|
||||||
|
osg::Group* getBoneByName(std::string boneName);
|
||||||
|
virtual void updateHolsteredWeapon(bool showHolsteredWeapons);
|
||||||
|
virtual void injectWeaponBones();
|
||||||
|
virtual void updateQuiver();
|
||||||
|
virtual std::string getHolsteredWeaponBoneName(const MWWorld::ConstPtr& weapon);
|
||||||
|
virtual std::string getHolsteredWeaponBoneName(const unsigned int weaponType);
|
||||||
|
virtual PartHolderPtr getWeaponPart(const std::string& model, const std::string& bonename, bool enchantedGlow, osg::Vec4f* glowColor);
|
||||||
|
virtual PartHolderPtr getWeaponPart(const std::string& model, const std::string& bonename)
|
||||||
|
{
|
||||||
|
osg::Vec4f stubColor = osg::Vec4f(0,0,0,0);
|
||||||
|
return getWeaponPart(model, bonename, false, &stubColor);
|
||||||
|
};
|
||||||
|
|
||||||
|
PartHolderPtr mScabbard;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void addHiddenItemLight(const MWWorld::ConstPtr& item, const ESM::Light* esmLight);
|
void addHiddenItemLight(const MWWorld::ConstPtr& item, const ESM::Light* esmLight);
|
||||||
|
|
99
apps/openmw/mwrender/actorspaths.cpp
Normal file
99
apps/openmw/mwrender/actorspaths.cpp
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
#include "actorspaths.hpp"
|
||||||
|
#include "vismask.hpp"
|
||||||
|
|
||||||
|
#include <components/sceneutil/agentpath.hpp>
|
||||||
|
|
||||||
|
#include <osg/PositionAttitudeTransform>
|
||||||
|
|
||||||
|
namespace MWRender
|
||||||
|
{
|
||||||
|
ActorsPaths::ActorsPaths(const osg::ref_ptr<osg::Group>& root, bool enabled)
|
||||||
|
: mRootNode(root)
|
||||||
|
, mEnabled(enabled)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ActorsPaths::~ActorsPaths()
|
||||||
|
{
|
||||||
|
if (mEnabled)
|
||||||
|
disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ActorsPaths::toggle()
|
||||||
|
{
|
||||||
|
if (mEnabled)
|
||||||
|
disable();
|
||||||
|
else
|
||||||
|
enable();
|
||||||
|
|
||||||
|
return mEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActorsPaths::update(const MWWorld::ConstPtr& actor, const std::deque<osg::Vec3f>& path,
|
||||||
|
const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end,
|
||||||
|
const DetourNavigator::Settings& settings)
|
||||||
|
{
|
||||||
|
if (!mEnabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto group = mGroups.find(actor);
|
||||||
|
if (group != mGroups.end())
|
||||||
|
mRootNode->removeChild(group->second);
|
||||||
|
|
||||||
|
const auto newGroup = SceneUtil::createAgentPathGroup(path, halfExtents, start, end, settings);
|
||||||
|
if (newGroup)
|
||||||
|
{
|
||||||
|
newGroup->setNodeMask(Mask_Debug);
|
||||||
|
mRootNode->addChild(newGroup);
|
||||||
|
mGroups[actor] = newGroup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActorsPaths::remove(const MWWorld::ConstPtr& actor)
|
||||||
|
{
|
||||||
|
const auto group = mGroups.find(actor);
|
||||||
|
if (group != mGroups.end())
|
||||||
|
{
|
||||||
|
mRootNode->removeChild(group->second);
|
||||||
|
mGroups.erase(group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActorsPaths::removeCell(const MWWorld::CellStore* const store)
|
||||||
|
{
|
||||||
|
for (auto it = mGroups.begin(); it != mGroups.end(); )
|
||||||
|
{
|
||||||
|
if (it->first.getCell() == store)
|
||||||
|
{
|
||||||
|
mRootNode->removeChild(it->second);
|
||||||
|
it = mGroups.erase(it);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActorsPaths::updatePtr(const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& updated)
|
||||||
|
{
|
||||||
|
const auto it = mGroups.find(old);
|
||||||
|
if (it == mGroups.end())
|
||||||
|
return;
|
||||||
|
auto group = std::move(it->second);
|
||||||
|
mGroups.erase(it);
|
||||||
|
mGroups.insert(std::make_pair(updated, std::move(group)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActorsPaths::enable()
|
||||||
|
{
|
||||||
|
std::for_each(mGroups.begin(), mGroups.end(),
|
||||||
|
[&] (const Groups::value_type& v) { mRootNode->addChild(v.second); });
|
||||||
|
mEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActorsPaths::disable()
|
||||||
|
{
|
||||||
|
std::for_each(mGroups.begin(), mGroups.end(),
|
||||||
|
[&] (const Groups::value_type& v) { mRootNode->removeChild(v.second); });
|
||||||
|
mEnabled = false;
|
||||||
|
}
|
||||||
|
}
|
51
apps/openmw/mwrender/actorspaths.hpp
Normal file
51
apps/openmw/mwrender/actorspaths.hpp
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
#ifndef OPENMW_MWRENDER_AGENTSPATHS_H
|
||||||
|
#define OPENMW_MWRENDER_AGENTSPATHS_H
|
||||||
|
|
||||||
|
#include <apps/openmw/mwworld/ptr.hpp>
|
||||||
|
|
||||||
|
#include <components/detournavigator/navigator.hpp>
|
||||||
|
|
||||||
|
#include <osg/ref_ptr>
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <deque>
|
||||||
|
|
||||||
|
namespace osg
|
||||||
|
{
|
||||||
|
class Group;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace MWRender
|
||||||
|
{
|
||||||
|
class ActorsPaths
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ActorsPaths(const osg::ref_ptr<osg::Group>& root, bool enabled);
|
||||||
|
~ActorsPaths();
|
||||||
|
|
||||||
|
bool toggle();
|
||||||
|
|
||||||
|
void update(const MWWorld::ConstPtr& actor, const std::deque<osg::Vec3f>& path,
|
||||||
|
const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end,
|
||||||
|
const DetourNavigator::Settings& settings);
|
||||||
|
|
||||||
|
void remove(const MWWorld::ConstPtr& actor);
|
||||||
|
|
||||||
|
void removeCell(const MWWorld::CellStore* const store);
|
||||||
|
|
||||||
|
void updatePtr(const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& updated);
|
||||||
|
|
||||||
|
void enable();
|
||||||
|
|
||||||
|
void disable();
|
||||||
|
|
||||||
|
private:
|
||||||
|
using Groups = std::map<MWWorld::ConstPtr, osg::ref_ptr<osg::Group>>;
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Group> mRootNode;
|
||||||
|
Groups mGroups;
|
||||||
|
bool mEnabled;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -15,13 +15,13 @@
|
||||||
|
|
||||||
#include <components/debug/debuglog.hpp>
|
#include <components/debug/debuglog.hpp>
|
||||||
|
|
||||||
#include <components/nifosg/nifloader.hpp>
|
|
||||||
|
|
||||||
#include <components/resource/resourcesystem.hpp>
|
#include <components/resource/resourcesystem.hpp>
|
||||||
#include <components/resource/scenemanager.hpp>
|
#include <components/resource/scenemanager.hpp>
|
||||||
#include <components/resource/keyframemanager.hpp>
|
#include <components/resource/keyframemanager.hpp>
|
||||||
#include <components/resource/imagemanager.hpp>
|
#include <components/resource/imagemanager.hpp>
|
||||||
|
|
||||||
|
#include <components/misc/constants.hpp>
|
||||||
|
|
||||||
#include <components/nifosg/nifloader.hpp> // KeyframeHolder
|
#include <components/nifosg/nifloader.hpp> // KeyframeHolder
|
||||||
#include <components/nifosg/controller.hpp>
|
#include <components/nifosg/controller.hpp>
|
||||||
|
|
||||||
|
@ -190,12 +190,6 @@ namespace
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
RemoveFinishedCallbackVisitor(int effectId)
|
|
||||||
: RemoveVisitor()
|
|
||||||
, mHasMagicEffects(false)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void apply(osg::Node &node)
|
virtual void apply(osg::Node &node)
|
||||||
{
|
{
|
||||||
traverse(node);
|
traverse(node);
|
||||||
|
@ -228,9 +222,6 @@ namespace
|
||||||
virtual void apply(osg::Geometry&)
|
virtual void apply(osg::Geometry&)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
int mEffectId;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class RemoveCallbackVisitor : public RemoveVisitor
|
class RemoveCallbackVisitor : public RemoveVisitor
|
||||||
|
@ -1748,21 +1739,29 @@ namespace MWRender
|
||||||
|
|
||||||
if (alpha != 1.f)
|
if (alpha != 1.f)
|
||||||
{
|
{
|
||||||
osg::StateSet* stateset (new osg::StateSet);
|
// If we have an existing material for alpha transparency, just override alpha level
|
||||||
|
osg::StateSet* stateset = mObjectRoot->getOrCreateStateSet();
|
||||||
|
osg::Material* material = static_cast<osg::Material*>(stateset->getAttribute(osg::StateAttribute::MATERIAL));
|
||||||
|
if (material)
|
||||||
|
{
|
||||||
|
material->setAlpha(osg::Material::FRONT_AND_BACK, alpha);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
osg::BlendFunc* blendfunc (new osg::BlendFunc);
|
||||||
|
stateset->setAttributeAndModes(blendfunc, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
|
||||||
|
|
||||||
osg::BlendFunc* blendfunc (new osg::BlendFunc);
|
// FIXME: overriding diffuse/ambient/emissive colors
|
||||||
stateset->setAttributeAndModes(blendfunc, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
|
material = new osg::Material;
|
||||||
|
material->setColorMode(osg::Material::OFF);
|
||||||
|
material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,alpha));
|
||||||
|
material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1));
|
||||||
|
stateset->setAttributeAndModes(material, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
|
||||||
|
|
||||||
// FIXME: overriding diffuse/ambient/emissive colors
|
mObjectRoot->setStateSet(stateset);
|
||||||
osg::Material* material (new osg::Material);
|
|
||||||
material->setColorMode(osg::Material::OFF);
|
|
||||||
material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,alpha));
|
|
||||||
material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1));
|
|
||||||
stateset->setAttributeAndModes(material, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
|
|
||||||
|
|
||||||
mObjectRoot->setStateSet(stateset);
|
mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot);
|
||||||
|
}
|
||||||
mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1798,9 +1797,12 @@ namespace MWRender
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
effect += 3;
|
// TODO: use global attenuation settings
|
||||||
float radius = effect * 66.f;
|
|
||||||
float linearAttenuation = 0.5f / effect;
|
// 1 pt of Light magnitude corresponds to 1 foot of radius
|
||||||
|
float radius = effect * std::ceil(Constants::UnitsPerFoot);
|
||||||
|
const float linearValue = 3.f; // Currently hardcoded: unmodified Morrowind attenuation settings
|
||||||
|
float linearAttenuation = linearValue / radius;
|
||||||
|
|
||||||
if (!mGlowLight || linearAttenuation != mGlowLight->getLight(0)->getLinearAttenuation())
|
if (!mGlowLight || linearAttenuation != mGlowLight->getLight(0)->getLinearAttenuation())
|
||||||
{
|
{
|
||||||
|
@ -1822,7 +1824,8 @@ namespace MWRender
|
||||||
mGlowLight->setLight(light);
|
mGlowLight->setLight(light);
|
||||||
}
|
}
|
||||||
|
|
||||||
mGlowLight->setRadius(radius);
|
// Make the obvious cut-off a bit less obvious
|
||||||
|
mGlowLight->setRadius(radius * 3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,12 @@ CreatureWeaponAnimation::CreatureWeaponAnimation(const MWWorld::Ptr &ptr, const
|
||||||
setObjectRoot(model, true, false, true);
|
setObjectRoot(model, true, false, true);
|
||||||
|
|
||||||
if((ref->mBase->mFlags&ESM::Creature::Bipedal))
|
if((ref->mBase->mFlags&ESM::Creature::Bipedal))
|
||||||
|
{
|
||||||
|
if (mWeaponSheathing)
|
||||||
|
injectWeaponBones();
|
||||||
|
|
||||||
addAnimSource("meshes\\xbase_anim.nif", model);
|
addAnimSource("meshes\\xbase_anim.nif", model);
|
||||||
|
}
|
||||||
addAnimSource(model, model);
|
addAnimSource(model, model);
|
||||||
|
|
||||||
mPtr.getClass().getInventoryStore(mPtr).setInvListener(this, mPtr);
|
mPtr.getClass().getInventoryStore(mPtr).setInvListener(this, mPtr);
|
||||||
|
@ -84,6 +89,9 @@ void CreatureWeaponAnimation::updateParts()
|
||||||
mWeapon.reset();
|
mWeapon.reset();
|
||||||
mShield.reset();
|
mShield.reset();
|
||||||
|
|
||||||
|
updateHolsteredWeapon(!mShowWeapons);
|
||||||
|
updateQuiver();
|
||||||
|
|
||||||
if (mShowWeapons)
|
if (mShowWeapons)
|
||||||
updatePart(mWeapon, MWWorld::InventoryStore::Slot_CarriedRight);
|
updatePart(mWeapon, MWWorld::InventoryStore::Slot_CarriedRight);
|
||||||
if (mShowCarriedLeft)
|
if (mShowCarriedLeft)
|
||||||
|
@ -157,14 +165,21 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CreatureWeaponAnimation::isArrowAttached() const
|
||||||
|
{
|
||||||
|
return mAmmunition != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
void CreatureWeaponAnimation::attachArrow()
|
void CreatureWeaponAnimation::attachArrow()
|
||||||
{
|
{
|
||||||
WeaponAnimation::attachArrow(mPtr);
|
WeaponAnimation::attachArrow(mPtr);
|
||||||
|
updateQuiver();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CreatureWeaponAnimation::releaseArrow(float attackStrength)
|
void CreatureWeaponAnimation::releaseArrow(float attackStrength)
|
||||||
{
|
{
|
||||||
WeaponAnimation::releaseArrow(mPtr, attackStrength);
|
WeaponAnimation::releaseArrow(mPtr, attackStrength);
|
||||||
|
updateQuiver();
|
||||||
}
|
}
|
||||||
|
|
||||||
osg::Group *CreatureWeaponAnimation::getArrowBone()
|
osg::Group *CreatureWeaponAnimation::getArrowBone()
|
||||||
|
|
|
@ -54,6 +54,8 @@ namespace MWRender
|
||||||
/// to indicate the facing orientation of the character.
|
/// to indicate the facing orientation of the character.
|
||||||
virtual void setPitchFactor(float factor) { mPitchFactor = factor; }
|
virtual void setPitchFactor(float factor) { mPitchFactor = factor; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual bool isArrowAttached() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PartHolderPtr mWeapon;
|
PartHolderPtr mWeapon;
|
||||||
|
|
71
apps/openmw/mwrender/navmesh.cpp
Normal file
71
apps/openmw/mwrender/navmesh.cpp
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
#include "navmesh.hpp"
|
||||||
|
#include "vismask.hpp"
|
||||||
|
|
||||||
|
#include <components/sceneutil/navmesh.hpp>
|
||||||
|
|
||||||
|
#include <osg/PositionAttitudeTransform>
|
||||||
|
|
||||||
|
namespace MWRender
|
||||||
|
{
|
||||||
|
NavMesh::NavMesh(const osg::ref_ptr<osg::Group>& root, bool enabled)
|
||||||
|
: mRootNode(root)
|
||||||
|
, mEnabled(enabled)
|
||||||
|
, mGeneration(0)
|
||||||
|
, mRevision(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
NavMesh::~NavMesh()
|
||||||
|
{
|
||||||
|
if (mEnabled)
|
||||||
|
disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NavMesh::toggle()
|
||||||
|
{
|
||||||
|
if (mEnabled)
|
||||||
|
disable();
|
||||||
|
else
|
||||||
|
enable();
|
||||||
|
|
||||||
|
return mEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavMesh::update(const dtNavMesh& navMesh, const std::size_t id,
|
||||||
|
const std::size_t generation, const std::size_t revision, const DetourNavigator::Settings& settings)
|
||||||
|
{
|
||||||
|
if (!mEnabled || (mId == id && mGeneration >= generation && mRevision >= revision))
|
||||||
|
return;
|
||||||
|
|
||||||
|
mId = id;
|
||||||
|
mGeneration = generation;
|
||||||
|
mRevision = revision;
|
||||||
|
if (mGroup)
|
||||||
|
mRootNode->removeChild(mGroup);
|
||||||
|
mGroup = SceneUtil::createNavMeshGroup(navMesh, settings);
|
||||||
|
if (mGroup)
|
||||||
|
{
|
||||||
|
mGroup->setNodeMask(Mask_Debug);
|
||||||
|
mRootNode->addChild(mGroup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavMesh::reset()
|
||||||
|
{
|
||||||
|
if (mGroup)
|
||||||
|
mRootNode->removeChild(mGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavMesh::enable()
|
||||||
|
{
|
||||||
|
if (mGroup)
|
||||||
|
mRootNode->addChild(mGroup);
|
||||||
|
mEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavMesh::disable()
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
mEnabled = false;
|
||||||
|
}
|
||||||
|
}
|
43
apps/openmw/mwrender/navmesh.hpp
Normal file
43
apps/openmw/mwrender/navmesh.hpp
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
#ifndef OPENMW_MWRENDER_NAVMESH_H
|
||||||
|
#define OPENMW_MWRENDER_NAVMESH_H
|
||||||
|
|
||||||
|
#include <components/detournavigator/navigator.hpp>
|
||||||
|
|
||||||
|
#include <osg/ref_ptr>
|
||||||
|
|
||||||
|
namespace osg
|
||||||
|
{
|
||||||
|
class Group;
|
||||||
|
class Geometry;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace MWRender
|
||||||
|
{
|
||||||
|
class NavMesh
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
NavMesh(const osg::ref_ptr<osg::Group>& root, bool enabled);
|
||||||
|
~NavMesh();
|
||||||
|
|
||||||
|
bool toggle();
|
||||||
|
|
||||||
|
void update(const dtNavMesh& navMesh, const std::size_t number, const std::size_t generation,
|
||||||
|
const std::size_t revision, const DetourNavigator::Settings& settings);
|
||||||
|
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
void enable();
|
||||||
|
|
||||||
|
void disable();
|
||||||
|
|
||||||
|
private:
|
||||||
|
osg::ref_ptr<osg::Group> mRootNode;
|
||||||
|
bool mEnabled;
|
||||||
|
std::size_t mId = std::numeric_limits<std::size_t>::max();
|
||||||
|
std::size_t mGeneration;
|
||||||
|
std::size_t mRevision;
|
||||||
|
osg::ref_ptr<osg::Group> mGroup;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -21,6 +21,8 @@
|
||||||
#include <components/sceneutil/skeleton.hpp>
|
#include <components/sceneutil/skeleton.hpp>
|
||||||
#include <components/sceneutil/lightmanager.hpp>
|
#include <components/sceneutil/lightmanager.hpp>
|
||||||
|
|
||||||
|
#include <components/settings/settings.hpp>
|
||||||
|
|
||||||
#include <components/nifosg/nifloader.hpp> // TextKeyMapHolder
|
#include <components/nifosg/nifloader.hpp> // TextKeyMapHolder
|
||||||
|
|
||||||
#include "../mwworld/esmstore.hpp"
|
#include "../mwworld/esmstore.hpp"
|
||||||
|
@ -308,6 +310,12 @@ void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode)
|
||||||
if(mViewMode == viewMode)
|
if(mViewMode == viewMode)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// Disable weapon sheathing in the 1st-person mode
|
||||||
|
if (viewMode == VM_FirstPerson)
|
||||||
|
mWeaponSheathing = false;
|
||||||
|
else
|
||||||
|
mWeaponSheathing = Settings::Manager::getBool("weapon sheathing", "Game");
|
||||||
|
|
||||||
mViewMode = viewMode;
|
mViewMode = viewMode;
|
||||||
rebuild();
|
rebuild();
|
||||||
|
|
||||||
|
@ -389,6 +397,7 @@ void NpcAnimation::setRenderBin()
|
||||||
|
|
||||||
void NpcAnimation::rebuild()
|
void NpcAnimation::rebuild()
|
||||||
{
|
{
|
||||||
|
mScabbard.reset();
|
||||||
updateNpcBase();
|
updateNpcBase();
|
||||||
|
|
||||||
MWBase::Environment::get().getMechanicsManager()->forceStateUpdate(mPtr);
|
MWBase::Environment::get().getMechanicsManager()->forceStateUpdate(mPtr);
|
||||||
|
@ -460,6 +469,11 @@ void NpcAnimation::updateNpcBase()
|
||||||
|
|
||||||
setObjectRoot(smodel, true, true, false);
|
setObjectRoot(smodel, true, true, false);
|
||||||
|
|
||||||
|
if (mWeaponSheathing)
|
||||||
|
injectWeaponBones();
|
||||||
|
|
||||||
|
updateParts();
|
||||||
|
|
||||||
if(!is1stPerson)
|
if(!is1stPerson)
|
||||||
{
|
{
|
||||||
const std::string base = "meshes\\xbase_anim.nif";
|
const std::string base = "meshes\\xbase_anim.nif";
|
||||||
|
@ -488,8 +502,6 @@ void NpcAnimation::updateNpcBase()
|
||||||
mObjectRoot->addCullCallback(new OverrideFieldOfViewCallback(mFirstPersonFieldOfView));
|
mObjectRoot->addCullCallback(new OverrideFieldOfViewCallback(mFirstPersonFieldOfView));
|
||||||
}
|
}
|
||||||
|
|
||||||
updateParts();
|
|
||||||
|
|
||||||
mWeaponAnimationTime->updateStartTime();
|
mWeaponAnimationTime->updateStartTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -899,7 +911,8 @@ void NpcAnimation::showWeapons(bool showWeapon)
|
||||||
attachArrow();
|
attachArrow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (mAlpha != 1.f)
|
// Note: we will need to recreate shaders later if we use weapon sheathing anyway, so there is no point to update them here
|
||||||
|
if (mAlpha != 1.f && !mWeaponSheathing)
|
||||||
mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot);
|
mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -909,6 +922,9 @@ void NpcAnimation::showWeapons(bool showWeapon)
|
||||||
if (mPtr == MWMechanics::getPlayer())
|
if (mPtr == MWMechanics::getPlayer())
|
||||||
MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false);
|
MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateHolsteredWeapon(!mShowWeapons);
|
||||||
|
updateQuiver();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NpcAnimation::showCarriedLeft(bool show)
|
void NpcAnimation::showCarriedLeft(bool show)
|
||||||
|
@ -936,11 +952,13 @@ void NpcAnimation::showCarriedLeft(bool show)
|
||||||
void NpcAnimation::attachArrow()
|
void NpcAnimation::attachArrow()
|
||||||
{
|
{
|
||||||
WeaponAnimation::attachArrow(mPtr);
|
WeaponAnimation::attachArrow(mPtr);
|
||||||
|
updateQuiver();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NpcAnimation::releaseArrow(float attackStrength)
|
void NpcAnimation::releaseArrow(float attackStrength)
|
||||||
{
|
{
|
||||||
WeaponAnimation::releaseArrow(mPtr, attackStrength);
|
WeaponAnimation::releaseArrow(mPtr, attackStrength);
|
||||||
|
updateQuiver();
|
||||||
}
|
}
|
||||||
|
|
||||||
osg::Group* NpcAnimation::getArrowBone()
|
osg::Group* NpcAnimation::getArrowBone()
|
||||||
|
@ -1185,4 +1203,9 @@ void NpcAnimation::setAccurateAiming(bool enabled)
|
||||||
mAccurateAiming = enabled;
|
mAccurateAiming = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool NpcAnimation::isArrowAttached() const
|
||||||
|
{
|
||||||
|
return mAmmunition != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,6 +95,7 @@ private:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void addControllers();
|
virtual void addControllers();
|
||||||
|
virtual bool isArrowAttached() const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -46,6 +46,8 @@
|
||||||
#include <components/esm/loadcell.hpp>
|
#include <components/esm/loadcell.hpp>
|
||||||
#include <components/fallback/fallback.hpp>
|
#include <components/fallback/fallback.hpp>
|
||||||
|
|
||||||
|
#include <components/detournavigator/navigator.hpp>
|
||||||
|
|
||||||
#include <boost/algorithm/string.hpp>
|
#include <boost/algorithm/string.hpp>
|
||||||
|
|
||||||
#include "../mwworld/cellstore.hpp"
|
#include "../mwworld/cellstore.hpp"
|
||||||
|
@ -63,6 +65,8 @@
|
||||||
#include "water.hpp"
|
#include "water.hpp"
|
||||||
#include "terrainstorage.hpp"
|
#include "terrainstorage.hpp"
|
||||||
#include "util.hpp"
|
#include "util.hpp"
|
||||||
|
#include "navmesh.hpp"
|
||||||
|
#include "actorspaths.hpp"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
@ -189,13 +193,16 @@ namespace MWRender
|
||||||
Resource::ResourceSystem* mResourceSystem;
|
Resource::ResourceSystem* mResourceSystem;
|
||||||
};
|
};
|
||||||
|
|
||||||
RenderingManager::RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue,
|
RenderingManager::RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode,
|
||||||
const Fallback::Map* fallback, const std::string& resourcePath)
|
Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue,
|
||||||
|
const Fallback::Map* fallback, const std::string& resourcePath,
|
||||||
|
DetourNavigator::Navigator& navigator)
|
||||||
: mViewer(viewer)
|
: mViewer(viewer)
|
||||||
, mRootNode(rootNode)
|
, mRootNode(rootNode)
|
||||||
, mResourceSystem(resourceSystem)
|
, mResourceSystem(resourceSystem)
|
||||||
, mWorkQueue(workQueue)
|
, mWorkQueue(workQueue)
|
||||||
, mUnrefQueue(new SceneUtil::UnrefQueue)
|
, mUnrefQueue(new SceneUtil::UnrefQueue)
|
||||||
|
, mNavigator(navigator)
|
||||||
, mLandFogStart(0.f)
|
, mLandFogStart(0.f)
|
||||||
, mLandFogEnd(std::numeric_limits<float>::max())
|
, mLandFogEnd(std::numeric_limits<float>::max())
|
||||||
, mUnderwaterFogStart(0.f)
|
, mUnderwaterFogStart(0.f)
|
||||||
|
@ -228,6 +235,8 @@ namespace MWRender
|
||||||
|
|
||||||
mRootNode->addChild(mSceneRoot);
|
mRootNode->addChild(mSceneRoot);
|
||||||
|
|
||||||
|
mNavMesh.reset(new NavMesh(mRootNode, Settings::Manager::getBool("enable nav mesh render", "Navigator")));
|
||||||
|
mActorsPaths.reset(new ActorsPaths(mRootNode, Settings::Manager::getBool("enable agents paths render", "Navigator")));
|
||||||
mPathgrid.reset(new Pathgrid(mRootNode));
|
mPathgrid.reset(new Pathgrid(mRootNode));
|
||||||
|
|
||||||
mObjects.reset(new Objects(mResourceSystem, sceneRoot, mUnrefQueue.get()));
|
mObjects.reset(new Objects(mResourceSystem, sceneRoot, mUnrefQueue.get()));
|
||||||
|
@ -465,6 +474,7 @@ namespace MWRender
|
||||||
void RenderingManager::removeCell(const MWWorld::CellStore *store)
|
void RenderingManager::removeCell(const MWWorld::CellStore *store)
|
||||||
{
|
{
|
||||||
mPathgrid->removeCell(store);
|
mPathgrid->removeCell(store);
|
||||||
|
mActorsPaths->removeCell(store);
|
||||||
mObjects->removeCell(store);
|
mObjects->removeCell(store);
|
||||||
|
|
||||||
if (store->getCell()->isExterior())
|
if (store->getCell()->isExterior())
|
||||||
|
@ -482,7 +492,7 @@ namespace MWRender
|
||||||
{
|
{
|
||||||
mSky->setEnabled(enabled);
|
mSky->setEnabled(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RenderingManager::toggleBorders()
|
bool RenderingManager::toggleBorders()
|
||||||
{
|
{
|
||||||
mBorders = !mBorders;
|
mBorders = !mBorders;
|
||||||
|
@ -516,6 +526,14 @@ namespace MWRender
|
||||||
mViewer->getCamera()->setCullMask(mask);
|
mViewer->getCamera()->setCullMask(mask);
|
||||||
return enabled;
|
return enabled;
|
||||||
}
|
}
|
||||||
|
else if (mode == Render_NavMesh)
|
||||||
|
{
|
||||||
|
return mNavMesh->toggle();
|
||||||
|
}
|
||||||
|
else if (mode == Render_ActorsPaths)
|
||||||
|
{
|
||||||
|
return mActorsPaths->toggle();
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -581,6 +599,28 @@ namespace MWRender
|
||||||
mWater->update(dt);
|
mWater->update(dt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto navMeshes = mNavigator.getNavMeshes();
|
||||||
|
|
||||||
|
auto it = navMeshes.begin();
|
||||||
|
for (std::size_t i = 0; it != navMeshes.end() && i < mNavMeshNumber; ++i)
|
||||||
|
++it;
|
||||||
|
if (it == navMeshes.end())
|
||||||
|
{
|
||||||
|
mNavMesh->reset();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const auto locked = it->second.lockConst();
|
||||||
|
mNavMesh->update(locked->getValue(), mNavMeshNumber, locked->getGeneration(),
|
||||||
|
locked->getNavMeshRevision(), mNavigator.getSettings());
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
Log(Debug::Error) << "NavMesh render update exception: " << e.what();
|
||||||
|
}
|
||||||
|
}
|
||||||
mCamera->update(dt, paused);
|
mCamera->update(dt, paused);
|
||||||
|
|
||||||
osg::Vec3f focal, cameraPos;
|
osg::Vec3f focal, cameraPos;
|
||||||
|
@ -642,6 +682,7 @@ namespace MWRender
|
||||||
|
|
||||||
void RenderingManager::removeObject(const MWWorld::Ptr &ptr)
|
void RenderingManager::removeObject(const MWWorld::Ptr &ptr)
|
||||||
{
|
{
|
||||||
|
mActorsPaths->remove(ptr);
|
||||||
mObjects->removeObject(ptr);
|
mObjects->removeObject(ptr);
|
||||||
mWater->removeEmitter(ptr);
|
mWater->removeEmitter(ptr);
|
||||||
}
|
}
|
||||||
|
@ -749,8 +790,8 @@ namespace MWRender
|
||||||
|
|
||||||
osg::Vec3 directions[6] = {
|
osg::Vec3 directions[6] = {
|
||||||
rawCubemap ? osg::Vec3(1,0,0) : osg::Vec3(0,0,1),
|
rawCubemap ? osg::Vec3(1,0,0) : osg::Vec3(0,0,1),
|
||||||
osg::Vec3(0,0,-1),
|
osg::Vec3(0,0,-1),
|
||||||
osg::Vec3(-1,0,0),
|
osg::Vec3(-1,0,0),
|
||||||
rawCubemap ? osg::Vec3(0,0,1) : osg::Vec3(1,0,0),
|
rawCubemap ? osg::Vec3(0,0,1) : osg::Vec3(1,0,0),
|
||||||
osg::Vec3(0,1,0),
|
osg::Vec3(0,1,0),
|
||||||
osg::Vec3(0,-1,0)};
|
osg::Vec3(0,-1,0)};
|
||||||
|
@ -789,7 +830,7 @@ namespace MWRender
|
||||||
mFieldOfView = fovBackup;
|
mFieldOfView = fovBackup;
|
||||||
|
|
||||||
if (rawCubemap) // for raw cubemap don't run on GPU, just merge the images
|
if (rawCubemap) // for raw cubemap don't run on GPU, just merge the images
|
||||||
{
|
{
|
||||||
image->allocateImage(cubeSize * 6,cubeSize,images[0]->r(),images[0]->getPixelFormat(),images[0]->getDataType());
|
image->allocateImage(cubeSize * 6,cubeSize,images[0]->r(),images[0]->getPixelFormat(),images[0]->getDataType());
|
||||||
|
|
||||||
for (int i = 0; i < 6; ++i)
|
for (int i = 0; i < 6; ++i)
|
||||||
|
@ -797,7 +838,7 @@ namespace MWRender
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// run on GPU now:
|
// run on GPU now:
|
||||||
|
|
||||||
osg::ref_ptr<osg::TextureCubeMap> cubeTexture (new osg::TextureCubeMap);
|
osg::ref_ptr<osg::TextureCubeMap> cubeTexture (new osg::TextureCubeMap);
|
||||||
|
@ -1025,6 +1066,7 @@ namespace MWRender
|
||||||
void RenderingManager::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated)
|
void RenderingManager::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated)
|
||||||
{
|
{
|
||||||
mObjects->updatePtr(old, updated);
|
mObjects->updatePtr(old, updated);
|
||||||
|
mActorsPaths->updatePtr(old, updated);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderingManager::spawnEffect(const std::string &model, const std::string &texture, const osg::Vec3f &worldPosition, float scale, bool isMagicVFX)
|
void RenderingManager::spawnEffect(const std::string &model, const std::string &texture, const osg::Vec3f &worldPosition, float scale, bool isMagicVFX)
|
||||||
|
@ -1345,5 +1387,19 @@ namespace MWRender
|
||||||
return mTerrainStorage->getLandManager();
|
return mTerrainStorage->getLandManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RenderingManager::updateActorPath(const MWWorld::ConstPtr& actor, const std::deque<osg::Vec3f>& path,
|
||||||
|
const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end) const
|
||||||
|
{
|
||||||
|
mActorsPaths->update(actor, path, halfExtents, start, end, mNavigator.getSettings());
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderingManager::removeActorPath(const MWWorld::ConstPtr& actor) const
|
||||||
|
{
|
||||||
|
mActorsPaths->remove(actor);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderingManager::setNavMeshNumber(const std::size_t value)
|
||||||
|
{
|
||||||
|
mNavMeshNumber = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
#include "renderinginterface.hpp"
|
#include "renderinginterface.hpp"
|
||||||
#include "rendermode.hpp"
|
#include "rendermode.hpp"
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
|
|
||||||
namespace osg
|
namespace osg
|
||||||
{
|
{
|
||||||
class Group;
|
class Group;
|
||||||
|
@ -55,6 +57,12 @@ namespace SceneUtil
|
||||||
class UnrefQueue;
|
class UnrefQueue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace DetourNavigator
|
||||||
|
{
|
||||||
|
class Navigator;
|
||||||
|
struct Settings;
|
||||||
|
}
|
||||||
|
|
||||||
namespace MWRender
|
namespace MWRender
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -68,12 +76,16 @@ namespace MWRender
|
||||||
class Water;
|
class Water;
|
||||||
class TerrainStorage;
|
class TerrainStorage;
|
||||||
class LandManager;
|
class LandManager;
|
||||||
|
class NavMesh;
|
||||||
|
class ActorsPaths;
|
||||||
|
|
||||||
class RenderingManager : public MWRender::RenderingInterface
|
class RenderingManager : public MWRender::RenderingInterface
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue,
|
RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode,
|
||||||
const Fallback::Map* fallback, const std::string& resourcePath);
|
Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue,
|
||||||
|
const Fallback::Map* fallback, const std::string& resourcePath,
|
||||||
|
DetourNavigator::Navigator& navigator);
|
||||||
~RenderingManager();
|
~RenderingManager();
|
||||||
|
|
||||||
MWRender::Objects& getObjects();
|
MWRender::Objects& getObjects();
|
||||||
|
@ -211,6 +223,13 @@ namespace MWRender
|
||||||
|
|
||||||
bool toggleBorders();
|
bool toggleBorders();
|
||||||
|
|
||||||
|
void updateActorPath(const MWWorld::ConstPtr& actor, const std::deque<osg::Vec3f>& path,
|
||||||
|
const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end) const;
|
||||||
|
|
||||||
|
void removeActorPath(const MWWorld::ConstPtr& actor) const;
|
||||||
|
|
||||||
|
void setNavMeshNumber(const std::size_t value);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void updateProjectionMatrix();
|
void updateProjectionMatrix();
|
||||||
void updateTextureFiltering();
|
void updateTextureFiltering();
|
||||||
|
@ -235,6 +254,10 @@ namespace MWRender
|
||||||
|
|
||||||
osg::ref_ptr<osg::Light> mSunLight;
|
osg::ref_ptr<osg::Light> mSunLight;
|
||||||
|
|
||||||
|
DetourNavigator::Navigator& mNavigator;
|
||||||
|
std::unique_ptr<NavMesh> mNavMesh;
|
||||||
|
std::size_t mNavMeshNumber = 0;
|
||||||
|
std::unique_ptr<ActorsPaths> mActorsPaths;
|
||||||
std::unique_ptr<Pathgrid> mPathgrid;
|
std::unique_ptr<Pathgrid> mPathgrid;
|
||||||
std::unique_ptr<Objects> mObjects;
|
std::unique_ptr<Objects> mObjects;
|
||||||
std::unique_ptr<Water> mWater;
|
std::unique_ptr<Water> mWater;
|
||||||
|
|
|
@ -10,7 +10,9 @@ namespace MWRender
|
||||||
Render_Wireframe,
|
Render_Wireframe,
|
||||||
Render_Pathgrid,
|
Render_Pathgrid,
|
||||||
Render_Water,
|
Render_Water,
|
||||||
Render_Scene
|
Render_Scene,
|
||||||
|
Render_NavMesh,
|
||||||
|
Render_ActorsPaths,
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -401,11 +401,15 @@ public:
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
|
bool isUnderwater()
|
||||||
{
|
{
|
||||||
osg::Vec3f eyePoint = mCameraRelativeTransform->getLastEyePoint();
|
osg::Vec3f eyePoint = mCameraRelativeTransform->getLastEyePoint();
|
||||||
|
return mEnabled && eyePoint.z() < mWaterLevel;
|
||||||
|
}
|
||||||
|
|
||||||
if (mEnabled && eyePoint.z() < mWaterLevel)
|
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
|
||||||
|
{
|
||||||
|
if (isUnderwater())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
traverse(node, nv);
|
traverse(node, nv);
|
||||||
|
@ -1575,6 +1579,8 @@ void SkyManager::update(float duration)
|
||||||
mRainIntensityUniform->set((float) mWeatherAlpha);
|
mRainIntensityUniform->set((float) mWeatherAlpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switchUnderwaterRain();
|
||||||
|
|
||||||
if (mIsStorm)
|
if (mIsStorm)
|
||||||
{
|
{
|
||||||
osg::Quat quat;
|
osg::Quat quat;
|
||||||
|
@ -1626,6 +1632,15 @@ void SkyManager::updateRainParameters()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SkyManager::switchUnderwaterRain()
|
||||||
|
{
|
||||||
|
if (!mRainParticleSystem)
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool freeze = mUnderwaterSwitch->isUnderwater();
|
||||||
|
mRainParticleSystem->setFrozen(freeze);
|
||||||
|
}
|
||||||
|
|
||||||
void SkyManager::setWeather(const WeatherResult& weather)
|
void SkyManager::setWeather(const WeatherResult& weather)
|
||||||
{
|
{
|
||||||
if (!mCreated) return;
|
if (!mCreated) return;
|
||||||
|
|
|
@ -180,6 +180,7 @@ namespace MWRender
|
||||||
|
|
||||||
void createRain();
|
void createRain();
|
||||||
void destroyRain();
|
void destroyRain();
|
||||||
|
void switchUnderwaterRain();
|
||||||
void updateRainParameters();
|
void updateRainParameters();
|
||||||
|
|
||||||
Resource::SceneManager* mSceneManager;
|
Resource::SceneManager* mSceneManager;
|
||||||
|
|
|
@ -455,5 +455,8 @@ op 0x2000304: Show
|
||||||
op 0x2000305: Show, explicit
|
op 0x2000305: Show, explicit
|
||||||
op 0x2000306: OnActivate, explicit
|
op 0x2000306: OnActivate, explicit
|
||||||
op 0x2000307: ToggleBorders, tb
|
op 0x2000307: ToggleBorders, tb
|
||||||
|
op 0x2000308: ToggleNavMesh
|
||||||
|
op 0x2000309: ToggleActorsPaths
|
||||||
|
op 0x200030a: SetNavMeshNumber
|
||||||
|
|
||||||
opcodes 0x2000308-0x3ffffff unused
|
opcodes 0x200030b-0x3ffffff unused
|
||||||
|
|
|
@ -1317,6 +1317,53 @@ namespace MWScript
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class OpToggleNavMesh : public Interpreter::Opcode0
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
virtual void execute (Interpreter::Runtime& runtime)
|
||||||
|
{
|
||||||
|
bool enabled =
|
||||||
|
MWBase::Environment::get().getWorld()->toggleRenderMode (MWRender::Render_NavMesh);
|
||||||
|
|
||||||
|
runtime.getContext().report (enabled ?
|
||||||
|
"Navigation Mesh Rendering -> On" : "Navigation Mesh Rendering -> Off");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class OpToggleActorsPaths : public Interpreter::Opcode0
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
virtual void execute (Interpreter::Runtime& runtime)
|
||||||
|
{
|
||||||
|
bool enabled =
|
||||||
|
MWBase::Environment::get().getWorld()->toggleRenderMode (MWRender::Render_ActorsPaths);
|
||||||
|
|
||||||
|
runtime.getContext().report (enabled ?
|
||||||
|
"Agents Paths Rendering -> On" : "Agents Paths Rendering -> Off");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class OpSetNavMeshNumberToRender : public Interpreter::Opcode0
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
virtual void execute (Interpreter::Runtime& runtime)
|
||||||
|
{
|
||||||
|
const auto navMeshNumber = runtime[0].mInteger;
|
||||||
|
runtime.pop();
|
||||||
|
|
||||||
|
if (navMeshNumber < 0)
|
||||||
|
{
|
||||||
|
runtime.getContext().report("Invalid navmesh number: use not less than zero values");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MWBase::Environment::get().getWorld()->setNavMeshNumberToRender(static_cast<std::size_t>(navMeshNumber));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
void installOpcodes (Interpreter::Interpreter& interpreter)
|
void installOpcodes (Interpreter::Interpreter& interpreter)
|
||||||
{
|
{
|
||||||
interpreter.installSegment5 (Compiler::Misc::opcodeXBox, new OpXBox);
|
interpreter.installSegment5 (Compiler::Misc::opcodeXBox, new OpXBox);
|
||||||
|
@ -1417,6 +1464,9 @@ namespace MWScript
|
||||||
interpreter.installSegment3 (Compiler::Misc::opcodeShowSceneGraph, new OpShowSceneGraph<ImplicitRef>);
|
interpreter.installSegment3 (Compiler::Misc::opcodeShowSceneGraph, new OpShowSceneGraph<ImplicitRef>);
|
||||||
interpreter.installSegment3 (Compiler::Misc::opcodeShowSceneGraphExplicit, new OpShowSceneGraph<ExplicitRef>);
|
interpreter.installSegment3 (Compiler::Misc::opcodeShowSceneGraphExplicit, new OpShowSceneGraph<ExplicitRef>);
|
||||||
interpreter.installSegment5 (Compiler::Misc::opcodeToggleBorders, new OpToggleBorders);
|
interpreter.installSegment5 (Compiler::Misc::opcodeToggleBorders, new OpToggleBorders);
|
||||||
|
interpreter.installSegment5 (Compiler::Misc::opcodeToggleNavMesh, new OpToggleNavMesh);
|
||||||
|
interpreter.installSegment5 (Compiler::Misc::opcodeToggleActorsPaths, new OpToggleActorsPaths);
|
||||||
|
interpreter.installSegment5 (Compiler::Misc::opcodeSetNavMeshNumberToRender, new OpSetNavMeshNumberToRender);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -738,8 +738,7 @@ namespace MWScript
|
||||||
|
|
||||||
virtual void execute (Interpreter::Runtime& runtime)
|
virtual void execute (Interpreter::Runtime& runtime)
|
||||||
{
|
{
|
||||||
const MWWorld::Ptr ptr = MWMechanics::getPlayer();
|
MWBase::Environment::get().getWorld()->fixPosition();
|
||||||
MWBase::Environment::get().getWorld()->fixPosition(ptr);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -82,7 +82,7 @@ bool FFmpeg_Decoder::getNextPacket()
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Free the packet and look for another */
|
/* Free the packet and look for another */
|
||||||
av_free_packet(&mPacket);
|
av_packet_unref(&mPacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -90,9 +90,9 @@ bool FFmpeg_Decoder::getNextPacket()
|
||||||
|
|
||||||
bool FFmpeg_Decoder::getAVAudioData()
|
bool FFmpeg_Decoder::getAVAudioData()
|
||||||
{
|
{
|
||||||
int got_frame;
|
bool got_frame;
|
||||||
|
|
||||||
if((*mStream)->codec->codec_type != AVMEDIA_TYPE_AUDIO)
|
if(mCodecCtx->codec_type != AVMEDIA_TYPE_AUDIO)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
@ -100,19 +100,18 @@ bool FFmpeg_Decoder::getAVAudioData()
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
/* Decode some data, and check for errors */
|
/* Decode some data, and check for errors */
|
||||||
int len = 0;
|
int ret = 0;
|
||||||
if((len=avcodec_decode_audio4((*mStream)->codec, mFrame, &got_frame, &mPacket)) < 0)
|
ret = avcodec_receive_frame(mCodecCtx, mFrame);
|
||||||
|
if (ret == 0)
|
||||||
|
got_frame = true;
|
||||||
|
if (ret == AVERROR(EAGAIN))
|
||||||
|
ret = 0;
|
||||||
|
if (ret == 0)
|
||||||
|
ret = avcodec_send_packet(mCodecCtx, &mPacket);
|
||||||
|
if (ret < 0 && ret != AVERROR(EAGAIN))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
/* Move the unread data to the front and clear the end bits */
|
av_packet_unref(&mPacket);
|
||||||
int remaining = mPacket.size - len;
|
|
||||||
if(remaining <= 0)
|
|
||||||
av_free_packet(&mPacket);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
memmove(mPacket.data, &mPacket.data[len], remaining);
|
|
||||||
av_shrink_packet(&mPacket, remaining);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!got_frame || mFrame->nb_samples == 0)
|
if (!got_frame || mFrame->nb_samples == 0)
|
||||||
continue;
|
continue;
|
||||||
|
@ -139,8 +138,8 @@ bool FFmpeg_Decoder::getAVAudioData()
|
||||||
else
|
else
|
||||||
mFrameData = &mFrame->data[0];
|
mFrameData = &mFrame->data[0];
|
||||||
|
|
||||||
} while(got_frame == 0 || mFrame->nb_samples == 0);
|
} while(!got_frame || mFrame->nb_samples == 0);
|
||||||
mNextPts += (double)mFrame->nb_samples / (double)(*mStream)->codec->sample_rate;
|
mNextPts += (double)mFrame->nb_samples / mCodecCtx->sample_rate;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -213,7 +212,7 @@ void FFmpeg_Decoder::open(const std::string &fname)
|
||||||
|
|
||||||
for(size_t j = 0;j < mFormatCtx->nb_streams;j++)
|
for(size_t j = 0;j < mFormatCtx->nb_streams;j++)
|
||||||
{
|
{
|
||||||
if(mFormatCtx->streams[j]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
|
if(mFormatCtx->streams[j]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
|
||||||
{
|
{
|
||||||
mStream = &mFormatCtx->streams[j];
|
mStream = &mFormatCtx->streams[j];
|
||||||
break;
|
break;
|
||||||
|
@ -222,39 +221,48 @@ void FFmpeg_Decoder::open(const std::string &fname)
|
||||||
if(!mStream)
|
if(!mStream)
|
||||||
throw std::runtime_error("No audio streams in "+fname);
|
throw std::runtime_error("No audio streams in "+fname);
|
||||||
|
|
||||||
(*mStream)->codec->request_sample_fmt = (*mStream)->codec->sample_fmt;
|
AVCodec *codec = avcodec_find_decoder((*mStream)->codecpar->codec_id);
|
||||||
|
|
||||||
AVCodec *codec = avcodec_find_decoder((*mStream)->codec->codec_id);
|
|
||||||
if(!codec)
|
if(!codec)
|
||||||
{
|
{
|
||||||
std::string ss = "No codec found for id " +
|
std::string ss = "No codec found for id " +
|
||||||
std::to_string((*mStream)->codec->codec_id);
|
std::to_string((*mStream)->codecpar->codec_id);
|
||||||
throw std::runtime_error(ss);
|
throw std::runtime_error(ss);
|
||||||
}
|
}
|
||||||
if(avcodec_open2((*mStream)->codec, codec, nullptr) < 0)
|
|
||||||
throw std::runtime_error(std::string("Failed to open audio codec ") +
|
AVCodecContext *avctx = avcodec_alloc_context3(codec);
|
||||||
codec->long_name);
|
avcodec_parameters_to_context(avctx, (*mStream)->codecpar);
|
||||||
|
|
||||||
|
// This is not needed anymore above FFMpeg version 4.0
|
||||||
|
#if LIBAVCODEC_VERSION_INT < 3805796
|
||||||
|
av_codec_set_pkt_timebase(avctx, (*mStream)->time_base);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
mCodecCtx = avctx;
|
||||||
|
|
||||||
|
if(avcodec_open2(mCodecCtx, codec, nullptr) < 0)
|
||||||
|
throw std::runtime_error(std::string("Failed to open audio codec ") + codec->long_name);
|
||||||
|
|
||||||
mFrame = av_frame_alloc();
|
mFrame = av_frame_alloc();
|
||||||
|
|
||||||
if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_FLT ||
|
if(mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLT || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLTP)
|
||||||
(*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_FLTP)
|
|
||||||
mOutputSampleFormat = AV_SAMPLE_FMT_S16; // FIXME: Check for AL_EXT_FLOAT32 support
|
mOutputSampleFormat = AV_SAMPLE_FMT_S16; // FIXME: Check for AL_EXT_FLOAT32 support
|
||||||
else if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_U8P)
|
else if(mCodecCtx->sample_fmt == AV_SAMPLE_FMT_U8P)
|
||||||
mOutputSampleFormat = AV_SAMPLE_FMT_U8;
|
mOutputSampleFormat = AV_SAMPLE_FMT_U8;
|
||||||
else if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_S16P)
|
else if(mCodecCtx->sample_fmt == AV_SAMPLE_FMT_S16P)
|
||||||
mOutputSampleFormat = AV_SAMPLE_FMT_S16;
|
mOutputSampleFormat = AV_SAMPLE_FMT_S16;
|
||||||
else
|
else
|
||||||
mOutputSampleFormat = AV_SAMPLE_FMT_S16;
|
mOutputSampleFormat = AV_SAMPLE_FMT_S16;
|
||||||
|
|
||||||
mOutputChannelLayout = (*mStream)->codec->channel_layout;
|
mOutputChannelLayout = (*mStream)->codecpar->channel_layout;
|
||||||
if(mOutputChannelLayout == 0)
|
if(mOutputChannelLayout == 0)
|
||||||
mOutputChannelLayout = av_get_default_channel_layout((*mStream)->codec->channels);
|
mOutputChannelLayout = av_get_default_channel_layout(mCodecCtx->channels);
|
||||||
|
|
||||||
|
mCodecCtx->channel_layout = mOutputChannelLayout;
|
||||||
}
|
}
|
||||||
catch(...)
|
catch(...)
|
||||||
{
|
{
|
||||||
if(mStream)
|
if(mStream)
|
||||||
avcodec_close((*mStream)->codec);
|
avcodec_free_context(&mCodecCtx);
|
||||||
mStream = nullptr;
|
mStream = nullptr;
|
||||||
|
|
||||||
if (mFormatCtx != nullptr)
|
if (mFormatCtx != nullptr)
|
||||||
|
@ -275,10 +283,10 @@ void FFmpeg_Decoder::open(const std::string &fname)
|
||||||
void FFmpeg_Decoder::close()
|
void FFmpeg_Decoder::close()
|
||||||
{
|
{
|
||||||
if(mStream)
|
if(mStream)
|
||||||
avcodec_close((*mStream)->codec);
|
avcodec_free_context(&mCodecCtx);
|
||||||
mStream = nullptr;
|
mStream = nullptr;
|
||||||
|
|
||||||
av_free_packet(&mPacket);
|
av_packet_unref(&mPacket);
|
||||||
av_freep(&mFrame);
|
av_freep(&mFrame);
|
||||||
swr_free(&mSwr);
|
swr_free(&mSwr);
|
||||||
av_freep(&mDataBuf);
|
av_freep(&mDataBuf);
|
||||||
|
@ -308,7 +316,12 @@ void FFmpeg_Decoder::close()
|
||||||
|
|
||||||
std::string FFmpeg_Decoder::getName()
|
std::string FFmpeg_Decoder::getName()
|
||||||
{
|
{
|
||||||
|
// In the FFMpeg 4.0 a "filename" field was replaced by "url"
|
||||||
|
#if LIBAVCODEC_VERSION_INT < 3805796
|
||||||
return mFormatCtx->filename;
|
return mFormatCtx->filename;
|
||||||
|
#else
|
||||||
|
return mFormatCtx->url;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type)
|
void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type)
|
||||||
|
@ -341,11 +354,10 @@ void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
char str[1024];
|
char str[1024];
|
||||||
av_get_channel_layout_string(str, sizeof(str), (*mStream)->codec->channels,
|
av_get_channel_layout_string(str, sizeof(str), mCodecCtx->channels, mCodecCtx->channel_layout);
|
||||||
(*mStream)->codec->channel_layout);
|
|
||||||
Log(Debug::Error) << "Unsupported channel layout: "<< str;
|
Log(Debug::Error) << "Unsupported channel layout: "<< str;
|
||||||
|
|
||||||
if((*mStream)->codec->channels == 1)
|
if(mCodecCtx->channels == 1)
|
||||||
{
|
{
|
||||||
mOutputChannelLayout = AV_CH_LAYOUT_MONO;
|
mOutputChannelLayout = AV_CH_LAYOUT_MONO;
|
||||||
*chans = ChannelConfig_Mono;
|
*chans = ChannelConfig_Mono;
|
||||||
|
@ -357,27 +369,28 @@ void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*samplerate = (*mStream)->codec->sample_rate;
|
*samplerate = mCodecCtx->sample_rate;
|
||||||
int64_t ch_layout = (*mStream)->codec->channel_layout;
|
int64_t ch_layout = mCodecCtx->channel_layout;
|
||||||
if(ch_layout == 0)
|
if(ch_layout == 0)
|
||||||
ch_layout = av_get_default_channel_layout((*mStream)->codec->channels);
|
ch_layout = av_get_default_channel_layout(mCodecCtx->channels);
|
||||||
|
|
||||||
if(mOutputSampleFormat != (*mStream)->codec->sample_fmt ||
|
if(mOutputSampleFormat != mCodecCtx->sample_fmt ||
|
||||||
mOutputChannelLayout != ch_layout)
|
mOutputChannelLayout != ch_layout)
|
||||||
{
|
{
|
||||||
mSwr = swr_alloc_set_opts(mSwr, // SwrContext
|
mSwr = swr_alloc_set_opts(mSwr, // SwrContext
|
||||||
mOutputChannelLayout, // output ch layout
|
mOutputChannelLayout, // output ch layout
|
||||||
mOutputSampleFormat, // output sample format
|
mOutputSampleFormat, // output sample format
|
||||||
(*mStream)->codec->sample_rate, // output sample rate
|
mCodecCtx->sample_rate, // output sample rate
|
||||||
ch_layout, // input ch layout
|
ch_layout, // input ch layout
|
||||||
(*mStream)->codec->sample_fmt, // input sample format
|
mCodecCtx->sample_fmt, // input sample format
|
||||||
(*mStream)->codec->sample_rate, // input sample rate
|
mCodecCtx->sample_rate, // input sample rate
|
||||||
0, // logging level offset
|
0, // logging level offset
|
||||||
nullptr); // log context
|
nullptr); // log context
|
||||||
if(!mSwr)
|
if(!mSwr)
|
||||||
throw std::runtime_error("Couldn't allocate SwrContext");
|
throw std::runtime_error("Couldn't allocate SwrContext");
|
||||||
if(swr_init(mSwr) < 0)
|
int init=swr_init(mSwr);
|
||||||
throw std::runtime_error("Couldn't initialize SwrContext");
|
if(init < 0)
|
||||||
|
throw std::runtime_error("Couldn't initialize SwrContext: "+std::to_string(init));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -412,7 +425,7 @@ size_t FFmpeg_Decoder::getSampleOffset()
|
||||||
{
|
{
|
||||||
int delay = (mFrameSize-mFramePos) / av_get_channel_layout_nb_channels(mOutputChannelLayout) /
|
int delay = (mFrameSize-mFramePos) / av_get_channel_layout_nb_channels(mOutputChannelLayout) /
|
||||||
av_get_bytes_per_sample(mOutputSampleFormat);
|
av_get_bytes_per_sample(mOutputSampleFormat);
|
||||||
return (int)(mNextPts*(*mStream)->codec->sample_rate) - delay;
|
return (int)(mNextPts*mCodecCtx->sample_rate) - delay;
|
||||||
}
|
}
|
||||||
|
|
||||||
FFmpeg_Decoder::FFmpeg_Decoder(const VFS::Manager* vfs)
|
FFmpeg_Decoder::FFmpeg_Decoder(const VFS::Manager* vfs)
|
||||||
|
@ -437,7 +450,10 @@ FFmpeg_Decoder::FFmpeg_Decoder(const VFS::Manager* vfs)
|
||||||
static bool done_init = false;
|
static bool done_init = false;
|
||||||
if(!done_init)
|
if(!done_init)
|
||||||
{
|
{
|
||||||
|
// This is not needed anymore above FFMpeg version 4.0
|
||||||
|
#if LIBAVCODEC_VERSION_INT < 3805796
|
||||||
av_register_all();
|
av_register_all();
|
||||||
|
#endif
|
||||||
av_log_set_level(AV_LOG_ERROR);
|
av_log_set_level(AV_LOG_ERROR);
|
||||||
done_init = true;
|
done_init = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,18 +6,7 @@ extern "C"
|
||||||
{
|
{
|
||||||
#include <libavcodec/avcodec.h>
|
#include <libavcodec/avcodec.h>
|
||||||
#include <libavformat/avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
|
#include <libavutil/channel_layout.h>
|
||||||
// From libavutil version 52.2.0 and onward the declaration of
|
|
||||||
// AV_CH_LAYOUT_* is removed from libavcodec/avcodec.h and moved to
|
|
||||||
// libavutil/channel_layout.h
|
|
||||||
#if AV_VERSION_INT(52, 2, 0) <= AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \
|
|
||||||
LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO)
|
|
||||||
#include <libavutil/channel_layout.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1)
|
|
||||||
#define av_frame_alloc avcodec_alloc_frame
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// From version 54.56 binkaudio encoding format changed from S16 to FLTP. See:
|
// From version 54.56 binkaudio encoding format changed from S16 to FLTP. See:
|
||||||
// https://gitorious.org/ffmpeg/ffmpeg/commit/7bfd1766d1c18f07b0a2dd042418a874d49ea60d
|
// https://gitorious.org/ffmpeg/ffmpeg/commit/7bfd1766d1c18f07b0a2dd042418a874d49ea60d
|
||||||
|
@ -38,6 +27,7 @@ namespace MWSound
|
||||||
class FFmpeg_Decoder final : public Sound_Decoder
|
class FFmpeg_Decoder final : public Sound_Decoder
|
||||||
{
|
{
|
||||||
AVFormatContext *mFormatCtx;
|
AVFormatContext *mFormatCtx;
|
||||||
|
AVCodecContext *mCodecCtx;
|
||||||
AVStream **mStream;
|
AVStream **mStream;
|
||||||
|
|
||||||
AVPacket mPacket;
|
AVPacket mPacket;
|
||||||
|
|
|
@ -48,7 +48,7 @@ namespace MWSound
|
||||||
{
|
{
|
||||||
ssize_t clock_delay = (mFrameSize-mFramePos) / av_get_channel_layout_nb_channels(mOutputChannelLayout) /
|
ssize_t clock_delay = (mFrameSize-mFramePos) / av_get_channel_layout_nb_channels(mOutputChannelLayout) /
|
||||||
av_get_bytes_per_sample(mOutputSampleFormat);
|
av_get_bytes_per_sample(mOutputSampleFormat);
|
||||||
return (size_t)(mAudioClock*mAVStream->codec->sample_rate) - clock_delay;
|
return (size_t)(mAudioClock*mAudioContext->sample_rate) - clock_delay;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string getStreamName()
|
std::string getStreamName()
|
||||||
|
@ -61,7 +61,7 @@ namespace MWSound
|
||||||
|
|
||||||
virtual double getAudioClock()
|
virtual double getAudioClock()
|
||||||
{
|
{
|
||||||
return (double)getSampleOffset()/(double)mAVStream->codec->sample_rate -
|
return (double)getSampleOffset()/(double)mAudioContext->sample_rate -
|
||||||
MWBase::Environment::get().getSoundManager()->getTrackTimeDelay(mAudioTrack);
|
MWBase::Environment::get().getSoundManager()->getTrackTimeDelay(mAudioTrack);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -265,9 +265,10 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot
|
||||||
writer.save (stream);
|
writer.save (stream);
|
||||||
|
|
||||||
Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen();
|
Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen();
|
||||||
|
int messagesCount = MWBase::Environment::get().getWindowManager()->getMessagesCount();
|
||||||
// Using only Cells for progress information, since they typically have the largest records by far
|
// Using only Cells for progress information, since they typically have the largest records by far
|
||||||
listener.setProgressRange(MWBase::Environment::get().getWorld()->countSavedGameCells());
|
listener.setProgressRange(MWBase::Environment::get().getWorld()->countSavedGameCells());
|
||||||
listener.setLabel("#{sNotifyMessage4}", true);
|
listener.setLabel("#{sNotifyMessage4}", true, messagesCount > 0);
|
||||||
|
|
||||||
Loading::ScopedLoad load(&listener);
|
Loading::ScopedLoad load(&listener);
|
||||||
|
|
||||||
|
@ -389,9 +390,10 @@ void MWState::StateManager::loadGame (const Character *character, const std::str
|
||||||
std::map<int, int> contentFileMap = buildContentFileIndexMap (reader);
|
std::map<int, int> contentFileMap = buildContentFileIndexMap (reader);
|
||||||
|
|
||||||
Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen();
|
Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen();
|
||||||
|
int messagesCount = MWBase::Environment::get().getWindowManager()->getMessagesCount();
|
||||||
|
|
||||||
listener.setProgressRange(100);
|
listener.setProgressRange(100);
|
||||||
listener.setLabel("#{sLoadingMessage14}");
|
listener.setLabel("#{sLoadingMessage14}", false, messagesCount > 0);
|
||||||
|
|
||||||
Loading::ScopedLoad load(&listener);
|
Loading::ScopedLoad load(&listener);
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,22 @@ namespace MWWorld
|
||||||
return mCellRef.mEnchantmentCharge;
|
return mCellRef.mEnchantmentCharge;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float CellRef::getNormalizedEnchantmentCharge(int maxCharge) const
|
||||||
|
{
|
||||||
|
if (maxCharge == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else if (mCellRef.mEnchantmentCharge == -1)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return mCellRef.mEnchantmentCharge / static_cast<float>(maxCharge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void CellRef::setEnchantmentCharge(float charge)
|
void CellRef::setEnchantmentCharge(float charge)
|
||||||
{
|
{
|
||||||
if (charge != mCellRef.mEnchantmentCharge)
|
if (charge != mCellRef.mEnchantmentCharge)
|
||||||
|
|
|
@ -56,6 +56,9 @@ namespace MWWorld
|
||||||
// Remaining enchantment charge. This could be -1 if the charge was not touched yet (i.e. full).
|
// Remaining enchantment charge. This could be -1 if the charge was not touched yet (i.e. full).
|
||||||
float getEnchantmentCharge() const;
|
float getEnchantmentCharge() const;
|
||||||
|
|
||||||
|
// Remaining enchantment charge rescaled to the supplied maximum charge (such as one of the enchantment).
|
||||||
|
float getNormalizedEnchantmentCharge(int maxCharge) const;
|
||||||
|
|
||||||
void setEnchantmentCharge(float charge);
|
void setEnchantmentCharge(float charge);
|
||||||
|
|
||||||
// For weapon or armor, this is the remaining item health.
|
// For weapon or armor, this is the remaining item health.
|
||||||
|
|
|
@ -432,7 +432,7 @@ namespace MWWorld
|
||||||
mHasState = true;
|
mHasState = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int CellStore::count() const
|
std::size_t CellStore::count() const
|
||||||
{
|
{
|
||||||
return mMergedRefs.size();
|
return mMergedRefs.size();
|
||||||
}
|
}
|
||||||
|
|
|
@ -240,7 +240,7 @@ namespace MWWorld
|
||||||
|
|
||||||
ESM::FogState* getFog () const;
|
ESM::FogState* getFog () const;
|
||||||
|
|
||||||
int count() const;
|
std::size_t count() const;
|
||||||
///< Return total number of references, including deleted ones.
|
///< Return total number of references, including deleted ones.
|
||||||
|
|
||||||
void load ();
|
void load ();
|
||||||
|
@ -283,7 +283,7 @@ namespace MWWorld
|
||||||
/// \attention This function also lists deleted (count 0) objects!
|
/// \attention This function also lists deleted (count 0) objects!
|
||||||
/// \return Iteration completed?
|
/// \return Iteration completed?
|
||||||
template<class Visitor>
|
template<class Visitor>
|
||||||
bool forEachConst (Visitor& visitor) const
|
bool forEachConst (Visitor&& visitor) const
|
||||||
{
|
{
|
||||||
if (mState != State_Loaded)
|
if (mState != State_Loaded)
|
||||||
return false;
|
return false;
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue