1
0
Fork 1
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:
David Cernat 2020-09-30 13:32:15 +02:00
commit 0eedf8fd9f
77 changed files with 1372 additions and 476 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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");

View file

@ -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()

View file

@ -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;

View file

@ -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();

View file

@ -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);

View file

@ -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();

View file

@ -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;
}
}
}

View file

@ -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;

View file

@ -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);
};

View file

@ -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);

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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);

View file

@ -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();

View file

@ -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;
};
}

View file

@ -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))

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -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);

View file

@ -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;

View file

@ -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;

View file

@ -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);

View file

@ -196,6 +196,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener
float mTimeUntilWake;
bool mIsMovingBackward;
osg::Vec2f mSmoothedSpeed;
void setAttackTypeBasedOnMovement();

View file

@ -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;

View file

@ -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
/*

View file

@ -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

View file

@ -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.

View file

@ -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;
};

View file

@ -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();
}

View file

@ -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);

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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

View file

@ -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())
{
/*

View file

@ -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);
}
}

View file

@ -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;
}

View file

@ -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++);

View file

@ -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;
};
}

View file

@ -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);
}

View file

@ -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

View file

@ -208,6 +208,12 @@ bool CreatureWeaponAnimation::isArrowAttached() const
return mAmmunition != nullptr;
}
void CreatureWeaponAnimation::detachArrow()
{
WeaponAnimation::detachArrow(mPtr);
updateQuiver();
}
void CreatureWeaponAnimation::attachArrow()
{
WeaponAnimation::attachArrow(mPtr);

View file

@ -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();

View file

@ -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);

View file

@ -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();

View file

@ -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];
}

View file

@ -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;

View file

@ -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");

View file

@ -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);

View file

@ -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);

View file

@ -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);
}
};

View file

@ -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);

View file

@ -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();

View file

@ -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)
{

View file

@ -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.

View file

@ -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}")

View 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;
}

View 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

View file

@ -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

View file

@ -22,6 +22,7 @@ namespace ESM
int mArg; // skill or attribute
float mDuration;
float mTimeLeft;
int mEffectIndex;
};
// format 0, saved games only

View file

@ -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);
}

View file

@ -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;

View file

@ -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

View file

@ -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)
{

View file

@ -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)
{

View file

@ -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);

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -43,6 +43,16 @@
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QCheckBox" name="avoidCollisionsCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If enabled NPCs apply evasion maneuver to avoid collisions with others.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Load per-group KF-files and skeleton files from Animations folder&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>