mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-16 20:49:56 +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 -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
|
||||
- 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 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 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 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 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/libbullet2.87_2.87+dfsg-2_amd64.deb -o libbullet2.87_2.87+dfsg-2_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 -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 -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
|
||||
stage: build
|
||||
script:
|
||||
|
|
54
.travis.yml
54
.travis.yml
|
@ -1,10 +1,4 @@
|
|||
os:
|
||||
- linux
|
||||
- osx
|
||||
osx_image: xcode9.4
|
||||
language: cpp
|
||||
sudo: required
|
||||
dist: trusty
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
@ -20,16 +14,16 @@ addons:
|
|||
sources:
|
||||
- sourceline: 'ppa:openmw/openmw'
|
||||
- ubuntu-toolchain-r-test
|
||||
- llvm-toolchain-precise-3.6
|
||||
- llvm-toolchain-trusty-7
|
||||
packages: [
|
||||
# Dev
|
||||
cmake, clang-3.6, libunshield-dev, libtinyxml-dev,
|
||||
cmake, clang-7, clang-tools-7, gcc-8, g++-8,
|
||||
# Boost
|
||||
libboost-filesystem-dev, libboost-program-options-dev, libboost-system-dev,
|
||||
# FFmpeg
|
||||
libavcodec-dev, libavformat-dev, libavutil-dev, libswscale-dev,
|
||||
# Audio & Video
|
||||
libsdl2-dev, libqt4-dev, libopenal-dev,
|
||||
# Audio, Video and Misc. deps
|
||||
libsdl2-dev, libqt4-dev, libopenal-dev, libunshield-dev, libtinyxml-dev,
|
||||
# The other ones from OpenMW ppa
|
||||
libbullet-dev, libswresample-dev, libopenscenegraph-3.4-dev, libmygui-dev
|
||||
]
|
||||
|
@ -44,18 +38,44 @@ addons:
|
|||
branch_pattern: coverity_scan
|
||||
matrix:
|
||||
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:
|
||||
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
|
||||
allow_failures:
|
||||
- env: ANALYZE="scan-build-3.6 --use-cc clang-3.6 --use-c++ clang++-3.6 "
|
||||
- name: OpenMW (openmw-cs) 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="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_script: ./CI/before_script.${TRAVIS_OS_NAME}.sh
|
||||
before_install:
|
||||
- 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:
|
||||
- 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}" = "linux" ]; then ./openmw_test_suite; 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
|
||||
------
|
||||
|
||||
|
@ -8,6 +19,7 @@
|
|||
Bug #2256: Landing sound not playing when jumping immediately after landing
|
||||
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 #2446: Restore Attribute/Skill should allow restoring drained attributes
|
||||
Bug #2455: Creatures attacks degrade armor
|
||||
Bug #2562: Forcing AI to activate a teleport door sometimes causes a crash
|
||||
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 #3836: Script fails to compile when command argument contains "\n"
|
||||
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 #3911: [macOS] Typing in the "Content List name" dialog box produces double characters
|
||||
Bug #3920: RemoveSpellEffects doesn't remove constant effects
|
||||
|
@ -45,6 +58,7 @@
|
|||
Bug #4110: Fixed undo / redo menu text losing the assigned shortcuts
|
||||
Bug #4125: OpenMW logo cropped on bugtracker
|
||||
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 #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
|
||||
|
@ -54,6 +68,7 @@
|
|||
Bug #4274: Pre-0.43 death animations are not forward-compatible with 0.43+
|
||||
Bug #4286: Scripted animations can be interrupted
|
||||
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 #4304: "Follow" not working as a second AI package
|
||||
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 #4378: On-self absorb spells restore stats
|
||||
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 #4426: RotateWorld behavior is incorrect
|
||||
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 #4460: Script function "Equip" doesn't bypass beast restrictions
|
||||
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 #4467: Content selector: cyrillic characters are decoded incorrectly in plugin descriptions
|
||||
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 #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 #4684: Spell Absorption is additive
|
||||
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 #1221: Editor: Creature/NPC rendering
|
||||
Feature #1617: Editor: Enchantment effect record verifier
|
||||
Feature #1645: Casting effects from objects
|
||||
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 #3083: Play animation when NPC is casting spell via script
|
||||
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 #4636: Use sTo GMST in spellmaking menu
|
||||
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 #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 #4545: Enable is_pod string test
|
||||
Task #4605: Optimize skinning
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#!/bin/sh
|
||||
sudo ln -s /usr/bin/clang-3.6 /usr/local/bin/clang
|
||||
sudo ln -s /usr/bin/clang++-3.6 /usr/local/bin/clang++
|
||||
#!/bin/bash -ex
|
||||
|
||||
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
|
||||
|
||||
|
@ -6,5 +6,5 @@ brew outdated cmake || brew upgrade cmake
|
|||
brew outdated pkgconfig || brew upgrade pkgconfig
|
||||
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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/sh -e
|
||||
#!/bin/bash -ex
|
||||
|
||||
free -m
|
||||
|
||||
|
@ -8,8 +8,22 @@ GOOGLETEST_DIR="$(pwd)/googletest/build"
|
|||
mkdir build
|
||||
cd build
|
||||
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_UNITTESTS=1 \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||
|
|
|
@ -194,7 +194,11 @@ download() {
|
|||
}
|
||||
|
||||
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=""
|
||||
|
@ -304,6 +308,10 @@ if ! [ -z $UNITY_BUILD ]; then
|
|||
add_cmake_opts "-DOPENMW_UNITY_BUILD=True"
|
||||
fi
|
||||
|
||||
if [ ${BITS} -eq 64 ]; then
|
||||
GENERATOR="${GENERATOR} Win64"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "==================================="
|
||||
echo "Starting prebuild on MSVC${MSVC_DISPLAY_YEAR} WIN${BITS}"
|
||||
|
@ -324,7 +332,7 @@ if [ -z $SKIP_DOWNLOAD ]; then
|
|||
if [ -z $APPVEYOR ]; then
|
||||
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" \
|
||||
"boost-1.67.0-msvc${MSVC_YEAR}-win${BITS}.exe"
|
||||
"boost-1.67.0-msvc${MSVC_VER}-win${BITS}.exe"
|
||||
fi
|
||||
|
||||
# Bullet
|
||||
|
@ -430,7 +438,7 @@ fi
|
|||
rm -rf Boost
|
||||
CI_EXTRA_INNO_OPTIONS=""
|
||||
[ -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}"
|
||||
fi
|
||||
add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/sh
|
||||
#!/bin/sh -e
|
||||
|
||||
export CXX=clang++
|
||||
export CC=clang
|
||||
|
|
|
@ -153,7 +153,52 @@ if (USE_QT)
|
|||
endif()
|
||||
|
||||
# Sound setup
|
||||
|
||||
# Require at least ffmpeg 3.2 for now
|
||||
SET(FFVER_OK FALSE)
|
||||
|
||||
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
|
||||
add_definitions(-D__STDC_CONSTANT_MACROS)
|
||||
|
||||
|
@ -275,9 +320,6 @@ if (APPLE)
|
|||
"${APP_BUNDLE_DIR}/Contents/Resources/OpenMW.icns" COPYONLY)
|
||||
endif (APPLE)
|
||||
|
||||
# Set up DEBUG define
|
||||
set_directory_properties(PROPERTIES COMPILE_DEFINITIONS_DEBUG DEBUG=1)
|
||||
|
||||
if (NOT APPLE)
|
||||
set(OPENMW_MYGUI_FILES_ROOT ${OpenMW_BINARY_DIR})
|
||||
set(OPENMW_SHADERS_ROOT ${OpenMW_BINARY_DIR})
|
||||
|
@ -536,6 +578,11 @@ if(WIN32)
|
|||
endif(WIN32)
|
||||
|
||||
# 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/oics)
|
||||
if (BUILD_OPENCS)
|
||||
|
@ -602,7 +649,7 @@ if (WIN32)
|
|||
if (USE_DEBUG_CONSOLE AND BUILD_OPENMW)
|
||||
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 COMPILE_DEFINITIONS_DEBUG "_CONSOLE")
|
||||
set_target_properties(openmw PROPERTIES COMPILE_DEFINITIONS $<$<CONFIG:Debug>:_CONSOLE>)
|
||||
elseif (BUILD_OPENMW)
|
||||
# Turn off debug console, debug output will be written to visual studio output instead
|
||||
set_target_properties(openmw PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:WINDOWS")
|
||||
|
@ -612,7 +659,6 @@ if (WIN32)
|
|||
if (BUILD_OPENMW)
|
||||
# Release builds don't use the debug console
|
||||
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")
|
||||
endif()
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/fallback/validate.hpp>
|
||||
#include <components/misc/rng.hpp>
|
||||
#include <components/nifosg/nifloader.hpp>
|
||||
|
||||
#include "model/doc/document.hpp"
|
||||
|
@ -355,6 +356,8 @@ int CS::Editor::run()
|
|||
if (mLocal.empty())
|
||||
return 1;
|
||||
|
||||
Misc::Rng::init();
|
||||
|
||||
mStartup.show();
|
||||
|
||||
QApplication::setQuitOnLastWindowClosed (true);
|
||||
|
|
|
@ -317,8 +317,8 @@ void CSMPrefs::State::declare()
|
|||
declareShortcut ("table-remove", "Remove Row/Record", QKeySequence(Qt::Key_Delete));
|
||||
declareShortcut ("table-moveup", "Move Record Up", QKeySequence());
|
||||
declareShortcut ("table-movedown", "Move Record Down", QKeySequence());
|
||||
declareShortcut ("table-view", "View Record", QKeySequence());
|
||||
declareShortcut ("table-preview", "Preview Record", QKeySequence());
|
||||
declareShortcut ("table-view", "View Record", QKeySequence(Qt::ShiftModifier | Qt::Key_C));
|
||||
declareShortcut ("table-preview", "Preview Record", QKeySequence(Qt::ShiftModifier | Qt::Key_V));
|
||||
declareShortcut ("table-extendeddelete", "Extended Record Deletion", 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()
|
||||
{
|
||||
// Load default marker definitions, if game files do not have them for some reason
|
||||
std::pair<std::string, std::string> markers[] = {
|
||||
std::make_pair("divinemarker", "marker_divine.nif"),
|
||||
std::make_pair("doormarker", "marker_arrow.nif"),
|
||||
std::make_pair("northmarker", "marker_north.nif"),
|
||||
std::make_pair("templemarker", "marker_temple.nif"),
|
||||
std::make_pair("travelmarker", "marker_travel.nif")
|
||||
std::pair<std::string, std::string> staticMarkers[] = {
|
||||
std::make_pair("DivineMarker", "marker_divine.nif"),
|
||||
std::make_pair("DoorMarker", "marker_arrow.nif"),
|
||||
std::make_pair("NorthMarker", "marker_north.nif"),
|
||||
std::make_pair("TempleMarker", "marker_temple.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)
|
||||
{
|
||||
|
@ -995,6 +999,17 @@ void CSMWorld::Data::loadFallbackEntries()
|
|||
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)
|
||||
|
|
|
@ -520,7 +520,6 @@ std::pair<float, float> CSMWorld::ConstInfoSelectWrapper::getConditionFloatRange
|
|||
const float FloatMax = std::numeric_limits<float>::infinity();
|
||||
const float FloatMin = -std::numeric_limits<float>::infinity();
|
||||
const float Epsilon = std::numeric_limits<float>::epsilon();
|
||||
const std::pair<float, float> InvalidRange(FloatMax, FloatMin);
|
||||
|
||||
float value = mConstSelect.mValue.getFloat();
|
||||
|
||||
|
|
|
@ -292,6 +292,7 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id,
|
|||
|
||||
mEditAction = new QAction (tr ("Edit Record"), this);
|
||||
connect (mEditAction, SIGNAL (triggered()), this, SLOT (editRecord()));
|
||||
mEditAction->setIcon(QIcon(":edit-edit"));
|
||||
addAction (mEditAction);
|
||||
CSMPrefs::Shortcut* editShortcut = new CSMPrefs::Shortcut("table-edit", this);
|
||||
editShortcut->associateAction(mEditAction);
|
||||
|
@ -300,12 +301,14 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id,
|
|||
{
|
||||
mCreateAction = new QAction (tr ("Add Record"), this);
|
||||
connect (mCreateAction, SIGNAL (triggered()), this, SIGNAL (createRequest()));
|
||||
mCreateAction->setIcon(QIcon(":edit-add"));
|
||||
addAction (mCreateAction);
|
||||
CSMPrefs::Shortcut* createShortcut = new CSMPrefs::Shortcut("table-add", this);
|
||||
createShortcut->associateAction(mCreateAction);
|
||||
|
||||
mCloneAction = new QAction (tr ("Clone Record"), this);
|
||||
connect(mCloneAction, SIGNAL (triggered()), this, SLOT (cloneRecord()));
|
||||
mCloneAction->setIcon(QIcon(":edit-clone"));
|
||||
addAction(mCloneAction);
|
||||
CSMPrefs::Shortcut* cloneShortcut = new CSMPrefs::Shortcut("table-clone", this);
|
||||
cloneShortcut->associateAction(mCloneAction);
|
||||
|
@ -315,6 +318,7 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id,
|
|||
{
|
||||
mTouchAction = new QAction(tr("Touch Record"), this);
|
||||
connect(mTouchAction, SIGNAL(triggered()), this, SLOT(touchRecord()));
|
||||
mTouchAction->setIcon(QIcon(":edit-touch"));
|
||||
addAction(mTouchAction);
|
||||
CSMPrefs::Shortcut* touchShortcut = new CSMPrefs::Shortcut("table-touch", this);
|
||||
touchShortcut->associateAction(mTouchAction);
|
||||
|
@ -322,49 +326,56 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id,
|
|||
|
||||
mRevertAction = new QAction (tr ("Revert Record"), this);
|
||||
connect (mRevertAction, SIGNAL (triggered()), mDispatcher, SLOT (executeRevert()));
|
||||
mRevertAction->setIcon(QIcon(":edit-undo"));
|
||||
addAction (mRevertAction);
|
||||
CSMPrefs::Shortcut* revertShortcut = new CSMPrefs::Shortcut("table-revert", this);
|
||||
revertShortcut->associateAction(mRevertAction);
|
||||
|
||||
mDeleteAction = new QAction (tr ("Delete Record"), this);
|
||||
connect (mDeleteAction, SIGNAL (triggered()), mDispatcher, SLOT (executeDelete()));
|
||||
mDeleteAction->setIcon(QIcon(":edit-delete"));
|
||||
addAction (mDeleteAction);
|
||||
CSMPrefs::Shortcut* deleteShortcut = new CSMPrefs::Shortcut("table-remove", this);
|
||||
deleteShortcut->associateAction(mDeleteAction);
|
||||
|
||||
|
||||
mMoveUpAction = new QAction (tr ("Move Up"), this);
|
||||
connect (mMoveUpAction, SIGNAL (triggered()), this, SLOT (moveUpRecord()));
|
||||
mMoveUpAction->setIcon(QIcon(":record-up"));
|
||||
addAction (mMoveUpAction);
|
||||
CSMPrefs::Shortcut* moveUpShortcut = new CSMPrefs::Shortcut("table-moveup", this);
|
||||
moveUpShortcut->associateAction(mMoveUpAction);
|
||||
|
||||
mMoveDownAction = new QAction (tr ("Move Down"), this);
|
||||
connect (mMoveDownAction, SIGNAL (triggered()), this, SLOT (moveDownRecord()));
|
||||
mMoveDownAction->setIcon(QIcon(":record-down"));
|
||||
addAction (mMoveDownAction);
|
||||
CSMPrefs::Shortcut* moveDownShortcut = new CSMPrefs::Shortcut("table-movedown", this);
|
||||
moveDownShortcut->associateAction(mMoveDownAction);
|
||||
|
||||
mViewAction = new QAction (tr ("View"), this);
|
||||
connect (mViewAction, SIGNAL (triggered()), this, SLOT (viewRecord()));
|
||||
mViewAction->setIcon(QIcon(":/cell.png"));
|
||||
addAction (mViewAction);
|
||||
CSMPrefs::Shortcut* viewShortcut = new CSMPrefs::Shortcut("table-view", this);
|
||||
viewShortcut->associateAction(mViewAction);
|
||||
|
||||
mPreviewAction = new QAction (tr ("Preview"), this);
|
||||
connect (mPreviewAction, SIGNAL (triggered()), this, SLOT (previewRecord()));
|
||||
mPreviewAction->setIcon(QIcon(":edit-preview"));
|
||||
addAction (mPreviewAction);
|
||||
CSMPrefs::Shortcut* previewShortcut = new CSMPrefs::Shortcut("table-preview", this);
|
||||
previewShortcut->associateAction(mPreviewAction);
|
||||
|
||||
mExtendedDeleteAction = new QAction (tr ("Extended Delete Record"), this);
|
||||
connect (mExtendedDeleteAction, SIGNAL (triggered()), this, SLOT (executeExtendedDelete()));
|
||||
mExtendedDeleteAction->setIcon(QIcon(":edit-delete"));
|
||||
addAction (mExtendedDeleteAction);
|
||||
CSMPrefs::Shortcut* extendedDeleteShortcut = new CSMPrefs::Shortcut("table-extendeddelete", this);
|
||||
extendedDeleteShortcut->associateAction(mExtendedDeleteAction);
|
||||
|
||||
mExtendedRevertAction = new QAction (tr ("Extended Revert Record"), this);
|
||||
connect (mExtendedRevertAction, SIGNAL (triggered()), this, SLOT (executeExtendedRevert()));
|
||||
mExtendedRevertAction->setIcon(QIcon(":edit-undo"));
|
||||
addAction (mExtendedRevertAction);
|
||||
CSMPrefs::Shortcut* extendedRevertShortcut = new CSMPrefs::Shortcut("table-extendedrevert", this);
|
||||
extendedRevertShortcut->associateAction(mExtendedRevertAction);
|
||||
|
|
|
@ -4,6 +4,7 @@ set(GAME
|
|||
engine.cpp
|
||||
|
||||
${CMAKE_SOURCE_DIR}/files/windows/openmw.rc
|
||||
${CMAKE_SOURCE_DIR}/files/windows/openmw.exe.manifest
|
||||
)
|
||||
|
||||
if (ANDROID)
|
||||
|
@ -21,7 +22,7 @@ add_openmw_dir (mwrender
|
|||
actors objects renderingmanager animation rotatecontroller sky npcanimation vismask
|
||||
creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation
|
||||
bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation
|
||||
renderbin actoranimation landmanager
|
||||
renderbin actoranimation landmanager navmesh actorspaths
|
||||
)
|
||||
|
||||
add_openmw_dir (mwinput
|
||||
|
@ -70,7 +71,7 @@ add_openmw_dir (mwworld
|
|||
)
|
||||
|
||||
add_openmw_dir (mwphysics
|
||||
physicssystem trace collisiontype actor convert
|
||||
physicssystem trace collisiontype actor convert object heightfield
|
||||
)
|
||||
|
||||
add_openmw_dir (mwclass
|
||||
|
@ -133,6 +134,7 @@ target_link_libraries(openmw
|
|||
${FFmpeg_LIBRARIES}
|
||||
${MyGUI_LIBRARIES}
|
||||
${SDL2_LIBRARY}
|
||||
${RecastNavigation_LIBRARIES}
|
||||
"osg-ffmpeg-videoplayer"
|
||||
"oics"
|
||||
components
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
#include "engine.hpp"
|
||||
|
||||
#if defined(_WIN32)
|
||||
// For OutputDebugString
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
|
|
|
@ -233,6 +233,10 @@ namespace MWBase
|
|||
|
||||
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
|
||||
/// 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;
|
||||
|
|
|
@ -285,6 +285,8 @@ namespace MWBase
|
|||
|
||||
virtual void setEnemy (const MWWorld::Ptr& enemy) = 0;
|
||||
|
||||
virtual int getMessagesCount() const = 0;
|
||||
|
||||
virtual const Translation::Storage& getTranslationDataStorage() const = 0;
|
||||
|
||||
/// Warning: do not use MyGUI::InputManager::setKeyFocusWidget directly. Instead use this.
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <vector>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <deque>
|
||||
|
||||
#include <components/esm/cellid.hpp>
|
||||
|
||||
|
@ -54,6 +55,11 @@ namespace MWMechanics
|
|||
struct Movement;
|
||||
}
|
||||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
class Navigator;
|
||||
}
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class CellStore;
|
||||
|
@ -262,8 +268,8 @@ namespace MWBase
|
|||
///< Adjust position after load to be on ground. Must be called after model load.
|
||||
/// @param force do this even if the ptr is flying
|
||||
|
||||
virtual void fixPosition (const MWWorld::Ptr& actor) = 0;
|
||||
///< Attempt to fix position so that the Ptr is no longer inside collision geometry.
|
||||
virtual void fixPosition () = 0;
|
||||
///< 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.
|
||||
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 void setActorCollisionMode(const MWWorld::Ptr& ptr, bool enabled) = 0;
|
||||
virtual bool isActorCollisionEnabled(const MWWorld::Ptr& ptr) = 0;
|
||||
|
||||
virtual bool toggleCollisionMode() = 0;
|
||||
///< Toggle collision mode for player. If disabled player object should ignore
|
||||
/// collisions and gravity.
|
||||
|
@ -592,6 +601,15 @@ namespace MWBase
|
|||
|
||||
/// Preload VFX associated with this effect list
|
||||
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 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();
|
||||
std::string creatureId;
|
||||
|
||||
|
@ -151,21 +151,35 @@ namespace MWClass
|
|||
}
|
||||
}
|
||||
|
||||
if (creatureId.empty())
|
||||
return std::string();
|
||||
|
||||
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)
|
||||
if (type == sound->mType && !sound->mCreature.empty() && (Misc::StringUtils::ciEqual(creatureId, sound->mCreature)))
|
||||
sounds.push_back(&*sound);
|
||||
std::vector<const ESM::SoundGenerator*> fallbacksounds;
|
||||
if (!creatureId.empty())
|
||||
{
|
||||
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())
|
||||
return sounds[Misc::Rng::rollDice(sounds.size())]->mSound;
|
||||
if (!sounds.empty())
|
||||
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)
|
||||
return "Body Fall Large";
|
||||
if (!fallbacksounds.empty())
|
||||
return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size())]->mSound;
|
||||
}
|
||||
|
||||
return std::string();
|
||||
}
|
||||
|
|
|
@ -297,11 +297,11 @@ namespace MWClass
|
|||
{
|
||||
const MWWorld::InventoryStore& invStore = npc.getClass().getInventoryStore(npc);
|
||||
|
||||
if (ptr.getCellRef().getCharge() == 0)
|
||||
if (getItemHealth(ptr) == 0)
|
||||
return std::make_pair(0, "#{sInventoryMessage1}");
|
||||
|
||||
// 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())
|
||||
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
|
||||
{
|
||||
// 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())
|
||||
return std::make_pair(0, "");
|
||||
|
|
|
@ -137,7 +137,7 @@ namespace MWClass
|
|||
|
||||
// Persistent actors with 0 health do not play death animation
|
||||
if (data->mCreatureStats.isDead())
|
||||
data->mCreatureStats.setDeathAnimationFinished(ptr.getClass().isPersistent(ptr));
|
||||
data->mCreatureStats.setDeathAnimationFinished(isPersistent(ptr));
|
||||
|
||||
// spells
|
||||
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());
|
||||
|
||||
if (hasInventory)
|
||||
getInventoryStore(ptr).autoEquipShield(ptr);
|
||||
getInventoryStore(ptr).autoEquip(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -194,9 +194,9 @@ namespace MWClass
|
|||
models.push_back(model);
|
||||
|
||||
// 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)
|
||||
{
|
||||
MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot);
|
||||
|
@ -237,7 +237,7 @@ namespace MWClass
|
|||
|
||||
// Get the weapon used (if hand-to-hand, weapon = inv.end())
|
||||
MWWorld::Ptr weapon;
|
||||
if (ptr.getClass().hasInventoryStore(ptr))
|
||||
if (hasInventoryStore(ptr))
|
||||
{
|
||||
MWWorld::InventoryStore &inv = getInventoryStore(ptr);
|
||||
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.
|
||||
std::vector<MWWorld::Ptr> targetActors;
|
||||
if (!ptr.isEmpty() && ptr.getClass().isActor())
|
||||
ptr.getClass().getCreatureStats(ptr).getAiSequence().getCombatTargets(targetActors);
|
||||
stats.getAiSequence().getCombatTargets(targetActors);
|
||||
|
||||
std::pair<MWWorld::Ptr, osg::Vec3f> result = MWBase::Environment::get().getWorld()->getHitContact(ptr, dist, targetActors);
|
||||
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
|
||||
{
|
||||
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
|
||||
MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
|
||||
|
||||
// NOTE: 'object' and/or 'attacker' may be empty.
|
||||
if (!attacker.isEmpty() && attacker.getClass().isActor() && !stats.getAiSequence().isInCombat(attacker))
|
||||
|
@ -346,7 +345,7 @@ namespace MWClass
|
|||
setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker);
|
||||
|
||||
// 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);
|
||||
// First handle the attacked actor
|
||||
|
@ -522,7 +521,7 @@ namespace MWClass
|
|||
const MWBase::World *world = MWBase::Environment::get().getWorld();
|
||||
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)
|
||||
float runSpeed = walkSpeed;
|
||||
|
@ -632,25 +631,27 @@ namespace MWClass
|
|||
if(type >= 0)
|
||||
{
|
||||
std::vector<const ESM::SoundGenerator*> sounds;
|
||||
std::vector<const ESM::SoundGenerator*> fallbacksounds;
|
||||
|
||||
MWWorld::LiveCellRef<ESM::Creature>* ref = ptr.get<ESM::Creature>();
|
||||
|
||||
const std::string& ourId = (ref->mBase->mOriginal.empty()) ? ptr.getCellRef().getRefId() : ref->mBase->mOriginal;
|
||||
|
||||
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)))
|
||||
sounds.push_back(&*sound);
|
||||
if (type == sound->mType && sound->mCreature.empty())
|
||||
fallbacksounds.push_back(&*sound);
|
||||
++sound;
|
||||
}
|
||||
if(!sounds.empty())
|
||||
if (!sounds.empty())
|
||||
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 "";
|
||||
}
|
||||
|
||||
|
@ -806,7 +807,7 @@ namespace MWClass
|
|||
|
||||
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())
|
||||
return;
|
||||
|
||||
|
|
|
@ -357,7 +357,7 @@ namespace MWClass
|
|||
|
||||
// Persistent actors with 0 health do not play death animation
|
||||
if (data->mNpcStats.isDead())
|
||||
data->mNpcStats.setDeathAnimationFinished(ptr.getClass().isPersistent(ptr));
|
||||
data->mNpcStats.setDeathAnimationFinished(isPersistent(ptr));
|
||||
|
||||
// race powers
|
||||
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
|
||||
// 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);
|
||||
for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
|
||||
MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot);
|
||||
if (equipped != invStore.end())
|
||||
{
|
||||
MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot);
|
||||
if (equipped != invStore.end())
|
||||
std::vector<ESM::PartReference> parts;
|
||||
if(equipped->getTypeName() == typeid(ESM::Clothing).name())
|
||||
{
|
||||
std::vector<ESM::PartReference> parts;
|
||||
if(equipped->getTypeName() == typeid(ESM::Clothing).name())
|
||||
{
|
||||
const ESM::Clothing *clothes = equipped->get<ESM::Clothing>()->mBase;
|
||||
parts = clothes->mParts.mParts;
|
||||
}
|
||||
else if(equipped->getTypeName() == typeid(ESM::Armor).name())
|
||||
{
|
||||
const ESM::Armor *armor = equipped->get<ESM::Armor>()->mBase;
|
||||
parts = armor->mParts.mParts;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string model = equipped->getClass().getModel(*equipped);
|
||||
if (!model.empty())
|
||||
models.push_back(model);
|
||||
}
|
||||
const ESM::Clothing *clothes = equipped->get<ESM::Clothing>()->mBase;
|
||||
parts = clothes->mParts.mParts;
|
||||
}
|
||||
else if(equipped->getTypeName() == typeid(ESM::Armor).name())
|
||||
{
|
||||
const ESM::Armor *armor = equipped->get<ESM::Armor>()->mBase;
|
||||
parts = armor->mParts.mParts;
|
||||
}
|
||||
else
|
||||
{
|
||||
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)
|
||||
{
|
||||
std::string partname = female ? it->mFemale : it->mMale;
|
||||
if (partname.empty())
|
||||
partname = female ? it->mMale : it->mFemale;
|
||||
const ESM::BodyPart* part = MWBase::Environment::get().getWorld()->getStore().get<ESM::BodyPart>().search(partname);
|
||||
if (part && !part->mModel.empty())
|
||||
models.push_back("meshes/"+part->mModel);
|
||||
}
|
||||
for (std::vector<ESM::PartReference>::const_iterator it = parts.begin(); it != parts.end(); ++it)
|
||||
{
|
||||
std::string partname = female ? it->mFemale : it->mMale;
|
||||
if (partname.empty())
|
||||
partname = female ? it->mMale : it->mFemale;
|
||||
const ESM::BodyPart* part = MWBase::Environment::get().getWorld()->getStore().get<ESM::BodyPart>().search(partname);
|
||||
if (part && !part->mModel.empty())
|
||||
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.
|
||||
std::vector<MWWorld::Ptr> targetActors;
|
||||
if (!ptr.isEmpty() && ptr.getClass().isActor() && ptr != MWMechanics::getPlayer())
|
||||
ptr.getClass().getCreatureStats(ptr).getAiSequence().getCombatTargets(targetActors);
|
||||
if (ptr != MWMechanics::getPlayer())
|
||||
getCreatureStats(ptr).getAiSequence().getCombatTargets(targetActors);
|
||||
|
||||
// TODO: Use second to work out the hit angle
|
||||
std::pair<MWWorld::Ptr, osg::Vec3f> result = world->getHitContact(ptr, dist, targetActors);
|
||||
|
@ -597,7 +594,7 @@ namespace MWClass
|
|||
if(!weapon.isEmpty())
|
||||
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)
|
||||
{
|
||||
|
@ -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
|
||||
{
|
||||
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
||||
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
|
||||
MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
|
||||
bool wasDead = stats.isDead();
|
||||
|
||||
// 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
|
||||
if (!attacker.isEmpty() && attacker.getClass().isActor() && !ptr.isEmpty() && ptr.getClass().isActor())
|
||||
if (!attacker.isEmpty() && attacker.getClass().isActor())
|
||||
{
|
||||
MWMechanics::CreatureStats& statsAttacker = attacker.getClass().getCreatureStats(attacker);
|
||||
// First handle the attacked actor
|
||||
|
@ -702,7 +699,7 @@ namespace MWClass
|
|||
|
||||
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. */
|
||||
if(!script.empty())
|
||||
ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1);
|
||||
|
@ -891,12 +888,11 @@ namespace MWClass
|
|||
if(stats.getAiSequence().isInCombat())
|
||||
return std::shared_ptr<MWWorld::Action>(new MWWorld::FailedAction(""));
|
||||
|
||||
if(getCreatureStats(actor).getStance(MWMechanics::CreatureStats::Stance_Sneak)
|
||||
|| ptr.getClass().getCreatureStats(ptr).getKnockedDown())
|
||||
if(getCreatureStats(actor).getStance(MWMechanics::CreatureStats::Stance_Sneak) || stats.getKnockedDown())
|
||||
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr)); // stealing
|
||||
|
||||
// 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::ActionTalk(ptr));
|
||||
|
@ -927,7 +923,7 @@ namespace MWClass
|
|||
|
||||
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())
|
||||
return 0.f;
|
||||
|
||||
|
@ -993,7 +989,7 @@ namespace MWClass
|
|||
if(getEncumbrance(ptr) > getCapacity(ptr))
|
||||
return 0.f;
|
||||
|
||||
const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
|
||||
const MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
|
||||
if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead())
|
||||
return 0.f;
|
||||
|
||||
|
@ -1148,9 +1144,7 @@ namespace MWClass
|
|||
const bool hasHealth = it->getClass().hasItemHealth(*it);
|
||||
if (hasHealth)
|
||||
{
|
||||
int armorHealth = it->getClass().getItemHealth(*it);
|
||||
int armorMaxHealth = it->getClass().getItemMaxHealth(*it);
|
||||
ratings[i] *= (float(armorHealth) / armorMaxHealth);
|
||||
ratings[i] *= it->getClass().getItemNormalizedHealth(*it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1215,13 +1209,13 @@ namespace MWClass
|
|||
return (name == "left") ? "FootWaterLeft" : "FootWaterRight";
|
||||
if(world->isOnGround(ptr))
|
||||
{
|
||||
if (ptr.getClass().getNpcStats(ptr).isWerewolf()
|
||||
&& ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run))
|
||||
if (getNpcStats(ptr).isWerewolf()
|
||||
&& getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run))
|
||||
{
|
||||
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)
|
||||
return "";
|
||||
return std::string();
|
||||
}
|
||||
|
||||
const MWWorld::InventoryStore &inv = Npc::getInventoryStore(ptr);
|
||||
|
@ -1239,12 +1233,12 @@ namespace MWClass
|
|||
return (name == "left") ? "FootHeavyLeft" : "FootHeavyRight";
|
||||
}
|
||||
}
|
||||
return "";
|
||||
return std::string();
|
||||
}
|
||||
|
||||
// Morrowind ignores land soundgen for NPCs
|
||||
if(name == "land")
|
||||
return "";
|
||||
return std::string();
|
||||
if(name == "swimleft")
|
||||
return "Swim Left";
|
||||
if(name == "swimright")
|
||||
|
@ -1254,11 +1248,11 @@ namespace MWClass
|
|||
// only for biped creatures?
|
||||
|
||||
if(name == "moan")
|
||||
return "";
|
||||
return std::string();
|
||||
if(name == "roar")
|
||||
return "";
|
||||
return std::string();
|
||||
if(name == "scream")
|
||||
return "";
|
||||
return std::string();
|
||||
|
||||
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
|
||||
{
|
||||
return ptr.getClass().getNpcStats(ptr).getSkill(skill).getModified();
|
||||
return getNpcStats(ptr).getSkill(skill).getModified();
|
||||
}
|
||||
|
||||
int Npc::getBloodTexture(const MWWorld::ConstPtr &ptr) const
|
||||
|
@ -1354,7 +1348,7 @@ namespace MWClass
|
|||
|
||||
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())
|
||||
return;
|
||||
|
||||
|
|
|
@ -265,7 +265,7 @@ namespace MWClass
|
|||
std::string text;
|
||||
|
||||
// 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} ";
|
||||
|
||||
|
@ -295,7 +295,15 @@ namespace MWClass
|
|||
((oneOrTwoHanded != "") ? ", " + store.get<ESM::GameSetting>().find(oneOrTwoHanded)->mValue.getString() : "");
|
||||
|
||||
// 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
|
||||
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
|
||||
{
|
||||
if (hasItemHealth(ptr) && ptr.getCellRef().getCharge() == 0)
|
||||
if (hasItemHealth(ptr) && getItemHealth(ptr) == 0)
|
||||
return std::make_pair(0, "#{sInventoryMessage1}");
|
||||
|
||||
// Do not allow equip weapons from inventory during attack
|
||||
|
@ -391,7 +399,7 @@ namespace MWClass
|
|||
&& MWBase::Environment::get().getWindowManager()->isGuiMode())
|
||||
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())
|
||||
return std::make_pair (0, "");
|
||||
|
|
|
@ -96,7 +96,7 @@ namespace MWGui
|
|||
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;
|
||||
|
||||
|
@ -105,7 +105,11 @@ namespace MWGui
|
|||
MyGUI::IntSize size(mLoadingText->getTextSize().width+padding, mLoadingBox->getHeight());
|
||||
size.width = std::max(300, size.width);
|
||||
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)
|
||||
|
|
|
@ -34,7 +34,7 @@ namespace MWGui
|
|||
virtual ~LoadingScreen();
|
||||
|
||||
/// 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 loadingOff();
|
||||
virtual void setProgressRange (size_t range);
|
||||
|
|
|
@ -51,7 +51,7 @@ void MerchantRepair::setPtr(const MWWorld::Ptr &actor)
|
|||
{
|
||||
int maxDurability = iter->getClass().getItemMaxHealth(*iter);
|
||||
int durability = iter->getClass().getItemHealth(*iter);
|
||||
if (maxDurability == durability)
|
||||
if (maxDurability == durability || maxDurability == 0)
|
||||
continue;
|
||||
|
||||
int basePrice = iter->getClass().getValue(*iter);
|
||||
|
|
|
@ -35,6 +35,11 @@ namespace MWGui
|
|||
}
|
||||
}
|
||||
|
||||
int MessageBoxManager::getMessagesCount()
|
||||
{
|
||||
return mMessageBoxes.size();
|
||||
}
|
||||
|
||||
void MessageBoxManager::clear()
|
||||
{
|
||||
if (mInterMessageBoxe)
|
||||
|
|
|
@ -28,6 +28,8 @@ namespace MWGui
|
|||
bool createInteractiveMessageBox (const std::string& message, const std::vector<std::string>& buttons);
|
||||
bool isInteractiveMessageBox ();
|
||||
|
||||
int getMessagesCount();
|
||||
|
||||
const InteractiveMessageBox* getInteractiveMessageBox() const { return mInterMessageBoxe; }
|
||||
|
||||
/// Remove all message boxes
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/soundmanager.hpp"
|
||||
#include "../mwbase/inputmanager.hpp"
|
||||
#include "../mwbase/mechanicsmanager.hpp"
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
|
||||
#include "confirmationdialog.hpp"
|
||||
|
@ -437,6 +438,7 @@ namespace MWGui
|
|||
MWBase::Environment::get().getSoundManager()->processChangedSettings(changed);
|
||||
MWBase::Environment::get().getWindowManager()->processChangedSettings(changed);
|
||||
MWBase::Environment::get().getInputManager()->processChangedSettings(changed);
|
||||
MWBase::Environment::get().getMechanicsManager()->processChangedSettings(changed);
|
||||
}
|
||||
|
||||
void SettingsWindow::onKeyboardSwitchClicked(MyGUI::Widget* _sender)
|
||||
|
|
|
@ -91,8 +91,7 @@ namespace
|
|||
if (ench->mData.mType == ESM::Enchantment::ConstantEffect)
|
||||
leftChargePercent = 101;
|
||||
else
|
||||
leftChargePercent = (left.mBase.getCellRef().getEnchantmentCharge() == -1) ? 100
|
||||
: static_cast<int>(left.mBase.getCellRef().getEnchantmentCharge() / static_cast<float>(ench->mData.mCharge) * 100);
|
||||
leftChargePercent = static_cast<int>(left.mBase.getCellRef().getNormalizedEnchantmentCharge(ench->mData.mCharge) * 100);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,8 +103,7 @@ namespace
|
|||
if (ench->mData.mType == ESM::Enchantment::ConstantEffect)
|
||||
rightChargePercent = 101;
|
||||
else
|
||||
rightChargePercent = (right.mBase.getCellRef().getEnchantmentCharge() == -1) ? 100
|
||||
: static_cast<int>(right.mBase.getCellRef().getEnchantmentCharge() / static_cast<float>(ench->mData.mCharge) * 100);
|
||||
rightChargePercent = static_cast<int>(right.mBase.getCellRef().getNormalizedEnchantmentCharge(ench->mData.mCharge) * 100);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,8 +35,7 @@ namespace
|
|||
float price = static_cast<float>(item.getClass().getValue(item));
|
||||
if (item.getClass().hasItemHealth(item))
|
||||
{
|
||||
price *= item.getClass().getItemHealth(item);
|
||||
price /= item.getClass().getItemMaxHealth(item);
|
||||
price *= item.getClass().getItemNormalizedHealth(item);
|
||||
}
|
||||
return static_cast<int>(price * count);
|
||||
}
|
||||
|
|
|
@ -1369,8 +1369,7 @@ namespace MWGui
|
|||
const ESM::Enchantment* ench = mStore->get<ESM::Enchantment>()
|
||||
.find(item.getClass().getEnchantment(item));
|
||||
|
||||
int chargePercent = (item.getCellRef().getEnchantmentCharge() == -1) ? 100
|
||||
: static_cast<int>(item.getCellRef().getEnchantmentCharge() / static_cast<float>(ench->mData.mCharge) * 100);
|
||||
int chargePercent = static_cast<int>(item.getCellRef().getNormalizedEnchantmentCharge(ench->mData.mCharge) * 100);
|
||||
mHud->setSelectedEnchantItem(item, chargePercent);
|
||||
mSpellWindow->setTitle(item.getClass().getName(item));
|
||||
}
|
||||
|
@ -1386,7 +1385,7 @@ namespace MWGui
|
|||
int durabilityPercent = 100;
|
||||
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);
|
||||
mInventoryWindow->setTitle(item.getClass().getName(item));
|
||||
|
@ -1671,6 +1670,15 @@ namespace MWGui
|
|||
mHud->setEnemy(enemy);
|
||||
}
|
||||
|
||||
int WindowManager::getMessagesCount() const
|
||||
{
|
||||
int count = 0;
|
||||
if (mMessageBoxManager)
|
||||
count = mMessageBoxManager->getMessagesCount();
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
Loading::Listener* WindowManager::getLoadingScreen()
|
||||
{
|
||||
return mLoadingScreen;
|
||||
|
|
|
@ -316,6 +316,8 @@ namespace MWGui
|
|||
|
||||
virtual void setEnemy (const MWWorld::Ptr& enemy);
|
||||
|
||||
virtual int getMessagesCount() const;
|
||||
|
||||
virtual const Translation::Storage& getTranslationDataStorage() const;
|
||||
|
||||
void onSoulgemDialogButtonPressed (int button);
|
||||
|
|
|
@ -147,9 +147,6 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float
|
|||
|
||||
namespace MWMechanics
|
||||
{
|
||||
const float aiProcessingDistance = 7168;
|
||||
const float sqrAiProcessingDistance = aiProcessingDistance*aiProcessingDistance;
|
||||
|
||||
class SoulTrap : public MWMechanics::EffectSourceVisitor
|
||||
{
|
||||
MWWorld::Ptr mCreature;
|
||||
|
@ -364,7 +361,8 @@ namespace MWMechanics
|
|||
const ESM::Position& actor1Pos = actor1.getRefData().getPosition();
|
||||
const ESM::Position& actor2Pos = actor2.getRefData().getPosition();
|
||||
float sqrDist = (actor1Pos.asVec3() - actor2Pos.asVec3()).length2();
|
||||
if (sqrDist > sqrAiProcessingDistance)
|
||||
|
||||
if (sqrDist > mActorsProcessingRange*mActorsProcessingRange)
|
||||
return;
|
||||
|
||||
// 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
|
||||
|
||||
updateProcessingRange();
|
||||
}
|
||||
|
||||
Actors::~Actors()
|
||||
|
@ -1139,6 +1140,23 @@ namespace MWMechanics
|
|||
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)
|
||||
{
|
||||
removeActor(ptr);
|
||||
|
@ -1184,7 +1202,7 @@ namespace MWMechanics
|
|||
// Otherwise check if any actor in AI processing range sees the target actor
|
||||
std::vector<MWWorld::Ptr> actors;
|
||||
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)
|
||||
{
|
||||
if (*it == actor)
|
||||
|
@ -1242,7 +1260,7 @@ namespace MWMechanics
|
|||
{
|
||||
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)
|
||||
{
|
||||
MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first);
|
||||
|
@ -1288,7 +1306,8 @@ namespace MWMechanics
|
|||
if (timerUpdateEquippedLight >= updateEquippedLightInterval) timerUpdateEquippedLight = 0;
|
||||
|
||||
// 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();
|
||||
const osg::Vec3f playerPos = player.getRefData().getPosition().asVec3();
|
||||
|
@ -1301,7 +1320,7 @@ namespace MWMechanics
|
|||
int attackedByPlayerId = player.getClass().getCreatureStats(player).getHitAttemptActorId();
|
||||
if (attackedByPlayerId != -1)
|
||||
{
|
||||
const MWWorld::Ptr playerHitAttemptActor = MWBase::Environment::get().getWorld()->searchPtrViaActorId(attackedByPlayerId);
|
||||
const MWWorld::Ptr playerHitAttemptActor = world->searchPtrViaActorId(attackedByPlayerId);
|
||||
|
||||
if (!playerHitAttemptActor.isInCell())
|
||||
player.getClass().getCreatureStats(player).setHitAttemptActorId(-1);
|
||||
|
@ -1314,14 +1333,11 @@ namespace MWMechanics
|
|||
CharacterController* ctrl = iter->second->getCharacterController();
|
||||
|
||||
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
|
||||
// (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)
|
||||
// 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;
|
||||
// AI processing is only done within given distance to the player.
|
||||
bool inProcessingRange = distSqr <= mActorsProcessingRange*mActorsProcessingRange;
|
||||
|
||||
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 (iter->first != player && (iter->first.getClass().getCreatureStats(iter->first).isDead()
|
||||
|
@ -1335,10 +1351,10 @@ namespace MWMechanics
|
|||
|
||||
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
|
||||
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
|
||||
// a better solution might be to apply cell changes at the end of the frame
|
||||
|
@ -1363,7 +1379,7 @@ namespace MWMechanics
|
|||
MWWorld::Ptr headTrackTarget;
|
||||
|
||||
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
|
||||
// 2. Actors in combat and pursue mode do not bother to headtrack
|
||||
|
@ -1423,27 +1439,25 @@ namespace MWMechanics
|
|||
CharacterController* playerCharacter = nullptr;
|
||||
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 distSqr = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2();
|
||||
const float dist = (playerPos - iter->first.getRefData().getPosition().asVec3()).length();
|
||||
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)
|
||||
if (isPlayer)
|
||||
activeFlag = 2;
|
||||
int active = inAnimationRange ? 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);
|
||||
}
|
||||
int active = inRange ? activeFlag : 0;
|
||||
|
||||
CharacterController* ctrl = iter->second->getCharacterController();
|
||||
ctrl->setActive(active);
|
||||
|
||||
if (!inAnimationRange)
|
||||
if (!inRange)
|
||||
{
|
||||
iter->first.getRefData().getBaseNode()->setNodeMask(0);
|
||||
world->setActorCollisionMode(iter->first, false);
|
||||
continue;
|
||||
}
|
||||
else if (!isPlayer)
|
||||
iter->first.getRefData().getBaseNode()->setNodeMask(1<<3);
|
||||
|
||||
if (iter->first.getClass().getCreatureStats(iter->first).isParalyzed())
|
||||
ctrl->skipAnim();
|
||||
|
@ -1455,11 +1469,28 @@ namespace MWMechanics
|
|||
playerCharacter = ctrl;
|
||||
continue;
|
||||
}
|
||||
|
||||
world->setActorCollisionMode(iter->first, true);
|
||||
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)
|
||||
{
|
||||
playerCharacter->update(duration);
|
||||
playerCharacter->setVisibility(1.f);
|
||||
}
|
||||
|
||||
for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
|
||||
{
|
||||
|
@ -1572,6 +1603,7 @@ namespace MWMechanics
|
|||
continue;
|
||||
}
|
||||
|
||||
MWBase::Environment::get().getWorld()->removeActorPath(iter->first);
|
||||
CharacterController::KillResult killResult = iter->second->getCharacterController()->kill();
|
||||
if (killResult == CharacterController::Result_DeathAnimStarted)
|
||||
{
|
||||
|
@ -1671,7 +1703,7 @@ namespace MWMechanics
|
|||
restoreDynamicStats(iter->first, sleep);
|
||||
|
||||
if ((!iter->first.getRefData().getBaseNode()) ||
|
||||
(playerPos - iter->first.getRefData().getPosition().asVec3()).length2() > sqrAiProcessingDistance)
|
||||
(playerPos - iter->first.getRefData().getPosition().asVec3()).length2() > mActorsProcessingRange*mActorsProcessingRange)
|
||||
continue;
|
||||
|
||||
adjustMagicEffects (iter->first);
|
||||
|
@ -1915,7 +1947,7 @@ namespace MWMechanics
|
|||
std::list<MWWorld::Ptr> list;
|
||||
std::vector<MWWorld::Ptr> neighbors;
|
||||
osg::Vec3f position (actor.getRefData().getPosition().asVec3());
|
||||
getObjectsInRange(position, aiProcessingDistance, neighbors);
|
||||
getObjectsInRange(position, mActorsProcessingRange, neighbors);
|
||||
for(auto neighbor = neighbors.begin(); neighbor != neighbors.end(); ++neighbor)
|
||||
{
|
||||
if (*neighbor == actor)
|
||||
|
@ -1936,7 +1968,7 @@ namespace MWMechanics
|
|||
std::list<MWWorld::Ptr> list;
|
||||
std::vector<MWWorld::Ptr> neighbors;
|
||||
osg::Vec3f position (actor.getRefData().getPosition().asVec3());
|
||||
getObjectsInRange(position, aiProcessingDistance, neighbors);
|
||||
getObjectsInRange(position, mActorsProcessingRange, neighbors);
|
||||
|
||||
std::set<MWWorld::Ptr> followers;
|
||||
getActorsFollowing(actor, followers);
|
||||
|
|
|
@ -65,6 +65,9 @@ namespace MWMechanics
|
|||
/// paused we may want to do it manually (after equipping permanent enchantment)
|
||||
void updateMagicEffects (const MWWorld::Ptr& ptr);
|
||||
|
||||
void updateProcessingRange();
|
||||
float getProcessingRange() const;
|
||||
|
||||
void addActor (const MWWorld::Ptr& ptr, bool updateImmediately=false);
|
||||
///< Register an actor for stats management
|
||||
///
|
||||
|
@ -168,6 +171,7 @@ namespace MWMechanics
|
|||
private:
|
||||
PtrActorMap mActors;
|
||||
float mTimerDisposeSummonsCorpses;
|
||||
float mActorsProcessingRange;
|
||||
|
||||
};
|
||||
}
|
||||
|
|
|
@ -29,14 +29,13 @@ namespace MWMechanics
|
|||
|
||||
actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing);
|
||||
|
||||
if (target == MWWorld::Ptr() ||
|
||||
!target.getRefData().getCount() || !target.getRefData().isEnabled() // Really we should check whether the target is currently registered
|
||||
// with the MechanicsManager
|
||||
)
|
||||
return true; //Target doesn't exist
|
||||
// Stop if the target doesn't exist
|
||||
// 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;
|
||||
|
||||
//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
|
||||
{
|
||||
|
|
|
@ -26,9 +26,9 @@ bool MWMechanics::AiBreathe::execute (const MWWorld::Ptr& actor, CharacterContro
|
|||
{
|
||||
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);
|
||||
|
||||
return false;
|
||||
|
|
|
@ -37,7 +37,7 @@ bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::Charac
|
|||
if (!target)
|
||||
return true;
|
||||
|
||||
if (!mManual && !pathTo(actor, target.getRefData().getPosition().pos, duration, mDistance))
|
||||
if (!mManual && !pathTo(actor, target.getRefData().getPosition().asVec3(), duration, mDistance))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -128,7 +128,7 @@ namespace MWMechanics
|
|||
float targetReachedTolerance = 0.0f;
|
||||
if (storage.mLOS)
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -307,7 +307,7 @@ namespace MWMechanics
|
|||
osg::Vec3f localPos = actor.getRefData().getPosition().asVec3();
|
||||
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++)
|
||||
{
|
||||
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();
|
||||
if ((dist > fFleeDistance && !storage.mLOS)
|
||||
|| pathTo(actor, storage.mFleeDest, duration))
|
||||
|| pathTo(actor, PathFinder::makeOsgVec3(storage.mFleeDest), duration))
|
||||
{
|
||||
state = AiCombatStorage::FleeState_Idle;
|
||||
}
|
||||
|
|
|
@ -83,24 +83,13 @@ namespace MWMechanics
|
|||
actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false);
|
||||
|
||||
const MWWorld::Ptr follower = MWBase::Environment::get().getWorld()->getPtr(mTargetActorRefId, false);
|
||||
const float* const leaderPos = actor.getRefData().getPosition().pos;
|
||||
const float* const followerPos = follower.getRefData().getPosition().pos;
|
||||
double differenceBetween[3];
|
||||
const osg::Vec3f leaderPos = actor.getRefData().getPosition().asVec3();
|
||||
const osg::Vec3f followerPos = follower.getRefData().getPosition().asVec3();
|
||||
|
||||
for (short counter = 0; counter < 3; counter++)
|
||||
differenceBetween[counter] = (leaderPos[counter] - followerPos[counter]);
|
||||
|
||||
double distanceBetweenResult =
|
||||
(differenceBetween[0] * differenceBetween[0]) + (differenceBetween[1] * differenceBetween[1]) + (differenceBetween[2] *
|
||||
differenceBetween[2]);
|
||||
|
||||
if (distanceBetweenResult <= mMaxDist * mMaxDist)
|
||||
if ((leaderPos - followerPos).length2() <= mMaxDist * mMaxDist)
|
||||
{
|
||||
ESM::Pathgrid::Point point(static_cast<int>(mX), static_cast<int>(mY), static_cast<int>(mZ));
|
||||
point.mAutogenerated = 0;
|
||||
point.mConnectionNum = 0;
|
||||
point.mUnknown = 0;
|
||||
if (pathTo(actor,point,duration)) //Returns true on path complete
|
||||
const osg::Vec3f dest(mX, mY, mZ);
|
||||
if (pathTo(actor, dest, duration)) //Returns true on path complete
|
||||
{
|
||||
mRemainingDuration = mDuration;
|
||||
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)
|
||||
{
|
||||
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
|
||||
// with the MechanicsManager
|
||||
)
|
||||
return false; // Target is not here right now, wait for it to return
|
||||
// Target is not here right now, wait for it to return
|
||||
// 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;
|
||||
|
||||
actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing);
|
||||
|
||||
|
@ -94,6 +94,10 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte
|
|||
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
|
||||
if (!mActive)
|
||||
{
|
||||
|
@ -101,9 +105,7 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte
|
|||
|
||||
if (storage.mTimer < 0)
|
||||
{
|
||||
if ((actor.getRefData().getPosition().asVec3() - target.getRefData().getPosition().asVec3()).length2()
|
||||
< 500*500
|
||||
&& MWBase::Environment::get().getWorld()->getLOS(actor, target))
|
||||
if (targetDir.length2() < 500*500 && MWBase::Environment::get().getWorld()->getLOS(actor, target))
|
||||
mActive = true;
|
||||
storage.mTimer = 0.5f;
|
||||
}
|
||||
|
@ -111,8 +113,6 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte
|
|||
if (!mActive)
|
||||
return false;
|
||||
|
||||
ESM::Position pos = actor.getRefData().getPosition(); //position of the actor
|
||||
|
||||
// The distances below are approximations based on observations of the original engine.
|
||||
// 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.
|
||||
|
@ -145,9 +145,8 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte
|
|||
}
|
||||
}
|
||||
|
||||
if ((pos.pos[0]-mX)*(pos.pos[0]-mX) +
|
||||
(pos.pos[1]-mY)*(pos.pos[1]-mY) +
|
||||
(pos.pos[2]-mZ)*(pos.pos[2]-mZ) < followDistance*followDistance) //Close-ish to final position
|
||||
osg::Vec3f finalPos(mX, mY, mZ);
|
||||
if ((actorPos-finalPos).length2() < followDistance*followDistance) //Close-ish to final position
|
||||
{
|
||||
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 threshold = 30; // to avoid constant switching between moving/stopping
|
||||
|
@ -172,15 +169,9 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte
|
|||
else
|
||||
followDistance += threshold;
|
||||
|
||||
osg::Vec3f targetPos(target.getRefData().getPosition().asVec3());
|
||||
osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
|
||||
|
||||
osg::Vec3f dir = targetPos - actorPos;
|
||||
float targetDistSqr = dir.length2();
|
||||
|
||||
if (targetDistSqr <= followDistance * followDistance)
|
||||
if (targetDir.length2() <= 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)))
|
||||
{
|
||||
|
@ -191,16 +182,14 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte
|
|||
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)
|
||||
{
|
||||
//Check if you're far away
|
||||
float dist = distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]);
|
||||
|
||||
if (dist > 450)
|
||||
if (targetDir.length2() > 450 * 450)
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <components/esm/loadcell.hpp>
|
||||
#include <components/esm/loadland.hpp>
|
||||
#include <components/esm/loadmgef.hpp>
|
||||
#include <components/detournavigator/navigator.hpp>
|
||||
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
|
@ -90,70 +91,65 @@ void MWMechanics::AiPackage::reset()
|
|||
mTimer = AI_REACTION_TIME + 1.0f;
|
||||
mIsShortcutting = false;
|
||||
mShortcutProhibited = false;
|
||||
mShortcutFailPos = ESM::Pathgrid::Point();
|
||||
mShortcutFailPos = osg::Vec3f();
|
||||
|
||||
mPathFinder.clearPath();
|
||||
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
|
||||
|
||||
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
|
||||
//... 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.
|
||||
//... But AI processing distance may increase in the future.
|
||||
if (isNearInactiveCell(pos))
|
||||
if (isNearInactiveCell(position))
|
||||
{
|
||||
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
|
||||
world->updateActorPath(actor, mPathFinder.getPath(), halfExtents, position, dest);
|
||||
return false;
|
||||
}
|
||||
|
||||
// handle path building and shortcutting
|
||||
ESM::Pathgrid::Point start = pos.pos;
|
||||
|
||||
float distToTarget = distance(start, dest);
|
||||
bool isDestReached = (distToTarget <= destTolerance);
|
||||
const float distToTarget = distance(position, dest);
|
||||
const bool isDestReached = (distToTarget <= destTolerance);
|
||||
|
||||
if (!isDestReached && mTimer > AI_REACTION_TIME)
|
||||
{
|
||||
if (actor.getClass().isBipedal(actor))
|
||||
openDoors(actor);
|
||||
|
||||
bool wasShortcutting = mIsShortcutting;
|
||||
const bool wasShortcutting = mIsShortcutting;
|
||||
bool destInLOS = false;
|
||||
|
||||
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);
|
||||
const bool actorCanMoveByZ = canActorMoveByZAxis(actor);
|
||||
|
||||
// Prohibit shortcuts for AiWander, if the actor can not move in 3 dimensions.
|
||||
if (actorCanMoveByZ || getTypeId() != TypeIdWander)
|
||||
mIsShortcutting = shortcutPath(start, dest, actor, &destInLOS, actorCanMoveByZ); // try to shortcut first
|
||||
mIsShortcutting = actorCanMoveByZ
|
||||
&& shortcutPath(position, dest, actor, &destInLOS, actorCanMoveByZ); // try to shortcut first
|
||||
|
||||
if (!mIsShortcutting)
|
||||
{
|
||||
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;
|
||||
|
||||
// give priority to go directly on target if there is minimal opportunity
|
||||
if (destInLOS && mPathFinder.getPath().size() > 1)
|
||||
{
|
||||
// get point just before dest
|
||||
std::list<ESM::Pathgrid::Point>::const_iterator pPointBeforeDest = mPathFinder.getPath().end();
|
||||
--pPointBeforeDest;
|
||||
--pPointBeforeDest;
|
||||
auto pPointBeforeDest = mPathFinder.getPath().rbegin() + 1;
|
||||
|
||||
// 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.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
|
||||
{
|
||||
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
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
zTurn(actor, getZAngleToPoint(start, dest));
|
||||
smoothTurn(actor, getXAngleToPoint(start, dest), 0);
|
||||
zTurn(actor, getZAngleToPoint(position, dest));
|
||||
smoothTurn(actor, getXAngleToPoint(position, dest), 0);
|
||||
world->removeActorPath(actor);
|
||||
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
|
||||
evadeObstacles(actor, duration, pos);
|
||||
world->updateActorPath(actor, mPathFinder.getPath(), halfExtents, position, dest);
|
||||
|
||||
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
|
||||
zTurn(actor, mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]));
|
||||
smoothTurn(actor, mPathFinder.getXAngleToNext(pos.pos[0], pos.pos[1], pos.pos[2]), 0);
|
||||
zTurn(actor, mPathFinder.getZAngleToNext(position.x(), position.y()));
|
||||
smoothTurn(actor, mPathFinder.getXAngleToNext(position.x(), position.y(), position.z()), 0);
|
||||
|
||||
mObstacleCheck.update(actor, duration);
|
||||
|
||||
// handle obstacles on the way
|
||||
evadeObstacles(actor);
|
||||
|
||||
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
|
||||
if (!mObstacleCheck.check(actor, duration)) return;
|
||||
if (!mObstacleCheck.isEvading()) return;
|
||||
|
||||
// first check if obstacle is a door
|
||||
static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance();
|
||||
|
@ -219,13 +218,14 @@ void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor, float dur
|
|||
}
|
||||
else
|
||||
{
|
||||
mObstacleCheck.takeEvasiveAction(movement);
|
||||
mObstacleCheck.takeEvasiveAction(actor.getClass().getMovementSettings(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);
|
||||
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 ))
|
||||
{
|
||||
MWBase::Environment::get().getWorld()->activate(door, actor);
|
||||
world->activate(door, actor);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -248,7 +248,7 @@ void MWMechanics::AiPackage::openDoors(const MWWorld::Ptr& actor)
|
|||
MWWorld::Ptr keyPtr = invStore.search(keyId);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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
|
||||
isPathClear = !MWBase::Environment::get().getWorld()->castRay(
|
||||
static_cast<float>(startPoint.mX), static_cast<float>(startPoint.mY), static_cast<float>(startPoint.mZ),
|
||||
static_cast<float>(endPoint.mX), static_cast<float>(endPoint.mY), static_cast<float>(endPoint.mZ));
|
||||
startPoint.x(), startPoint.y(), startPoint.z(),
|
||||
endPoint.x(), endPoint.y(), endPoint.z());
|
||||
|
||||
if (destInLOS != nullptr) *destInLOS = isPathClear;
|
||||
|
||||
|
@ -294,46 +295,44 @@ bool MWMechanics::AiPackage::shortcutPath(const ESM::Pathgrid::Point& startPoint
|
|||
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))
|
||||
|| MWBase::Environment::get().getWorld()->isFlying(actor);
|
||||
|
||||
if (actorCanMoveByZ)
|
||||
if (canActorMoveByZAxis(actor))
|
||||
return true;
|
||||
|
||||
float actorSpeed = actor.getClass().getSpeed(actor);
|
||||
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 actorSpeed = actor.getClass().getSpeed(actor);
|
||||
const float maxAvoidDist = AI_REACTION_TIME * actorSpeed + actorSpeed / MAX_VEL_ANGULAR_RADIANS * 2; // *2 - for reliability
|
||||
const float distToTarget = osg::Vec2f(endPoint.x(), endPoint.y()).length();
|
||||
|
||||
float offsetXY = distToTarget > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2;
|
||||
|
||||
bool isClear = checkWayIsClear(PathFinder::MakeOsgVec3(startPoint), PathFinder::MakeOsgVec3(endPoint), offsetXY);
|
||||
const float offsetXY = distToTarget > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2;
|
||||
|
||||
// update shortcut prohibit state
|
||||
if (isClear)
|
||||
if (checkWayIsClear(startPoint, endPoint, offsetXY))
|
||||
{
|
||||
if (mShortcutProhibited)
|
||||
{
|
||||
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;
|
||||
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)
|
||||
|
@ -343,23 +342,22 @@ bool MWMechanics::AiPackage::isTargetMagicallyHidden(const MWWorld::Ptr& target)
|
|||
|| (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());
|
||||
if (playerCell->isExterior())
|
||||
{
|
||||
// get actor's distance from origin of center cell
|
||||
osg::Vec3f actorOffset(actorPos.asVec3());
|
||||
CoordinateConverter(playerCell).toLocal(actorOffset);
|
||||
CoordinateConverter(playerCell).toLocal(position);
|
||||
|
||||
// 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
|
||||
// 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;
|
||||
float minThreshold = (-1.0f * ESM::Land::REAL_SIZE) + distanceFromEdge;
|
||||
float maxThreshold = (2.0f * ESM::Land::REAL_SIZE) - distanceFromEdge;
|
||||
return (actorOffset[0] < minThreshold) || (maxThreshold < actorOffset[0])
|
||||
|| (actorOffset[1] < minThreshold) || (maxThreshold < actorOffset[1]);
|
||||
return (position.x() < minThreshold) || (maxThreshold < position.x())
|
||||
|| (position.y() < minThreshold) || (maxThreshold < position.y());
|
||||
}
|
||||
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
|
||||
float speed = actor.getClass().getSpeed(actor);
|
||||
|
@ -383,15 +381,37 @@ bool MWMechanics::AiPackage::isReachableRotatingOnTheRun(const MWWorld::Ptr& act
|
|||
radiusDir *= radius;
|
||||
|
||||
// pick up the nearest center candidate
|
||||
osg::Vec3f dest_ = PathFinder::MakeOsgVec3(dest);
|
||||
osg::Vec3f pos = actor.getRefData().getPosition().asVec3();
|
||||
osg::Vec3f center1 = 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:
|
||||
// no points of actor's circle should be farther from the center than destination point
|
||||
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 "aistate.hpp"
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class Ptr;
|
||||
|
@ -105,29 +107,35 @@ namespace MWMechanics
|
|||
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.
|
||||
static bool isReachableRotatingOnTheRun(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest);
|
||||
static bool isReachableRotatingOnTheRun(const MWWorld::Ptr& actor, const osg::Vec3f& dest);
|
||||
|
||||
protected:
|
||||
/// Handles path building and shortcutting with obstacles avoiding
|
||||
/** \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
|
||||
/// 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
|
||||
/// \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
|
||||
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);
|
||||
|
||||
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
|
||||
PathFinder mPathFinder;
|
||||
ObstacleCheck mObstacleCheck;
|
||||
|
@ -137,16 +145,14 @@ namespace MWMechanics
|
|||
std::string mTargetActorRefId;
|
||||
mutable int mTargetActorId;
|
||||
|
||||
osg::Vec3f mLastActorPos;
|
||||
|
||||
short mRotateOnTheRunChecks; // attempts to check rotation to the pathpoint on the run possibility
|
||||
|
||||
bool mIsShortcutting; // if shortcutting at the moment
|
||||
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:
|
||||
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
|
||||
|
||||
if(target == MWWorld::Ptr() || !target.getRefData().getCount() || !target.getRefData().isEnabled() // Really we should be checking whether the target is currently registered
|
||||
// with the MechanicsManager
|
||||
)
|
||||
return true; //Target doesn't exist
|
||||
// Stop if the target doesn't exist
|
||||
// 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;
|
||||
|
||||
if (isTargetMagicallyHidden(target))
|
||||
return true;
|
||||
|
||||
if(target.getClass().getCreatureStats(target).isDead())
|
||||
if (target.getClass().getCreatureStats(target).isDead())
|
||||
return true;
|
||||
|
||||
actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing);
|
||||
|
||||
//Set the target desition from the actor
|
||||
ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos;
|
||||
ESM::Position aPos = actor.getRefData().getPosition();
|
||||
//Set the target destination
|
||||
const osg::Vec3f dest = target.getRefData().getPosition().asVec3();
|
||||
const osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3();
|
||||
|
||||
float pathTolerance = 100.0;
|
||||
const float pathTolerance = 100.f;
|
||||
|
||||
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
|
||||
return true;
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
#include <components/esm/aisequence.hpp>
|
||||
#include <components/esm/loadcell.hpp>
|
||||
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/mechanicsmanager.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
||||
#include "../mwworld/class.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.
|
||||
// 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.
|
||||
return (pos1 - pos2).length2() <= 7168*7168;
|
||||
// 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.
|
||||
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)
|
||||
{
|
||||
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).setDrawState(DrawState_Nothing);
|
||||
|
||||
if (!isWithinMaxRange(osg::Vec3f(mX, mY, mZ), pos.asVec3()))
|
||||
if (!isWithinMaxRange(targetPos, actorPos))
|
||||
return false;
|
||||
|
||||
// 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.
|
||||
int destinationTolerance = 64;
|
||||
ESM::Pathgrid::Point dest(static_cast<int>(mX), static_cast<int>(mY), static_cast<int>(mZ));
|
||||
if (distance(pos.pos, dest) <= destinationTolerance)
|
||||
if (distance(actorPos, targetPos) <= destinationTolerance)
|
||||
{
|
||||
std::vector<MWWorld::Ptr> 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;
|
||||
return true;
|
||||
|
|
|
@ -50,7 +50,8 @@ namespace MWMechanics
|
|||
|
||||
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),
|
||||
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);
|
||||
init();
|
||||
|
@ -123,14 +124,13 @@ namespace MWMechanics
|
|||
*/
|
||||
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);
|
||||
if(cStats.isDead() || cStats.getHealth().getCurrent() <= 0)
|
||||
if (cStats.isDead() || cStats.getHealth().getCurrent() <= 0)
|
||||
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);
|
||||
if(!currentCell || cellChange)
|
||||
{
|
||||
|
@ -151,16 +151,23 @@ namespace MWMechanics
|
|||
// rebuild a path to it
|
||||
if (!mPathFinder.isPathConstructed() && mHasDestination)
|
||||
{
|
||||
ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(mDestination));
|
||||
ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos));
|
||||
|
||||
mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell()));
|
||||
if (mUsePathgrid)
|
||||
{
|
||||
mPathFinder.buildPathByPathgrid(pos.asVec3(), mDestination, 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())
|
||||
storage.setState(AiWanderStorage::Wander_Walking);
|
||||
}
|
||||
|
||||
doPerFrameActionsForState(actor, duration, storage, pos);
|
||||
doPerFrameActionsForState(actor, duration, storage);
|
||||
|
||||
playIdleDialogueRandomly(actor);
|
||||
|
||||
|
@ -169,14 +176,14 @@ namespace MWMechanics
|
|||
if (AI_REACTION_TIME <= lastReaction)
|
||||
{
|
||||
lastReaction = 0;
|
||||
return reactionTimeActions(actor, storage, currentCell, cellChange, pos, duration);
|
||||
return reactionTimeActions(actor, storage, currentCell, cellChange, pos);
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
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)
|
||||
storage.mCanWanderAlongPathGrid = false;
|
||||
|
@ -226,7 +233,7 @@ namespace MWMechanics
|
|||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
@ -251,7 +258,9 @@ namespace MWMechanics
|
|||
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);
|
||||
}
|
||||
|
||||
|
@ -268,9 +277,7 @@ namespace MWMechanics
|
|||
if (mHasDestination)
|
||||
return mDestination;
|
||||
|
||||
const ESM::Pathgrid::Point currentPosition = actor.getRefData().getPosition().pos;
|
||||
const osg::Vec3f currentPositionVec3f = osg::Vec3f(currentPosition.mX, currentPosition.mY, currentPosition.mZ);
|
||||
return currentPositionVec3f;
|
||||
return actor.getRefData().getPosition().asVec3();
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
void AiWander::wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance) {
|
||||
const ESM::Pathgrid::Point currentPosition = actor.getRefData().getPosition().pos;
|
||||
const osg::Vec3f currentPositionVec3f = osg::Vec3f(currentPosition.mX, currentPosition.mY, currentPosition.mZ);
|
||||
const auto currentPosition = actor.getRefData().getPosition().asVec3();
|
||||
|
||||
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);
|
||||
do {
|
||||
// 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 destinationY = mInitialActorPosition.y() + wanderRadius * std::sin(randomDirection);
|
||||
const float destinationZ = mInitialActorPosition.z();
|
||||
destinationPosition = ESM::Pathgrid::Point(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
|
||||
if ((!isWaterCreature && !destinationIsAtWater(actor, mDestination)) ||
|
||||
(isWaterCreature && !destinationThroughGround(currentPositionVec3f, mDestination))) {
|
||||
mPathFinder.buildSyncedPath(currentPosition, destinationPosition, actor.getCell(), getPathGridGraph(actor.getCell()));
|
||||
mPathFinder.addPointToPath(destinationPosition);
|
||||
(isWaterCreature && !destinationThroughGround(currentPosition, mDestination)))
|
||||
{
|
||||
// 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())
|
||||
{
|
||||
storage.setState(AiWanderStorage::Wander_Walking, true);
|
||||
mHasDestination = true;
|
||||
mUsePathgrid = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -348,7 +357,7 @@ namespace MWMechanics
|
|||
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)
|
||||
{
|
||||
|
@ -357,7 +366,7 @@ namespace MWMechanics
|
|||
break;
|
||||
|
||||
case AiWanderStorage::Wander_Walking:
|
||||
onWalkingStatePerFrameActions(actor, duration, storage, pos);
|
||||
onWalkingStatePerFrameActions(actor, duration, storage);
|
||||
break;
|
||||
|
||||
case AiWanderStorage::Wander_ChooseAction:
|
||||
|
@ -413,11 +422,10 @@ namespace MWMechanics
|
|||
}
|
||||
}
|
||||
|
||||
void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor,
|
||||
float duration, AiWanderStorage& storage, ESM::Position& pos)
|
||||
void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage)
|
||||
{
|
||||
// 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);
|
||||
storage.setState(AiWanderStorage::Wander_ChooseAction);
|
||||
|
@ -425,7 +433,7 @@ namespace MWMechanics
|
|||
else
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
|
||||
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())
|
||||
{
|
||||
|
@ -507,9 +515,9 @@ namespace MWMechanics
|
|||
// Only say Idle voices when player is in LOS
|
||||
// A bit counterintuitive, likely vanilla did this to reduce the appearance of
|
||||
// voices going through walls?
|
||||
const ESM::Position& pos = actor.getRefData().getPosition();
|
||||
if (roll < x && (player.getRefData().getPosition().asVec3() - pos.asVec3()).length2()
|
||||
< 3000 * 3000 // maybe should be fAudioVoiceDefaultMaxDistance*fAudioMaxDistanceMult instead
|
||||
const osg::Vec3f playerPos(player.getRefData().getPosition().asVec3());
|
||||
const osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
|
||||
if (roll < x && (playerPos - actorPos).length2() < 3000 * 3000 // maybe should be fAudioVoiceDefaultMaxDistance*fAudioMaxDistanceMult instead
|
||||
&& MWBase::Environment::get().getWorld()->getLOS(player, actor))
|
||||
MWBase::Environment::get().getDialogueManager()->say(actor, "idle");
|
||||
}
|
||||
|
@ -528,13 +536,12 @@ namespace MWMechanics
|
|||
MWWorld::Ptr player = getPlayer();
|
||||
osg::Vec3f playerPos(player.getRefData().getPosition().asVec3());
|
||||
osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
|
||||
float playerDistSqr = (playerPos - actorPos).length2();
|
||||
|
||||
int& greetingTimer = storage.mGreetingTimer;
|
||||
AiWanderStorage::GreetingState& greetingState = storage.mSaidGreeting;
|
||||
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)
|
||||
&& MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, actor))
|
||||
greetingTimer++;
|
||||
|
@ -570,7 +577,7 @@ namespace MWMechanics
|
|||
if (greetingState == AiWanderStorage::Greet_Done)
|
||||
{
|
||||
float resetDist = 2 * helloDistance;
|
||||
if (playerDistSqr >= resetDist*resetDist)
|
||||
if ((playerPos - actorPos).length2() >= resetDist*resetDist)
|
||||
greetingState = AiWanderStorage::Greet_None;
|
||||
}
|
||||
}
|
||||
|
@ -592,15 +599,17 @@ namespace MWMechanics
|
|||
ToWorldCoordinates(dest, storage.mCell->getCell());
|
||||
|
||||
// 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
|
||||
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())
|
||||
{
|
||||
mDestination = osg::Vec3f(dest.mX, dest.mY, dest.mZ);
|
||||
mDestination = destVec3f;
|
||||
mHasDestination = true;
|
||||
mUsePathgrid = true;
|
||||
// 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];
|
||||
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
|
||||
// at the end of playing idle?) If the door is opened then re-calculate
|
||||
// allowed nodes starting from the spawn point.
|
||||
std::list<ESM::Pathgrid::Point> paths = pathfinder.getPath();
|
||||
auto paths = pathfinder.getPath();
|
||||
while(paths.size() >= 2)
|
||||
{
|
||||
ESM::Pathgrid::Point pt = paths.back();
|
||||
const auto pt = paths.back();
|
||||
for(unsigned int j = 0; j < nodes.size(); j++)
|
||||
{
|
||||
// FIXME: doesn't handle a door with the same X/Y
|
||||
// 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);
|
||||
break;
|
||||
|
@ -731,7 +740,7 @@ namespace MWMechanics
|
|||
ESM::Pathgrid::Point worldDest = dest;
|
||||
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
|
||||
if (isPathGridOccupied)
|
||||
|
@ -752,18 +761,18 @@ namespace MWMechanics
|
|||
ESM::Pathgrid::Point connDest = points[randomIndex];
|
||||
|
||||
// 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();
|
||||
dir.normalize();
|
||||
|
||||
for (int j = 1; j <= 3; j++)
|
||||
{
|
||||
// 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;
|
||||
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)
|
||||
break;
|
||||
|
@ -799,7 +808,7 @@ namespace MWMechanics
|
|||
const ESM::Pathgrid *pathgrid =
|
||||
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);
|
||||
}
|
||||
|
@ -832,7 +841,7 @@ namespace MWMechanics
|
|||
CoordinateConverter(cell).toLocal(npcPos);
|
||||
|
||||
// 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
|
||||
// and if the point is connected to the closest current point
|
||||
|
@ -840,7 +849,7 @@ namespace MWMechanics
|
|||
int pointIndex = 0;
|
||||
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 &&
|
||||
getPathGridGraph(cellStore).isPointConnected(closestPointIndex, counter))
|
||||
{
|
||||
|
@ -867,7 +876,7 @@ namespace MWMechanics
|
|||
// 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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
osg::Vec3f vectorStart = PathFinder::MakeOsgVec3(start);
|
||||
osg::Vec3f delta = PathFinder::MakeOsgVec3(end) - vectorStart;
|
||||
osg::Vec3f vectorStart = PathFinder::makeOsgVec3(start);
|
||||
osg::Vec3f delta = PathFinder::makeOsgVec3(end) - vectorStart;
|
||||
float length = delta.length();
|
||||
delta.normalize();
|
||||
|
||||
|
@ -889,7 +898,7 @@ namespace MWMechanics
|
|||
// must not travel longer than distance between waypoints or NPC goes past waypoint
|
||||
distance = std::min(distance, static_cast<int>(length));
|
||||
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)
|
||||
|
@ -898,7 +907,7 @@ namespace MWMechanics
|
|||
unsigned int index = 0;
|
||||
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();
|
||||
if (tempDist < distanceToClosestNode)
|
||||
{
|
||||
|
|
|
@ -137,15 +137,15 @@ namespace MWMechanics
|
|||
short unsigned getRandomIdle();
|
||||
void setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos);
|
||||
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 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 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);
|
||||
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);
|
||||
void wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance);
|
||||
bool destinationIsAtWater(const MWWorld::Ptr &actor, const osg::Vec3f& destination);
|
||||
|
@ -164,6 +164,7 @@ namespace MWMechanics
|
|||
|
||||
bool mHasDestination;
|
||||
osg::Vec3f mDestination;
|
||||
bool mUsePathgrid;
|
||||
|
||||
void getNeighbouringNodes(ESM::Pathgrid::Point dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points);
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include "../mwrender/animation.hpp"
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/mechanicsmanager.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/soundmanager.hpp"
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
|
@ -1849,7 +1850,7 @@ void CharacterController::updateAnimQueue()
|
|||
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();
|
||||
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);
|
||||
}
|
||||
|
||||
if (!mMovementAnimationControlled)
|
||||
if (!animationOnly && !mMovementAnimationControlled)
|
||||
world->queueMovement(mPtr, vec);
|
||||
}
|
||||
else
|
||||
else if (!animationOnly)
|
||||
// We must always queue movement, even if there is none, to apply gravity.
|
||||
world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f));
|
||||
|
||||
|
@ -2261,7 +2262,8 @@ void CharacterController::update(float duration)
|
|||
playDeath(1.f, mDeathState);
|
||||
}
|
||||
// We must always queue movement, even if there is none, to apply gravity.
|
||||
world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f));
|
||||
if (!animationOnly)
|
||||
world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f));
|
||||
}
|
||||
|
||||
bool isPersist = isPersistentAnimPlaying();
|
||||
|
@ -2295,7 +2297,7 @@ void CharacterController::update(float duration)
|
|||
moved.z() = 1.0;
|
||||
|
||||
// Update movement
|
||||
if(mMovementAnimationControlled && mPtr.getClass().isActor())
|
||||
if(!animationOnly && mMovementAnimationControlled && mPtr.getClass().isActor())
|
||||
world->queueMovement(mPtr, moved);
|
||||
|
||||
mSkipAnim = false;
|
||||
|
@ -2521,6 +2523,7 @@ void CharacterController::resurrect()
|
|||
mAnimation->disable(mCurrentDeath);
|
||||
mCurrentDeath.clear();
|
||||
mDeathState = CharState_None;
|
||||
mWeaponType = WeapType_None;
|
||||
}
|
||||
|
||||
void CharacterController::updateContinuousVfx()
|
||||
|
@ -2544,20 +2547,6 @@ void CharacterController::updateMagicEffects()
|
|||
{
|
||||
if (!mPtr.getClass().isActor())
|
||||
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;
|
||||
mAnimation->setVampire(vampire);
|
||||
|
@ -2566,6 +2555,32 @@ void CharacterController::updateMagicEffects()
|
|||
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()
|
||||
{
|
||||
float *move = mPtr.getClass().getMovementSettings(mPtr).mPosition;
|
||||
|
|
|
@ -257,7 +257,7 @@ public:
|
|||
|
||||
void updatePtr(const MWWorld::Ptr &ptr);
|
||||
|
||||
void update(float duration);
|
||||
void update(float duration, bool animationOnly=false);
|
||||
|
||||
void persistAnimationState();
|
||||
void unpersistAnimationState();
|
||||
|
@ -292,6 +292,7 @@ public:
|
|||
bool isTurning() const;
|
||||
bool isAttackingOrSpell() const;
|
||||
|
||||
void setVisibility(float visibility);
|
||||
void setAttackingOrSpell(bool attackingOrSpell);
|
||||
void castSpell(const std::string spellId, bool manualSpell=false);
|
||||
void setAIAttackType(const std::string& attackType);
|
||||
|
|
|
@ -371,11 +371,9 @@ namespace MWMechanics
|
|||
return;
|
||||
|
||||
const bool weaphashealth = weapon.getClass().hasItemHealth(weapon);
|
||||
if(weaphashealth)
|
||||
if (weaphashealth)
|
||||
{
|
||||
int weaphealth = weapon.getClass().getItemHealth(weapon);
|
||||
int weapmaxhealth = weapon.getClass().getItemMaxHealth(weapon);
|
||||
damage *= (float(weaphealth) / weapmaxhealth);
|
||||
damage *= weapon.getClass().getItemNormalizedHealth(weapon);
|
||||
}
|
||||
|
||||
static const float fDamageStrengthBase = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
|
||||
|
|
|
@ -33,11 +33,12 @@ namespace MWMechanics
|
|||
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(
|
||||
static_cast<float>(point.mX - mCellX),
|
||||
static_cast<float>(point.mY - mCellY),
|
||||
static_cast<float>(point.mZ));
|
||||
point.x() - static_cast<float>(mCellX),
|
||||
point.y() - static_cast<float>(mCellY),
|
||||
point.z()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ namespace MWMechanics
|
|||
/// in-place conversion from world to local
|
||||
void toLocal(osg::Vec3f& point);
|
||||
|
||||
osg::Vec3f toLocalVec3(const ESM::Pathgrid::Point& point);
|
||||
osg::Vec3f toLocalVec3(const osg::Vec3f& point);
|
||||
|
||||
private:
|
||||
int mCellX;
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include "../mwworld/ptr.hpp"
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/statemanager.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
#include "../mwbase/dialoguemanager.hpp"
|
||||
|
@ -431,6 +432,29 @@ namespace MWMechanics
|
|||
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)
|
||||
{
|
||||
return mActors.isActorDetected(actor, observer);
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#ifndef GAME_MWMECHANICS_MECHANICSMANAGERIMP_H
|
||||
#define GAME_MWMECHANICS_MECHANICSMANAGERIMP_H
|
||||
|
||||
#include <components/settings/settings.hpp>
|
||||
|
||||
#include "../mwbase/mechanicsmanager.hpp"
|
||||
|
||||
#include "../mwworld/ptr.hpp"
|
||||
|
@ -52,71 +54,71 @@ namespace MWMechanics
|
|||
|
||||
MechanicsManager();
|
||||
|
||||
virtual void add (const MWWorld::Ptr& ptr);
|
||||
virtual void add (const MWWorld::Ptr& ptr) override;
|
||||
///< 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
|
||||
|
||||
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
|
||||
|
||||
virtual void drop(const MWWorld::CellStore *cellStore);
|
||||
virtual void drop(const MWWorld::CellStore *cellStore) override;
|
||||
///< 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
|
||||
/// GUI accordingly.
|
||||
|
||||
virtual void update (float duration, bool paused);
|
||||
virtual void update (float duration, bool paused) override;
|
||||
///< Update objects
|
||||
///
|
||||
/// \param paused In game type does not currently advance (this usually means some GUI
|
||||
/// 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.
|
||||
|
||||
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.
|
||||
|
||||
virtual void setPlayerBirthsign (const std::string& id);
|
||||
virtual void setPlayerBirthsign (const std::string& id) override;
|
||||
///< Set player birthsign.
|
||||
|
||||
virtual void setPlayerClass (const std::string& id);
|
||||
virtual void setPlayerClass (const std::string& id) override;
|
||||
///< 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.
|
||||
|
||||
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.
|
||||
/// @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
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
/// 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.
|
||||
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
|
||||
|
@ -126,122 +128,119 @@ namespace MWMechanics
|
|||
* @return was the crime seen?
|
||||
*/
|
||||
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
|
||||
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
|
||||
/// @note No-op for non-player attackers
|
||||
virtual void actorKilled (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker);
|
||||
|
||||
/// 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);
|
||||
virtual void actorKilled (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker) override;
|
||||
|
||||
/// 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
|
||||
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
|
||||
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.
|
||||
/// @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
|
||||
/// @return Success or error
|
||||
virtual bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist=false);
|
||||
virtual void skipAnimation(const MWWorld::Ptr& ptr);
|
||||
virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string &groupName);
|
||||
virtual void persistAnimationStates();
|
||||
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) override;
|
||||
virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string &groupName) override;
|
||||
virtual void persistAnimationStates() override;
|
||||
|
||||
/// 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)
|
||||
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 getActorsInRange(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) override;
|
||||
|
||||
/// 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> getActorsFollowing(const MWWorld::Ptr& actor);
|
||||
virtual std::list<int> getActorsFollowingIndices(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) override;
|
||||
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> getEnemiesNearby(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) override;
|
||||
|
||||
/// 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
|
||||
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 isAIActive();
|
||||
virtual bool toggleAI() override;
|
||||
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?
|
||||
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
|
||||
/// 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).
|
||||
/// <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?
|
||||
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?
|
||||
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 applyWerewolfAcrobatics(const MWWorld::Ptr& actor);
|
||||
virtual void setWerewolf(const MWWorld::Ptr& actor, bool werewolf) override;
|
||||
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 isRunning(const MWWorld::Ptr& ptr);
|
||||
virtual bool isSneaking(const MWWorld::Ptr& ptr);
|
||||
virtual bool isAttackPreparing(const MWWorld::Ptr& ptr) override;
|
||||
virtual bool isRunning(const MWWorld::Ptr& ptr) override;
|
||||
virtual bool isSneaking(const MWWorld::Ptr& ptr) override;
|
||||
|
||||
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 reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim,
|
||||
OffenseType type, int arg=0);
|
||||
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -96,11 +96,6 @@ namespace MWMechanics
|
|||
mEvadeDuration = 0;
|
||||
}
|
||||
|
||||
bool ObstacleCheck::isNormalState() const
|
||||
{
|
||||
return mWalkState == State_Norm;
|
||||
}
|
||||
|
||||
bool ObstacleCheck::isEvading() const
|
||||
{
|
||||
return mWalkState == State_Evade;
|
||||
|
@ -128,7 +123,7 @@ namespace MWMechanics
|
|||
* 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();
|
||||
ESM::Position pos = actor.getRefData().getPosition();
|
||||
|
@ -180,9 +175,7 @@ namespace MWMechanics
|
|||
case State_Evade:
|
||||
{
|
||||
mEvadeDuration += duration;
|
||||
if(mEvadeDuration < DURATION_TO_EVADE)
|
||||
return true;
|
||||
else
|
||||
if(mEvadeDuration >= DURATION_TO_EVADE)
|
||||
{
|
||||
// tried to evade, assume all is ok and start again
|
||||
mWalkState = State_Norm;
|
||||
|
@ -191,10 +184,9 @@ namespace MWMechanics
|
|||
}
|
||||
/* 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[1] = evadeDirections[mEvadeDirectionIndex][1];
|
||||
|
|
|
@ -27,15 +27,13 @@ namespace MWMechanics
|
|||
// Clear the timers and set the state machine to default
|
||||
void clear();
|
||||
|
||||
bool isNormalState() const;
|
||||
bool isEvading() const;
|
||||
|
||||
// Returns true if there is an obstacle and an evasive action
|
||||
// should be taken
|
||||
bool check(const MWWorld::Ptr& actor, float duration, float scaleMinimumDistance = 1.0f);
|
||||
// Updates internal state, call each frame for moving actor
|
||||
void update(const MWWorld::Ptr& actor, float duration, float scaleMinimumDistance = 1.0f);
|
||||
|
||||
// change direction to try to fix "stuck" actor
|
||||
void takeEvasiveAction(MWMechanics::Movement& actorMovement);
|
||||
void takeEvasiveAction(MWMechanics::Movement& actorMovement) const;
|
||||
|
||||
private:
|
||||
|
||||
|
|
|
@ -1,13 +1,20 @@
|
|||
#include "pathfinding.hpp"
|
||||
|
||||
#include <iterator>
|
||||
#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/environment.hpp"
|
||||
|
||||
#include "../mwphysics/collisiontype.hpp"
|
||||
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
#include "../mwworld/class.hpp"
|
||||
|
||||
#include "pathgrid.hpp"
|
||||
#include "coordinateconverter.hpp"
|
||||
|
@ -29,7 +36,7 @@ namespace
|
|||
// points to a quadtree may help
|
||||
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)
|
||||
{
|
||||
// found a closer one
|
||||
|
@ -57,56 +64,19 @@ namespace
|
|||
(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
|
||||
{
|
||||
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)
|
||||
{
|
||||
osg::Vec3f dir = to - from;
|
||||
|
@ -121,18 +91,6 @@ namespace MWMechanics
|
|||
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
|
||||
* 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
|
||||
* 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
|
||||
|
@ -169,52 +127,44 @@ namespace MWMechanics
|
|||
* j = @.x in local coordinates (i.e. within the cell)
|
||||
* k = @.x in world coordinates
|
||||
*/
|
||||
void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint,
|
||||
const ESM::Pathgrid::Point &endPoint,
|
||||
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph)
|
||||
void PathFinder::buildPathByPathgridImpl(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
||||
const PathgridGraph& pathgridGraph, std::back_insert_iterator<std::deque<osg::Vec3f>> out)
|
||||
{
|
||||
mPath.clear();
|
||||
|
||||
// TODO: consider removing mCell / mPathgrid in favor of mPathgridGraph
|
||||
if(mCell != cell || !mPathgrid)
|
||||
{
|
||||
mCell = cell;
|
||||
mPathgrid = pathgridGraph.getPathgrid();
|
||||
}
|
||||
const auto pathgrid = pathgridGraph.getPathgrid();
|
||||
|
||||
// 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
|
||||
// physics take care of any blockages.
|
||||
if(!mPathgrid || mPathgrid->mPoints.empty())
|
||||
if(!pathgrid || pathgrid->mPoints.empty())
|
||||
{
|
||||
mPath.push_back(endPoint);
|
||||
*out++ = endPoint;
|
||||
return;
|
||||
}
|
||||
|
||||
// NOTE: GetClosestPoint expects local coordinates
|
||||
// NOTE: getClosestPoint expects local coordinates
|
||||
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
|
||||
// outside an area enclosed by walls, but there is a pathgrid
|
||||
// point right behind the wall that is closer than any pathgrid
|
||||
// point outside the wall
|
||||
osg::Vec3f startPointInLocalCoords(converter.toLocalVec3(startPoint));
|
||||
int startNode = GetClosestPoint(mPathgrid, startPointInLocalCoords);
|
||||
int startNode = getClosestPoint(pathgrid, startPointInLocalCoords);
|
||||
|
||||
osg::Vec3f endPointInLocalCoords(converter.toLocalVec3(endPoint));
|
||||
std::pair<int, bool> endNode = getClosestReachablePoint(mPathgrid, &pathgridGraph,
|
||||
std::pair<int, bool> endNode = getClosestReachablePoint(pathgrid, &pathgridGraph,
|
||||
endPointInLocalCoords,
|
||||
startNode);
|
||||
|
||||
// if it's shorter for actor to travel from start to end, than to travel from either
|
||||
// start or end to nearest pathgrid point, just travel from start to end.
|
||||
float startToEndLength2 = (endPointInLocalCoords - startPointInLocalCoords).length2();
|
||||
float endTolastNodeLength2 = DistanceSquared(mPathgrid->mPoints[endNode.first], endPointInLocalCoords);
|
||||
float startTo1stNodeLength2 = DistanceSquared(mPathgrid->mPoints[startNode], startPointInLocalCoords);
|
||||
float endTolastNodeLength2 = distanceSquared(pathgrid->mPoints[endNode.first], endPointInLocalCoords);
|
||||
float startTo1stNodeLength2 = distanceSquared(pathgrid->mPoints[startNode], startPointInLocalCoords);
|
||||
if ((startToEndLength2 < startTo1stNodeLength2) || (startToEndLength2 < endTolastNodeLength2))
|
||||
{
|
||||
mPath.push_back(endPoint);
|
||||
*out++ = endPoint;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -225,21 +175,21 @@ namespace MWMechanics
|
|||
// nodes are the same
|
||||
if(startNode == endNode.first)
|
||||
{
|
||||
ESM::Pathgrid::Point temp(mPathgrid->mPoints[startNode]);
|
||||
ESM::Pathgrid::Point temp(pathgrid->mPoints[startNode]);
|
||||
converter.toWorld(temp);
|
||||
mPath.push_back(temp);
|
||||
*out++ = makeOsgVec3(temp);
|
||||
}
|
||||
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.
|
||||
// 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());
|
||||
osg::Vec3f firstNodeVec3f = MakeOsgVec3(mPathgrid->mPoints[startNode]);
|
||||
osg::Vec3f secondNodeVec3f = MakeOsgVec3(secondNode);
|
||||
ESM::Pathgrid::Point secondNode = *(++path.begin());
|
||||
osg::Vec3f firstNodeVec3f = makeOsgVec3(pathgrid->mPoints[startNode]);
|
||||
osg::Vec3f secondNodeVec3f = makeOsgVec3(secondNode);
|
||||
osg::Vec3f toSecondNodeVec3f = secondNodeVec3f - firstNodeVec3f;
|
||||
osg::Vec3f toStartPointVec3f = startPointInLocalCoords - firstNodeVec3f;
|
||||
if (toSecondNodeVec3f * toStartPointVec3f > 0)
|
||||
|
@ -248,19 +198,21 @@ namespace MWMechanics
|
|||
converter.toWorld(temp);
|
||||
// Add Z offset since path node can overlap with other objects.
|
||||
// Also ignore doors in raytesting.
|
||||
int mask = MWPhysics::CollisionType_World;
|
||||
const int mask = MWPhysics::CollisionType_World;
|
||||
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)
|
||||
mPath.pop_front();
|
||||
path.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
// convert supplied path to world coordinates
|
||||
for (std::list<ESM::Pathgrid::Point>::iterator iter(mPath.begin()); iter != mPath.end(); ++iter)
|
||||
{
|
||||
converter.toWorld(*iter);
|
||||
}
|
||||
std::transform(path.begin(), path.end(), out,
|
||||
[&] (ESM::Pathgrid::Point& point)
|
||||
{
|
||||
converter.toWorld(point);
|
||||
return makeOsgVec3(point);
|
||||
});
|
||||
}
|
||||
|
||||
// If endNode found is NOT the closest PathGrid point to the endPoint,
|
||||
|
@ -276,7 +228,7 @@ namespace MWMechanics
|
|||
//
|
||||
// The AI routines will have to deal with such situations.
|
||||
if(endNode.second)
|
||||
mPath.push_back(endPoint);
|
||||
*out++ = endPoint;
|
||||
}
|
||||
|
||||
float PathFinder::getZAngleToNext(float x, float y) const
|
||||
|
@ -286,9 +238,9 @@ namespace MWMechanics
|
|||
if(mPath.empty())
|
||||
return 0.;
|
||||
|
||||
const ESM::Pathgrid::Point &nextPoint = *mPath.begin();
|
||||
float directionX = nextPoint.mX - x;
|
||||
float directionY = nextPoint.mY - y;
|
||||
const auto& nextPoint = mPath.front();
|
||||
const float directionX = nextPoint.x() - x;
|
||||
const float directionY = nextPoint.y() - y;
|
||||
|
||||
return std::atan2(directionX, directionY);
|
||||
}
|
||||
|
@ -300,62 +252,64 @@ namespace MWMechanics
|
|||
if(mPath.empty())
|
||||
return 0.;
|
||||
|
||||
const ESM::Pathgrid::Point &nextPoint = *mPath.begin();
|
||||
osg::Vec3f dir = MakeOsgVec3(nextPoint) - osg::Vec3f(x,y,z);
|
||||
const osg::Vec3f dir = mPath.front() - osg::Vec3f(x, y, z);
|
||||
|
||||
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())
|
||||
return true;
|
||||
if (mPath.empty())
|
||||
return;
|
||||
|
||||
const ESM::Pathgrid::Point& nextPoint = *mPath.begin();
|
||||
if (sqrDistanceIgnoreZ(nextPoint, x, y) < tolerance*tolerance)
|
||||
{
|
||||
const auto tolerance = mPath.size() > 1 ? pointTolerance : destinationTolerance;
|
||||
|
||||
if (sqrDistanceIgnoreZ(mPath.front(), position) < tolerance * tolerance)
|
||||
mPath.pop_front();
|
||||
if(mPath.empty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// see header for the rationale
|
||||
void PathFinder::buildSyncedPath(const ESM::Pathgrid::Point &startPoint,
|
||||
const ESM::Pathgrid::Point &endPoint,
|
||||
const MWWorld::CellStore* cell, const MWMechanics::PathgridGraph& pathgridGraph)
|
||||
void PathFinder::buildPathByPathgrid(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
||||
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph)
|
||||
{
|
||||
if (mPath.size() < 2)
|
||||
{
|
||||
// if path has one point, then it's the destination.
|
||||
// don't need to worry about bad path for this case
|
||||
buildPath(startPoint, endPoint, cell, pathgridGraph);
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
mPath.clear();
|
||||
mCell = cell;
|
||||
|
||||
buildPathByPathgridImpl(startPoint, endPoint, pathgridGraph, std::back_inserter(mPath));
|
||||
|
||||
mConstructed = true;
|
||||
}
|
||||
|
||||
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
|
||||
#define GAME_MWMECHANICS_PATHFINDING_H
|
||||
|
||||
#include <list>
|
||||
#include <deque>
|
||||
#include <cassert>
|
||||
#include <iterator>
|
||||
|
||||
#include <components/detournavigator/flags.hpp>
|
||||
#include <components/esm/defs.hpp>
|
||||
#include <components/esm/loadpgrd.hpp>
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class CellStore;
|
||||
class ConstPtr;
|
||||
}
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
class PathgridGraph;
|
||||
|
||||
float distance(const ESM::Pathgrid::Point& point, float x, float y, float);
|
||||
float distance(const ESM::Pathgrid::Point& a, const ESM::Pathgrid::Point& b);
|
||||
float getZAngleToDir(const osg::Vec3f& dir);
|
||||
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);
|
||||
inline float distance(const osg::Vec3f& lhs, const osg::Vec3f& rhs)
|
||||
{
|
||||
return (lhs - rhs).length();
|
||||
}
|
||||
|
||||
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
|
||||
// distance after which actor (failed previously to shortcut) will try again
|
||||
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;
|
||||
// magnitude of pits/obstacles is defined by PATHFIND_Z_REACH
|
||||
bool checkWayIsClear(const osg::Vec3f& from, const osg::Vec3f& to, float offsetXY);
|
||||
|
@ -35,31 +59,33 @@ namespace MWMechanics
|
|||
class PathFinder
|
||||
{
|
||||
public:
|
||||
PathFinder();
|
||||
|
||||
static const int PathTolerance = 32;
|
||||
|
||||
static float sgn(float val)
|
||||
PathFinder()
|
||||
: mConstructed(false)
|
||||
, mCell(nullptr)
|
||||
{
|
||||
if(val > 0)
|
||||
return 1.0;
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
static int sgn(int a)
|
||||
void clearPath()
|
||||
{
|
||||
if(a > 0)
|
||||
return 1;
|
||||
return -1;
|
||||
mConstructed = false;
|
||||
mPath.clear();
|
||||
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,
|
||||
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph);
|
||||
void 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);
|
||||
|
||||
bool checkPathCompleted(float x, float y, float tolerance = PathTolerance);
|
||||
///< \Returns true if we are within \a tolerance units of the last path point.
|
||||
/// Remove front point if exist and within tolerance
|
||||
void update(const osg::Vec3f& position, const float pointTolerance, const float destinationTolerance);
|
||||
|
||||
bool checkPathCompleted() const
|
||||
{
|
||||
return mConstructed && mPath.empty();
|
||||
}
|
||||
|
||||
/// In radians
|
||||
float getZAngleToNext(float x, float y) const;
|
||||
|
@ -68,49 +94,43 @@ namespace MWMechanics
|
|||
|
||||
bool isPathConstructed() const
|
||||
{
|
||||
return !mPath.empty();
|
||||
return mConstructed && !mPath.empty();
|
||||
}
|
||||
|
||||
int getPathSize() const
|
||||
std::size_t getPathSize() const
|
||||
{
|
||||
return mPath.size();
|
||||
}
|
||||
|
||||
const std::list<ESM::Pathgrid::Point>& getPath() const
|
||||
const std::deque<osg::Vec3f>& getPath() const
|
||||
{
|
||||
return mPath;
|
||||
}
|
||||
|
||||
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)
|
||||
const MWWorld::CellStore* getPathCell() const
|
||||
{
|
||||
return mCell;
|
||||
}
|
||||
|
||||
void addPointToPath(const osg::Vec3f& point)
|
||||
{
|
||||
mConstructed = true;
|
||||
mPath.push_back(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]));
|
||||
}
|
||||
|
||||
/// 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]));
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
@ -119,9 +139,9 @@ namespace MWMechanics
|
|||
// 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
|
||||
//
|
||||
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
|
||||
|
@ -130,18 +150,18 @@ namespace MWMechanics
|
|||
//
|
||||
// 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());
|
||||
|
||||
float distanceBetween = DistanceSquared(grid->mPoints[0], pos);
|
||||
float distanceBetween = distanceSquared(grid->mPoints[0], pos);
|
||||
int closestIndex = 0;
|
||||
|
||||
// TODO: if this full scan causes performance problems mapping pathgrid
|
||||
// points to a quadtree may help
|
||||
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)
|
||||
{
|
||||
distanceBetween = potentialDistBetween;
|
||||
|
@ -153,10 +173,17 @@ namespace MWMechanics
|
|||
}
|
||||
|
||||
private:
|
||||
std::list<ESM::Pathgrid::Point> mPath;
|
||||
bool mConstructed;
|
||||
std::deque<osg::Vec3f> mPath;
|
||||
|
||||
const ESM::Pathgrid *mPathgrid;
|
||||
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
|
||||
* coordinates). Essentially trading speed w/ memory.
|
||||
*/
|
||||
std::list<ESM::Pathgrid::Point> PathgridGraph::aStarSearch(const int start,
|
||||
const int goal) const
|
||||
std::deque<ESM::Pathgrid::Point> PathgridGraph::aStarSearch(const int start, const int goal) const
|
||||
{
|
||||
std::list<ESM::Pathgrid::Point> path;
|
||||
std::deque<ESM::Pathgrid::Point> path;
|
||||
if(!isPointConnected(start, goal))
|
||||
{
|
||||
return path; // there is no path, return an empty path
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#ifndef GAME_MWMECHANICS_PATHGRID_H
|
||||
#define GAME_MWMECHANICS_PATHGRID_H
|
||||
|
||||
#include <list>
|
||||
#include <deque>
|
||||
|
||||
#include <components/esm/loadpgrd.hpp>
|
||||
|
||||
|
@ -38,8 +38,8 @@ namespace MWMechanics
|
|||
// cells) coordinates
|
||||
//
|
||||
// NOTE: if start equals end an empty path is returned
|
||||
std::list<ESM::Pathgrid::Point> aStarSearch(const int start,
|
||||
const int end) const;
|
||||
std::deque<ESM::Pathgrid::Point> aStarSearch(const int start, const int end) const;
|
||||
|
||||
private:
|
||||
|
||||
const ESM::Cell *mCell;
|
||||
|
|
|
@ -324,6 +324,34 @@ namespace MWMechanics
|
|||
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)
|
||||
: mCaster(caster)
|
||||
, mTarget(target)
|
||||
|
@ -444,22 +472,31 @@ namespace MWMechanics
|
|||
MWBase::Environment::get().getWindowManager()->setEnemy(target);
|
||||
|
||||
// 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
|
||||
// if that is worth replicating.
|
||||
// Unlike Reflect, this is done once per spell absorption effect source
|
||||
bool absorbed = false;
|
||||
if (spell && caster != target && target.getClass().isActor())
|
||||
{
|
||||
float absorb = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude();
|
||||
absorbed = (Misc::Rng::roll0to99() < absorb);
|
||||
if (absorbed)
|
||||
CreatureStats& stats = target.getClass().getCreatureStats(target);
|
||||
if (stats.getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude() > 0.f)
|
||||
{
|
||||
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 = target.getClass().getCreatureStats(target).getMagicka();
|
||||
magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost);
|
||||
target.getClass().getCreatureStats(target).setMagicka(magicka);
|
||||
GetAbsorptionProbability check(target);
|
||||
stats.getActiveSpells().visitEffectSources(check);
|
||||
stats.getSpells().visitEffectSources(check);
|
||||
if (target.getClass().hasInventoryStore(target))
|
||||
target.getClass().getInventoryStore(target).visitEffectSources(check);
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
else
|
||||
{
|
||||
const float slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1]) / 2.f;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include <components/sceneutil/positionattitudetransform.hpp>
|
||||
#include <components/resource/bulletshape.hpp>
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
|
||||
|
@ -28,6 +29,31 @@ Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr<const Resource::BulletShape>
|
|||
mHalfExtents = shape->mCollisionBoxHalfExtents;
|
||||
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)
|
||||
if (std::abs(mHalfExtents.x()-mHalfExtents.y())<mHalfExtents.x()*0.05 && mHalfExtents.z() >= mHalfExtents.x())
|
||||
{
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
#include <memory>
|
||||
|
||||
#include "../mwworld/ptr.hpp"
|
||||
#include "ptrholder.hpp"
|
||||
|
||||
#include <osg/Vec3f>
|
||||
#include <osg/Quat>
|
||||
|
@ -22,30 +22,6 @@ namespace Resource
|
|||
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
|
||||
{
|
||||
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 <unordered_map>
|
||||
#include <fstream>
|
||||
#include <array>
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include <osg/Group>
|
||||
|
||||
|
@ -16,6 +21,12 @@
|
|||
|
||||
#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/resource/resourcesystem.hpp>
|
||||
#include <components/resource/bulletshapemanager.hpp>
|
||||
|
@ -48,12 +59,12 @@
|
|||
#include "actor.hpp"
|
||||
#include "convert.hpp"
|
||||
#include "trace.h"
|
||||
#include "object.hpp"
|
||||
#include "heightfield.hpp"
|
||||
|
||||
namespace MWPhysics
|
||||
{
|
||||
|
||||
static const float sMaxSlope = 49.0f;
|
||||
static const float sStepSizeUp = 34.0f;
|
||||
static const float sStepSizeDown = 62.0f;
|
||||
static const float sMinStep = 10.f;
|
||||
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)
|
||||
: 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)
|
||||
{
|
||||
osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance = mShapeManager->getInstance(mesh);
|
||||
|
@ -1366,6 +1216,33 @@ namespace MWPhysics
|
|||
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)
|
||||
{
|
||||
PtrVelocityList::iterator iter = mMovementQueue.begin();
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <memory>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <algorithm>
|
||||
|
||||
#include <osg/Quat>
|
||||
#include <osg/ref_ptr>
|
||||
|
@ -49,6 +50,9 @@ namespace MWPhysics
|
|||
class Object;
|
||||
class Actor;
|
||||
|
||||
static const float sMaxSlope = 49.0f;
|
||||
static const float sStepSizeUp = 34.0f;
|
||||
|
||||
class PhysicsSystem
|
||||
{
|
||||
public:
|
||||
|
@ -85,7 +89,11 @@ namespace MWPhysics
|
|||
|
||||
void removeHeightField (int x, int y);
|
||||
|
||||
const HeightField* getHeightField(int x, int y) const;
|
||||
|
||||
bool toggleCollisionMode();
|
||||
bool isActorCollisionEnabled(const MWWorld::Ptr& ptr);
|
||||
void setActorCollisionMode(const MWWorld::Ptr& ptr, bool enabled);
|
||||
|
||||
void stepSimulation(float dt);
|
||||
void debugDraw();
|
||||
|
@ -170,6 +178,12 @@ namespace MWPhysics
|
|||
|
||||
bool isOnSolidGround (const MWWorld::Ptr& actor) const;
|
||||
|
||||
template <class Function>
|
||||
void forEachAnimatedObject(Function&& function) const
|
||||
{
|
||||
std::for_each(mAnimatedObjects.begin(), mAnimatedObjects.end(), function);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
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 <utility>
|
||||
|
||||
#include <osg/Node>
|
||||
|
@ -9,11 +8,22 @@
|
|||
#include <components/esm/loadligh.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/lightutil.hpp>
|
||||
#include <components/sceneutil/visitor.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/world.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
|
||||
removeEffects();
|
||||
|
||||
mWeaponSheathing = Settings::Manager::getBool("weapon sheathing", "Game");
|
||||
}
|
||||
|
||||
ActorAnimation::~ActorAnimation()
|
||||
|
@ -51,6 +63,302 @@ ActorAnimation::~ActorAnimation()
|
|||
{
|
||||
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*/)
|
||||
|
@ -63,6 +371,24 @@ void ActorAnimation::itemAdded(const MWWorld::ConstPtr& item, int /*count*/)
|
|||
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*/)
|
||||
|
@ -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)
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <osg/ref_ptr>
|
||||
|
||||
#include "../mwworld/containerstore.hpp"
|
||||
#include "../mwworld/inventorystore.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 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:
|
||||
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/nifosg/nifloader.hpp>
|
||||
|
||||
#include <components/resource/resourcesystem.hpp>
|
||||
#include <components/resource/scenemanager.hpp>
|
||||
#include <components/resource/keyframemanager.hpp>
|
||||
#include <components/resource/imagemanager.hpp>
|
||||
|
||||
#include <components/misc/constants.hpp>
|
||||
|
||||
#include <components/nifosg/nifloader.hpp> // KeyframeHolder
|
||||
#include <components/nifosg/controller.hpp>
|
||||
|
||||
|
@ -190,12 +190,6 @@ namespace
|
|||
{
|
||||
}
|
||||
|
||||
RemoveFinishedCallbackVisitor(int effectId)
|
||||
: RemoveVisitor()
|
||||
, mHasMagicEffects(false)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void apply(osg::Node &node)
|
||||
{
|
||||
traverse(node);
|
||||
|
@ -228,9 +222,6 @@ namespace
|
|||
virtual void apply(osg::Geometry&)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
int mEffectId;
|
||||
};
|
||||
|
||||
class RemoveCallbackVisitor : public RemoveVisitor
|
||||
|
@ -1748,21 +1739,29 @@ namespace MWRender
|
|||
|
||||
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);
|
||||
stateset->setAttributeAndModes(blendfunc, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
|
||||
// FIXME: overriding diffuse/ambient/emissive colors
|
||||
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
|
||||
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);
|
||||
|
||||
mObjectRoot->setStateSet(stateset);
|
||||
|
||||
mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot);
|
||||
mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1798,9 +1797,12 @@ namespace MWRender
|
|||
}
|
||||
else
|
||||
{
|
||||
effect += 3;
|
||||
float radius = effect * 66.f;
|
||||
float linearAttenuation = 0.5f / effect;
|
||||
// TODO: use global attenuation settings
|
||||
|
||||
// 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())
|
||||
{
|
||||
|
@ -1822,7 +1824,8 @@ namespace MWRender
|
|||
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);
|
||||
|
||||
if((ref->mBase->mFlags&ESM::Creature::Bipedal))
|
||||
{
|
||||
if (mWeaponSheathing)
|
||||
injectWeaponBones();
|
||||
|
||||
addAnimSource("meshes\\xbase_anim.nif", model);
|
||||
}
|
||||
addAnimSource(model, model);
|
||||
|
||||
mPtr.getClass().getInventoryStore(mPtr).setInvListener(this, mPtr);
|
||||
|
@ -84,6 +89,9 @@ void CreatureWeaponAnimation::updateParts()
|
|||
mWeapon.reset();
|
||||
mShield.reset();
|
||||
|
||||
updateHolsteredWeapon(!mShowWeapons);
|
||||
updateQuiver();
|
||||
|
||||
if (mShowWeapons)
|
||||
updatePart(mWeapon, MWWorld::InventoryStore::Slot_CarriedRight);
|
||||
if (mShowCarriedLeft)
|
||||
|
@ -157,14 +165,21 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot)
|
|||
}
|
||||
}
|
||||
|
||||
bool CreatureWeaponAnimation::isArrowAttached() const
|
||||
{
|
||||
return mAmmunition != nullptr;
|
||||
}
|
||||
|
||||
void CreatureWeaponAnimation::attachArrow()
|
||||
{
|
||||
WeaponAnimation::attachArrow(mPtr);
|
||||
updateQuiver();
|
||||
}
|
||||
|
||||
void CreatureWeaponAnimation::releaseArrow(float attackStrength)
|
||||
{
|
||||
WeaponAnimation::releaseArrow(mPtr, attackStrength);
|
||||
updateQuiver();
|
||||
}
|
||||
|
||||
osg::Group *CreatureWeaponAnimation::getArrowBone()
|
||||
|
|
|
@ -54,6 +54,8 @@ namespace MWRender
|
|||
/// to indicate the facing orientation of the character.
|
||||
virtual void setPitchFactor(float factor) { mPitchFactor = factor; }
|
||||
|
||||
protected:
|
||||
virtual bool isArrowAttached() const;
|
||||
|
||||
private:
|
||||
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/lightmanager.hpp>
|
||||
|
||||
#include <components/settings/settings.hpp>
|
||||
|
||||
#include <components/nifosg/nifloader.hpp> // TextKeyMapHolder
|
||||
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
|
@ -308,6 +310,12 @@ void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode)
|
|||
if(mViewMode == viewMode)
|
||||
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;
|
||||
rebuild();
|
||||
|
||||
|
@ -389,6 +397,7 @@ void NpcAnimation::setRenderBin()
|
|||
|
||||
void NpcAnimation::rebuild()
|
||||
{
|
||||
mScabbard.reset();
|
||||
updateNpcBase();
|
||||
|
||||
MWBase::Environment::get().getMechanicsManager()->forceStateUpdate(mPtr);
|
||||
|
@ -460,6 +469,11 @@ void NpcAnimation::updateNpcBase()
|
|||
|
||||
setObjectRoot(smodel, true, true, false);
|
||||
|
||||
if (mWeaponSheathing)
|
||||
injectWeaponBones();
|
||||
|
||||
updateParts();
|
||||
|
||||
if(!is1stPerson)
|
||||
{
|
||||
const std::string base = "meshes\\xbase_anim.nif";
|
||||
|
@ -488,8 +502,6 @@ void NpcAnimation::updateNpcBase()
|
|||
mObjectRoot->addCullCallback(new OverrideFieldOfViewCallback(mFirstPersonFieldOfView));
|
||||
}
|
||||
|
||||
updateParts();
|
||||
|
||||
mWeaponAnimationTime->updateStartTime();
|
||||
}
|
||||
|
||||
|
@ -899,7 +911,8 @@ void NpcAnimation::showWeapons(bool showWeapon)
|
|||
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);
|
||||
}
|
||||
else
|
||||
|
@ -909,6 +922,9 @@ void NpcAnimation::showWeapons(bool showWeapon)
|
|||
if (mPtr == MWMechanics::getPlayer())
|
||||
MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false);
|
||||
}
|
||||
|
||||
updateHolsteredWeapon(!mShowWeapons);
|
||||
updateQuiver();
|
||||
}
|
||||
|
||||
void NpcAnimation::showCarriedLeft(bool show)
|
||||
|
@ -936,11 +952,13 @@ void NpcAnimation::showCarriedLeft(bool show)
|
|||
void NpcAnimation::attachArrow()
|
||||
{
|
||||
WeaponAnimation::attachArrow(mPtr);
|
||||
updateQuiver();
|
||||
}
|
||||
|
||||
void NpcAnimation::releaseArrow(float attackStrength)
|
||||
{
|
||||
WeaponAnimation::releaseArrow(mPtr, attackStrength);
|
||||
updateQuiver();
|
||||
}
|
||||
|
||||
osg::Group* NpcAnimation::getArrowBone()
|
||||
|
@ -1185,4 +1203,9 @@ void NpcAnimation::setAccurateAiming(bool enabled)
|
|||
mAccurateAiming = enabled;
|
||||
}
|
||||
|
||||
bool NpcAnimation::isArrowAttached() const
|
||||
{
|
||||
return mAmmunition != nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -95,6 +95,7 @@ private:
|
|||
|
||||
protected:
|
||||
virtual void addControllers();
|
||||
virtual bool isArrowAttached() const;
|
||||
|
||||
public:
|
||||
/**
|
||||
|
|
|
@ -46,6 +46,8 @@
|
|||
#include <components/esm/loadcell.hpp>
|
||||
#include <components/fallback/fallback.hpp>
|
||||
|
||||
#include <components/detournavigator/navigator.hpp>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
|
@ -63,6 +65,8 @@
|
|||
#include "water.hpp"
|
||||
#include "terrainstorage.hpp"
|
||||
#include "util.hpp"
|
||||
#include "navmesh.hpp"
|
||||
#include "actorspaths.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
@ -189,13 +193,16 @@ namespace MWRender
|
|||
Resource::ResourceSystem* mResourceSystem;
|
||||
};
|
||||
|
||||
RenderingManager::RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue,
|
||||
const Fallback::Map* fallback, const std::string& resourcePath)
|
||||
RenderingManager::RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode,
|
||||
Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue,
|
||||
const Fallback::Map* fallback, const std::string& resourcePath,
|
||||
DetourNavigator::Navigator& navigator)
|
||||
: mViewer(viewer)
|
||||
, mRootNode(rootNode)
|
||||
, mResourceSystem(resourceSystem)
|
||||
, mWorkQueue(workQueue)
|
||||
, mUnrefQueue(new SceneUtil::UnrefQueue)
|
||||
, mNavigator(navigator)
|
||||
, mLandFogStart(0.f)
|
||||
, mLandFogEnd(std::numeric_limits<float>::max())
|
||||
, mUnderwaterFogStart(0.f)
|
||||
|
@ -228,6 +235,8 @@ namespace MWRender
|
|||
|
||||
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));
|
||||
|
||||
mObjects.reset(new Objects(mResourceSystem, sceneRoot, mUnrefQueue.get()));
|
||||
|
@ -465,6 +474,7 @@ namespace MWRender
|
|||
void RenderingManager::removeCell(const MWWorld::CellStore *store)
|
||||
{
|
||||
mPathgrid->removeCell(store);
|
||||
mActorsPaths->removeCell(store);
|
||||
mObjects->removeCell(store);
|
||||
|
||||
if (store->getCell()->isExterior())
|
||||
|
@ -516,6 +526,14 @@ namespace MWRender
|
|||
mViewer->getCamera()->setCullMask(mask);
|
||||
return enabled;
|
||||
}
|
||||
else if (mode == Render_NavMesh)
|
||||
{
|
||||
return mNavMesh->toggle();
|
||||
}
|
||||
else if (mode == Render_ActorsPaths)
|
||||
{
|
||||
return mActorsPaths->toggle();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -581,6 +599,28 @@ namespace MWRender
|
|||
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);
|
||||
|
||||
osg::Vec3f focal, cameraPos;
|
||||
|
@ -642,6 +682,7 @@ namespace MWRender
|
|||
|
||||
void RenderingManager::removeObject(const MWWorld::Ptr &ptr)
|
||||
{
|
||||
mActorsPaths->remove(ptr);
|
||||
mObjects->removeObject(ptr);
|
||||
mWater->removeEmitter(ptr);
|
||||
}
|
||||
|
@ -1025,6 +1066,7 @@ namespace MWRender
|
|||
void RenderingManager::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &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)
|
||||
|
@ -1345,5 +1387,19 @@ namespace MWRender
|
|||
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 "rendermode.hpp"
|
||||
|
||||
#include <deque>
|
||||
|
||||
namespace osg
|
||||
{
|
||||
class Group;
|
||||
|
@ -55,6 +57,12 @@ namespace SceneUtil
|
|||
class UnrefQueue;
|
||||
}
|
||||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
class Navigator;
|
||||
struct Settings;
|
||||
}
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
|
||||
|
@ -68,12 +76,16 @@ namespace MWRender
|
|||
class Water;
|
||||
class TerrainStorage;
|
||||
class LandManager;
|
||||
class NavMesh;
|
||||
class ActorsPaths;
|
||||
|
||||
class RenderingManager : public MWRender::RenderingInterface
|
||||
{
|
||||
public:
|
||||
RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue,
|
||||
const Fallback::Map* fallback, const std::string& resourcePath);
|
||||
RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode,
|
||||
Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue,
|
||||
const Fallback::Map* fallback, const std::string& resourcePath,
|
||||
DetourNavigator::Navigator& navigator);
|
||||
~RenderingManager();
|
||||
|
||||
MWRender::Objects& getObjects();
|
||||
|
@ -211,6 +223,13 @@ namespace MWRender
|
|||
|
||||
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:
|
||||
void updateProjectionMatrix();
|
||||
void updateTextureFiltering();
|
||||
|
@ -235,6 +254,10 @@ namespace MWRender
|
|||
|
||||
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<Objects> mObjects;
|
||||
std::unique_ptr<Water> mWater;
|
||||
|
|
|
@ -10,7 +10,9 @@ namespace MWRender
|
|||
Render_Wireframe,
|
||||
Render_Pathgrid,
|
||||
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();
|
||||
return mEnabled && eyePoint.z() < mWaterLevel;
|
||||
}
|
||||
|
||||
if (mEnabled && eyePoint.z() < mWaterLevel)
|
||||
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
|
||||
{
|
||||
if (isUnderwater())
|
||||
return;
|
||||
|
||||
traverse(node, nv);
|
||||
|
@ -1575,6 +1579,8 @@ void SkyManager::update(float duration)
|
|||
mRainIntensityUniform->set((float) mWeatherAlpha);
|
||||
}
|
||||
|
||||
switchUnderwaterRain();
|
||||
|
||||
if (mIsStorm)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (!mCreated) return;
|
||||
|
|
|
@ -180,6 +180,7 @@ namespace MWRender
|
|||
|
||||
void createRain();
|
||||
void destroyRain();
|
||||
void switchUnderwaterRain();
|
||||
void updateRainParameters();
|
||||
|
||||
Resource::SceneManager* mSceneManager;
|
||||
|
|
|
@ -455,5 +455,8 @@ op 0x2000304: Show
|
|||
op 0x2000305: Show, explicit
|
||||
op 0x2000306: OnActivate, explicit
|
||||
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)
|
||||
{
|
||||
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::opcodeShowSceneGraphExplicit, new OpShowSceneGraph<ExplicitRef>);
|
||||
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)
|
||||
{
|
||||
const MWWorld::Ptr ptr = MWMechanics::getPlayer();
|
||||
MWBase::Environment::get().getWorld()->fixPosition(ptr);
|
||||
MWBase::Environment::get().getWorld()->fixPosition();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -82,7 +82,7 @@ bool FFmpeg_Decoder::getNextPacket()
|
|||
}
|
||||
|
||||
/* Free the packet and look for another */
|
||||
av_free_packet(&mPacket);
|
||||
av_packet_unref(&mPacket);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -90,9 +90,9 @@ bool FFmpeg_Decoder::getNextPacket()
|
|||
|
||||
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;
|
||||
|
||||
do {
|
||||
|
@ -100,19 +100,18 @@ bool FFmpeg_Decoder::getAVAudioData()
|
|||
return false;
|
||||
|
||||
/* Decode some data, and check for errors */
|
||||
int len = 0;
|
||||
if((len=avcodec_decode_audio4((*mStream)->codec, mFrame, &got_frame, &mPacket)) < 0)
|
||||
int ret = 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;
|
||||
|
||||
/* Move the unread data to the front and clear the end bits */
|
||||
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);
|
||||
}
|
||||
av_packet_unref(&mPacket);
|
||||
|
||||
if (!got_frame || mFrame->nb_samples == 0)
|
||||
continue;
|
||||
|
@ -139,8 +138,8 @@ bool FFmpeg_Decoder::getAVAudioData()
|
|||
else
|
||||
mFrameData = &mFrame->data[0];
|
||||
|
||||
} while(got_frame == 0 || mFrame->nb_samples == 0);
|
||||
mNextPts += (double)mFrame->nb_samples / (double)(*mStream)->codec->sample_rate;
|
||||
} while(!got_frame || mFrame->nb_samples == 0);
|
||||
mNextPts += (double)mFrame->nb_samples / mCodecCtx->sample_rate;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -213,7 +212,7 @@ void FFmpeg_Decoder::open(const std::string &fname)
|
|||
|
||||
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];
|
||||
break;
|
||||
|
@ -222,39 +221,48 @@ void FFmpeg_Decoder::open(const std::string &fname)
|
|||
if(!mStream)
|
||||
throw std::runtime_error("No audio streams in "+fname);
|
||||
|
||||
(*mStream)->codec->request_sample_fmt = (*mStream)->codec->sample_fmt;
|
||||
|
||||
AVCodec *codec = avcodec_find_decoder((*mStream)->codec->codec_id);
|
||||
AVCodec *codec = avcodec_find_decoder((*mStream)->codecpar->codec_id);
|
||||
if(!codec)
|
||||
{
|
||||
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);
|
||||
}
|
||||
if(avcodec_open2((*mStream)->codec, codec, nullptr) < 0)
|
||||
throw std::runtime_error(std::string("Failed to open audio codec ") +
|
||||
codec->long_name);
|
||||
|
||||
AVCodecContext *avctx = avcodec_alloc_context3(codec);
|
||||
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();
|
||||
|
||||
if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_FLT ||
|
||||
(*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_FLTP)
|
||||
if(mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLT || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLTP)
|
||||
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;
|
||||
else if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_S16P)
|
||||
else if(mCodecCtx->sample_fmt == AV_SAMPLE_FMT_S16P)
|
||||
mOutputSampleFormat = AV_SAMPLE_FMT_S16;
|
||||
else
|
||||
mOutputSampleFormat = AV_SAMPLE_FMT_S16;
|
||||
|
||||
mOutputChannelLayout = (*mStream)->codec->channel_layout;
|
||||
mOutputChannelLayout = (*mStream)->codecpar->channel_layout;
|
||||
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(...)
|
||||
{
|
||||
if(mStream)
|
||||
avcodec_close((*mStream)->codec);
|
||||
avcodec_free_context(&mCodecCtx);
|
||||
mStream = nullptr;
|
||||
|
||||
if (mFormatCtx != nullptr)
|
||||
|
@ -275,10 +283,10 @@ void FFmpeg_Decoder::open(const std::string &fname)
|
|||
void FFmpeg_Decoder::close()
|
||||
{
|
||||
if(mStream)
|
||||
avcodec_close((*mStream)->codec);
|
||||
avcodec_free_context(&mCodecCtx);
|
||||
mStream = nullptr;
|
||||
|
||||
av_free_packet(&mPacket);
|
||||
av_packet_unref(&mPacket);
|
||||
av_freep(&mFrame);
|
||||
swr_free(&mSwr);
|
||||
av_freep(&mDataBuf);
|
||||
|
@ -308,7 +316,12 @@ void FFmpeg_Decoder::close()
|
|||
|
||||
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;
|
||||
#else
|
||||
return mFormatCtx->url;
|
||||
#endif
|
||||
}
|
||||
|
||||
void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type)
|
||||
|
@ -341,11 +354,10 @@ void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *
|
|||
else
|
||||
{
|
||||
char str[1024];
|
||||
av_get_channel_layout_string(str, sizeof(str), (*mStream)->codec->channels,
|
||||
(*mStream)->codec->channel_layout);
|
||||
av_get_channel_layout_string(str, sizeof(str), mCodecCtx->channels, mCodecCtx->channel_layout);
|
||||
Log(Debug::Error) << "Unsupported channel layout: "<< str;
|
||||
|
||||
if((*mStream)->codec->channels == 1)
|
||||
if(mCodecCtx->channels == 1)
|
||||
{
|
||||
mOutputChannelLayout = AV_CH_LAYOUT_MONO;
|
||||
*chans = ChannelConfig_Mono;
|
||||
|
@ -357,27 +369,28 @@ void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *
|
|||
}
|
||||
}
|
||||
|
||||
*samplerate = (*mStream)->codec->sample_rate;
|
||||
int64_t ch_layout = (*mStream)->codec->channel_layout;
|
||||
*samplerate = mCodecCtx->sample_rate;
|
||||
int64_t ch_layout = mCodecCtx->channel_layout;
|
||||
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)
|
||||
{
|
||||
mSwr = swr_alloc_set_opts(mSwr, // SwrContext
|
||||
mOutputChannelLayout, // output ch layout
|
||||
mOutputSampleFormat, // output sample format
|
||||
(*mStream)->codec->sample_rate, // output sample rate
|
||||
mCodecCtx->sample_rate, // output sample rate
|
||||
ch_layout, // input ch layout
|
||||
(*mStream)->codec->sample_fmt, // input sample format
|
||||
(*mStream)->codec->sample_rate, // input sample rate
|
||||
mCodecCtx->sample_fmt, // input sample format
|
||||
mCodecCtx->sample_rate, // input sample rate
|
||||
0, // logging level offset
|
||||
nullptr); // log context
|
||||
if(!mSwr)
|
||||
throw std::runtime_error("Couldn't allocate SwrContext");
|
||||
if(swr_init(mSwr) < 0)
|
||||
throw std::runtime_error("Couldn't initialize SwrContext");
|
||||
int init=swr_init(mSwr);
|
||||
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) /
|
||||
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)
|
||||
|
@ -437,7 +450,10 @@ FFmpeg_Decoder::FFmpeg_Decoder(const VFS::Manager* vfs)
|
|||
static bool done_init = false;
|
||||
if(!done_init)
|
||||
{
|
||||
// This is not needed anymore above FFMpeg version 4.0
|
||||
#if LIBAVCODEC_VERSION_INT < 3805796
|
||||
av_register_all();
|
||||
#endif
|
||||
av_log_set_level(AV_LOG_ERROR);
|
||||
done_init = true;
|
||||
}
|
||||
|
|
|
@ -6,18 +6,7 @@ extern "C"
|
|||
{
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.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
|
||||
#include <libavutil/channel_layout.h>
|
||||
|
||||
// From version 54.56 binkaudio encoding format changed from S16 to FLTP. See:
|
||||
// https://gitorious.org/ffmpeg/ffmpeg/commit/7bfd1766d1c18f07b0a2dd042418a874d49ea60d
|
||||
|
@ -38,6 +27,7 @@ namespace MWSound
|
|||
class FFmpeg_Decoder final : public Sound_Decoder
|
||||
{
|
||||
AVFormatContext *mFormatCtx;
|
||||
AVCodecContext *mCodecCtx;
|
||||
AVStream **mStream;
|
||||
|
||||
AVPacket mPacket;
|
||||
|
|
|
@ -48,7 +48,7 @@ namespace MWSound
|
|||
{
|
||||
ssize_t clock_delay = (mFrameSize-mFramePos) / av_get_channel_layout_nb_channels(mOutputChannelLayout) /
|
||||
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()
|
||||
|
@ -61,7 +61,7 @@ namespace MWSound
|
|||
|
||||
virtual double getAudioClock()
|
||||
{
|
||||
return (double)getSampleOffset()/(double)mAVStream->codec->sample_rate -
|
||||
return (double)getSampleOffset()/(double)mAudioContext->sample_rate -
|
||||
MWBase::Environment::get().getSoundManager()->getTrackTimeDelay(mAudioTrack);
|
||||
}
|
||||
|
||||
|
|
|
@ -265,9 +265,10 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot
|
|||
writer.save (stream);
|
||||
|
||||
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
|
||||
listener.setProgressRange(MWBase::Environment::get().getWorld()->countSavedGameCells());
|
||||
listener.setLabel("#{sNotifyMessage4}", true);
|
||||
listener.setLabel("#{sNotifyMessage4}", true, messagesCount > 0);
|
||||
|
||||
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);
|
||||
|
||||
Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen();
|
||||
int messagesCount = MWBase::Environment::get().getWindowManager()->getMessagesCount();
|
||||
|
||||
listener.setProgressRange(100);
|
||||
listener.setLabel("#{sLoadingMessage14}");
|
||||
listener.setLabel("#{sLoadingMessage14}", false, messagesCount > 0);
|
||||
|
||||
Loading::ScopedLoad load(&listener);
|
||||
|
||||
|
|
|
@ -70,6 +70,22 @@ namespace MWWorld
|
|||
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)
|
||||
{
|
||||
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).
|
||||
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);
|
||||
|
||||
// For weapon or armor, this is the remaining item health.
|
||||
|
|
|
@ -432,7 +432,7 @@ namespace MWWorld
|
|||
mHasState = true;
|
||||
}
|
||||
|
||||
int CellStore::count() const
|
||||
std::size_t CellStore::count() const
|
||||
{
|
||||
return mMergedRefs.size();
|
||||
}
|
||||
|
|
|
@ -240,7 +240,7 @@ namespace MWWorld
|
|||
|
||||
ESM::FogState* getFog () const;
|
||||
|
||||
int count() const;
|
||||
std::size_t count() const;
|
||||
///< Return total number of references, including deleted ones.
|
||||
|
||||
void load ();
|
||||
|
@ -283,7 +283,7 @@ namespace MWWorld
|
|||
/// \attention This function also lists deleted (count 0) objects!
|
||||
/// \return Iteration completed?
|
||||
template<class Visitor>
|
||||
bool forEachConst (Visitor& visitor) const
|
||||
bool forEachConst (Visitor&& visitor) const
|
||||
{
|
||||
if (mState != State_Loaded)
|
||||
return false;
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue