mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-30 03:15:32 +00:00
Add OpenMW commits up to 30 Sep 2020
# Conflicts: # apps/openmw/mwmechanics/aicombat.cpp # apps/openmw/mwmechanics/character.cpp # apps/openmw/mwmechanics/summoning.cpp # components/CMakeLists.txt
This commit is contained in:
commit
0eedf8fd9f
77 changed files with 1372 additions and 476 deletions
|
@ -60,7 +60,7 @@ variables: &cs-targets
|
|||
- choco source add -n=openmw-proxy -s="https://repo.openmw.org/repository/Chocolately/" --priority=1
|
||||
- choco install git --force --params "/GitAndUnixToolsOnPath" -y
|
||||
- choco install 7zip -y
|
||||
- choco install cmake.install --installargs 'ADD_CMAKE_TO_PATH=System' --version=3.18.0 -y
|
||||
- choco install cmake.install --installargs 'ADD_CMAKE_TO_PATH=System' -y
|
||||
- choco install vswhere -y
|
||||
- choco install ninja -y
|
||||
- choco install python -y
|
||||
|
|
|
@ -3,12 +3,14 @@
|
|||
|
||||
Bug #1662: Qt4 and Windows binaries crash if there's a non-ASCII character in a file path/config path
|
||||
Bug #1952: Incorrect particle lighting
|
||||
Bug #2069: Fireflies in Fireflies invade Morrowind look wrong
|
||||
Bug #2311: Targeted scripts are not properly supported on non-unique RefIDs
|
||||
Bug #3676: NiParticleColorModifier isn't applied properly
|
||||
Bug #3714: Savegame fails to load due to conflict between SpellState and MagicEffects
|
||||
Bug #4021: Attributes and skills are not stored as floats
|
||||
Bug #4055: Local scripts don't inherit variables from their base record
|
||||
Bug #4623: Corprus implementation is incorrect
|
||||
Bug #4631: Setting MSAA level too high doesn't fall back to highest supported level
|
||||
Bug #4764: Data race in osg ParticleSystem
|
||||
Bug #4774: Guards are ignorant of an invisible player that tries to attack them
|
||||
Bug #5108: Savegame bloating due to inefficient fog textures format
|
||||
|
@ -44,8 +46,12 @@
|
|||
Bug #5531: Actors flee using current rotation by axis x
|
||||
Bug #5539: Window resize breaks when going from a lower resolution to full screen resolution
|
||||
Bug #5548: Certain exhausted topics can be highlighted again even though there's no new dialogue
|
||||
Bug #5557: Diagonal movement is noticeably slower with analogue stick
|
||||
Bug #5603: Setting constant effect cast style doesn't correct effects view
|
||||
Bug #5611: Usable items with "0 Uses" should be used only once
|
||||
Feature #390: 3rd person look "over the shoulder"
|
||||
Feature #2386: Distant Statics in the form of Object Paging
|
||||
Feature #4894: Consider actors as obstacles for pathfinding
|
||||
Feature #5297: Add a search function to the "Datafiles" tab of the OpenMW launcher
|
||||
Feature #5362: Show the soul gems' trapped soul in count dialog
|
||||
Feature #5445: Handle NiLines
|
||||
|
@ -55,6 +61,8 @@
|
|||
Feature #5524: Resume failed script execution after reload
|
||||
Feature #5525: Search fields tweaks (utf-8)
|
||||
Feature #5545: Option to allow stealing from an unconscious NPC during combat
|
||||
Feature #5579: MCP SetAngle enhancement
|
||||
Feature #5610: Actors movement should be smoother
|
||||
Task #5480: Drop Qt4 support
|
||||
Task #5520: Improve cell name autocompleter implementation
|
||||
|
||||
|
|
|
@ -69,7 +69,7 @@ NMAKE=""
|
|||
NINJA=""
|
||||
PDBS=""
|
||||
PLATFORM=""
|
||||
CONFIGURATION=""
|
||||
CONFIGURATIONS=()
|
||||
TEST_FRAMEWORK=""
|
||||
GOOGLE_INSTALL_ROOT=""
|
||||
INSTALL_PREFIX="."
|
||||
|
@ -129,7 +129,7 @@ while [ $# -gt 0 ]; do
|
|||
PDBS=true ;;
|
||||
|
||||
c )
|
||||
CONFIGURATION=$1
|
||||
CONFIGURATIONS+=( $1 )
|
||||
shift ;;
|
||||
|
||||
t )
|
||||
|
@ -143,8 +143,10 @@ while [ $# -gt 0 ]; do
|
|||
cat <<EOF
|
||||
Usage: $0 [-cdehkpuvVi]
|
||||
Options:
|
||||
-c <Release/Debug>
|
||||
-c <Release/Debug/RelWithDebInfo>
|
||||
Set the configuration, can also be set with environment variable CONFIGURATION.
|
||||
For mutli-config generators, this is ignored, and all configurations are set up.
|
||||
For single-config generators, several configurations can be set up at once by specifying -c multiple times.
|
||||
-d
|
||||
Skip checking the downloads.
|
||||
-D
|
||||
|
@ -164,7 +166,7 @@ Options:
|
|||
-v <2017/2019>
|
||||
Choose the Visual Studio version to use.
|
||||
-n
|
||||
Produce NMake makefiles instead of a Visual Studio solution. Cannout be used with -N.
|
||||
Produce NMake makefiles instead of a Visual Studio solution. Cannot be used with -N.
|
||||
-N
|
||||
Produce Ninja (multi-config if CMake is new enough to support it) files instead of a Visual Studio solution. Cannot be used with -n..
|
||||
-P
|
||||
|
@ -187,7 +189,7 @@ done
|
|||
|
||||
if [ -n "$NMAKE" ] || [ -n "$NINJA" ]; then
|
||||
if [ -n "$NMAKE" ] && [ -n "$NINJA" ]; then
|
||||
echo "Cannout run in NMake and Ninja mode at the same time."
|
||||
echo "Cannot run in NMake and Ninja mode at the same time."
|
||||
wrappedExit 1
|
||||
fi
|
||||
ACTIVATE_MSVC=true
|
||||
|
@ -293,29 +295,40 @@ add_cmake_opts() {
|
|||
CMAKE_OPTS="$CMAKE_OPTS $@"
|
||||
}
|
||||
|
||||
RUNTIME_DLLS=""
|
||||
declare -A RUNTIME_DLLS
|
||||
RUNTIME_DLLS["Release"]=""
|
||||
RUNTIME_DLLS["Debug"]=""
|
||||
RUNTIME_DLLS["RelWithDebInfo"]=""
|
||||
add_runtime_dlls() {
|
||||
RUNTIME_DLLS="$RUNTIME_DLLS $@"
|
||||
local CONFIG=$1
|
||||
shift
|
||||
RUNTIME_DLLS[$CONFIG]="${RUNTIME_DLLS[$CONFIG]} $@"
|
||||
}
|
||||
|
||||
OSG_PLUGINS=""
|
||||
declare -A OSG_PLUGINS
|
||||
OSG_PLUGINS["Release"]=""
|
||||
OSG_PLUGINS["Debug"]=""
|
||||
OSG_PLUGINS["RelWithDebInfo"]=""
|
||||
add_osg_dlls() {
|
||||
OSG_PLUGINS="$OSG_PLUGINS $@"
|
||||
local CONFIG=$1
|
||||
shift
|
||||
OSG_PLUGINS[$CONFIG]="${OSG_PLUGINS[$CONFIG]} $@"
|
||||
}
|
||||
|
||||
QT_PLATFORMS=""
|
||||
declare -A QT_PLATFORMS
|
||||
QT_PLATFORMS["Release"]=""
|
||||
QT_PLATFORMS["Debug"]=""
|
||||
QT_PLATFORMS["RelWithDebInfo"]=""
|
||||
add_qt_platform_dlls() {
|
||||
QT_PLATFORMS="$QT_PLATFORMS $@"
|
||||
local CONFIG=$1
|
||||
shift
|
||||
QT_PLATFORMS[$CONFIG]="${QT_PLATFORMS[$CONFIG]} $@"
|
||||
}
|
||||
|
||||
if [ -z $PLATFORM ]; then
|
||||
PLATFORM="$(uname -m)"
|
||||
fi
|
||||
|
||||
if [ -z $CONFIGURATION ]; then
|
||||
CONFIGURATION="Debug"
|
||||
fi
|
||||
|
||||
if [ -z $VS_VERSION ]; then
|
||||
VS_VERSION="2017"
|
||||
fi
|
||||
|
@ -377,23 +390,6 @@ case $PLATFORM in
|
|||
;;
|
||||
esac
|
||||
|
||||
case $CONFIGURATION in
|
||||
debug|Debug|DEBUG )
|
||||
CONFIGURATION=Debug
|
||||
BUILD_CONFIG=Debug
|
||||
;;
|
||||
|
||||
release|Release|RELEASE )
|
||||
CONFIGURATION=Release
|
||||
BUILD_CONFIG=Release
|
||||
;;
|
||||
|
||||
relwithdebinfo|RelWithDebInfo|RELWITHDEBINFO )
|
||||
CONFIGURATION=Release
|
||||
BUILD_CONFIG=RelWithDebInfo
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ $BITS -eq 64 ] && [ $MSVC_REAL_VER -lt 16 ]; then
|
||||
GENERATOR="${GENERATOR} Win64"
|
||||
fi
|
||||
|
@ -411,6 +407,79 @@ if [ -n "$NINJA" ]; then
|
|||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$SINGLE_CONFIG" ]; then
|
||||
if [ ${#CONFIGURATIONS[@]} -eq 0 ]; then
|
||||
if [ -n "${CONFIGURATION:-}" ]; then
|
||||
CONFIGURATIONS=("$CONFIGURATION")
|
||||
else
|
||||
CONFIGURATIONS=("Debug")
|
||||
fi
|
||||
elif [ ${#CONFIGURATIONS[@]} -ne 1 ]; then
|
||||
# It's simplest just to recursively call the script a few times.
|
||||
RECURSIVE_OPTIONS=()
|
||||
if [ -n "$VERBOSE" ]; then
|
||||
RECURSIVE_OPTIONS+=("-V")
|
||||
fi
|
||||
if [ -n "$SKIP_DOWNLOAD" ]; then
|
||||
RECURSIVE_OPTIONS+=("-d")
|
||||
fi
|
||||
if [ -n "$BULLET_DOUBLE" ]; then
|
||||
RECURSIVE_OPTIONS+=("-D")
|
||||
fi
|
||||
if [ -n "$SKIP_EXTRACT" ]; then
|
||||
RECURSIVE_OPTIONS+=("-e")
|
||||
fi
|
||||
if [ -n "$KEEP" ]; then
|
||||
RECURSIVE_OPTIONS+=("-k")
|
||||
fi
|
||||
if [ -n "$UNITY_BUILD" ]; then
|
||||
RECURSIVE_OPTIONS+=("-u")
|
||||
fi
|
||||
if [ -n "$NMAKE" ]; then
|
||||
RECURSIVE_OPTIONS+=("-n")
|
||||
fi
|
||||
if [ -n "$NINJA" ]; then
|
||||
RECURSIVE_OPTIONS+=("-N")
|
||||
fi
|
||||
if [ -n "$PDBS" ]; then
|
||||
RECURSIVE_OPTIONS+=("-P")
|
||||
fi
|
||||
if [ -n "$TEST_FRAMEWORK" ]; then
|
||||
RECURSIVE_OPTIONS+=("-t")
|
||||
fi
|
||||
RECURSIVE_OPTIONS+=("-v $VS_VERSION")
|
||||
RECURSIVE_OPTIONS+=("-p $PLATFORM")
|
||||
RECURSIVE_OPTIONS+=("-i '$INSTALL_PREFIX'")
|
||||
|
||||
for config in ${CONFIGURATIONS[@]}; do
|
||||
$0 ${RECURSIVE_OPTIONS[@]} -c $config
|
||||
done
|
||||
|
||||
wrappedExit 1
|
||||
fi
|
||||
else
|
||||
if [ ${#CONFIGURATIONS[@]} -ne 0 ]; then
|
||||
echo "Ignoring configurations argument - generator is multi-config"
|
||||
fi
|
||||
CONFIGURATIONS=("Release" "Debug" "RelWithDebInfo")
|
||||
fi
|
||||
|
||||
for i in ${!CONFIGURATIONS[@]}; do
|
||||
case ${CONFIGURATIONS[$i]} in
|
||||
debug|Debug|DEBUG )
|
||||
CONFIGURATIONS[$i]=Debug
|
||||
;;
|
||||
|
||||
release|Release|RELEASE )
|
||||
CONFIGURATIONS[$i]=Release
|
||||
;;
|
||||
|
||||
relwithdebinfo|RelWithDebInfo|RELWITHDEBINFO )
|
||||
CONFIGURATIONS[$i]=RelWithDebInfo
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ $MSVC_REAL_VER -ge 16 ] && [ -z "$NMAKE" ] && [ -z "$NINJA" ]; then
|
||||
if [ $BITS -eq 64 ]; then
|
||||
add_cmake_opts "-G\"$GENERATOR\" -A x64"
|
||||
|
@ -422,7 +491,7 @@ else
|
|||
fi
|
||||
|
||||
if [ -n "$SINGLE_CONFIG" ]; then
|
||||
add_cmake_opts "-DCMAKE_BUILD_TYPE=${BUILD_CONFIG}"
|
||||
add_cmake_opts "-DCMAKE_BUILD_TYPE=${CONFIGURATIONS[0]}"
|
||||
fi
|
||||
|
||||
if ! [ -z $UNITY_BUILD ]; then
|
||||
|
@ -454,52 +523,52 @@ if [ -z $SKIP_DOWNLOAD ]; then
|
|||
# Boost
|
||||
if [ -z $APPVEYOR ]; then
|
||||
download "Boost ${BOOST_VER}" \
|
||||
"https://sourceforge.net/projects/boost/files/boost-binaries/${BOOST_VER}/boost_${BOOST_VER_URL}-msvc-${MSVC_VER}-${BITS}.exe" \
|
||||
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/${BOOST_VER}/boost_${BOOST_VER_URL}-msvc-${MSVC_VER}-${BITS}.exe" \
|
||||
"boost-${BOOST_VER}-msvc${MSVC_VER}-win${BITS}.exe"
|
||||
fi
|
||||
|
||||
# Bullet
|
||||
download "Bullet 2.89 (${BULLET_DBL_DISPLAY})" \
|
||||
"https://rgw.ctrl-c.liu.se/openmw/Deps/Bullet-2.89-msvc${MSVC_YEAR}-win${BITS}${BULLET_DBL}.7z" \
|
||||
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/Bullet-2.89-msvc${MSVC_YEAR}-win${BITS}${BULLET_DBL}.7z" \
|
||||
"Bullet-2.89-msvc${MSVC_YEAR}-win${BITS}${BULLET_DBL}.7z"
|
||||
|
||||
# FFmpeg
|
||||
download "FFmpeg 4.2.2" \
|
||||
"https://ffmpeg.zeranoe.com/builds/win${BITS}/shared/ffmpeg-4.2.2-win${BITS}-shared.zip" \
|
||||
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/ffmpeg-4.2.2-win${BITS}.zip" \
|
||||
"ffmpeg-4.2.2-win${BITS}.zip" \
|
||||
"https://ffmpeg.zeranoe.com/builds/win${BITS}/dev/ffmpeg-4.2.2-win${BITS}-dev.zip" \
|
||||
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/ffmpeg-4.2.2-dev-win${BITS}.zip" \
|
||||
"ffmpeg-4.2.2-dev-win${BITS}.zip"
|
||||
|
||||
# MyGUI
|
||||
download "MyGUI 3.4.0" \
|
||||
"https://rgw.ctrl-c.liu.se/openmw/Deps/MyGUI-3.4.0-msvc${MSVC_REAL_YEAR}-win${BITS}.7z" \
|
||||
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/MyGUI-3.4.0-msvc${MSVC_REAL_YEAR}-win${BITS}.7z" \
|
||||
"MyGUI-3.4.0-msvc${MSVC_REAL_YEAR}-win${BITS}.7z"
|
||||
|
||||
if [ -n "$PDBS" ]; then
|
||||
download "MyGUI symbols" \
|
||||
"https://rgw.ctrl-c.liu.se/openmw/Deps/MyGUI-3.4.0-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z" \
|
||||
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/MyGUI-3.4.0-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z" \
|
||||
"MyGUI-3.4.0-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z"
|
||||
fi
|
||||
|
||||
# OpenAL
|
||||
download "OpenAL-Soft 1.20.1" \
|
||||
"http://openal-soft.org/openal-binaries/openal-soft-1.20.1-bin.zip" \
|
||||
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/OpenAL-Soft-1.20.1.zip" \
|
||||
"OpenAL-Soft-1.20.1.zip"
|
||||
|
||||
# OSG
|
||||
download "OpenSceneGraph 3.6.5" \
|
||||
"https://rgw.ctrl-c.liu.se/openmw/Deps/OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}.7z" \
|
||||
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}.7z" \
|
||||
"OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}.7z"
|
||||
|
||||
if [ -n "$PDBS" ]; then
|
||||
download "OpenSceneGraph symbols" \
|
||||
"https://rgw.ctrl-c.liu.se/openmw/Deps/OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z" \
|
||||
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z" \
|
||||
"OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z"
|
||||
fi
|
||||
|
||||
# SDL2
|
||||
download "SDL 2.0.12" \
|
||||
"https://www.libsdl.org/release/SDL2-devel-2.0.12-VC.zip" \
|
||||
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/SDL2-2.0.12.zip" \
|
||||
"SDL2-2.0.12.zip"
|
||||
|
||||
# Google test and mock
|
||||
|
@ -525,7 +594,7 @@ elif [ -n "$NINJA" ]; then
|
|||
fi
|
||||
|
||||
if [ -n "$SINGLE_CONFIG" ]; then
|
||||
BUILD_DIR="${BUILD_DIR}_${BUILD_CONFIG}"
|
||||
BUILD_DIR="${BUILD_DIR}_${CONFIGURATIONS[0]}"
|
||||
fi
|
||||
|
||||
if [ -z $KEEP ]; then
|
||||
|
@ -626,7 +695,9 @@ printf "FFmpeg 4.2.2... "
|
|||
rm -rf "ffmpeg-4.2.2-win${BITS}-dev"
|
||||
fi
|
||||
export FFMPEG_HOME="$(real_pwd)/FFmpeg"
|
||||
add_runtime_dlls "$(pwd)/FFmpeg/bin/"{avcodec-58,avformat-58,avutil-56,swresample-3,swscale-5}.dll
|
||||
for config in ${CONFIGURATIONS[@]}; do
|
||||
add_runtime_dlls $config "$(pwd)/FFmpeg/bin/"{avcodec-58,avformat-58,avutil-56,swresample-3,swscale-5}.dll
|
||||
done
|
||||
if [ $BITS -eq 32 ]; then
|
||||
add_cmake_opts "-DCMAKE_EXE_LINKER_FLAGS=\"/machine:X86 /safeseh:no\""
|
||||
fi
|
||||
|
@ -651,14 +722,16 @@ printf "MyGUI 3.4.0... "
|
|||
mv "MyGUI-3.4.0-msvc${MSVC_REAL_YEAR}-win${BITS}" MyGUI
|
||||
fi
|
||||
export MYGUI_HOME="$(real_pwd)/MyGUI"
|
||||
if [ $CONFIGURATION == "Debug" ]; then
|
||||
SUFFIX="_d"
|
||||
MYGUI_CONFIGURATION="Debug"
|
||||
else
|
||||
SUFFIX=""
|
||||
MYGUI_CONFIGURATION="RelWithDebInfo"
|
||||
fi
|
||||
add_runtime_dlls "$(pwd)/MyGUI/bin/${MYGUI_CONFIGURATION}/MyGUIEngine${SUFFIX}.dll"
|
||||
for CONFIGURATION in ${CONFIGURATIONS[@]}; do
|
||||
if [ $CONFIGURATION == "Debug" ]; then
|
||||
SUFFIX="_d"
|
||||
MYGUI_CONFIGURATION="Debug"
|
||||
else
|
||||
SUFFIX=""
|
||||
MYGUI_CONFIGURATION="RelWithDebInfo"
|
||||
fi
|
||||
add_runtime_dlls $CONFIGURATION "$(pwd)/MyGUI/bin/${MYGUI_CONFIGURATION}/MyGUIEngine${SUFFIX}.dll"
|
||||
done
|
||||
echo Done.
|
||||
}
|
||||
cd $DEPS
|
||||
|
@ -675,7 +748,9 @@ printf "OpenAL-Soft 1.20.1... "
|
|||
OPENAL_SDK="$(real_pwd)/openal-soft-1.20.1-bin"
|
||||
add_cmake_opts -DOPENAL_INCLUDE_DIR="${OPENAL_SDK}/include/AL" \
|
||||
-DOPENAL_LIBRARY="${OPENAL_SDK}/libs/Win${BITS}/OpenAL32.lib"
|
||||
add_runtime_dlls "$(pwd)/openal-soft-1.20.1-bin/bin/WIN${BITS}/soft_oal.dll:OpenAL32.dll"
|
||||
for config in ${CONFIGURATIONS[@]}; do
|
||||
add_runtime_dlls $config "$(pwd)/openal-soft-1.20.1-bin/bin/WIN${BITS}/soft_oal.dll:OpenAL32.dll"
|
||||
done
|
||||
echo Done.
|
||||
}
|
||||
cd $DEPS
|
||||
|
@ -698,15 +773,17 @@ printf "OSG 3.6.5... "
|
|||
fi
|
||||
OSG_SDK="$(real_pwd)/OSG"
|
||||
add_cmake_opts -DOSG_DIR="$OSG_SDK"
|
||||
if [ $CONFIGURATION == "Debug" ]; then
|
||||
SUFFIX="d"
|
||||
else
|
||||
SUFFIX=""
|
||||
fi
|
||||
add_runtime_dlls "$(pwd)/OSG/bin/"{OpenThreads,zlib,libpng}${SUFFIX}.dll \
|
||||
"$(pwd)/OSG/bin/osg"{,Animation,DB,FX,GA,Particle,Text,Util,Viewer,Shadow}${SUFFIX}.dll
|
||||
add_osg_dlls "$(pwd)/OSG/bin/osgPlugins-3.6.5/osgdb_"{bmp,dds,freetype,jpeg,osg,png,tga}${SUFFIX}.dll
|
||||
add_osg_dlls "$(pwd)/OSG/bin/osgPlugins-3.6.5/osgdb_serializers_osg"{,animation,fx,ga,particle,text,util,viewer,shadow}${SUFFIX}.dll
|
||||
for CONFIGURATION in ${CONFIGURATIONS[@]}; do
|
||||
if [ $CONFIGURATION == "Debug" ]; then
|
||||
SUFFIX="d"
|
||||
else
|
||||
SUFFIX=""
|
||||
fi
|
||||
add_runtime_dlls $CONFIGURATION "$(pwd)/OSG/bin/"{OpenThreads,zlib,libpng}${SUFFIX}.dll \
|
||||
"$(pwd)/OSG/bin/osg"{,Animation,DB,FX,GA,Particle,Text,Util,Viewer,Shadow}${SUFFIX}.dll
|
||||
add_osg_dlls $CONFIGURATION "$(pwd)/OSG/bin/osgPlugins-3.6.5/osgdb_"{bmp,dds,freetype,jpeg,osg,png,tga}${SUFFIX}.dll
|
||||
add_osg_dlls $CONFIGURATION "$(pwd)/OSG/bin/osgPlugins-3.6.5/osgdb_serializers_osg"{,animation,fx,ga,particle,text,util,viewer,shadow}${SUFFIX}.dll
|
||||
done
|
||||
echo Done.
|
||||
}
|
||||
cd $DEPS
|
||||
|
@ -778,26 +855,30 @@ fi
|
|||
cd $QT_SDK
|
||||
add_cmake_opts -DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" \
|
||||
-DCMAKE_PREFIX_PATH="$QT_SDK"
|
||||
if [ $CONFIGURATION == "Debug" ]; then
|
||||
SUFFIX="d"
|
||||
else
|
||||
SUFFIX=""
|
||||
fi
|
||||
add_runtime_dlls "$(pwd)/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${SUFFIX}.dll
|
||||
add_qt_platform_dlls "$(pwd)/plugins/platforms/qwindows${SUFFIX}.dll"
|
||||
for CONFIGURATION in ${CONFIGURATIONS[@]}; do
|
||||
if [ $CONFIGURATION == "Debug" ]; then
|
||||
DLLSUFFIX="d"
|
||||
else
|
||||
DLLSUFFIX=""
|
||||
fi
|
||||
add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${DLLSUFFIX}.dll
|
||||
add_qt_platform_dlls $CONFIGURATION "$(pwd)/plugins/platforms/qwindows${DLLSUFFIX}.dll"
|
||||
done
|
||||
echo Done.
|
||||
else
|
||||
QT_SDK="C:/Qt/5.13/msvc2017${SUFFIX}"
|
||||
add_cmake_opts -DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" \
|
||||
-DCMAKE_PREFIX_PATH="$QT_SDK"
|
||||
if [ $CONFIGURATION == "Debug" ]; then
|
||||
SUFFIX="d"
|
||||
else
|
||||
SUFFIX=""
|
||||
fi
|
||||
DIR=$(windowsPathAsUnix "${QT_SDK}")
|
||||
add_runtime_dlls "${DIR}/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${SUFFIX}.dll
|
||||
add_qt_platform_dlls "${DIR}/plugins/platforms/qwindows${SUFFIX}.dll"
|
||||
for CONFIGURATION in ${CONFIGURATIONS[@]}; do
|
||||
if [ $CONFIGURATION == "Debug" ]; then
|
||||
DLLSUFFIX="d"
|
||||
else
|
||||
DLLSUFFIX=""
|
||||
fi
|
||||
DIR=$(windowsPathAsUnix "${QT_SDK}")
|
||||
add_runtime_dlls $CONFIGURATION "${DIR}/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${DLLSUFFIX}.dll
|
||||
add_qt_platform_dlls $CONFIGURATION "${DIR}/plugins/platforms/qwindows${DLLSUFFIX}.dll"
|
||||
done
|
||||
echo Done.
|
||||
fi
|
||||
}
|
||||
|
@ -813,7 +894,9 @@ printf "SDL 2.0.12... "
|
|||
eval 7z x -y SDL2-2.0.12.zip $STRIP
|
||||
fi
|
||||
export SDL2DIR="$(real_pwd)/SDL2-2.0.12"
|
||||
add_runtime_dlls "$(pwd)/SDL2-2.0.12/lib/x${ARCHSUFFIX}/SDL2.dll"
|
||||
for config in ${CONFIGURATIONS[@]}; do
|
||||
add_runtime_dlls $config "$(pwd)/SDL2-2.0.12/lib/x${ARCHSUFFIX}/SDL2.dll"
|
||||
done
|
||||
echo Done.
|
||||
}
|
||||
cd $DEPS
|
||||
|
@ -823,41 +906,51 @@ if [ ! -z $TEST_FRAMEWORK ]; then
|
|||
printf "Google test 1.10.0 ..."
|
||||
|
||||
cd googletest
|
||||
if [ ! -d build ]; then
|
||||
mkdir build
|
||||
fi
|
||||
mkdir -p build${MSVC_REAL_YEAR}
|
||||
|
||||
cd build
|
||||
cd build${MSVC_REAL_YEAR}
|
||||
|
||||
GOOGLE_INSTALL_ROOT="${DEPS_INSTALL}/GoogleTest"
|
||||
if [ $CONFIGURATION == "Debug" ]; then
|
||||
|
||||
for CONFIGURATION in ${CONFIGURATIONS[@]}; do
|
||||
# FindGMock.cmake mentions Release explicitly, but not RelWithDebInfo. Only one optimised library config can be used, so go for the safer one.
|
||||
GTEST_CONFIG=$([ $CONFIGURATION == "RelWithDebInfo" ] && echo "Release" || echo "$CONFIGURATION" )
|
||||
if [ $GTEST_CONFIG == "Debug" ]; then
|
||||
DEBUG_SUFFIX="d"
|
||||
else
|
||||
DEBUG_SUFFIX=""
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -d $GOOGLE_INSTALL_ROOT ]; then
|
||||
if [ ! -f "$GOOGLE_INSTALL_ROOT/lib/gtest${DEBUG_SUFFIX}.lib" ]; then
|
||||
# Always use MSBuild solution files as they don't need the environment activating
|
||||
cmake .. -DCMAKE_USE_WIN32_THREADS_INIT=1 -G "Visual Studio $MSVC_REAL_VER $MSVC_REAL_YEAR$([ $BITS -eq 64 ] && [ $MSVC_REAL_VER -lt 16 ] && echo " Win64")" $([ $MSVC_REAL_VER -ge 16 ] && echo "-A $([ $BITS -eq 64 ] && echo "x64" || echo "Win32")") -DBUILD_SHARED_LIBS=1
|
||||
cmake --build . --config "${GTEST_CONFIG}"
|
||||
cmake --install . --config "${GTEST_CONFIG}" --prefix "${GOOGLE_INSTALL_ROOT}"
|
||||
fi
|
||||
|
||||
cmake .. -DCMAKE_BUILD_TYPE="${CONFIGURATION}" -DCMAKE_INSTALL_PREFIX="${GOOGLE_INSTALL_ROOT}" -DCMAKE_USE_WIN32_THREADS_INIT=1 -G "${GENERATOR}" -DBUILD_SHARED_LIBS=1
|
||||
cmake --build . --config "${CONFIGURATION}"
|
||||
cmake --build . --target install --config "${CONFIGURATION}"
|
||||
|
||||
add_runtime_dlls "${GOOGLE_INSTALL_ROOT}\bin\gtest_main${DEBUG_SUFFIX}.dll"
|
||||
add_runtime_dlls "${GOOGLE_INSTALL_ROOT}\bin\gtest${DEBUG_SUFFIX}.dll"
|
||||
add_runtime_dlls "${GOOGLE_INSTALL_ROOT}\bin\gmock_main${DEBUG_SUFFIX}.dll"
|
||||
add_runtime_dlls "${GOOGLE_INSTALL_ROOT}\bin\gmock${DEBUG_SUFFIX}.dll"
|
||||
fi
|
||||
add_runtime_dlls $CONFIGURATION "${GOOGLE_INSTALL_ROOT}\bin\gtest_main${DEBUG_SUFFIX}.dll"
|
||||
add_runtime_dlls $CONFIGURATION "${GOOGLE_INSTALL_ROOT}\bin\gtest${DEBUG_SUFFIX}.dll"
|
||||
add_runtime_dlls $CONFIGURATION "${GOOGLE_INSTALL_ROOT}\bin\gmock_main${DEBUG_SUFFIX}.dll"
|
||||
add_runtime_dlls $CONFIGURATION "${GOOGLE_INSTALL_ROOT}\bin\gmock${DEBUG_SUFFIX}.dll"
|
||||
done
|
||||
|
||||
add_cmake_opts -DBUILD_UNITTESTS=yes
|
||||
# FindGTest and FindGMock do not work perfectly on Windows
|
||||
# but we can help them by telling them everything we know about installation
|
||||
add_cmake_opts -DGMOCK_ROOT="$GOOGLE_INSTALL_ROOT"
|
||||
add_cmake_opts -DGTEST_ROOT="$GOOGLE_INSTALL_ROOT"
|
||||
add_cmake_opts -DGTEST_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gtest${DEBUG_SUFFIX}.lib"
|
||||
add_cmake_opts -DGTEST_MAIN_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gtest_main${DEBUG_SUFFIX}.lib"
|
||||
add_cmake_opts -DGMOCK_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gmock${DEBUG_SUFFIX}.lib"
|
||||
add_cmake_opts -DGMOCK_MAIN_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gmock_main${DEBUG_SUFFIX}.lib"
|
||||
add_cmake_opts -DGTEST_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gtest.lib"
|
||||
add_cmake_opts -DGTEST_MAIN_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gtest_main.lib"
|
||||
add_cmake_opts -DGMOCK_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gmock.lib"
|
||||
add_cmake_opts -DGMOCK_MAIN_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gmock_main.lib"
|
||||
add_cmake_opts -DGTEST_LIBRARY_DEBUG="$GOOGLE_INSTALL_ROOT/lib/gtestd.lib"
|
||||
add_cmake_opts -DGTEST_MAIN_LIBRARY_DEBUG="$GOOGLE_INSTALL_ROOT/lib/gtest_maind.lib"
|
||||
add_cmake_opts -DGMOCK_LIBRARY_DEBUG="$GOOGLE_INSTALL_ROOT/lib/gmockd.lib"
|
||||
add_cmake_opts -DGMOCK_MAIN_LIBRARY_DEBUG="$GOOGLE_INSTALL_ROOT/lib/gmock_maind.lib"
|
||||
add_cmake_opts -DGTEST_LINKED_AS_SHARED_LIBRARY=True
|
||||
add_cmake_opts -DGTEST_LIBRARY_TYPE=SHARED
|
||||
add_cmake_opts -DGTEST_MAIN_LIBRARY_TYPE=SHARED
|
||||
|
||||
echo Done.
|
||||
|
||||
fi
|
||||
|
@ -904,45 +997,47 @@ if [ ! -z $CI ]; then
|
|||
fi
|
||||
# NOTE: Disable this when/if we want to run test cases
|
||||
#if [ -z $CI ]; then
|
||||
echo "- Copying Runtime DLLs..."
|
||||
DLL_PREFIX=""
|
||||
if [ -z $SINGLE_CONFIG ]; then
|
||||
mkdir -p $BUILD_CONFIG
|
||||
DLL_PREFIX="$BUILD_CONFIG/"
|
||||
fi
|
||||
for DLL in $RUNTIME_DLLS; do
|
||||
TARGET="$(basename "$DLL")"
|
||||
if [[ "$DLL" == *":"* ]]; then
|
||||
originalIFS="$IFS"
|
||||
IFS=':'; SPLIT=( ${DLL} ); IFS=$originalIFS
|
||||
DLL=${SPLIT[0]}
|
||||
TARGET=${SPLIT[1]}
|
||||
for CONFIGURATION in ${CONFIGURATIONS[@]}; do
|
||||
echo "- Copying Runtime DLLs for $CONFIGURATION..."
|
||||
DLL_PREFIX=""
|
||||
if [ -z $SINGLE_CONFIG ]; then
|
||||
mkdir -p $CONFIGURATION
|
||||
DLL_PREFIX="$CONFIGURATION/"
|
||||
fi
|
||||
echo " ${TARGET}."
|
||||
cp "$DLL" "${DLL_PREFIX}$TARGET"
|
||||
for DLL in ${RUNTIME_DLLS[$CONFIGURATION]}; do
|
||||
TARGET="$(basename "$DLL")"
|
||||
if [[ "$DLL" == *":"* ]]; then
|
||||
originalIFS="$IFS"
|
||||
IFS=':'; SPLIT=( ${DLL} ); IFS=$originalIFS
|
||||
DLL=${SPLIT[0]}
|
||||
TARGET=${SPLIT[1]}
|
||||
fi
|
||||
echo " ${TARGET}."
|
||||
cp "$DLL" "${DLL_PREFIX}$TARGET"
|
||||
done
|
||||
echo
|
||||
echo "- OSG Plugin DLLs..."
|
||||
mkdir -p ${DLL_PREFIX}osgPlugins-3.6.5
|
||||
for DLL in ${OSG_PLUGINS[$CONFIGURATION]}; do
|
||||
echo " $(basename $DLL)."
|
||||
cp "$DLL" ${DLL_PREFIX}osgPlugins-3.6.5
|
||||
done
|
||||
echo
|
||||
echo "- Qt Platform DLLs..."
|
||||
mkdir -p ${DLL_PREFIX}platforms
|
||||
for DLL in ${QT_PLATFORMS[$CONFIGURATION]}; do
|
||||
echo " $(basename $DLL)"
|
||||
cp "$DLL" "${DLL_PREFIX}platforms"
|
||||
done
|
||||
echo
|
||||
done
|
||||
echo
|
||||
echo "- OSG Plugin DLLs..."
|
||||
mkdir -p ${DLL_PREFIX}osgPlugins-3.6.5
|
||||
for DLL in $OSG_PLUGINS; do
|
||||
echo " $(basename $DLL)."
|
||||
cp "$DLL" ${DLL_PREFIX}osgPlugins-3.6.5
|
||||
done
|
||||
echo
|
||||
echo "- Qt Platform DLLs..."
|
||||
mkdir -p ${DLL_PREFIX}platforms
|
||||
for DLL in $QT_PLATFORMS; do
|
||||
echo " $(basename $DLL)"
|
||||
cp "$DLL" "${DLL_PREFIX}platforms"
|
||||
done
|
||||
echo
|
||||
#fi
|
||||
|
||||
if [ -n "$ACTIVATE_MSVC" ]; then
|
||||
echo -n "- Activating MSVC in the current shell... "
|
||||
command -v vswhere >/dev/null 2>&1 || { echo "Error: vswhere is not on the path."; wrappedExit 1; }
|
||||
|
||||
MSVC_INSTALLATION_PATH=$(vswhere -legacy -products '*' -version "[$MSVC_VER,$(awk "BEGIN { print $MSVC_REAL_VER + 1; exit }"))" -property installationPath)
|
||||
MSVC_INSTALLATION_PATH=$(vswhere -products '*' -version "[$MSVC_REAL_VER,$(awk "BEGIN { print $MSVC_REAL_VER + 1; exit }"))" -property installationPath)
|
||||
if [ -z "$MSVC_INSTALLATION_PATH" ]; then
|
||||
echo "vswhere was unable to find MSVC $MSVC_DISPLAY_YEAR"
|
||||
wrappedExit 1
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
project(OpenMW)
|
||||
cmake_minimum_required(VERSION 3.1.0)
|
||||
|
||||
# for link time optimization, remove if cmake version is >= 3.9
|
||||
if(POLICY CMP0069)
|
||||
cmake_policy(SET CMP0069 NEW)
|
||||
endif()
|
||||
|
||||
# Apps and tools
|
||||
option(BUILD_OPENMW "Build OpenMW" ON)
|
||||
option(BUILD_LAUNCHER "Build Launcher" ON)
|
||||
|
@ -100,6 +105,7 @@ option(OSG_STATIC "Link static build of OpenSceneGraph into the binaries" FALSE)
|
|||
option(QT_STATIC "Link static build of QT into the binaries" FALSE)
|
||||
|
||||
option(OPENMW_UNITY_BUILD "Use fewer compilation units to speed up compile time" FALSE)
|
||||
option(OPENMW_LTO_BUILD "Build OpenMW with Link-Time Optimization (Needs ~2GB of RAM)" OFF)
|
||||
|
||||
# what is necessary to build documentation
|
||||
IF( BUILD_DOCS )
|
||||
|
@ -113,7 +119,6 @@ option(OPENMW_OSX_DEPLOYMENT OFF)
|
|||
|
||||
if (MSVC)
|
||||
option(OPENMW_MP_BUILD "Build OpenMW with /MP flag" OFF)
|
||||
option(OPENMW_LTO_BUILD "Build OpenMW with Link-Time Optimization (Needs ~2GB of RAM)" OFF)
|
||||
endif()
|
||||
|
||||
# Set up common paths
|
||||
|
@ -411,6 +416,26 @@ endif()
|
|||
|
||||
# CXX Compiler settings
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
|
||||
if(OPENMW_LTO_BUILD)
|
||||
if(NOT CMAKE_VERSION VERSION_LESS 3.9)
|
||||
include(CheckIPOSupported)
|
||||
check_ipo_supported(RESULT HAVE_IPO OUTPUT HAVE_IPO_OUTPUT)
|
||||
if(HAVE_IPO)
|
||||
message(STATUS "LTO enabled for Release configuration.")
|
||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE)
|
||||
else()
|
||||
message(WARNING "Requested option OPENMW_LTO_BUILD not supported by this compiler: ${HAVE_IPO_OUTPUT}")
|
||||
if(MSVC)
|
||||
message(STATUS "Note: Flags used to be set manually for this setting with MSVC. We now rely on CMake for this. Upgrade CMake to at least 3.13 to re-enable this setting.")
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
message(WARNING "Requested option OPENMW_LTO_BUILD not supported by this cmake version: ${CMAKE_VERSION}. Upgrade CMake to at least 3.9 to enable support for certain compilers. Newer CMake versions support more compilers.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wundef -Wno-unused-parameter -std=c++14 -pedantic -Wno-long-long")
|
||||
add_definitions( -DBOOST_NO_CXX11_SCOPED_ENUMS=ON )
|
||||
|
@ -430,14 +455,6 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang)
|
|||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-but-set-parameter")
|
||||
endif()
|
||||
elseif (MSVC)
|
||||
# Enable link-time code generation globally for all linking
|
||||
if (OPENMW_LTO_BUILD)
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GL")
|
||||
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /LTCG")
|
||||
set(CMAKE_STATIC_LINKER_FLAGS_RELEASE "${CMAKE_STATIC_LINKER_FLAGS_RELEASE} /LTCG")
|
||||
endif()
|
||||
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /FORCE:MULTIPLE")
|
||||
endif (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang)
|
||||
|
||||
|
|
|
@ -88,6 +88,7 @@ bool Launcher::AdvancedPage::loadSettings()
|
|||
loadSettingBool(uncappedDamageFatigueCheckBox, "uncapped damage fatigue", "Game");
|
||||
loadSettingBool(normaliseRaceSpeedCheckBox, "normalise race speed", "Game");
|
||||
loadSettingBool(swimUpwardCorrectionCheckBox, "swim upward correction", "Game");
|
||||
loadSettingBool(avoidCollisionsCheckBox, "NPCs avoid collisions", "Game");
|
||||
int unarmedFactorsStrengthIndex = mEngineSettings.getInt("strength influences hand to hand", "Game");
|
||||
if (unarmedFactorsStrengthIndex >= 0 && unarmedFactorsStrengthIndex <= 2)
|
||||
unarmedFactorsStrengthComboBox->setCurrentIndex(unarmedFactorsStrengthIndex);
|
||||
|
@ -112,6 +113,7 @@ bool Launcher::AdvancedPage::loadSettings()
|
|||
loadSettingBool(shieldSheathingCheckBox, "shield sheathing", "Game");
|
||||
}
|
||||
loadSettingBool(turnToMovementDirectionCheckBox, "turn to movement direction", "Game");
|
||||
loadSettingBool(smoothMovementCheckBox, "smooth movement", "Game");
|
||||
|
||||
const bool distantTerrain = mEngineSettings.getBool("distant terrain", "Terrain");
|
||||
const bool objectPaging = mEngineSettings.getBool("object paging", "Terrain");
|
||||
|
@ -200,6 +202,7 @@ void Launcher::AdvancedPage::saveSettings()
|
|||
saveSettingBool(uncappedDamageFatigueCheckBox, "uncapped damage fatigue", "Game");
|
||||
saveSettingBool(normaliseRaceSpeedCheckBox, "normalise race speed", "Game");
|
||||
saveSettingBool(swimUpwardCorrectionCheckBox, "swim upward correction", "Game");
|
||||
saveSettingBool(avoidCollisionsCheckBox, "NPCs avoid collisions", "Game");
|
||||
int unarmedFactorsStrengthIndex = unarmedFactorsStrengthComboBox->currentIndex();
|
||||
if (unarmedFactorsStrengthIndex != mEngineSettings.getInt("strength influences hand to hand", "Game"))
|
||||
mEngineSettings.setInt("strength influences hand to hand", "Game", unarmedFactorsStrengthIndex);
|
||||
|
@ -220,6 +223,7 @@ void Launcher::AdvancedPage::saveSettings()
|
|||
saveSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game");
|
||||
saveSettingBool(shieldSheathingCheckBox, "shield sheathing", "Game");
|
||||
saveSettingBool(turnToMovementDirectionCheckBox, "turn to movement direction", "Game");
|
||||
saveSettingBool(smoothMovementCheckBox, "smooth movement", "Game");
|
||||
|
||||
const bool distantTerrain = mEngineSettings.getBool("distant terrain", "Terrain");
|
||||
const bool objectPaging = mEngineSettings.getBool("object paging", "Terrain");
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <SDL.h>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/debug/gldebug.hpp>
|
||||
|
||||
#include <components/misc/rng.hpp>
|
||||
|
||||
|
@ -612,7 +613,7 @@ void OMW::Engine::createWindow(Settings::Manager& settings)
|
|||
bool fullscreen = settings.getBool("fullscreen", "Video");
|
||||
bool windowBorder = settings.getBool("window border", "Video");
|
||||
bool vsync = settings.getBool("vsync", "Video");
|
||||
int antialiasing = settings.getInt("antialiasing", "Video");
|
||||
unsigned int antialiasing = std::max(0, settings.getInt("antialiasing", "Video"));
|
||||
|
||||
int pos_x = SDL_WINDOWPOS_CENTERED_DISPLAY(screen),
|
||||
pos_y = SDL_WINDOWPOS_CENTERED_DISPLAY(screen);
|
||||
|
@ -638,6 +639,8 @@ void OMW::Engine::createWindow(Settings::Manager& settings)
|
|||
checkSDLError(SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8));
|
||||
checkSDLError(SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0));
|
||||
checkSDLError(SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24));
|
||||
if (Debug::shouldDebugOpenGL())
|
||||
checkSDLError(SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG));
|
||||
|
||||
if (antialiasing > 0)
|
||||
{
|
||||
|
@ -645,62 +648,80 @@ void OMW::Engine::createWindow(Settings::Manager& settings)
|
|||
checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing));
|
||||
}
|
||||
|
||||
while (!mWindow)
|
||||
osg::ref_ptr<SDLUtil::GraphicsWindowSDL2> graphicsWindow;
|
||||
while (!graphicsWindow || !graphicsWindow->valid())
|
||||
{
|
||||
mWindow = SDL_CreateWindow("OpenMW", pos_x, pos_y, width, height, flags);
|
||||
if (!mWindow)
|
||||
while (!mWindow)
|
||||
{
|
||||
// Try with a lower AA
|
||||
if (antialiasing > 0)
|
||||
mWindow = SDL_CreateWindow("OpenMW", pos_x, pos_y, width, height, flags);
|
||||
if (!mWindow)
|
||||
{
|
||||
Log(Debug::Warning) << "Warning: " << antialiasing << "x antialiasing not supported, trying " << antialiasing/2;
|
||||
antialiasing /= 2;
|
||||
Settings::Manager::setInt("antialiasing", "Video", antialiasing);
|
||||
checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing));
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::stringstream error;
|
||||
error << "Failed to create SDL window: " << SDL_GetError();
|
||||
throw std::runtime_error(error.str());
|
||||
// Try with a lower AA
|
||||
if (antialiasing > 0)
|
||||
{
|
||||
Log(Debug::Warning) << "Warning: " << antialiasing << "x antialiasing not supported, trying " << antialiasing/2;
|
||||
antialiasing /= 2;
|
||||
Settings::Manager::setInt("antialiasing", "Video", antialiasing);
|
||||
checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing));
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::stringstream error;
|
||||
error << "Failed to create SDL window: " << SDL_GetError();
|
||||
throw std::runtime_error(error.str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setWindowIcon();
|
||||
|
||||
osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
|
||||
SDL_GetWindowPosition(mWindow, &traits->x, &traits->y);
|
||||
SDL_GetWindowSize(mWindow, &traits->width, &traits->height);
|
||||
traits->windowName = SDL_GetWindowTitle(mWindow);
|
||||
traits->windowDecoration = !(SDL_GetWindowFlags(mWindow)&SDL_WINDOW_BORDERLESS);
|
||||
traits->screenNum = SDL_GetWindowDisplayIndex(mWindow);
|
||||
traits->vsync = vsync;
|
||||
traits->inheritedWindowData = new SDLUtil::GraphicsWindowSDL2::WindowData(mWindow);
|
||||
|
||||
graphicsWindow = new SDLUtil::GraphicsWindowSDL2(traits);
|
||||
if (!graphicsWindow->valid()) throw std::runtime_error("Failed to create GraphicsContext");
|
||||
|
||||
if (traits->samples < antialiasing)
|
||||
{
|
||||
Log(Debug::Warning) << "Warning: Framebuffer MSAA level is only " << traits->samples << "x instead of " << antialiasing << "x. Trying " << antialiasing / 2 << "x instead.";
|
||||
graphicsWindow->closeImplementation();
|
||||
SDL_DestroyWindow(mWindow);
|
||||
mWindow = nullptr;
|
||||
antialiasing /= 2;
|
||||
Settings::Manager::setInt("antialiasing", "Video", antialiasing);
|
||||
checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (traits->red < 8)
|
||||
Log(Debug::Warning) << "Warning: Framebuffer only has a " << traits->red << " bit red channel.";
|
||||
if (traits->green < 8)
|
||||
Log(Debug::Warning) << "Warning: Framebuffer only has a " << traits->green << " bit green channel.";
|
||||
if (traits->blue < 8)
|
||||
Log(Debug::Warning) << "Warning: Framebuffer only has a " << traits->blue << " bit blue channel.";
|
||||
if (traits->depth < 8)
|
||||
Log(Debug::Warning) << "Warning: Framebuffer only has " << traits->red << " bits of depth precision.";
|
||||
|
||||
traits->alpha = 0; // set to 0 to stop ScreenCaptureHandler reading the alpha channel
|
||||
}
|
||||
|
||||
setWindowIcon();
|
||||
|
||||
osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
|
||||
SDL_GetWindowPosition(mWindow, &traits->x, &traits->y);
|
||||
SDL_GetWindowSize(mWindow, &traits->width, &traits->height);
|
||||
traits->windowName = SDL_GetWindowTitle(mWindow);
|
||||
traits->windowDecoration = !(SDL_GetWindowFlags(mWindow)&SDL_WINDOW_BORDERLESS);
|
||||
traits->screenNum = SDL_GetWindowDisplayIndex(mWindow);
|
||||
// We tried to get rid of the hardcoding but failed: https://github.com/OpenMW/openmw/pull/1771
|
||||
// Here goes kcat's quote:
|
||||
// It's ultimately a chicken and egg problem, and the reason why the code is like it was in the first place.
|
||||
// It needs a context to get the current attributes, but it needs the attributes to set up the context.
|
||||
// So it just specifies the same values that were given to SDL in the hopes that it's good enough to what the window eventually gets.
|
||||
traits->red = 8;
|
||||
traits->green = 8;
|
||||
traits->blue = 8;
|
||||
traits->alpha = 0; // set to 0 to stop ScreenCaptureHandler reading the alpha channel
|
||||
traits->depth = 24;
|
||||
traits->stencil = 8;
|
||||
traits->vsync = vsync;
|
||||
traits->doubleBuffer = true;
|
||||
traits->inheritedWindowData = new SDLUtil::GraphicsWindowSDL2::WindowData(mWindow);
|
||||
|
||||
osg::ref_ptr<SDLUtil::GraphicsWindowSDL2> graphicsWindow = new SDLUtil::GraphicsWindowSDL2(traits);
|
||||
if(!graphicsWindow->valid()) throw std::runtime_error("Failed to create GraphicsContext");
|
||||
|
||||
osg::ref_ptr<osg::Camera> camera = mViewer->getCamera();
|
||||
camera->setGraphicsContext(graphicsWindow);
|
||||
camera->setViewport(0, 0, traits->width, traits->height);
|
||||
camera->setViewport(0, 0, graphicsWindow->getTraits()->width, graphicsWindow->getTraits()->height);
|
||||
|
||||
if (Debug::shouldDebugOpenGL())
|
||||
mViewer->setRealizeOperation(new Debug::EnableGLDebugOperation());
|
||||
|
||||
mViewer->realize();
|
||||
|
||||
mViewer->getEventQueue()->getCurrentEventState()->setWindowRectangle(0, 0, traits->width, traits->height);
|
||||
mViewer->getEventQueue()->getCurrentEventState()->setWindowRectangle(0, 0, graphicsWindow->getTraits()->width, graphicsWindow->getTraits()->height);
|
||||
}
|
||||
|
||||
void OMW::Engine::setWindowIcon()
|
||||
|
|
|
@ -1161,6 +1161,8 @@ namespace MWClass
|
|||
|
||||
float Npc::getMaxSpeed(const MWWorld::Ptr& ptr) const
|
||||
{
|
||||
// TODO: This function is called several times per frame for each NPC.
|
||||
// It would be better to calculate it only once per frame for each NPC and save the result in CreatureStats.
|
||||
const MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
|
||||
if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead())
|
||||
return 0.f;
|
||||
|
|
|
@ -405,7 +405,7 @@ namespace MWGui
|
|||
}
|
||||
|
||||
// Clean up summoned creatures as well
|
||||
std::map<MWMechanics::CreatureStats::SummonKey, int>& creatureMap = creatureStats.getSummonedCreatureMap();
|
||||
std::map<ESM::SummonKey, int>& creatureMap = creatureStats.getSummonedCreatureMap();
|
||||
for (const auto& creature : creatureMap)
|
||||
MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(mPtr, creature.second);
|
||||
creatureMap.clear();
|
||||
|
|
|
@ -75,6 +75,7 @@ void MerchantRepair::setPtr(const MWWorld::Ptr &actor)
|
|||
|
||||
int x = static_cast<int>((maxDurability - durability) / r);
|
||||
x = static_cast<int>(fRepairMult * x);
|
||||
x = std::max(1, x);
|
||||
|
||||
int price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mActor, x, true);
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ namespace MWGui
|
|||
const MWWorld::ESMStore &store =
|
||||
MWBase::Environment::get().getWorld()->getStore();
|
||||
|
||||
int price = static_cast<int>(spell.mData.mCost*store.get<ESM::GameSetting>().find("fSpellValueMult")->mValue.getFloat());
|
||||
int price = std::max(1, static_cast<int>(spell.mData.mCost*store.get<ESM::GameSetting>().find("fSpellValueMult")->mValue.getFloat()));
|
||||
price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr,price,true);
|
||||
|
||||
MWWorld::Ptr player = MWMechanics::getPlayer();
|
||||
|
|
|
@ -517,7 +517,8 @@ namespace MWGui
|
|||
float fSpellMakingValueMult =
|
||||
store.get<ESM::GameSetting>().find("fSpellMakingValueMult")->mValue.getFloat();
|
||||
|
||||
int price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, static_cast<int>(y * fSpellMakingValueMult),true);
|
||||
int price = std::max(1, static_cast<int>(y * fSpellMakingValueMult));
|
||||
price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, price, true);
|
||||
|
||||
mPriceLabel->setCaption(MyGUI::utility::toString(int(price)));
|
||||
|
||||
|
@ -780,5 +781,24 @@ namespace MWGui
|
|||
{
|
||||
mAddEffectDialog.setConstantEffect(constant);
|
||||
mConstantEffect = constant;
|
||||
|
||||
if (!constant)
|
||||
return;
|
||||
|
||||
for (auto it = mEffects.begin(); it != mEffects.end();)
|
||||
{
|
||||
if (it->mRange != ESM::RT_Self)
|
||||
{
|
||||
auto& store = MWBase::Environment::get().getWorld()->getStore();
|
||||
auto magicEffect = store.get<ESM::MagicEffect>().find(it->mEffectID);
|
||||
if ((magicEffect->mData.mFlags & ESM::MagicEffect::CastSelf) == 0)
|
||||
{
|
||||
it = mEffects.erase(it);
|
||||
continue;
|
||||
}
|
||||
it->mRange = ESM::RT_Self;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,8 +25,8 @@
|
|||
namespace MWGui
|
||||
{
|
||||
|
||||
void EffectSourceVisitor::visit (MWMechanics::EffectKey key,
|
||||
const std::string& sourceName, const std::string& sourceId, int casterActorId,
|
||||
void EffectSourceVisitor::visit (MWMechanics::EffectKey key, int effectIndex,
|
||||
const std::string& sourceName, const std::string& sourceId, int casterActorId,
|
||||
float magnitude, float remainingTime, float totalTime)
|
||||
{
|
||||
MagicEffectInfo newEffectSource;
|
||||
|
|
|
@ -46,8 +46,8 @@ namespace MWGui
|
|||
|
||||
virtual ~EffectSourceVisitor() {}
|
||||
|
||||
virtual void visit (MWMechanics::EffectKey key,
|
||||
const std::string& sourceName, const std::string& sourceId, int casterActorId,
|
||||
virtual void visit (MWMechanics::EffectKey key, int effectIndex,
|
||||
const std::string& sourceName, const std::string& sourceId, int casterActorId,
|
||||
float magnitude, float remainingTime = -1, float totalTime = -1);
|
||||
};
|
||||
|
||||
|
|
|
@ -111,8 +111,9 @@ namespace MWGui
|
|||
|
||||
for (int i=0; i<3; ++i)
|
||||
{
|
||||
int price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer
|
||||
(mPtr,pcStats.getSkill (skills[i].first).getBase() * gmst.find("iTrainingMod")->mValue.getInteger(),true);
|
||||
int price = static_cast<int>(pcStats.getSkill (skills[i].first).getBase() * gmst.find("iTrainingMod")->mValue.getInteger());
|
||||
price = std::max(1, price);
|
||||
price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, price, true);
|
||||
|
||||
MyGUI::Button* button = mTrainingOptions->createWidget<MyGUI::Button>(price <= playerGold ? "SandTextButton" : "SandTextButtonDisabled", // can't use setEnabled since that removes tooltip
|
||||
MyGUI::IntCoord(5, 5+i*18, mTrainingOptions->getWidth()-10, 18), MyGUI::Align::Default);
|
||||
|
|
|
@ -74,9 +74,14 @@ namespace MWGui
|
|||
{
|
||||
ESM::Position PlayerPos = player.getRefData().getPosition();
|
||||
float d = sqrt(pow(pos.pos[0] - PlayerPos.pos[0], 2) + pow(pos.pos[1] - PlayerPos.pos[1], 2) + pow(pos.pos[2] - PlayerPos.pos[2], 2));
|
||||
price = static_cast<int>(d / gmst.find("fTravelMult")->mValue.getFloat());
|
||||
float fTravelMult = gmst.find("fTravelMult")->mValue.getFloat();
|
||||
if (fTravelMult != 0)
|
||||
price = static_cast<int>(d / fTravelMult);
|
||||
else
|
||||
price = static_cast<int>(d);
|
||||
}
|
||||
|
||||
price = std::max(1, price);
|
||||
price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, price, true);
|
||||
|
||||
// Add price for the travelling followers
|
||||
|
|
|
@ -152,7 +152,7 @@ namespace MWInput
|
|||
|
||||
float xAxis = mBindingsManager->getActionValue(A_MoveLeftRight);
|
||||
float yAxis = mBindingsManager->getActionValue(A_MoveForwardBackward);
|
||||
bool isRunning = xAxis > .75 || xAxis < .25 || yAxis > .75 || yAxis < .25;
|
||||
bool isRunning = osg::Vec2f(xAxis * 2 - 1, yAxis * 2 - 1).length2() > 0.25f;
|
||||
if ((mAlwaysRunActive && alwaysRunAllowed) || isRunning)
|
||||
player.setRunState(!mBindingsManager->actionIsActive(A_Run));
|
||||
else
|
||||
|
|
|
@ -252,9 +252,8 @@ namespace MWMechanics
|
|||
std::string name = it->second.mDisplayName;
|
||||
|
||||
float magnitude = effectIt->mMagnitude;
|
||||
|
||||
if (magnitude)
|
||||
visitor.visit(MWMechanics::EffectKey(effectIt->mEffectId, effectIt->mArg), name, it->first, it->second.mCasterActorId, magnitude, effectIt->mTimeLeft, effectIt->mDuration);
|
||||
visitor.visit(MWMechanics::EffectKey(effectIt->mEffectId, effectIt->mArg), effectIt->mEffectIndex, name, it->first, it->second.mCasterActorId, magnitude, effectIt->mTimeLeft, effectIt->mDuration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -300,14 +299,14 @@ namespace MWMechanics
|
|||
mSpellsChanged = true;
|
||||
}
|
||||
|
||||
void ActiveSpells::purgeEffect(short effectId, const std::string& sourceId)
|
||||
void ActiveSpells::purgeEffect(short effectId, const std::string& sourceId, int effectIndex)
|
||||
{
|
||||
for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it)
|
||||
{
|
||||
for (std::vector<ActiveEffect>::iterator effectIt = it->second.mEffects.begin();
|
||||
effectIt != it->second.mEffects.end();)
|
||||
{
|
||||
if (effectIt->mEffectId == effectId && it->first == sourceId)
|
||||
if (effectIt->mEffectId == effectId && it->first == sourceId && (effectIndex < 0 || effectIndex == effectIt->mEffectIndex))
|
||||
effectIt = it->second.mEffects.erase(effectIt);
|
||||
else
|
||||
++effectIt;
|
||||
|
|
|
@ -85,7 +85,7 @@ namespace MWMechanics
|
|||
void purgeEffect (short effectId);
|
||||
|
||||
/// Remove all active effects with this effect id and source id
|
||||
void purgeEffect (short effectId, const std::string& sourceId);
|
||||
void purgeEffect (short effectId, const std::string& sourceId, int effectIndex=-1);
|
||||
|
||||
/// Remove all active effects, if roll succeeds (for each effect)
|
||||
void purgeAll(float chance, bool spellOnly = false);
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <components/sceneutil/positionattitudetransform.hpp>
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/misc/rng.hpp>
|
||||
#include <components/misc/mathutil.hpp>
|
||||
#include <components/settings/settings.hpp>
|
||||
|
||||
/*
|
||||
|
@ -55,6 +56,7 @@
|
|||
#include "aicombataction.hpp"
|
||||
#include "aifollow.hpp"
|
||||
#include "aipursue.hpp"
|
||||
#include "aiwander.hpp"
|
||||
#include "actor.hpp"
|
||||
#include "summoning.hpp"
|
||||
#include "combat.hpp"
|
||||
|
@ -107,12 +109,13 @@ class CheckActorCommanded : public MWMechanics::EffectSourceVisitor
|
|||
MWWorld::Ptr mActor;
|
||||
public:
|
||||
bool mCommanded;
|
||||
|
||||
CheckActorCommanded(const MWWorld::Ptr& actor)
|
||||
: mActor(actor)
|
||||
, mCommanded(false){}
|
||||
, mCommanded(false){}
|
||||
|
||||
virtual void visit (MWMechanics::EffectKey key,
|
||||
const std::string& sourceName, const std::string& sourceId, int casterActorId,
|
||||
virtual void visit (MWMechanics::EffectKey key, int effectIndex,
|
||||
const std::string& sourceName, const std::string& sourceId, int casterActorId,
|
||||
float magnitude, float remainingTime = -1, float totalTime = -1)
|
||||
{
|
||||
if (((key.mId == ESM::MagicEffect::CommandHumanoid && mActor.getClass().isNpc())
|
||||
|
@ -175,8 +178,8 @@ namespace MWMechanics
|
|||
GetStuntedMagickaDuration(const MWWorld::Ptr& actor)
|
||||
: mRemainingTime(0.f){}
|
||||
|
||||
virtual void visit (MWMechanics::EffectKey key,
|
||||
const std::string& sourceName, const std::string& sourceId, int casterActorId,
|
||||
virtual void visit (MWMechanics::EffectKey key, int effectIndex,
|
||||
const std::string& sourceName, const std::string& sourceId, int casterActorId,
|
||||
float magnitude, float remainingTime = -1, float totalTime = -1)
|
||||
{
|
||||
if (mRemainingTime == -1) return;
|
||||
|
@ -205,8 +208,8 @@ namespace MWMechanics
|
|||
{
|
||||
}
|
||||
|
||||
virtual void visit (MWMechanics::EffectKey key,
|
||||
const std::string& sourceName, const std::string& sourceId, int casterActorId,
|
||||
virtual void visit (MWMechanics::EffectKey key, int effectIndex,
|
||||
const std::string& sourceName, const std::string& sourceId, int casterActorId,
|
||||
float magnitude, float remainingTime = -1, float totalTime = -1)
|
||||
{
|
||||
if (magnitude <= 0)
|
||||
|
@ -225,8 +228,8 @@ namespace MWMechanics
|
|||
{
|
||||
|
||||
public:
|
||||
virtual void visit (MWMechanics::EffectKey key,
|
||||
const std::string& sourceName, const std::string& sourceId, int casterActorId,
|
||||
virtual void visit (MWMechanics::EffectKey key, int effectIndex,
|
||||
const std::string& sourceName, const std::string& sourceId, int casterActorId,
|
||||
float magnitude, float remainingTime = -1, float totalTime = -1)
|
||||
{
|
||||
if (key.mId != ESM::MagicEffect::Corprus)
|
||||
|
@ -250,8 +253,8 @@ namespace MWMechanics
|
|||
{
|
||||
}
|
||||
|
||||
virtual void visit (MWMechanics::EffectKey key,
|
||||
const std::string& sourceName, const std::string& sourceId, int casterActorId,
|
||||
virtual void visit (MWMechanics::EffectKey key, int effectIndex,
|
||||
const std::string& sourceName, const std::string& sourceId, int casterActorId,
|
||||
float magnitude, float remainingTime = -1, float totalTime = -1)
|
||||
{
|
||||
if (mTrapped)
|
||||
|
@ -456,7 +459,7 @@ namespace MWMechanics
|
|||
const osg::Vec3f actor2Pos(targetActor.getRefData().getPosition().asVec3());
|
||||
float sqrDist = (actor1Pos - actor2Pos).length2();
|
||||
|
||||
if (sqrDist > maxDistance*maxDistance)
|
||||
if (sqrDist > std::min(maxDistance * maxDistance, sqrHeadTrackDistance))
|
||||
return;
|
||||
|
||||
// stop tracking when target is behind the actor
|
||||
|
@ -464,10 +467,7 @@ namespace MWMechanics
|
|||
osg::Vec3f targetDirection(actor2Pos - actor1Pos);
|
||||
actorDirection.z() = 0;
|
||||
targetDirection.z() = 0;
|
||||
actorDirection.normalize();
|
||||
targetDirection.normalize();
|
||||
if (std::acos(actorDirection * targetDirection) < osg::DegreesToRadians(90.f)
|
||||
&& sqrDist <= sqrHeadTrackDistance
|
||||
if (actorDirection * targetDirection > 0
|
||||
&& MWBase::Environment::get().getWorld()->getLOS(actor, targetActor) // check LOS and awareness last as it's the most expensive function
|
||||
&& MWBase::Environment::get().getMechanicsManager()->awarenessCheck(targetActor, actor))
|
||||
{
|
||||
|
@ -505,6 +505,9 @@ namespace MWMechanics
|
|||
|
||||
void Actors::updateMovementSpeed(const MWWorld::Ptr& actor)
|
||||
{
|
||||
if (mSmoothMovement)
|
||||
return;
|
||||
|
||||
CreatureStats &stats = actor.getClass().getCreatureStats(actor);
|
||||
MWMechanics::AiSequence& seq = stats.getAiSequence();
|
||||
|
||||
|
@ -513,9 +516,10 @@ namespace MWMechanics
|
|||
osg::Vec3f targetPos = seq.getActivePackage().getDestination();
|
||||
osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3();
|
||||
float distance = (targetPos - actorPos).length();
|
||||
|
||||
if (distance < DECELERATE_DISTANCE)
|
||||
{
|
||||
float speedCoef = std::max(0.7f, 0.1f * (distance/64.f + 2.f));
|
||||
float speedCoef = std::max(0.7f, 0.2f + 0.8f * distance / DECELERATE_DISTANCE);
|
||||
auto& movement = actor.getClass().getMovementSettings(actor);
|
||||
movement.mPosition[0] *= speedCoef;
|
||||
movement.mPosition[1] *= speedCoef;
|
||||
|
@ -619,8 +623,11 @@ namespace MWMechanics
|
|||
|
||||
if (!actorState.isTurningToPlayer())
|
||||
{
|
||||
actorState.setAngleToPlayer(std::atan2(dir.x(), dir.y()));
|
||||
actorState.setTurningToPlayer(true);
|
||||
float angle = std::atan2(dir.x(), dir.y());
|
||||
actorState.setAngleToPlayer(angle);
|
||||
float deltaAngle = Misc::normalizeAngle(angle - actor.getRefData().getPosition().rot[2]);
|
||||
if (!mSmoothMovement || std::abs(deltaAngle) > osg::DegreesToRadians(60.f))
|
||||
actorState.setTurningToPlayer(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -939,7 +946,7 @@ namespace MWMechanics
|
|||
{
|
||||
}
|
||||
|
||||
virtual void visit (MWMechanics::EffectKey key,
|
||||
virtual void visit (MWMechanics::EffectKey key, int /*effectIndex*/,
|
||||
const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/,
|
||||
float magnitude, float remainingTime = -1, float /*totalTime*/ = -1)
|
||||
{
|
||||
|
@ -1260,6 +1267,7 @@ namespace MWMechanics
|
|||
{
|
||||
UpdateSummonedCreatures updateSummonedCreatures(ptr);
|
||||
creatureStats.getActiveSpells().visitEffectSources(updateSummonedCreatures);
|
||||
creatureStats.getSpells().visitEffectSources(updateSummonedCreatures);
|
||||
if (ptr.getClass().hasInventoryStore(ptr))
|
||||
ptr.getClass().getInventoryStore(ptr).visitEffectSources(updateSummonedCreatures);
|
||||
updateSummonedCreatures.process(mTimerDisposeSummonsCorpses == 0.f);
|
||||
|
@ -1568,7 +1576,7 @@ namespace MWMechanics
|
|||
}
|
||||
}
|
||||
|
||||
Actors::Actors()
|
||||
Actors::Actors() : mSmoothMovement(Settings::Manager::getBool("smooth movement", "Game"))
|
||||
{
|
||||
mTimerDisposeSummonsCorpses = 0.2f; // We should add a delay between summoned creature death and its corpse despawning
|
||||
|
||||
|
@ -1778,6 +1786,131 @@ namespace MWMechanics
|
|||
|
||||
}
|
||||
|
||||
void Actors::predictAndAvoidCollisions()
|
||||
{
|
||||
const float minGap = 10.f;
|
||||
const float maxDistToCheck = 100.f;
|
||||
const float maxTimeToCheck = 1.f;
|
||||
static const bool giveWayWhenIdle = Settings::Manager::getBool("NPCs give way", "Game");
|
||||
|
||||
MWWorld::Ptr player = getPlayer();
|
||||
MWBase::World* world = MWBase::Environment::get().getWorld();
|
||||
for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
|
||||
{
|
||||
const MWWorld::Ptr& ptr = iter->first;
|
||||
if (ptr == player)
|
||||
continue; // Don't interfere with player controls.
|
||||
|
||||
Movement& movement = ptr.getClass().getMovementSettings(ptr);
|
||||
osg::Vec2f origMovement(movement.mPosition[0], movement.mPosition[1]);
|
||||
bool isMoving = origMovement.length2() > 0.01;
|
||||
|
||||
// Moving NPCs always should avoid collisions.
|
||||
// Standing NPCs give way to moving ones if they are not in combat (or pursue) mode and either
|
||||
// follow player or have a AIWander package with non-empty wander area.
|
||||
bool shouldAvoidCollision = isMoving;
|
||||
bool shouldTurnToApproachingActor = !isMoving;
|
||||
MWWorld::Ptr currentTarget; // Combat or pursue target (NPCs should not avoid collision with their targets).
|
||||
for (const auto& package : ptr.getClass().getCreatureStats(ptr).getAiSequence())
|
||||
{
|
||||
if (package->getTypeId() == AiPackageTypeId::Follow)
|
||||
shouldAvoidCollision = true;
|
||||
else if (package->getTypeId() == AiPackageTypeId::Wander && giveWayWhenIdle)
|
||||
{
|
||||
if (!dynamic_cast<const AiWander*>(package.get())->isStationary())
|
||||
shouldAvoidCollision = true;
|
||||
}
|
||||
else if (package->getTypeId() == AiPackageTypeId::Combat || package->getTypeId() == AiPackageTypeId::Pursue)
|
||||
{
|
||||
currentTarget = package->getTarget();
|
||||
shouldAvoidCollision = isMoving;
|
||||
shouldTurnToApproachingActor = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!shouldAvoidCollision)
|
||||
continue;
|
||||
|
||||
float maxSpeed = ptr.getClass().getMaxSpeed(ptr);
|
||||
osg::Vec2f baseSpeed = origMovement * maxSpeed;
|
||||
osg::Vec3f basePos = ptr.getRefData().getPosition().asVec3();
|
||||
float baseRotZ = ptr.getRefData().getPosition().rot[2];
|
||||
osg::Vec3f halfExtents = world->getHalfExtents(ptr);
|
||||
|
||||
float timeToCollision = maxTimeToCheck;
|
||||
osg::Vec2f movementCorrection(0, 0);
|
||||
float angleToApproachingActor = 0;
|
||||
|
||||
// Iterate through all other actors and predict collisions.
|
||||
for(PtrActorMap::iterator otherIter(mActors.begin()); otherIter != mActors.end(); ++otherIter)
|
||||
{
|
||||
const MWWorld::Ptr& otherPtr = otherIter->first;
|
||||
if (otherPtr == ptr || otherPtr == currentTarget)
|
||||
continue;
|
||||
|
||||
osg::Vec3f otherHalfExtents = world->getHalfExtents(otherPtr);
|
||||
osg::Vec3f deltaPos = otherPtr.getRefData().getPosition().asVec3() - basePos;
|
||||
osg::Vec2f relPos = Misc::rotateVec2f(osg::Vec2f(deltaPos.x(), deltaPos.y()), baseRotZ);
|
||||
|
||||
// Ignore actors which are not close enough or come from behind.
|
||||
if (deltaPos.length2() > maxDistToCheck * maxDistToCheck || relPos.y() < 0)
|
||||
continue;
|
||||
|
||||
// Don't check for a collision if vertical distance is greater then the actor's height.
|
||||
if (deltaPos.z() > halfExtents.z() * 2 || deltaPos.z() < -otherHalfExtents.z() * 2)
|
||||
continue;
|
||||
|
||||
osg::Vec3f speed = otherPtr.getClass().getMovementSettings(otherPtr).asVec3() *
|
||||
otherPtr.getClass().getMaxSpeed(otherPtr);
|
||||
float rotZ = otherPtr.getRefData().getPosition().rot[2];
|
||||
osg::Vec2f relSpeed = Misc::rotateVec2f(osg::Vec2f(speed.x(), speed.y()), baseRotZ - rotZ) - baseSpeed;
|
||||
|
||||
float collisionDist = minGap + world->getHalfExtents(ptr).x() + world->getHalfExtents(otherPtr).x();
|
||||
collisionDist = std::min(collisionDist, relPos.length());
|
||||
|
||||
// Find the earliest `t` when |relPos + relSpeed * t| == collisionDist.
|
||||
float vr = relPos.x() * relSpeed.x() + relPos.y() * relSpeed.y();
|
||||
float v2 = relSpeed.length2();
|
||||
float Dh = vr * vr - v2 * (relPos.length2() - collisionDist * collisionDist);
|
||||
if (Dh <= 0 || v2 == 0)
|
||||
continue; // No solution; distance is always >= collisionDist.
|
||||
float t = (-vr - std::sqrt(Dh)) / v2;
|
||||
|
||||
if (t < 0 || t > timeToCollision)
|
||||
continue;
|
||||
|
||||
// Check visibility and awareness last as it's expensive.
|
||||
if (!MWBase::Environment::get().getWorld()->getLOS(otherPtr, ptr))
|
||||
continue;
|
||||
if (!MWBase::Environment::get().getMechanicsManager()->awarenessCheck(otherPtr, ptr))
|
||||
continue;
|
||||
|
||||
timeToCollision = t;
|
||||
angleToApproachingActor = std::atan2(deltaPos.x(), deltaPos.y());
|
||||
osg::Vec2f posAtT = relPos + relSpeed * t;
|
||||
float coef = (posAtT.x() * relSpeed.x() + posAtT.y() * relSpeed.y()) / (collisionDist * maxSpeed);
|
||||
movementCorrection = posAtT * coef;
|
||||
// Step to the side rather than backward. Otherwise player will be able to push the NPC far away from it's original location.
|
||||
movementCorrection.y() = std::max(0.f, movementCorrection.y());
|
||||
}
|
||||
|
||||
if (timeToCollision < maxTimeToCheck)
|
||||
{
|
||||
// Try to evade the nearest collision.
|
||||
osg::Vec2f newMovement = origMovement + movementCorrection;
|
||||
if (isMoving)
|
||||
{ // Keep the original speed.
|
||||
newMovement.normalize();
|
||||
newMovement *= origMovement.length();
|
||||
}
|
||||
movement.mPosition[0] = newMovement.x();
|
||||
movement.mPosition[1] = newMovement.y();
|
||||
if (shouldTurnToApproachingActor)
|
||||
zTurn(ptr, angleToApproachingActor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Actors::update (float duration, bool paused)
|
||||
{
|
||||
if(!paused)
|
||||
|
@ -1939,14 +2072,12 @@ namespace MWMechanics
|
|||
|
||||
MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first);
|
||||
bool firstPersonPlayer = isPlayer && world->isFirstPerson();
|
||||
bool inCombatOrPursue = stats.getAiSequence().isInCombat() || stats.getAiSequence().hasPackage(AiPackageTypeId::Pursue);
|
||||
|
||||
// 1. Unconsious actor can not track target
|
||||
// 2. Actors in combat and pursue mode do not bother to headtrack
|
||||
// 3. Player character does not use headtracking in the 1st-person view
|
||||
if (!stats.getKnockedDown() &&
|
||||
!stats.getAiSequence().isInCombat() &&
|
||||
!stats.getAiSequence().hasPackage(AiPackageTypeId::Pursue) &&
|
||||
!firstPersonPlayer)
|
||||
if (!stats.getKnockedDown() && !firstPersonPlayer && !inCombatOrPursue)
|
||||
{
|
||||
for(PtrActorMap::iterator it(mActors.begin()); it != mActors.end(); ++it)
|
||||
{
|
||||
|
@ -1956,6 +2087,17 @@ namespace MWMechanics
|
|||
}
|
||||
}
|
||||
|
||||
if (!stats.getKnockedDown() && !isPlayer && inCombatOrPursue)
|
||||
{
|
||||
// Actors in combat and pursue mode always look at their target.
|
||||
for (const auto& package : stats.getAiSequence())
|
||||
{
|
||||
headTrackTarget = package->getTarget();
|
||||
if (!headTrackTarget.isEmpty())
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ctrl->setHeadTrackTarget(headTrackTarget);
|
||||
}
|
||||
|
||||
|
@ -1997,6 +2139,10 @@ namespace MWMechanics
|
|||
}
|
||||
}
|
||||
|
||||
static const bool avoidCollisions = Settings::Manager::getBool("NPCs avoid collisions", "Game");
|
||||
if (avoidCollisions)
|
||||
predictAndAvoidCollisions();
|
||||
|
||||
timerUpdateAITargets += duration;
|
||||
timerUpdateHeadTrack += duration;
|
||||
timerUpdateEquippedLight += duration;
|
||||
|
@ -2233,7 +2379,7 @@ namespace MWMechanics
|
|||
|
||||
// Remove the summoned creature's summoned creatures as well
|
||||
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
|
||||
std::map<CreatureStats::SummonKey, int>& creatureMap = stats.getSummonedCreatureMap();
|
||||
std::map<ESM::SummonKey, int>& creatureMap = stats.getSummonedCreatureMap();
|
||||
for (const auto& creature : creatureMap)
|
||||
cleanupSummonedCreature(stats, creature.second);
|
||||
creatureMap.clear();
|
||||
|
@ -2270,10 +2416,11 @@ namespace MWMechanics
|
|||
|
||||
for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
|
||||
{
|
||||
iter->first.getClass().getCreatureStats(iter->first).getActiveSpells().update(duration);
|
||||
|
||||
if (iter->first.getClass().getCreatureStats(iter->first).isDead())
|
||||
{
|
||||
iter->first.getClass().getCreatureStats(iter->first).getActiveSpells().update(duration);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!sleep || iter->first == player)
|
||||
restoreDynamicStats(iter->first, hours, sleep);
|
||||
|
@ -2290,13 +2437,14 @@ namespace MWMechanics
|
|||
if (iter->first.getClass().isNpc())
|
||||
calculateNpcStatModifiers(iter->first, duration);
|
||||
|
||||
iter->first.getClass().getCreatureStats(iter->first).getActiveSpells().update(duration);
|
||||
|
||||
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(iter->first);
|
||||
if (animation)
|
||||
{
|
||||
animation->removeEffects();
|
||||
MWBase::Environment::get().getWorld()->applyLoopingParticles(iter->first);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fastForwardAi();
|
||||
|
|
|
@ -63,6 +63,8 @@ namespace MWMechanics
|
|||
|
||||
void purgeSpellEffects (int casterActorId);
|
||||
|
||||
void predictAndAvoidCollisions();
|
||||
|
||||
public:
|
||||
|
||||
Actors();
|
||||
|
@ -229,6 +231,7 @@ namespace MWMechanics
|
|||
float mTimerDisposeSummonsCorpses;
|
||||
float mActorsProcessingRange;
|
||||
|
||||
bool mSmoothMovement;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ namespace MWMechanics
|
|||
template<class T>
|
||||
void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount)
|
||||
{
|
||||
ESM::NPC copy = *MWBase::Environment::get().getWorld()->getStore().get<ESM::NPC>().find(actorId);
|
||||
T copy = *MWBase::Environment::get().getWorld()->getStore().get<T>().find(actorId);
|
||||
for(auto& it : copy.mInventory.mList)
|
||||
{
|
||||
if(Misc::StringUtils::ciEqual(it.mItem, itemId))
|
||||
|
|
|
@ -23,7 +23,7 @@ bool MWMechanics::AiBreathe::execute (const MWWorld::Ptr& actor, CharacterContro
|
|||
actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
|
||||
|
||||
actorClass.getMovementSettings(actor).mPosition[1] = 1;
|
||||
smoothTurn(actor, -180, 0);
|
||||
smoothTurn(actor, -osg::PI / 2, 0);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,10 @@
|
|||
|
||||
#include <components/esm/aisequence.hpp>
|
||||
|
||||
#include <components/misc/mathutil.hpp>
|
||||
|
||||
#include <components/sceneutil/positionattitudetransform.hpp>
|
||||
|
||||
/*
|
||||
Start of tes3mp addition
|
||||
|
||||
|
@ -20,8 +24,6 @@
|
|||
End of tes3mp addition
|
||||
*/
|
||||
|
||||
#include <components/sceneutil/positionattitudetransform.hpp>
|
||||
|
||||
#include "../mwphysics/collisiontype.hpp"
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
|
@ -308,10 +310,6 @@ namespace MWMechanics
|
|||
|
||||
if (storage.mReadyToAttack)
|
||||
{
|
||||
storage.startCombatMove(isRangedCombat, distToTarget, rangeAttack, actor, target);
|
||||
// start new attack
|
||||
storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat);
|
||||
|
||||
if (isRangedCombat)
|
||||
{
|
||||
// rotate actor taking into account target movement direction and projectile speed
|
||||
|
@ -327,6 +325,10 @@ namespace MWMechanics
|
|||
storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir);
|
||||
storage.mMovement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated
|
||||
}
|
||||
|
||||
storage.startCombatMove(isRangedCombat, distToTarget, rangeAttack, actor, target);
|
||||
// start new attack
|
||||
storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -440,9 +442,13 @@ namespace MWMechanics
|
|||
void AiCombat::updateActorsMovement(const MWWorld::Ptr& actor, float duration, AiCombatStorage& storage)
|
||||
{
|
||||
// apply combat movement
|
||||
float deltaAngle = storage.mMovement.mRotation[2] - actor.getRefData().getPosition().rot[2];
|
||||
osg::Vec2f movement = Misc::rotateVec2f(
|
||||
osg::Vec2f(storage.mMovement.mPosition[0], storage.mMovement.mPosition[1]), -deltaAngle);
|
||||
|
||||
MWMechanics::Movement& actorMovementSettings = actor.getClass().getMovementSettings(actor);
|
||||
actorMovementSettings.mPosition[0] = storage.mMovement.mPosition[0];
|
||||
actorMovementSettings.mPosition[1] = storage.mMovement.mPosition[1];
|
||||
actorMovementSettings.mPosition[0] = movement.x();
|
||||
actorMovementSettings.mPosition[1] = movement.y();
|
||||
actorMovementSettings.mPosition[2] = storage.mMovement.mPosition[2];
|
||||
|
||||
rotateActorOnAxis(actor, 2, actorMovementSettings, storage);
|
||||
|
@ -453,26 +459,11 @@ namespace MWMechanics
|
|||
MWMechanics::Movement& actorMovementSettings, AiCombatStorage& storage)
|
||||
{
|
||||
actorMovementSettings.mRotation[axis] = 0;
|
||||
float& targetAngleRadians = storage.mMovement.mRotation[axis];
|
||||
if (targetAngleRadians != 0)
|
||||
{
|
||||
// Some attack animations contain small amount of movement.
|
||||
// Since we use cone shapes for melee, we can use a threshold to avoid jittering
|
||||
std::shared_ptr<Action>& currentAction = storage.mCurrentAction;
|
||||
bool isRangedCombat = false;
|
||||
currentAction->getCombatRange(isRangedCombat);
|
||||
// Check if the actor now facing desired direction, no need to turn any more
|
||||
if (isRangedCombat)
|
||||
{
|
||||
if (smoothTurn(actor, targetAngleRadians, axis))
|
||||
targetAngleRadians = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (smoothTurn(actor, targetAngleRadians, axis, osg::DegreesToRadians(3.f)))
|
||||
targetAngleRadians = 0;
|
||||
}
|
||||
}
|
||||
bool isRangedCombat = false;
|
||||
storage.mCurrentAction->getCombatRange(isRangedCombat);
|
||||
float eps = isRangedCombat ? osg::DegreesToRadians(0.5) : osg::DegreesToRadians(3.f);
|
||||
float targetAngleRadians = storage.mMovement.mRotation[axis];
|
||||
smoothTurn(actor, targetAngleRadians, axis, eps);
|
||||
}
|
||||
|
||||
MWWorld::Ptr AiCombat::getTarget() const
|
||||
|
@ -557,12 +548,19 @@ namespace MWMechanics
|
|||
// Note: do not use for ranged combat yet since in couple with back up behaviour can move actor out of cliff
|
||||
else if (actor.getClass().isBipedal(actor))
|
||||
{
|
||||
// apply sideway movement (kind of dodging) with some probability
|
||||
// if actor is within range of target's weapon
|
||||
if (distToTarget <= rangeAttackOfTarget && Misc::Rng::rollClosedProbability() < 0.25)
|
||||
float moveDuration = 0;
|
||||
float angleToTarget = Misc::normalizeAngle(mMovement.mRotation[2] - actor.getRefData().getPosition().rot[2]);
|
||||
// Apply a big side step if enemy tries to get around and come from behind.
|
||||
// Otherwise apply a random side step (kind of dodging) with some probability
|
||||
// if actor is within range of target's weapon.
|
||||
if (std::abs(angleToTarget) > osg::PI / 4)
|
||||
moveDuration = 0.2;
|
||||
else if (distToTarget <= rangeAttackOfTarget && Misc::Rng::rollClosedProbability() < 0.25)
|
||||
moveDuration = 0.1f + 0.1f * Misc::Rng::rollClosedProbability();
|
||||
if (moveDuration > 0)
|
||||
{
|
||||
mMovement.mPosition[0] = Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f; // to the left/right
|
||||
mTimerCombatMove = 0.1f + 0.1f * Misc::Rng::rollClosedProbability();
|
||||
mTimerCombatMove = moveDuration;
|
||||
mCombatMove = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <components/esm/loadmgef.hpp>
|
||||
#include <components/detournavigator/navigator.hpp>
|
||||
#include <components/misc/coordinateconverter.hpp>
|
||||
#include <components/settings/settings.hpp>
|
||||
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
|
@ -87,6 +88,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
|
|||
//... But AI processing distance may increase in the future.
|
||||
if (isNearInactiveCell(position))
|
||||
{
|
||||
actor.getClass().getMovementSettings(actor).mPosition[0] = 0;
|
||||
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
|
||||
world->updateActorPath(actor, mPathFinder.getPath(), halfExtents, position, dest);
|
||||
return false;
|
||||
|
@ -169,12 +171,34 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
|
|||
}
|
||||
|
||||
// turn to next path point by X,Z axes
|
||||
zTurn(actor, mPathFinder.getZAngleToNext(position.x(), position.y()));
|
||||
float zAngleToNext = mPathFinder.getZAngleToNext(position.x(), position.y());
|
||||
zTurn(actor, zAngleToNext);
|
||||
smoothTurn(actor, mPathFinder.getXAngleToNext(position.x(), position.y(), position.z()), 0);
|
||||
|
||||
const auto destination = mPathFinder.getPath().empty() ? dest : mPathFinder.getPath().front();
|
||||
mObstacleCheck.update(actor, destination, duration);
|
||||
|
||||
static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game");
|
||||
if (smoothMovement)
|
||||
{
|
||||
const float smoothTurnReservedDist = 150;
|
||||
auto& movement = actor.getClass().getMovementSettings(actor);
|
||||
float distToNextSqr = osg::Vec2f(destination.x() - position.x(), destination.y() - position.y()).length2();
|
||||
float diffAngle = zAngleToNext - actor.getRefData().getPosition().rot[2];
|
||||
if (std::cos(diffAngle) < -0.1)
|
||||
movement.mPosition[0] = movement.mPosition[1] = 0;
|
||||
else if (distToNextSqr > smoothTurnReservedDist * smoothTurnReservedDist)
|
||||
{ // Go forward (and slowly turn towards the next path point)
|
||||
movement.mPosition[0] = 0;
|
||||
movement.mPosition[1] = 1;
|
||||
}
|
||||
else
|
||||
{ // Next path point is near, so use diagonal movement to follow the path precisely.
|
||||
movement.mPosition[0] = std::sin(diffAngle);
|
||||
movement.mPosition[1] = std::max(std::cos(diffAngle), 0.f);
|
||||
}
|
||||
}
|
||||
|
||||
// handle obstacles on the way
|
||||
evadeObstacles(actor);
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
namespace MWMechanics
|
||||
{
|
||||
static const int COUNT_BEFORE_RESET = 10;
|
||||
static const float DOOR_CHECK_INTERVAL = 1.5f;
|
||||
static const float IDLE_POSITION_CHECK_INTERVAL = 1.5f;
|
||||
|
||||
// to prevent overcrowding
|
||||
static const int DESTINATION_TOLERANCE = 64;
|
||||
|
@ -96,6 +96,7 @@ namespace MWMechanics
|
|||
|
||||
void stopMovement(const MWWorld::Ptr& actor)
|
||||
{
|
||||
actor.getClass().getMovementSettings(actor).mPosition[0] = 0;
|
||||
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
|
||||
}
|
||||
|
||||
|
@ -424,15 +425,14 @@ namespace MWMechanics
|
|||
|
||||
void AiWander::onIdleStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage)
|
||||
{
|
||||
// Check if an idle actor is too close to a door - if so start walking
|
||||
storage.mDoorCheckDuration += duration;
|
||||
// Check if an idle actor is too far from all allowed nodes or too close to a door - if so start walking.
|
||||
storage.mCheckIdlePositionTimer += duration;
|
||||
|
||||
if (storage.mDoorCheckDuration >= DOOR_CHECK_INTERVAL)
|
||||
if (storage.mCheckIdlePositionTimer >= IDLE_POSITION_CHECK_INTERVAL && !isStationary())
|
||||
{
|
||||
storage.mDoorCheckDuration = 0; // restart timer
|
||||
static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance();
|
||||
if (mDistance && // actor is not intended to be stationary
|
||||
proximityToDoor(actor, distance*1.6f))
|
||||
storage.mCheckIdlePositionTimer = 0; // restart timer
|
||||
static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance() * 1.6f;
|
||||
if (proximityToDoor(actor, distance) || !isNearAllowedNode(actor, storage, distance))
|
||||
{
|
||||
storage.setState(AiWanderStorage::Wander_MoveNow);
|
||||
storage.mTrimCurrentNode = false; // just in case
|
||||
|
@ -451,6 +451,20 @@ namespace MWMechanics
|
|||
}
|
||||
}
|
||||
|
||||
bool AiWander::isNearAllowedNode(const MWWorld::Ptr& actor, const AiWanderStorage& storage, float distance) const
|
||||
{
|
||||
const osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3();
|
||||
auto cell = actor.getCell()->getCell();
|
||||
for (const ESM::Pathgrid::Point& node : storage.mAllowedNodes)
|
||||
{
|
||||
osg::Vec3f point(node.mX, node.mY, node.mZ);
|
||||
Misc::CoordinateConverter(cell).toWorld(point);
|
||||
if ((actorPos - point).length2() < distance * distance)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage)
|
||||
{
|
||||
// Is there no destination or are we there yet?
|
||||
|
@ -468,6 +482,9 @@ namespace MWMechanics
|
|||
|
||||
void AiWander::onChooseActionStatePerFrameActions(const MWWorld::Ptr& actor, AiWanderStorage& storage)
|
||||
{
|
||||
// Wait while fully stop before starting idle animation (important if "smooth movement" is enabled).
|
||||
if (actor.getClass().getCurrentSpeed(actor) > 0)
|
||||
return;
|
||||
|
||||
unsigned short idleAnimation = getRandomIdle();
|
||||
storage.mIdleAnimation = idleAnimation;
|
||||
|
|
|
@ -53,7 +53,7 @@ namespace MWMechanics
|
|||
ESM::Pathgrid::Point mCurrentNode;
|
||||
bool mTrimCurrentNode;
|
||||
|
||||
float mDoorCheckDuration;
|
||||
float mCheckIdlePositionTimer;
|
||||
int mStuckCount;
|
||||
|
||||
AiWanderStorage():
|
||||
|
@ -66,7 +66,7 @@ namespace MWMechanics
|
|||
mPopulateAvailableNodes(true),
|
||||
mAllowedNodes(),
|
||||
mTrimCurrentNode(false),
|
||||
mDoorCheckDuration(0), // TODO: maybe no longer needed
|
||||
mCheckIdlePositionTimer(0),
|
||||
mStuckCount(0)
|
||||
{};
|
||||
|
||||
|
@ -117,6 +117,8 @@ namespace MWMechanics
|
|||
return mDestination;
|
||||
}
|
||||
|
||||
bool isStationary() const { return mDistance == 0; }
|
||||
|
||||
private:
|
||||
void stopWalking(const MWWorld::Ptr& actor);
|
||||
|
||||
|
@ -137,6 +139,7 @@ namespace MWMechanics
|
|||
void wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance);
|
||||
bool destinationIsAtWater(const MWWorld::Ptr &actor, const osg::Vec3f& destination);
|
||||
void completeManualWalking(const MWWorld::Ptr &actor, AiWanderStorage &storage);
|
||||
bool isNearAllowedNode(const MWWorld::Ptr &actor, const AiWanderStorage& storage, float distance) const;
|
||||
|
||||
const int mDistance; // how far the actor can wander from the spawn point
|
||||
const int mDuration;
|
||||
|
|
|
@ -1414,6 +1414,8 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
|||
1.0f, "unequip start", "unequip stop", 0.0f, 0);
|
||||
mUpperBodyState = UpperCharState_UnEquipingWeap;
|
||||
|
||||
mAnimation->detachArrow();
|
||||
|
||||
// If we do not have the "unequip detach" key, hide weapon manually.
|
||||
if (mAnimation->getTextKeyTime(weapgroup+": unequip detach") < 0)
|
||||
mAnimation->showWeapons(false);
|
||||
|
@ -1946,7 +1948,9 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
|||
mUpperBodyState = UpperCharState_MinAttackToMaxAttack;
|
||||
break;
|
||||
}
|
||||
playSwishSound(0.0f);
|
||||
|
||||
if(weapclass != ESM::WeaponType::Ranged && weapclass != ESM::WeaponType::Thrown)
|
||||
playSwishSound(0.0f);
|
||||
}
|
||||
|
||||
if(mAttackType == "shoot")
|
||||
|
@ -2141,22 +2145,65 @@ void CharacterController::update(float duration, bool animationOnly)
|
|||
End of tes3mp addition
|
||||
*/
|
||||
|
||||
if (isPlayer)
|
||||
{
|
||||
// TODO: Move this code to mwinput.
|
||||
// Joystick analogue movement.
|
||||
movementSettings.mSpeedFactor = std::max(std::abs(vec.x()), std::abs(vec.y()));
|
||||
|
||||
// Due to the half way split between walking/running, we multiply speed by 2 while walking, unless a keyboard was used.
|
||||
if(!isrunning && !sneak && !flying && movementSettings.mSpeedFactor <= 0.5f)
|
||||
movementSettings.mSpeedFactor *= 2.f;
|
||||
} else
|
||||
movementSettings.mSpeedFactor = std::min(vec.length(), 1.f);
|
||||
movementSettings.mSpeedFactor = std::min(vec.length(), 1.f);
|
||||
vec.normalize();
|
||||
|
||||
// TODO: Move this check to mwinput.
|
||||
// Joystick analogue movement.
|
||||
// Due to the half way split between walking/running, we multiply speed by 2 while walking, unless a keyboard was used.
|
||||
if (isPlayer && !isrunning && !sneak && !flying && movementSettings.mSpeedFactor <= 0.5f)
|
||||
movementSettings.mSpeedFactor *= 2.f;
|
||||
|
||||
static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game");
|
||||
if (smoothMovement && !isFirstPersonPlayer)
|
||||
{
|
||||
float angle = mPtr.getRefData().getPosition().rot[2];
|
||||
osg::Vec2f targetSpeed = Misc::rotateVec2f(osg::Vec2f(vec.x(), vec.y()), -angle) * movementSettings.mSpeedFactor;
|
||||
osg::Vec2f delta = targetSpeed - mSmoothedSpeed;
|
||||
float speedDelta = movementSettings.mSpeedFactor - mSmoothedSpeed.length();
|
||||
float deltaLen = delta.length();
|
||||
|
||||
float maxDelta;
|
||||
if (std::abs(speedDelta) < deltaLen / 2)
|
||||
// Turning is smooth for player and less smooth for NPCs (otherwise NPC can miss a path point).
|
||||
maxDelta = duration * (isPlayer ? 3.f : 6.f);
|
||||
else if (isPlayer && speedDelta < -deltaLen / 2)
|
||||
// As soon as controls are released, mwinput switches player from running to walking.
|
||||
// So stopping should be instant for player, otherwise it causes a small twitch.
|
||||
maxDelta = 1;
|
||||
else // In all other cases speeding up and stopping are smooth.
|
||||
maxDelta = duration * 3.f;
|
||||
|
||||
if (deltaLen > maxDelta)
|
||||
delta *= maxDelta / deltaLen;
|
||||
mSmoothedSpeed += delta;
|
||||
|
||||
osg::Vec2f newSpeed = Misc::rotateVec2f(mSmoothedSpeed, angle);
|
||||
movementSettings.mSpeedFactor = newSpeed.normalize();
|
||||
vec.x() = newSpeed.x();
|
||||
vec.y() = newSpeed.y();
|
||||
|
||||
const float eps = 0.001f;
|
||||
if (movementSettings.mSpeedFactor < eps)
|
||||
{
|
||||
movementSettings.mSpeedFactor = 0;
|
||||
vec.x() = 0;
|
||||
vec.y() = 1;
|
||||
}
|
||||
else if ((vec.y() < 0) != mIsMovingBackward)
|
||||
{
|
||||
if (targetSpeed.length() < eps || (movementSettings.mPosition[1] < 0) == mIsMovingBackward)
|
||||
vec.y() = mIsMovingBackward ? -eps : eps;
|
||||
}
|
||||
vec.normalize();
|
||||
}
|
||||
|
||||
float effectiveRotation = rot.z();
|
||||
bool canMove = cls.getMaxSpeed(mPtr) > 0;
|
||||
static const bool turnToMovementDirection = Settings::Manager::getBool("turn to movement direction", "Game");
|
||||
if (turnToMovementDirection && !isFirstPersonPlayer)
|
||||
if (!turnToMovementDirection || isFirstPersonPlayer)
|
||||
movementSettings.mIsStrafing = std::abs(vec.x()) > std::abs(vec.y()) * 2;
|
||||
else if (canMove)
|
||||
{
|
||||
float targetMovementAngle = vec.y() >= 0 ? std::atan2(-vec.x(), vec.y()) : std::atan2(vec.x(), -vec.y());
|
||||
movementSettings.mIsStrafing = (stats.getDrawState() != MWMechanics::DrawState_Nothing || inwater)
|
||||
|
@ -2176,14 +2223,14 @@ void CharacterController::update(float duration, bool animationOnly)
|
|||
stats.setSideMovementAngle(stats.getSideMovementAngle() + delta);
|
||||
effectiveRotation += delta;
|
||||
}
|
||||
else
|
||||
movementSettings.mIsStrafing = std::abs(vec.x()) > std::abs(vec.y()) * 2;
|
||||
|
||||
mAnimation->setLegsYawRadians(stats.getSideMovementAngle());
|
||||
if (stats.getDrawState() == MWMechanics::DrawState_Nothing || inwater)
|
||||
mAnimation->setUpperBodyYawRadians(stats.getSideMovementAngle() / 2);
|
||||
else
|
||||
mAnimation->setUpperBodyYawRadians(stats.getSideMovementAngle() / 4);
|
||||
if (smoothMovement && !isPlayer && !inwater)
|
||||
mAnimation->setUpperBodyYawRadians(mAnimation->getUpperBodyYawRadians() + mAnimation->getHeadYaw() / 2);
|
||||
|
||||
speed = cls.getCurrentSpeed(mPtr);
|
||||
vec.x() *= speed;
|
||||
|
@ -2375,13 +2422,11 @@ void CharacterController::update(float duration, bool animationOnly)
|
|||
: (sneak ? CharState_SneakBack
|
||||
: (isrunning ? CharState_RunBack : CharState_WalkBack)));
|
||||
}
|
||||
else if (effectiveRotation != 0.0f)
|
||||
else
|
||||
{
|
||||
// Do not play turning animation for player if rotation speed is very slow.
|
||||
// Actual threshold should take framerate in account.
|
||||
float rotationThreshold = 0.f;
|
||||
if (isPlayer)
|
||||
rotationThreshold = 0.015 * 60 * duration;
|
||||
float rotationThreshold = (isPlayer ? 0.015f : 0.001f) * 60 * duration;
|
||||
|
||||
// It seems only bipedal actors use turning animations.
|
||||
// Also do not use turning animations in the first-person view and when sneaking.
|
||||
|
@ -2915,10 +2960,9 @@ void CharacterController::setVisibility(float visibility)
|
|||
void CharacterController::setAttackTypeBasedOnMovement()
|
||||
{
|
||||
float *move = mPtr.getClass().getMovementSettings(mPtr).mPosition;
|
||||
|
||||
if (move[1] && !move[0]) // forward-backward
|
||||
if (std::abs(move[1]) > std::abs(move[0]) + 0.2f) // forward-backward
|
||||
mAttackType = "thrust";
|
||||
else if (move[0] && !move[1]) //sideway
|
||||
else if (std::abs(move[0]) > std::abs(move[1]) + 0.2f) // sideway
|
||||
mAttackType = "slash";
|
||||
else
|
||||
mAttackType = "chop";
|
||||
|
@ -3126,19 +3170,21 @@ void CharacterController::updateHeadTracking(float duration)
|
|||
return;
|
||||
const osg::Vec3f actorDirection = mPtr.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0);
|
||||
|
||||
zAngleRadians = std::atan2(direction.x(), direction.y()) - std::atan2(actorDirection.x(), actorDirection.y());
|
||||
xAngleRadians = -std::asin(direction.z());
|
||||
|
||||
const double xLimit = osg::DegreesToRadians(40.0);
|
||||
const double zLimit = osg::DegreesToRadians(30.0);
|
||||
zAngleRadians = osg::clampBetween(Misc::normalizeAngle(zAngleRadians), -xLimit, xLimit);
|
||||
xAngleRadians = osg::clampBetween(Misc::normalizeAngle(xAngleRadians), -zLimit, zLimit);
|
||||
zAngleRadians = std::atan2(actorDirection.x(), actorDirection.y()) - std::atan2(direction.x(), direction.y());
|
||||
xAngleRadians = std::asin(direction.z());
|
||||
}
|
||||
|
||||
const double xLimit = osg::DegreesToRadians(40.0);
|
||||
const double zLimit = osg::DegreesToRadians(30.0);
|
||||
double zLimitOffset = mAnimation->getUpperBodyYawRadians();
|
||||
xAngleRadians = osg::clampBetween(Misc::normalizeAngle(xAngleRadians), -xLimit, xLimit);
|
||||
zAngleRadians = osg::clampBetween(Misc::normalizeAngle(zAngleRadians),
|
||||
-zLimit + zLimitOffset, zLimit + zLimitOffset);
|
||||
|
||||
float factor = duration*5;
|
||||
factor = std::min(factor, 1.f);
|
||||
xAngleRadians = (1.f-factor) * mAnimation->getHeadPitch() + factor * (-xAngleRadians);
|
||||
zAngleRadians = (1.f-factor) * mAnimation->getHeadYaw() + factor * (-zAngleRadians);
|
||||
xAngleRadians = (1.f-factor) * mAnimation->getHeadPitch() + factor * xAngleRadians;
|
||||
zAngleRadians = (1.f-factor) * mAnimation->getHeadYaw() + factor * zAngleRadians;
|
||||
|
||||
mAnimation->setHeadPitch(xAngleRadians);
|
||||
mAnimation->setHeadYaw(zAngleRadians);
|
||||
|
|
|
@ -196,6 +196,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener
|
|||
float mTimeUntilWake;
|
||||
|
||||
bool mIsMovingBackward;
|
||||
osg::Vec2f mSmoothedSpeed;
|
||||
|
||||
void setAttackTypeBasedOnMovement();
|
||||
|
||||
|
|
|
@ -707,7 +707,7 @@ namespace MWMechanics
|
|||
return mTimeOfDeath;
|
||||
}
|
||||
|
||||
std::map<CreatureStats::SummonKey, int>& CreatureStats::getSummonedCreatureMap()
|
||||
std::map<ESM::SummonKey, int>& CreatureStats::getSummonedCreatureMap()
|
||||
{
|
||||
return mSummonedCreatures;
|
||||
}
|
||||
|
@ -725,9 +725,9 @@ namespace MWMechanics
|
|||
*/
|
||||
void CreatureStats::setSummonedCreatureActorId(std::string refId, int actorId)
|
||||
{
|
||||
for (std::map<CreatureStats::SummonKey, int>::iterator it = mSummonedCreatures.begin(); it != mSummonedCreatures.end(); )
|
||||
for (std::map<ESM::SummonKey, int>::iterator it = mSummonedCreatures.begin(); it != mSummonedCreatures.end(); )
|
||||
{
|
||||
if (Misc::StringUtils::ciEqual(getSummonedCreature(it->first.first), refId) && it->second == -1)
|
||||
if (Misc::StringUtils::ciEqual(getSummonedCreature(it->first.mEffectId), refId) && it->second == -1)
|
||||
{
|
||||
it->second = actorId;
|
||||
break;
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "drawstate.hpp"
|
||||
|
||||
#include <components/esm/attr.hpp>
|
||||
#include <components/esm/magiceffects.hpp>
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
|
@ -83,10 +84,8 @@ namespace MWMechanics
|
|||
// The difference between view direction and lower body direction.
|
||||
float mSideMovementAngle;
|
||||
|
||||
public:
|
||||
typedef std::pair<int, std::string> SummonKey; // <ESM::MagicEffect index, spell ID>
|
||||
private:
|
||||
std::map<SummonKey, int> mSummonedCreatures; // <SummonKey, ActorId>
|
||||
std::map<ESM::SummonKey, int> mSummonedCreatures; // <SummonKey, ActorId>
|
||||
|
||||
// Contains ActorIds of summoned creatures with an expired lifetime that have not been deleted yet.
|
||||
// This may be necessary when the creature is in an inactive cell.
|
||||
|
@ -245,7 +244,7 @@ namespace MWMechanics
|
|||
void setBlock(bool value);
|
||||
bool getBlock() const;
|
||||
|
||||
std::map<SummonKey, int>& getSummonedCreatureMap(); // <SummonKey, ActorId of summoned creature>
|
||||
std::map<ESM::SummonKey, int>& getSummonedCreatureMap(); // <SummonKey, ActorId of summoned creature>
|
||||
std::vector<int>& getSummonedCreatureGraveyard(); // ActorIds
|
||||
|
||||
/*
|
||||
|
|
|
@ -316,7 +316,7 @@ namespace MWMechanics
|
|||
float priceMultipler = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find ("fEnchantmentValueMult")->mValue.getFloat();
|
||||
int price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mEnchanter, static_cast<int>(getEnchantPoints() * priceMultipler), true);
|
||||
price *= getEnchantItemsCount() * getTypeMultiplier();
|
||||
return price;
|
||||
return std::max(1, price);
|
||||
}
|
||||
|
||||
int Enchanting::getGemCharge() const
|
||||
|
|
|
@ -58,6 +58,7 @@ namespace MWMechanics
|
|||
std::vector<ActiveSpells::ActiveEffect> absorbEffects;
|
||||
ActiveSpells::ActiveEffect absorbEffect = appliedEffect;
|
||||
absorbEffect.mMagnitude *= -1;
|
||||
absorbEffect.mEffectIndex = appliedEffect.mEffectIndex;
|
||||
absorbEffects.emplace_back(absorbEffect);
|
||||
|
||||
// Morrowind negates reflected Absorb spells so the original caster won't be harmed.
|
||||
|
|
|
@ -74,8 +74,8 @@ namespace MWMechanics
|
|||
{
|
||||
virtual ~EffectSourceVisitor() { }
|
||||
|
||||
virtual void visit (MWMechanics::EffectKey key,
|
||||
const std::string& sourceName, const std::string& sourceId, int casterActorId,
|
||||
virtual void visit (EffectKey key, int effectIndex,
|
||||
const std::string& sourceName, const std::string& sourceId, int casterActorId,
|
||||
float magnitude, float remainingTime = -1, float totalTime = -1) = 0;
|
||||
};
|
||||
|
||||
|
|
|
@ -88,6 +88,24 @@ namespace
|
|||
const auto halfExtents = world->getHalfExtents(actor);
|
||||
return 2.0 * halfExtents.z();
|
||||
}
|
||||
|
||||
// Returns true if turn in `p2` is less than 10 degrees and all the 3 points are almost on one line.
|
||||
bool isAlmostStraight(const osg::Vec3f& p1, const osg::Vec3f& p2, const osg::Vec3f& p3, float pointTolerance) {
|
||||
osg::Vec3f v1 = p1 - p2;
|
||||
osg::Vec3f v3 = p3 - p2;
|
||||
v1.z() = v3.z() = 0;
|
||||
float dotProduct = v1.x() * v3.x() + v1.y() * v3.y();
|
||||
float crossProduct = v1.x() * v3.y() - v1.y() * v3.x();
|
||||
|
||||
// Check that the angle between v1 and v3 is less or equal than 10 degrees.
|
||||
static const float cos170 = std::cos(osg::PI / 180 * 170);
|
||||
bool checkAngle = dotProduct <= cos170 * v1.length() * v3.length();
|
||||
|
||||
// Check that distance from p2 to the line (p1, p3) is less or equal than `pointTolerance`.
|
||||
bool checkDist = std::abs(crossProduct) <= pointTolerance * (p3 - p1).length() * 2;
|
||||
|
||||
return checkAngle && checkDist;
|
||||
}
|
||||
}
|
||||
|
||||
namespace MWMechanics
|
||||
|
@ -286,6 +304,11 @@ namespace MWMechanics
|
|||
while (mPath.size() > 1 && sqrDistanceIgnoreZ(mPath.front(), position) < pointTolerance * pointTolerance)
|
||||
mPath.pop_front();
|
||||
|
||||
while (mPath.size() > 2 && isAlmostStraight(mPath[0], mPath[1], mPath[2], pointTolerance))
|
||||
mPath.erase(mPath.begin() + 1);
|
||||
if (mPath.size() > 1 && isAlmostStraight(position, mPath[0], mPath[1], pointTolerance))
|
||||
mPath.pop_front();
|
||||
|
||||
if (mPath.size() == 1 && sqrDistanceIgnoreZ(mPath.front(), position) < destinationTolerance * destinationTolerance)
|
||||
mPath.pop_front();
|
||||
}
|
||||
|
|
|
@ -40,7 +40,8 @@ void Repair::repair(const MWWorld::Ptr &itemToRepair)
|
|||
|
||||
// reduce number of uses left
|
||||
int uses = mTool.getClass().getItemHealth(mTool);
|
||||
mTool.getCellRef().setCharge(uses-1);
|
||||
uses -= std::min(uses, 1);
|
||||
mTool.getCellRef().setCharge(uses);
|
||||
|
||||
MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player);
|
||||
|
||||
|
|
|
@ -47,6 +47,10 @@ namespace MWMechanics
|
|||
!lock.getClass().hasToolTip(lock)) //If it's unlocked or can not be unlocked back out immediately
|
||||
return;
|
||||
|
||||
int uses = lockpick.getClass().getItemHealth(lockpick);
|
||||
if (uses == 0)
|
||||
return;
|
||||
|
||||
int lockStrength = lock.getCellRef().getLockLevel();
|
||||
|
||||
float pickQuality = lockpick.get<ESM::Lockpick>()->mBase->mData.mQuality;
|
||||
|
@ -99,9 +103,7 @@ namespace MWMechanics
|
|||
resultMessage = "#{sLockFail}";
|
||||
}
|
||||
|
||||
int uses = lockpick.getClass().getItemHealth(lockpick);
|
||||
--uses;
|
||||
lockpick.getCellRef().setCharge(uses);
|
||||
lockpick.getCellRef().setCharge(uses-1);
|
||||
if (!uses)
|
||||
lockpick.getContainerStore()->remove(lockpick, 1, mActor);
|
||||
}
|
||||
|
@ -109,7 +111,11 @@ namespace MWMechanics
|
|||
void Security::probeTrap(const MWWorld::Ptr &trap, const MWWorld::Ptr &probe,
|
||||
std::string& resultMessage, std::string& resultSound)
|
||||
{
|
||||
if (trap.getCellRef().getTrap() == "")
|
||||
if (trap.getCellRef().getTrap().empty())
|
||||
return;
|
||||
|
||||
int uses = probe.getClass().getItemHealth(probe);
|
||||
if (uses == 0)
|
||||
return;
|
||||
|
||||
float probeQuality = probe.get<ESM::Probe>()->mBase->mData.mQuality;
|
||||
|
@ -165,9 +171,7 @@ namespace MWMechanics
|
|||
resultMessage = "#{sTrapFail}";
|
||||
}
|
||||
|
||||
int uses = probe.getClass().getItemHealth(probe);
|
||||
--uses;
|
||||
probe.getCellRef().setCharge(uses);
|
||||
probe.getCellRef().setCharge(uses-1);
|
||||
if (!uses)
|
||||
probe.getContainerStore()->remove(probe, 1, mActor);
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include "../mwworld/inventorystore.hpp"
|
||||
|
||||
#include "creaturestats.hpp"
|
||||
#include "spellutil.hpp"
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
|
@ -23,8 +24,8 @@ namespace MWMechanics
|
|||
|
||||
GetAbsorptionProbability() = default;
|
||||
|
||||
virtual void visit (MWMechanics::EffectKey key,
|
||||
const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/,
|
||||
virtual void visit (MWMechanics::EffectKey key, int /*effectIndex*/,
|
||||
const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/,
|
||||
float magnitude, float /*remainingTime*/, float /*totalTime*/)
|
||||
{
|
||||
if (key.mId == ESM::MagicEffect::SpellAbsorption)
|
||||
|
@ -43,9 +44,9 @@ namespace MWMechanics
|
|||
}
|
||||
};
|
||||
|
||||
bool absorbSpell (const ESM::Spell* spell, const MWWorld::Ptr& caster, const MWWorld::Ptr& target)
|
||||
bool absorbSpell (const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target)
|
||||
{
|
||||
if (!spell || caster == target || !target.getClass().isActor())
|
||||
if (spellId.empty() || caster == target || !target.getClass().isActor())
|
||||
return false;
|
||||
|
||||
CreatureStats& stats = target.getClass().getCreatureStats(target);
|
||||
|
@ -62,13 +63,27 @@ namespace MWMechanics
|
|||
if (Misc::Rng::roll0to99() >= chance)
|
||||
return false;
|
||||
|
||||
const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find("VFX_Absorb");
|
||||
const auto& esmStore = MWBase::Environment::get().getWorld()->getStore();
|
||||
const ESM::Static* absorbStatic = esmStore.get<ESM::Static>().find("VFX_Absorb");
|
||||
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target);
|
||||
if (animation && !absorbStatic->mModel.empty())
|
||||
animation->addEffect( "meshes\\" + absorbStatic->mModel, ESM::MagicEffect::SpellAbsorption, false, std::string());
|
||||
const ESM::Spell* spell = esmStore.get<ESM::Spell>().search(spellId);
|
||||
int spellCost = 0;
|
||||
if (spell)
|
||||
{
|
||||
spellCost = spell->mData.mCost;
|
||||
}
|
||||
else
|
||||
{
|
||||
const ESM::Enchantment* enchantment = esmStore.get<ESM::Enchantment>().search(spellId);
|
||||
if (enchantment)
|
||||
spellCost = getEffectiveEnchantmentCastCost(static_cast<float>(enchantment->mData.mCost), caster);
|
||||
}
|
||||
|
||||
// Magicka is increased by the cost of the spell
|
||||
DynamicStat<float> magicka = stats.getMagicka();
|
||||
magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost);
|
||||
magicka.setCurrent(magicka.getCurrent() + spellCost);
|
||||
stats.setMagicka(magicka);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
#ifndef MWMECHANICS_SPELLABSORPTION_H
|
||||
#define MWMECHANICS_SPELLABSORPTION_H
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
struct Spell;
|
||||
}
|
||||
#include <string>
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
|
@ -14,7 +11,7 @@ namespace MWWorld
|
|||
namespace MWMechanics
|
||||
{
|
||||
// Try to absorb a spell based on the magnitude of every Spell Absorption effect source on the target.
|
||||
bool absorbSpell(const ESM::Spell* spell, const MWWorld::Ptr& caster, const MWWorld::Ptr& target);
|
||||
bool absorbSpell(const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -136,10 +136,11 @@ namespace MWMechanics
|
|||
// effects, we display a "can't re-cast" message
|
||||
|
||||
// Try absorbing the spell. Some handling must still happen for absorbed effects.
|
||||
bool absorbed = absorbSpell(spell, caster, target);
|
||||
bool absorbed = absorbSpell(mId, caster, target);
|
||||
|
||||
int currentEffectIndex = 0;
|
||||
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (effects.mList.begin());
|
||||
!target.isEmpty() && effectIt != effects.mList.end(); ++effectIt)
|
||||
!target.isEmpty() && effectIt != effects.mList.end(); ++effectIt, ++currentEffectIndex)
|
||||
{
|
||||
if (effectIt->mRange != range)
|
||||
continue;
|
||||
|
@ -206,6 +207,7 @@ namespace MWMechanics
|
|||
effect.mArg = MWMechanics::EffectKey(*effectIt).mArg;
|
||||
effect.mMagnitude = magnitude;
|
||||
effect.mTimeLeft = 0.f;
|
||||
effect.mEffectIndex = currentEffectIndex;
|
||||
|
||||
// Avoid applying absorb effects if the caster is the target
|
||||
// We still need the spell to be added
|
||||
|
@ -309,7 +311,8 @@ namespace MWMechanics
|
|||
if (isSummoningEffect(effectIt->mEffectID) && !target.isEmpty() && target.getClass().isActor())
|
||||
{
|
||||
CreatureStats& targetStats = target.getClass().getCreatureStats(target);
|
||||
std::map<CreatureStats::SummonKey, int>::iterator findCreature = targetStats.getSummonedCreatureMap().find(std::make_pair(effectIt->mEffectID, mId));
|
||||
ESM::SummonKey key(effectIt->mEffectID, mId, currentEffectIndex);
|
||||
auto findCreature = targetStats.getSummonedCreatureMap().find(key);
|
||||
if (findCreature != targetStats.getSummonedCreatureMap().end())
|
||||
{
|
||||
/*
|
||||
|
|
|
@ -61,7 +61,10 @@ namespace MWMechanics
|
|||
for (const auto& effect : spell->mEffects.mList)
|
||||
{
|
||||
if (iter.second.mPurgedEffects.find(i) != iter.second.mPurgedEffects.end())
|
||||
{
|
||||
++i;
|
||||
continue; // effect was purged
|
||||
}
|
||||
|
||||
float random = 1.f;
|
||||
if (iter.second.mEffectRands.find(i) != iter.second.mEffectRands.end())
|
||||
|
@ -119,7 +122,7 @@ namespace MWMechanics
|
|||
|
||||
SpellParams params;
|
||||
params.mEffectRands = random;
|
||||
mSpells.insert (std::make_pair (spell, params));
|
||||
mSpells.emplace(spell, params);
|
||||
mSpellsChanged = true;
|
||||
}
|
||||
}
|
||||
|
@ -293,7 +296,8 @@ namespace MWMechanics
|
|||
const ESM::Spell * spell = it.first;
|
||||
for (const auto& effectIt : it.second)
|
||||
{
|
||||
visitor.visit(effectIt.first, spell->mName, spell->mId, -1, effectIt.second.getMagnitude());
|
||||
// FIXME: since Spells merges effects with the same ID, there is no sense to use multiple effects with same ID here
|
||||
visitor.visit(effectIt.first, -1, spell->mName, spell->mId, -1, effectIt.second.getMagnitude());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -329,20 +333,24 @@ namespace MWMechanics
|
|||
|
||||
void Spells::purgeEffect(int effectId, const std::string & sourceId)
|
||||
{
|
||||
const ESM::Spell * spell = SpellList::getSpell(sourceId);
|
||||
// Effect source may be not a spell
|
||||
const ESM::Spell * spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(sourceId);
|
||||
if (spell == nullptr)
|
||||
return;
|
||||
|
||||
auto spellIt = mSpells.find(spell);
|
||||
if (spellIt == mSpells.end())
|
||||
return;
|
||||
|
||||
int i = 0;
|
||||
int index = 0;
|
||||
for (auto& effectIt : spellIt->first->mEffects.mList)
|
||||
{
|
||||
if (effectIt.mEffectID == effectId)
|
||||
{
|
||||
spellIt->second.mPurgedEffects.insert(i);
|
||||
spellIt->second.mPurgedEffects.insert(index);
|
||||
mSpellsChanged = true;
|
||||
}
|
||||
++i;
|
||||
++index;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -462,7 +470,7 @@ namespace MWMechanics
|
|||
ESM::SpellState::SpellParams params;
|
||||
params.mEffectRands = it.second.mEffectRands;
|
||||
params.mPurgedEffects = it.second.mPurgedEffects;
|
||||
state.mSpells.insert(std::make_pair(it.first->mId, params));
|
||||
state.mSpells.emplace(it.first->mId, params);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
#include "steering.hpp"
|
||||
|
||||
#include <components/misc/mathutil.hpp>
|
||||
#include <components/settings/settings.hpp>
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/ptr.hpp"
|
||||
|
||||
|
@ -12,19 +15,8 @@ namespace MWMechanics
|
|||
|
||||
bool smoothTurn(const MWWorld::Ptr& actor, float targetAngleRadians, int axis, float epsilonRadians)
|
||||
{
|
||||
float currentAngle (actor.getRefData().getPosition().rot[axis]);
|
||||
float diff (targetAngleRadians - currentAngle);
|
||||
if (std::abs(diff) >= osg::DegreesToRadians(180.f))
|
||||
{
|
||||
if (diff >= 0)
|
||||
{
|
||||
diff = diff - osg::DegreesToRadians(360.f);
|
||||
}
|
||||
else
|
||||
{
|
||||
diff = osg::DegreesToRadians(360.f) + diff;
|
||||
}
|
||||
}
|
||||
MWMechanics::Movement& movement = actor.getClass().getMovementSettings(actor);
|
||||
float diff = Misc::normalizeAngle(targetAngleRadians - actor.getRefData().getPosition().rot[axis]);
|
||||
float absDiff = std::abs(diff);
|
||||
|
||||
// The turning animation actually moves you slightly, so the angle will be wrong again.
|
||||
|
@ -33,10 +25,14 @@ bool smoothTurn(const MWWorld::Ptr& actor, float targetAngleRadians, int axis, f
|
|||
return true;
|
||||
|
||||
float limit = getAngularVelocity(actor.getClass().getMaxSpeed(actor)) * MWBase::Environment::get().getFrameDuration();
|
||||
static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game");
|
||||
if (smoothMovement)
|
||||
limit *= std::min(absDiff / osg::PI + 0.1, 0.5);
|
||||
|
||||
if (absDiff > limit)
|
||||
diff = osg::sign(diff) * limit;
|
||||
|
||||
actor.getClass().getMovementSettings(actor).mRotation[axis] = diff;
|
||||
movement.mRotation[axis] = diff;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -80,25 +80,25 @@ namespace MWMechanics
|
|||
{
|
||||
}
|
||||
|
||||
void UpdateSummonedCreatures::visit(EffectKey key, const std::string &sourceName, const std::string &sourceId, int casterActorId, float magnitude, float remainingTime, float totalTime)
|
||||
void UpdateSummonedCreatures::visit(EffectKey key, int effectIndex, const std::string &sourceName, const std::string &sourceId, int casterActorId, float magnitude, float remainingTime, float totalTime)
|
||||
{
|
||||
if (isSummoningEffect(key.mId) && magnitude > 0)
|
||||
{
|
||||
mActiveEffects.insert(std::make_pair(key.mId, sourceId));
|
||||
mActiveEffects.insert(ESM::SummonKey(key.mId, sourceId, effectIndex));
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateSummonedCreatures::process(bool cleanup)
|
||||
{
|
||||
MWMechanics::CreatureStats& creatureStats = mActor.getClass().getCreatureStats(mActor);
|
||||
std::map<CreatureStats::SummonKey, int>& creatureMap = creatureStats.getSummonedCreatureMap();
|
||||
std::map<ESM::SummonKey, int>& creatureMap = creatureStats.getSummonedCreatureMap();
|
||||
|
||||
for (std::set<std::pair<int, std::string> >::iterator it = mActiveEffects.begin(); it != mActiveEffects.end(); ++it)
|
||||
for (std::set<ESM::SummonKey>::iterator it = mActiveEffects.begin(); it != mActiveEffects.end(); ++it)
|
||||
{
|
||||
bool found = creatureMap.find(std::make_pair(it->first, it->second)) != creatureMap.end();
|
||||
bool found = creatureMap.find(*it) != creatureMap.end();
|
||||
if (!found)
|
||||
{
|
||||
std::string creatureID = getSummonedCreature(it->first);
|
||||
std::string creatureID = getSummonedCreature(it->mEffectId);
|
||||
if (!creatureID.empty())
|
||||
{
|
||||
int creatureActorId = -1;
|
||||
|
@ -146,10 +146,10 @@ namespace MWMechanics
|
|||
objectList->packetOrigin = mwmp::CLIENT_GAMEPLAY;
|
||||
|
||||
MWMechanics::CreatureStats *actorCreatureStats = &mActor.getClass().getCreatureStats(mActor);
|
||||
int effectId = it->first;
|
||||
std::string spellId = it->second;
|
||||
float duration = actorCreatureStats->getActiveSpells().getEffectDuration(effectId, it->second);
|
||||
objectList->addObjectSpawn(placed, mActor, spellId, effectId, duration);
|
||||
int effectId = it->mEffectId;
|
||||
std::string sourceId = it->mSourceId;
|
||||
float duration = actorCreatureStats->getActiveSpells().getEffectDuration(effectId, sourceId);
|
||||
objectList->addObjectSpawn(placed, mActor, sourceId, effectId, duration);
|
||||
objectList->sendObjectSpawn();
|
||||
}
|
||||
|
||||
|
@ -161,7 +161,7 @@ namespace MWMechanics
|
|||
// still insert into creatureMap so we don't try to spawn again every frame, that would spam the warning log
|
||||
}
|
||||
|
||||
creatureMap.insert(std::make_pair(*it, creatureActorId));
|
||||
creatureMap.emplace(*it, creatureActorId);
|
||||
/*
|
||||
End of tes3mp change (major)
|
||||
*/
|
||||
|
@ -170,7 +170,7 @@ namespace MWMechanics
|
|||
}
|
||||
|
||||
// Update summon effects
|
||||
for (std::map<CreatureStats::SummonKey, int>::iterator it = creatureMap.begin(); it != creatureMap.end(); )
|
||||
for (std::map<ESM::SummonKey, int>::iterator it = creatureMap.begin(); it != creatureMap.end(); )
|
||||
{
|
||||
bool found = mActiveEffects.find(it->first) != mActiveEffects.end();
|
||||
if (!found)
|
||||
|
@ -192,7 +192,7 @@ namespace MWMechanics
|
|||
if (!cleanup)
|
||||
return;
|
||||
|
||||
for (std::map<CreatureStats::SummonKey, int>::iterator it = creatureMap.begin(); it != creatureMap.end(); )
|
||||
for (std::map<ESM::SummonKey, int>::iterator it = creatureMap.begin(); it != creatureMap.end(); )
|
||||
{
|
||||
/*
|
||||
Start of tes3mp addition
|
||||
|
@ -214,9 +214,11 @@ namespace MWMechanics
|
|||
if (ptr.isEmpty() || (ptr.getClass().getCreatureStats(ptr).isDead() && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished()))
|
||||
{
|
||||
// Purge the magic effect so a new creature can be summoned if desired
|
||||
creatureStats.getActiveSpells().purgeEffect(it->first.first, it->first.second);
|
||||
const ESM::SummonKey& key = it->first;
|
||||
creatureStats.getActiveSpells().purgeEffect(key.mEffectId, key.mSourceId, key.mEffectIndex);
|
||||
creatureStats.getSpells().purgeEffect(key.mEffectId, key.mSourceId);
|
||||
if (mActor.getClass().hasInventoryStore(mActor))
|
||||
mActor.getClass().getInventoryStore(mActor).purgeEffect(it->first.first, it->first.second);
|
||||
mActor.getClass().getInventoryStore(mActor).purgeEffect(key.mEffectId, key.mSourceId, false, key.mEffectIndex);
|
||||
|
||||
MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(mActor, it->second);
|
||||
creatureMap.erase(it++);
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
|
||||
#include "../mwworld/ptr.hpp"
|
||||
|
||||
#include <components/esm/magiceffects.hpp>
|
||||
|
||||
#include "magiceffects.hpp"
|
||||
|
||||
namespace MWMechanics
|
||||
|
@ -20,8 +22,8 @@ namespace MWMechanics
|
|||
UpdateSummonedCreatures(const MWWorld::Ptr& actor);
|
||||
virtual ~UpdateSummonedCreatures() = default;
|
||||
|
||||
virtual void visit (MWMechanics::EffectKey key,
|
||||
const std::string& sourceName, const std::string& sourceId, int casterActorId,
|
||||
virtual void visit (MWMechanics::EffectKey key, int effectIndex,
|
||||
const std::string& sourceName, const std::string& sourceId, int casterActorId,
|
||||
float magnitude, float remainingTime = -1, float totalTime = -1);
|
||||
|
||||
/// To call after all effect sources have been visited
|
||||
|
@ -30,7 +32,7 @@ namespace MWMechanics
|
|||
private:
|
||||
MWWorld::Ptr mActor;
|
||||
|
||||
std::set<std::pair<int, std::string> > mActiveEffects;
|
||||
std::set<ESM::SummonKey> mActiveEffects;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -508,15 +508,30 @@ void ObjectList::spawnObjects(MWWorld::CellStore* cellStore)
|
|||
newPtr.getCellRef().getRefNum(), newPtr.getCellRef().getMpNum(), creatureActorId);
|
||||
|
||||
// Check if this creature is present in the summoner's summoned creature map
|
||||
std::map<std::pair<int, std::string>, int>& creatureMap = masterCreatureStats.getSummonedCreatureMap();
|
||||
bool foundSummonedCreature = creatureMap.find(std::make_pair(baseObject.summonEffectId, baseObject.summonSpellId)) != creatureMap.end();
|
||||
std::map<ESM::SummonKey, int>& creatureMap = masterCreatureStats.getSummonedCreatureMap();
|
||||
|
||||
bool foundSummonedCreature = false;
|
||||
|
||||
for (std::map<ESM::SummonKey, int>::iterator it = creatureMap.begin(); it != creatureMap.end(); )
|
||||
{
|
||||
if (it->first.mEffectId == baseObject.summonEffectId && it->first.mSourceId == baseObject.summonSpellId)
|
||||
{
|
||||
foundSummonedCreature = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If it is, update its creatureActorId
|
||||
if (foundSummonedCreature)
|
||||
{
|
||||
masterCreatureStats.setSummonedCreatureActorId(baseObject.refId, creatureActorId);
|
||||
}
|
||||
// If not, add it to the summoned creature map
|
||||
else
|
||||
creatureMap.insert(std::make_pair(std::make_pair(baseObject.summonEffectId, baseObject.summonSpellId), creatureActorId));
|
||||
{
|
||||
ESM::SummonKey summonKey(baseObject.summonEffectId, baseObject.summonSpellId, -1);
|
||||
creatureMap.emplace(summonKey, creatureActorId);
|
||||
}
|
||||
|
||||
creatureStats.setFriendlyHits(0);
|
||||
}
|
||||
|
|
|
@ -474,6 +474,7 @@ public:
|
|||
void setAlpha(float alpha);
|
||||
virtual void setPitchFactor(float factor) {}
|
||||
virtual void attachArrow() {}
|
||||
virtual void detachArrow() {}
|
||||
virtual void releaseArrow(float attackStrength) {}
|
||||
virtual void enableHeadAnimation(bool enable) {}
|
||||
// TODO: move outside of this class
|
||||
|
|
|
@ -208,6 +208,12 @@ bool CreatureWeaponAnimation::isArrowAttached() const
|
|||
return mAmmunition != nullptr;
|
||||
}
|
||||
|
||||
void CreatureWeaponAnimation::detachArrow()
|
||||
{
|
||||
WeaponAnimation::detachArrow(mPtr);
|
||||
updateQuiver();
|
||||
}
|
||||
|
||||
void CreatureWeaponAnimation::attachArrow()
|
||||
{
|
||||
WeaponAnimation::attachArrow(mPtr);
|
||||
|
|
|
@ -40,6 +40,7 @@ namespace MWRender
|
|||
void updatePart(PartHolderPtr& scene, int slot);
|
||||
|
||||
virtual void attachArrow();
|
||||
virtual void detachArrow();
|
||||
virtual void releaseArrow(float attackStrength);
|
||||
// WeaponAnimation
|
||||
virtual osg::Group* getArrowBone();
|
||||
|
|
|
@ -363,6 +363,7 @@ void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode)
|
|||
mViewMode = viewMode;
|
||||
MWBase::Environment::get().getWorld()->scaleObject(mPtr, mPtr.getCellRef().getScale()); // apply race height after view change
|
||||
|
||||
mAmmunition.reset();
|
||||
rebuild();
|
||||
setRenderBin();
|
||||
}
|
||||
|
@ -1051,6 +1052,12 @@ void NpcAnimation::attachArrow()
|
|||
updateQuiver();
|
||||
}
|
||||
|
||||
void NpcAnimation::detachArrow()
|
||||
{
|
||||
WeaponAnimation::detachArrow(mPtr);
|
||||
updateQuiver();
|
||||
}
|
||||
|
||||
void NpcAnimation::releaseArrow(float attackStrength)
|
||||
{
|
||||
WeaponAnimation::releaseArrow(mPtr, attackStrength);
|
||||
|
|
|
@ -142,6 +142,7 @@ public:
|
|||
virtual void showCarriedLeft(bool show);
|
||||
|
||||
virtual void attachArrow();
|
||||
virtual void detachArrow();
|
||||
virtual void releaseArrow(float attackStrength);
|
||||
|
||||
virtual osg::Group* getArrowBone();
|
||||
|
|
|
@ -289,7 +289,9 @@ namespace MWRender
|
|||
}
|
||||
virtual void apply(osg::Geometry& geom)
|
||||
{
|
||||
mResult.mNumVerts += geom.getVertexArray()->getNumElements();
|
||||
if (osg::Array* array = geom.getVertexArray())
|
||||
mResult.mNumVerts += array->getNumElements();
|
||||
|
||||
++mResult.mStateSetCounter[mCurrentStateSet];
|
||||
++mGlobalStateSetCounter[mCurrentStateSet];
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ namespace MWRender
|
|||
{
|
||||
public:
|
||||
|
||||
TerrainStorage(Resource::ResourceSystem* resourceSystem, const std::string& normalMapPattern = "", const std::string& normalHeightMapPatteern = "", bool autoUseNormalMaps = false, const std::string& specularMapPattern = "", bool autoUseSpecularMaps = false);
|
||||
TerrainStorage(Resource::ResourceSystem* resourceSystem, const std::string& normalMapPattern = "", const std::string& normalHeightMapPattern = "", bool autoUseNormalMaps = false, const std::string& specularMapPattern = "", bool autoUseSpecularMaps = false);
|
||||
~TerrainStorage();
|
||||
|
||||
virtual osg::ref_ptr<const ESMTerrain::LandObject> getLand (int cellX, int cellY) override;
|
||||
|
|
|
@ -443,6 +443,7 @@ Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem
|
|||
mWaterGeom = SceneUtil::createWaterGeometry(Constants::CellSizeInUnits*150, 40, 900);
|
||||
mWaterGeom->setDrawCallback(new DepthClampCallback);
|
||||
mWaterGeom->setNodeMask(Mask_Water);
|
||||
mWaterGeom->setDataVariance(osg::Object::STATIC);
|
||||
|
||||
mWaterNode = new osg::PositionAttitudeTransform;
|
||||
mWaterNode->setName("Water Root");
|
||||
|
|
|
@ -108,6 +108,11 @@ void WeaponAnimation::attachArrow(MWWorld::Ptr actor)
|
|||
}
|
||||
}
|
||||
|
||||
void WeaponAnimation::detachArrow(MWWorld::Ptr actor)
|
||||
{
|
||||
mAmmunition.reset();
|
||||
}
|
||||
|
||||
void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength)
|
||||
{
|
||||
MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor);
|
||||
|
|
|
@ -36,6 +36,8 @@ namespace MWRender
|
|||
/// @note If no weapon (or an invalid weapon) is equipped, this function is a no-op.
|
||||
void attachArrow(MWWorld::Ptr actor);
|
||||
|
||||
void detachArrow(MWWorld::Ptr actor);
|
||||
|
||||
/// @note If no weapon (or an invalid weapon) is equipped, this function is a no-op.
|
||||
void releaseArrow(MWWorld::Ptr actor, float attackStrength);
|
||||
|
||||
|
|
|
@ -219,12 +219,20 @@ namespace MWScript
|
|||
float ay = ptr.getRefData().getPosition().rot[1];
|
||||
float az = ptr.getRefData().getPosition().rot[2];
|
||||
|
||||
// XYZ axis use the inverse (XYZ) rotation order like vanilla SetAngle.
|
||||
// UWV axis use the standard (ZYX) rotation order like TESCS/OpenMW-CS and the rest of the game.
|
||||
if (axis == "x")
|
||||
MWBase::Environment::get().getWorld()->rotateObject(ptr,angle,ay,az);
|
||||
MWBase::Environment::get().getWorld()->rotateObject(ptr,angle,ay,az,MWBase::RotationFlag_inverseOrder);
|
||||
else if (axis == "y")
|
||||
MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,angle,az);
|
||||
MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,angle,az,MWBase::RotationFlag_inverseOrder);
|
||||
else if (axis == "z")
|
||||
MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,angle);
|
||||
MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,angle,MWBase::RotationFlag_inverseOrder);
|
||||
else if (axis == "u")
|
||||
MWBase::Environment::get().getWorld()->rotateObject(ptr,angle,ay,az,MWBase::RotationFlag_none);
|
||||
else if (axis == "w")
|
||||
MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,angle,az,MWBase::RotationFlag_none);
|
||||
else if (axis == "v")
|
||||
MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,angle,MWBase::RotationFlag_none);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -978,7 +978,7 @@ void MWWorld::InventoryStore::visitEffectSources(MWMechanics::EffectSourceVisito
|
|||
float magnitude = effect.mMagnMin + (effect.mMagnMax - effect.mMagnMin) * params.mRandom;
|
||||
magnitude *= params.mMultiplier;
|
||||
if (magnitude > 0)
|
||||
visitor.visit(MWMechanics::EffectKey(effect), (**iter).getClass().getName(**iter), (**iter).getCellRef().getRefId(), -1, magnitude);
|
||||
visitor.visit(MWMechanics::EffectKey(effect), i-1, (**iter).getClass().getName(**iter), (**iter).getCellRef().getRefId(), -1, magnitude);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -992,7 +992,7 @@ void MWWorld::InventoryStore::purgeEffect(short effectId, bool wholeSpell)
|
|||
}
|
||||
}
|
||||
|
||||
void MWWorld::InventoryStore::purgeEffect(short effectId, const std::string &sourceId, bool wholeSpell)
|
||||
void MWWorld::InventoryStore::purgeEffect(short effectId, const std::string &sourceId, bool wholeSpell, int effectIndex)
|
||||
{
|
||||
TEffectMagnitudes::iterator effectMagnitudeIt = mPermanentMagicEffectMagnitudes.find(sourceId);
|
||||
if (effectMagnitudeIt == mPermanentMagicEffectMagnitudes.end())
|
||||
|
@ -1025,6 +1025,9 @@ void MWWorld::InventoryStore::purgeEffect(short effectId, const std::string &sou
|
|||
if (effectIt->mEffectID != effectId)
|
||||
continue;
|
||||
|
||||
if (effectIndex >= 0 && effectIndex != i)
|
||||
continue;
|
||||
|
||||
if (wholeSpell)
|
||||
{
|
||||
mPermanentMagicEffectMagnitudes.erase(sourceId);
|
||||
|
|
|
@ -206,7 +206,7 @@ namespace MWWorld
|
|||
void purgeEffect (short effectId, bool wholeSpell = false);
|
||||
///< Remove a magic effect
|
||||
|
||||
void purgeEffect (short effectId, const std::string& sourceId, bool wholeSpell = false);
|
||||
void purgeEffect (short effectId, const std::string& sourceId, bool wholeSpell = false, int effectIndex=-1);
|
||||
///< Remove a magic effect
|
||||
|
||||
virtual void clear();
|
||||
|
|
|
@ -3573,7 +3573,7 @@ namespace MWWorld
|
|||
{
|
||||
}
|
||||
|
||||
virtual void visit (MWMechanics::EffectKey key,
|
||||
virtual void visit (MWMechanics::EffectKey key, int /*effectIndex*/,
|
||||
const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/,
|
||||
float /*magnitude*/, float /*remainingTime*/ = -1, float /*totalTime*/ = -1)
|
||||
{
|
||||
|
|
|
@ -308,9 +308,9 @@ namespace MWWorld
|
|||
///< Return a pointer to a liveCellRef with the given name.
|
||||
/// \param activeOnly do non search inactive cells.
|
||||
|
||||
Ptr searchPtr (const std::string& name, bool activeOnly, bool searchInContainers = true) override;
|
||||
Ptr searchPtr (const std::string& name, bool activeOnly, bool searchInContainers = false) override;
|
||||
///< Return a pointer to a liveCellRef with the given name.
|
||||
/// \param activeOnly do non search inactive cells.
|
||||
/// \param activeOnly do not search inactive cells.
|
||||
|
||||
Ptr searchPtrViaActorId (int actorId) override;
|
||||
///< Search is limited to the active cells.
|
||||
|
|
|
@ -91,10 +91,9 @@ add_component_dir (misc
|
|||
gcd constants utf8stream stringops resourcehelpers rng messageformatparser weakcache
|
||||
)
|
||||
|
||||
add_component_dir(debug
|
||||
debugging debuglog
|
||||
)
|
||||
|
||||
add_component_dir (debug
|
||||
debugging debuglog gldebug
|
||||
)
|
||||
|
||||
IF(NOT WIN32 AND NOT APPLE)
|
||||
add_definitions(-DGLOBAL_DATA_PATH="${GLOBAL_DATA_PATH}")
|
||||
|
|
164
components/debug/gldebug.cpp
Normal file
164
components/debug/gldebug.cpp
Normal file
|
@ -0,0 +1,164 @@
|
|||
// This file is based heavily on code from https://github.com/ThermalPixel/osgdemos/blob/master/osgdebug/EnableGLDebugOperation.cpp
|
||||
// The original licence is included below:
|
||||
/*
|
||||
Copyright (c) 2014, Andreas Klein
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
The views and conclusions contained in the software and documentation are those
|
||||
of the authors and should not be interpreted as representing official policies,
|
||||
either expressed or implied, of the FreeBSD Project.
|
||||
*/
|
||||
|
||||
#include "gldebug.hpp"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
// OpenGL constants not provided by OSG:
|
||||
#include <SDL_opengl_glext.h>
|
||||
|
||||
void debugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam)
|
||||
{
|
||||
#ifdef GL_DEBUG_OUTPUT
|
||||
std::string srcStr;
|
||||
switch (source)
|
||||
{
|
||||
case GL_DEBUG_SOURCE_API:
|
||||
srcStr = "API";
|
||||
break;
|
||||
case GL_DEBUG_SOURCE_WINDOW_SYSTEM:
|
||||
srcStr = "WINDOW_SYSTEM";
|
||||
break;
|
||||
case GL_DEBUG_SOURCE_SHADER_COMPILER:
|
||||
srcStr = "SHADER_COMPILER";
|
||||
break;
|
||||
case GL_DEBUG_SOURCE_THIRD_PARTY:
|
||||
srcStr = "THIRD_PARTY";
|
||||
break;
|
||||
case GL_DEBUG_SOURCE_APPLICATION:
|
||||
srcStr = "APPLICATION";
|
||||
break;
|
||||
case GL_DEBUG_SOURCE_OTHER:
|
||||
srcStr = "OTHER";
|
||||
break;
|
||||
default:
|
||||
srcStr = "UNDEFINED";
|
||||
break;
|
||||
}
|
||||
|
||||
std::string typeStr;
|
||||
|
||||
Debug::Level logSeverity = Debug::Warning;
|
||||
switch (type)
|
||||
{
|
||||
case GL_DEBUG_TYPE_ERROR:
|
||||
typeStr = "ERROR";
|
||||
logSeverity = Debug::Error;
|
||||
break;
|
||||
case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR:
|
||||
typeStr = "DEPRECATED_BEHAVIOR";
|
||||
break;
|
||||
case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:
|
||||
typeStr = "UNDEFINED_BEHAVIOR";
|
||||
break;
|
||||
case GL_DEBUG_TYPE_PORTABILITY:
|
||||
typeStr = "PORTABILITY";
|
||||
break;
|
||||
case GL_DEBUG_TYPE_PERFORMANCE:
|
||||
typeStr = "PERFORMANCE";
|
||||
break;
|
||||
case GL_DEBUG_TYPE_OTHER:
|
||||
typeStr = "OTHER";
|
||||
break;
|
||||
default:
|
||||
typeStr = "UNDEFINED";
|
||||
break;
|
||||
}
|
||||
|
||||
Log(logSeverity) << "OpenGL " << typeStr << " [" << srcStr << "]: " << message;
|
||||
#endif
|
||||
}
|
||||
|
||||
void enableGLDebugExtension(unsigned int contextID)
|
||||
{
|
||||
#ifdef GL_DEBUG_OUTPUT
|
||||
typedef void (GL_APIENTRY *DEBUGPROC)(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam);
|
||||
typedef void (GL_APIENTRY *GLDebugMessageControlFunction)(GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled);
|
||||
typedef void (GL_APIENTRY *GLDebugMessageCallbackFunction)(DEBUGPROC, const void* userParam);
|
||||
|
||||
GLDebugMessageControlFunction glDebugMessageControl = nullptr;
|
||||
GLDebugMessageCallbackFunction glDebugMessageCallback = nullptr;
|
||||
|
||||
if (osg::isGLExtensionSupported(contextID, "GL_KHR_debug"))
|
||||
{
|
||||
osg::setGLExtensionFuncPtr(glDebugMessageCallback, "glDebugMessageCallback");
|
||||
osg::setGLExtensionFuncPtr(glDebugMessageControl, "glDebugMessageControl");
|
||||
}
|
||||
else if (osg::isGLExtensionSupported(contextID, "GL_ARB_debug_output"))
|
||||
{
|
||||
osg::setGLExtensionFuncPtr(glDebugMessageCallback, "glDebugMessageCallbackARB");
|
||||
osg::setGLExtensionFuncPtr(glDebugMessageControl, "glDebugMessageControlARB");
|
||||
}
|
||||
else if (osg::isGLExtensionSupported(contextID, "GL_AMD_debug_output"))
|
||||
{
|
||||
osg::setGLExtensionFuncPtr(glDebugMessageCallback, "glDebugMessageCallbackAMD");
|
||||
osg::setGLExtensionFuncPtr(glDebugMessageControl, "glDebugMessageControlAMD");
|
||||
}
|
||||
|
||||
if (glDebugMessageCallback && glDebugMessageControl)
|
||||
{
|
||||
glEnable(GL_DEBUG_OUTPUT);
|
||||
glDebugMessageControl(GL_DONT_CARE, GL_DEBUG_TYPE_ERROR, GL_DEBUG_SEVERITY_MEDIUM, 0, nullptr, true);
|
||||
glDebugMessageCallback(debugCallback, nullptr);
|
||||
|
||||
Log(Debug::Info) << "OpenGL debug callback attached.";
|
||||
}
|
||||
else
|
||||
#endif
|
||||
Log(Debug::Error) << "Unable to attach OpenGL debug callback.";
|
||||
}
|
||||
|
||||
Debug::EnableGLDebugOperation::EnableGLDebugOperation() : osg::GraphicsOperation("EnableGLDebugOperation", false)
|
||||
{
|
||||
}
|
||||
|
||||
void Debug::EnableGLDebugOperation::operator()(osg::GraphicsContext* graphicsContext)
|
||||
{
|
||||
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mMutex);
|
||||
|
||||
unsigned int contextID = graphicsContext->getState()->getContextID();
|
||||
enableGLDebugExtension(contextID);
|
||||
}
|
||||
|
||||
bool Debug::shouldDebugOpenGL()
|
||||
{
|
||||
const char* env = std::getenv("OPENMW_DEBUG_OPENGL");
|
||||
if (!env)
|
||||
return false;
|
||||
std::string str(env);
|
||||
if (str.length() == 0)
|
||||
return true;
|
||||
|
||||
return str.find("OFF") == std::string::npos && str.find("0") == std::string::npos && str.find("NO") == std::string::npos;
|
||||
}
|
21
components/debug/gldebug.hpp
Normal file
21
components/debug/gldebug.hpp
Normal file
|
@ -0,0 +1,21 @@
|
|||
#ifndef OPENMW_COMPONENTS_DEBUG_GLDEBUG_H
|
||||
#define OPENMW_COMPONENTS_DEBUG_GLDEBUG_H
|
||||
|
||||
#include <osgViewer/ViewerEventHandlers>
|
||||
|
||||
namespace Debug
|
||||
{
|
||||
class EnableGLDebugOperation : public osg::GraphicsOperation
|
||||
{
|
||||
public:
|
||||
EnableGLDebugOperation();
|
||||
|
||||
virtual void operator()(osg::GraphicsContext* graphicsContext);
|
||||
|
||||
private:
|
||||
OpenThreads::Mutex mMutex;
|
||||
};
|
||||
|
||||
bool shouldDebugOpenGL();
|
||||
}
|
||||
#endif
|
|
@ -24,6 +24,7 @@ namespace ESM
|
|||
esm.writeHNT ("ARG_", effectIt->mArg);
|
||||
esm.writeHNT ("MAGN", effectIt->mMagnitude);
|
||||
esm.writeHNT ("DURA", effectIt->mDuration);
|
||||
esm.writeHNT ("EIND", effectIt->mEffectIndex);
|
||||
esm.writeHNT ("LEFT", effectIt->mTimeLeft);
|
||||
}
|
||||
}
|
||||
|
@ -53,6 +54,8 @@ namespace ESM
|
|||
esm.getHNOT(effect.mArg, "ARG_");
|
||||
esm.getHNT (effect.mMagnitude, "MAGN");
|
||||
esm.getHNT (effect.mDuration, "DURA");
|
||||
effect.mEffectIndex = -1;
|
||||
esm.getHNOT (effect.mEffectIndex, "EIND");
|
||||
if (format < 9)
|
||||
effect.mTimeLeft = effect.mDuration;
|
||||
else
|
||||
|
|
|
@ -22,6 +22,7 @@ namespace ESM
|
|||
int mArg; // skill or attribute
|
||||
float mDuration;
|
||||
float mTimeLeft;
|
||||
int mEffectIndex;
|
||||
};
|
||||
|
||||
// format 0, saved games only
|
||||
|
|
|
@ -115,9 +115,11 @@ void ESM::CreatureStats::load (ESMReader &esm)
|
|||
int magicEffect;
|
||||
esm.getHT(magicEffect);
|
||||
std::string source = esm.getHNOString("SOUR");
|
||||
int effectIndex = -1;
|
||||
esm.getHNOT (effectIndex, "EIND");
|
||||
int actorId;
|
||||
esm.getHNT (actorId, "ACID");
|
||||
mSummonedCreatureMap[std::make_pair(magicEffect, source)] = actorId;
|
||||
mSummonedCreatureMap[SummonKey(magicEffect, source, effectIndex)] = actorId;
|
||||
}
|
||||
|
||||
while (esm.isNextSub("GRAV"))
|
||||
|
@ -212,16 +214,19 @@ void ESM::CreatureStats::save (ESMWriter &esm) const
|
|||
mAiSequence.save(esm);
|
||||
mMagicEffects.save(esm);
|
||||
|
||||
for (std::map<std::pair<int, std::string>, int>::const_iterator it = mSummonedCreatureMap.begin(); it != mSummonedCreatureMap.end(); ++it)
|
||||
for (const auto& summon : mSummonedCreatureMap)
|
||||
{
|
||||
esm.writeHNT ("SUMM", it->first.first);
|
||||
esm.writeHNString ("SOUR", it->first.second);
|
||||
esm.writeHNT ("ACID", it->second);
|
||||
esm.writeHNT ("SUMM", summon.first.mEffectId);
|
||||
esm.writeHNString ("SOUR", summon.first.mSourceId);
|
||||
int effectIndex = summon.first.mEffectIndex;
|
||||
if (effectIndex != -1)
|
||||
esm.writeHNT ("EIND", effectIndex);
|
||||
esm.writeHNT ("ACID", summon.second);
|
||||
}
|
||||
|
||||
for (std::vector<int>::const_iterator it = mSummonGraveyard.begin(); it != mSummonGraveyard.end(); ++it)
|
||||
for (int key : mSummonGraveyard)
|
||||
{
|
||||
esm.writeHNT ("GRAV", *it);
|
||||
esm.writeHNT ("GRAV", key);
|
||||
}
|
||||
|
||||
esm.writeHNT("AISE", mHasAiSettings);
|
||||
|
@ -231,11 +236,11 @@ void ESM::CreatureStats::save (ESMWriter &esm) const
|
|||
mAiSettings[i].save(esm);
|
||||
}
|
||||
|
||||
for (std::map<std::string, CorprusStats>::const_iterator it = mCorprusSpells.begin(); it != mCorprusSpells.end(); ++it)
|
||||
for (const auto& corprusSpell : mCorprusSpells)
|
||||
{
|
||||
esm.writeHNString("CORP", it->first);
|
||||
esm.writeHNString("CORP", corprusSpell.first);
|
||||
|
||||
const CorprusStats & stats = it->second;
|
||||
const CorprusStats & stats = corprusSpell.second;
|
||||
esm.writeHNT("WORS", stats.mWorsenings);
|
||||
esm.writeHNT("TIME", stats.mNextWorsening);
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ namespace ESM
|
|||
bool mHasAiSettings;
|
||||
StatState<int> mAiSettings[4];
|
||||
|
||||
std::map<std::pair<int, std::string>, int> mSummonedCreatureMap;
|
||||
std::map<SummonKey, int> mSummonedCreatureMap;
|
||||
std::vector<int> mSummonGraveyard;
|
||||
|
||||
ESM::TimeStamp mTradeTime;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#define COMPONENTS_ESM_MAGICEFFECTS_H
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
|
@ -18,6 +19,41 @@ namespace ESM
|
|||
void save (ESMWriter &esm) const;
|
||||
};
|
||||
|
||||
struct SummonKey
|
||||
{
|
||||
SummonKey(int effectId, const std::string& sourceId, int index)
|
||||
{
|
||||
mEffectId = effectId;
|
||||
mSourceId = sourceId;
|
||||
mEffectIndex = index;
|
||||
}
|
||||
|
||||
bool operator==(const SummonKey &other) const
|
||||
{
|
||||
return mEffectId == other.mEffectId &&
|
||||
mSourceId == other.mSourceId &&
|
||||
mEffectIndex == other.mEffectIndex;
|
||||
}
|
||||
|
||||
bool operator<(const SummonKey &other) const
|
||||
{
|
||||
if (mEffectId < other.mEffectId)
|
||||
return true;
|
||||
if (mEffectId > other.mEffectId)
|
||||
return false;
|
||||
|
||||
if (mSourceId < other.mSourceId)
|
||||
return true;
|
||||
if (mSourceId > other.mSourceId)
|
||||
return false;
|
||||
|
||||
return mEffectIndex < other.mEffectIndex;
|
||||
}
|
||||
|
||||
int mEffectId;
|
||||
std::string mSourceId;
|
||||
int mEffectIndex;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#include "esmwriter.hpp"
|
||||
|
||||
unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE;
|
||||
int ESM::SavedGame::sCurrentFormat = 13;
|
||||
int ESM::SavedGame::sCurrentFormat = 14;
|
||||
|
||||
void ESM::SavedGame::load (ESMReader &esm)
|
||||
{
|
||||
|
|
|
@ -449,7 +449,7 @@ void MaterialColorController::apply(osg::StateSet *stateset, osg::NodeVisitor *n
|
|||
}
|
||||
|
||||
FlipController::FlipController(const Nif::NiFlipController *ctrl, const std::vector<osg::ref_ptr<osg::Texture2D> >& textures)
|
||||
: mTexSlot(ctrl->mTexSlot)
|
||||
: mTexSlot(0) // always affects diffuse
|
||||
, mDelta(ctrl->mDelta)
|
||||
, mTextures(textures)
|
||||
{
|
||||
|
|
|
@ -799,22 +799,23 @@ namespace NifOsg
|
|||
{
|
||||
const Nif::NiFlipController* flipctrl = static_cast<const Nif::NiFlipController*>(ctrl.getPtr());
|
||||
std::vector<osg::ref_ptr<osg::Texture2D> > textures;
|
||||
|
||||
// inherit wrap settings from the target slot
|
||||
osg::Texture2D* inherit = dynamic_cast<osg::Texture2D*>(stateset->getTextureAttribute(0, osg::StateAttribute::TEXTURE));
|
||||
osg::Texture2D::WrapMode wrapS = osg::Texture2D::REPEAT;
|
||||
osg::Texture2D::WrapMode wrapT = osg::Texture2D::REPEAT;
|
||||
if (inherit)
|
||||
{
|
||||
wrapS = inherit->getWrap(osg::Texture2D::WRAP_S);
|
||||
wrapT = inherit->getWrap(osg::Texture2D::WRAP_T);
|
||||
}
|
||||
|
||||
for (unsigned int i=0; i<flipctrl->mSources.length(); ++i)
|
||||
{
|
||||
Nif::NiSourceTexturePtr st = flipctrl->mSources[i];
|
||||
if (st.empty())
|
||||
continue;
|
||||
|
||||
// inherit wrap settings from the target slot
|
||||
osg::Texture2D* inherit = dynamic_cast<osg::Texture2D*>(stateset->getTextureAttribute(flipctrl->mTexSlot, osg::StateAttribute::TEXTURE));
|
||||
osg::Texture2D::WrapMode wrapS = osg::Texture2D::CLAMP_TO_EDGE;
|
||||
osg::Texture2D::WrapMode wrapT = osg::Texture2D::CLAMP_TO_EDGE;
|
||||
if (inherit)
|
||||
{
|
||||
wrapS = inherit->getWrap(osg::Texture2D::WRAP_S);
|
||||
wrapT = inherit->getWrap(osg::Texture2D::WRAP_T);
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Image> image (handleSourceTexture(st.getPtr(), imageManager));
|
||||
osg::ref_ptr<osg::Texture2D> texture (new osg::Texture2D(image));
|
||||
if (image)
|
||||
|
@ -1451,7 +1452,7 @@ namespace NifOsg
|
|||
// If this loop is changed such that the base texture isn't guaranteed to end up in texture unit 0, the shadow casting shader will need to be updated accordingly.
|
||||
for (size_t i=0; i<texprop->textures.size(); ++i)
|
||||
{
|
||||
if (texprop->textures[i].inUse)
|
||||
if (texprop->textures[i].inUse || (i == Nif::NiTexturingProperty::BaseTexture && !texprop->controller.empty()))
|
||||
{
|
||||
switch(i)
|
||||
{
|
||||
|
@ -1477,32 +1478,46 @@ namespace NifOsg
|
|||
}
|
||||
}
|
||||
|
||||
const Nif::NiTexturingProperty::Texture& tex = texprop->textures[i];
|
||||
if(tex.texture.empty() && texprop->controller.empty())
|
||||
{
|
||||
if (i == 0)
|
||||
Log(Debug::Warning) << "Base texture is in use but empty on shape \"" << nodeName << "\" in " << mFilename;
|
||||
continue;
|
||||
}
|
||||
|
||||
unsigned int uvSet = 0;
|
||||
// create a new texture, will later attempt to share using the SharedStateManager
|
||||
osg::ref_ptr<osg::Texture2D> texture2d;
|
||||
if (!tex.texture.empty())
|
||||
if (texprop->textures[i].inUse)
|
||||
{
|
||||
const Nif::NiSourceTexture *st = tex.texture.getPtr();
|
||||
osg::ref_ptr<osg::Image> image = handleSourceTexture(st, imageManager);
|
||||
texture2d = new osg::Texture2D(image);
|
||||
if (image)
|
||||
texture2d->setTextureSize(image->s(), image->t());
|
||||
const Nif::NiTexturingProperty::Texture& tex = texprop->textures[i];
|
||||
if(tex.texture.empty() && texprop->controller.empty())
|
||||
{
|
||||
if (i == 0)
|
||||
Log(Debug::Warning) << "Base texture is in use but empty on shape \"" << nodeName << "\" in " << mFilename;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!tex.texture.empty())
|
||||
{
|
||||
const Nif::NiSourceTexture *st = tex.texture.getPtr();
|
||||
osg::ref_ptr<osg::Image> image = handleSourceTexture(st, imageManager);
|
||||
texture2d = new osg::Texture2D(image);
|
||||
if (image)
|
||||
texture2d->setTextureSize(image->s(), image->t());
|
||||
}
|
||||
else
|
||||
texture2d = new osg::Texture2D;
|
||||
|
||||
bool wrapT = tex.clamp & 0x1;
|
||||
bool wrapS = (tex.clamp >> 1) & 0x1;
|
||||
|
||||
texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
|
||||
texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
|
||||
|
||||
uvSet = tex.uvSet;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Texture only comes from NiFlipController, so tex is ignored, set defaults
|
||||
texture2d = new osg::Texture2D;
|
||||
|
||||
bool wrapT = tex.clamp & 0x1;
|
||||
bool wrapS = (tex.clamp >> 1) & 0x1;
|
||||
|
||||
texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
|
||||
texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
|
||||
texture2d->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
|
||||
texture2d->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
|
||||
uvSet = 0;
|
||||
}
|
||||
|
||||
unsigned int texUnit = boundTextures.size();
|
||||
|
||||
|
@ -1590,7 +1605,7 @@ namespace NifOsg
|
|||
break;
|
||||
}
|
||||
|
||||
boundTextures.push_back(tex.uvSet);
|
||||
boundTextures.push_back(uvSet);
|
||||
}
|
||||
}
|
||||
handleTextureControllers(texprop, composite, imageManager, stateset, animflags);
|
||||
|
|
|
@ -115,6 +115,29 @@ namespace Resource
|
|||
return mWarningImage;
|
||||
}
|
||||
|
||||
bool killAlpha = false;
|
||||
if (reader->supportedExtensions().count("tga"))
|
||||
{
|
||||
// Morrowind ignores the alpha channel of 16bpp TGA files even when the header says not to
|
||||
unsigned char header[18];
|
||||
stream->read((char*)header, 18);
|
||||
if (stream->gcount() != 18)
|
||||
{
|
||||
Log(Debug::Error) << "Error loading " << filename << ": couldn't read TGA header";
|
||||
mCache->addEntryToObjectCache(normalized, mWarningImage);
|
||||
return mWarningImage;
|
||||
}
|
||||
int type = header[2];
|
||||
int depth;
|
||||
if (type == 1 || type == 9)
|
||||
depth = header[7];
|
||||
else
|
||||
depth = header[16];
|
||||
int alphaBPP = header[17] & 0x0F;
|
||||
killAlpha = depth == 16 && alphaBPP == 1;
|
||||
stream->seekg(0);
|
||||
}
|
||||
|
||||
osgDB::ReaderWriter::ReadResult result = reader->readImage(*stream, mOptions);
|
||||
if (!result.success())
|
||||
{
|
||||
|
@ -149,6 +172,18 @@ namespace Resource
|
|||
image = newImage;
|
||||
}
|
||||
}
|
||||
else if (killAlpha)
|
||||
{
|
||||
osg::ref_ptr<osg::Image> newImage = new osg::Image;
|
||||
newImage->setFileName(image->getFileName());
|
||||
newImage->allocateImage(image->s(), image->t(), image->r(), GL_RGB, GL_UNSIGNED_BYTE);
|
||||
// OSG just won't write the alpha as there's nowhere to put it.
|
||||
for (int s = 0; s < image->s(); ++s)
|
||||
for (int t = 0; t < image->t(); ++t)
|
||||
for (int r = 0; r < image->r(); ++r)
|
||||
newImage->setColor(image->getColor(s, t, r), s, t, r);
|
||||
image = newImage;
|
||||
}
|
||||
|
||||
mCache->addEntryToObjectCache(normalized, image);
|
||||
return image;
|
||||
|
|
|
@ -118,6 +118,29 @@ void GraphicsWindowSDL2::init()
|
|||
|
||||
setSwapInterval(_traits->vsync);
|
||||
|
||||
// Update traits with what we've actually been given
|
||||
// Use intermediate to avoid signed/unsigned mismatch
|
||||
int intermediateLocation;
|
||||
SDL_GL_GetAttribute(SDL_GL_RED_SIZE, &intermediateLocation);
|
||||
_traits->red = intermediateLocation;
|
||||
SDL_GL_GetAttribute(SDL_GL_GREEN_SIZE, &intermediateLocation);
|
||||
_traits->green = intermediateLocation;
|
||||
SDL_GL_GetAttribute(SDL_GL_BLUE_SIZE, &intermediateLocation);
|
||||
_traits->blue = intermediateLocation;
|
||||
SDL_GL_GetAttribute(SDL_GL_ALPHA_SIZE, &intermediateLocation);
|
||||
_traits->alpha = intermediateLocation;
|
||||
SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &intermediateLocation);
|
||||
_traits->depth = intermediateLocation;
|
||||
SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &intermediateLocation);
|
||||
_traits->stencil = intermediateLocation;
|
||||
|
||||
SDL_GL_GetAttribute(SDL_GL_DOUBLEBUFFER, &intermediateLocation);
|
||||
_traits->doubleBuffer = intermediateLocation;
|
||||
SDL_GL_GetAttribute(SDL_GL_MULTISAMPLEBUFFERS, &intermediateLocation);
|
||||
_traits->sampleBuffers = intermediateLocation;
|
||||
SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, &intermediateLocation);
|
||||
_traits->samples = intermediateLocation;
|
||||
|
||||
SDL_GL_MakeCurrent(oldWin, oldCtx);
|
||||
|
||||
mValid = true;
|
||||
|
|
|
@ -61,45 +61,39 @@ namespace Shader
|
|||
return true;
|
||||
}
|
||||
|
||||
bool parseIncludes(boost::filesystem::path shaderPath, std::string& source, const std::string& templateName)
|
||||
// Recursively replaces include statements with the actual source of the included files.
|
||||
// Adjusts #line statements accordingly and detects cyclic includes.
|
||||
// includingFiles is the set of files that include this file directly or indirectly, and is intentionally not a reference to allow automatic cleanup.
|
||||
static bool parseIncludes(boost::filesystem::path shaderPath, std::string& source, const std::string& fileName, int& fileNumber, std::set<boost::filesystem::path> includingFiles)
|
||||
{
|
||||
// An include is cyclic if it is being included by itself
|
||||
if (includingFiles.insert(shaderPath/fileName).second == false)
|
||||
{
|
||||
Log(Debug::Error) << "Shader " << fileName << " error: Detected cyclic #includes";
|
||||
return false;
|
||||
}
|
||||
|
||||
Misc::StringUtils::replaceAll(source, "\r\n", "\n");
|
||||
|
||||
std::set<boost::filesystem::path> includedFiles;
|
||||
size_t foundPos = 0;
|
||||
int fileNumber = 1;
|
||||
while ((foundPos = source.find("#include")) != std::string::npos)
|
||||
{
|
||||
size_t start = source.find('"', foundPos);
|
||||
if (start == std::string::npos || start == source.size()-1)
|
||||
if (start == std::string::npos || start == source.size() - 1)
|
||||
{
|
||||
Log(Debug::Error) << "Shader " << templateName << " error: Invalid #include";
|
||||
Log(Debug::Error) << "Shader " << fileName << " error: Invalid #include";
|
||||
return false;
|
||||
}
|
||||
size_t end = source.find('"', start+1);
|
||||
size_t end = source.find('"', start + 1);
|
||||
if (end == std::string::npos)
|
||||
{
|
||||
Log(Debug::Error) << "Shader " << templateName << " error: Invalid #include";
|
||||
Log(Debug::Error) << "Shader " << fileName << " error: Invalid #include";
|
||||
return false;
|
||||
}
|
||||
std::string includeFilename = source.substr(start+1, end-(start+1));
|
||||
std::string includeFilename = source.substr(start + 1, end - (start + 1));
|
||||
boost::filesystem::path includePath = shaderPath / includeFilename;
|
||||
boost::filesystem::ifstream includeFstream;
|
||||
includeFstream.open(includePath);
|
||||
if (includeFstream.fail())
|
||||
{
|
||||
Log(Debug::Error) << "Shader " << templateName << " error: Failed to open include " << includePath.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
std::stringstream buffer;
|
||||
buffer << includeFstream.rdbuf();
|
||||
std::string stringRepresentation = buffer.str();
|
||||
addLineDirectivesAfterConditionalBlocks(stringRepresentation);
|
||||
|
||||
// insert #line directives so we get correct line numbers in compiler errors
|
||||
int includedFileNumber = fileNumber++;
|
||||
|
||||
// Determine the line number that will be used for the #line directive following the included source
|
||||
size_t lineDirectivePosition = source.rfind("#line", foundPos);
|
||||
int lineNumber;
|
||||
if (lineDirectivePosition != std::string::npos)
|
||||
|
@ -116,16 +110,30 @@ namespace Shader
|
|||
}
|
||||
lineNumber += std::count(source.begin() + lineDirectivePosition, source.begin() + foundPos, '\n');
|
||||
|
||||
// Include the file recursively
|
||||
boost::filesystem::ifstream includeFstream;
|
||||
includeFstream.open(includePath);
|
||||
if (includeFstream.fail())
|
||||
{
|
||||
Log(Debug::Error) << "Shader " << fileName << " error: Failed to open include " << includePath.string();
|
||||
return false;
|
||||
}
|
||||
int includedFileNumber = fileNumber++;
|
||||
|
||||
std::stringstream buffer;
|
||||
buffer << includeFstream.rdbuf();
|
||||
std::string stringRepresentation = buffer.str();
|
||||
if (!addLineDirectivesAfterConditionalBlocks(stringRepresentation)
|
||||
|| !parseIncludes(shaderPath, stringRepresentation, includeFilename, fileNumber, includingFiles))
|
||||
{
|
||||
Log(Debug::Error) << "In file included from " << fileName << "." << lineNumber;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::stringstream toInsert;
|
||||
toInsert << "#line 0 " << includedFileNumber << "\n" << stringRepresentation << "\n#line " << lineNumber << " 0\n";
|
||||
|
||||
source.replace(foundPos, (end-foundPos+1), toInsert.str());
|
||||
|
||||
if (includedFiles.insert(includePath).second == false)
|
||||
{
|
||||
Log(Debug::Error) << "Shader " << templateName << " error: Detected cyclic #includes";
|
||||
return false;
|
||||
}
|
||||
source.replace(foundPos, (end - foundPos + 1), toInsert.str());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -282,21 +290,22 @@ namespace Shader
|
|||
TemplateMap::iterator templateIt = mShaderTemplates.find(templateName);
|
||||
if (templateIt == mShaderTemplates.end())
|
||||
{
|
||||
boost::filesystem::path p = (boost::filesystem::path(mPath) / templateName);
|
||||
boost::filesystem::path path = (boost::filesystem::path(mPath) / templateName);
|
||||
boost::filesystem::ifstream stream;
|
||||
stream.open(p);
|
||||
stream.open(path);
|
||||
if (stream.fail())
|
||||
{
|
||||
Log(Debug::Error) << "Failed to open " << p.string();
|
||||
Log(Debug::Error) << "Failed to open " << path.string();
|
||||
return nullptr;
|
||||
}
|
||||
std::stringstream buffer;
|
||||
buffer << stream.rdbuf();
|
||||
|
||||
// parse includes
|
||||
int fileNumber = 1;
|
||||
std::string source = buffer.str();
|
||||
if (!addLineDirectivesAfterConditionalBlocks(source)
|
||||
|| !parseIncludes(boost::filesystem::path(mPath), source, templateName))
|
||||
|| !parseIncludes(boost::filesystem::path(mPath), source, templateName, fileNumber, {}))
|
||||
return nullptr;
|
||||
|
||||
templateIt = mShaderTemplates.insert(std::make_pair(templateName, source)).first;
|
||||
|
|
|
@ -331,8 +331,43 @@ If enabled then the character turns lower body to the direction of movement. Upp
|
|||
|
||||
This setting can be controlled in Advanced tab of the launcher.
|
||||
|
||||
smooth movement
|
||||
---------------
|
||||
|
||||
:Type: boolean
|
||||
:Range: True/False
|
||||
:Default: False
|
||||
|
||||
Makes NPCs and player movement more smooth.
|
||||
|
||||
Recommended to use with "turn to movement direction" enabled.
|
||||
|
||||
This setting can be controlled in Advanced tab of the launcher.
|
||||
|
||||
NPCs avoid collisions
|
||||
---------------------
|
||||
|
||||
:Type: boolean
|
||||
:Range: True/False
|
||||
:Default: False
|
||||
|
||||
If enabled NPCs apply evasion maneuver to avoid collisions with others.
|
||||
|
||||
This setting can be controlled in Advanced tab of the launcher.
|
||||
|
||||
NPCs give way
|
||||
-------------
|
||||
|
||||
:Type: boolean
|
||||
:Range: True/False
|
||||
:Default: True
|
||||
|
||||
Standing NPCs give way to moving ones. Works only if 'NPCs avoid collisions' is enabled.
|
||||
|
||||
This setting can only be configured by editing the settings configuration file.
|
||||
|
||||
swim upward correction
|
||||
----------------
|
||||
----------------------
|
||||
|
||||
:Type: boolean
|
||||
:Range: True/False
|
||||
|
|
5
extern/recastnavigation/CMakeLists.txt
vendored
5
extern/recastnavigation/CMakeLists.txt
vendored
|
@ -1,5 +1,10 @@
|
|||
cmake_minimum_required(VERSION 3.0)
|
||||
|
||||
# for link time optimization, remove if cmake version is >= 3.9
|
||||
if(POLICY CMP0069)
|
||||
cmake_policy(SET CMP0069 NEW)
|
||||
endif()
|
||||
|
||||
project(RecastNavigation)
|
||||
|
||||
# lib versions
|
||||
|
|
|
@ -325,6 +325,15 @@ uncapped damage fatigue = false
|
|||
# Turn lower body to movement direction. 'true' makes diagonal movement more realistic.
|
||||
turn to movement direction = false
|
||||
|
||||
# Makes all movements of NPCs and player more smooth.
|
||||
smooth movement = false
|
||||
|
||||
# All actors avoid collisions with other actors.
|
||||
NPCs avoid collisions = false
|
||||
|
||||
# Give way to moving actors when idle. Requires 'NPCs avoid collisions' to be enabled.
|
||||
NPCs give way = true
|
||||
|
||||
# Makes player swim a bit upward from the line of sight.
|
||||
swim upward correction = false
|
||||
|
||||
|
|
|
@ -43,6 +43,16 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QCheckBox" name="avoidCollisionsCheckBox">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>If enabled NPCs apply evasion maneuver to avoid collisions with others.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>NPCs avoid collisions</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QCheckBox" name="enableNavigatorCheckBox">
|
||||
<property name="toolTip">
|
||||
|
@ -233,6 +243,16 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QCheckBox" name="smoothMovementCheckBox">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Smooth movement</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="distantLandCheckBox">
|
||||
<property name="toolTip">
|
||||
|
@ -283,7 +303,7 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<item row="6" column="0">
|
||||
<widget class="QCheckBox" name="animSourcesCheckBox">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Load per-group KF-files and skeleton files from Animations folder</p></body></html></string>
|
||||
|
|
Loading…
Reference in a new issue