Merge pull request #457 from OpenMW/master while resolving conflicts

Conflicts:
	CMakeLists.txt
	apps/openmw/engine.cpp
	apps/openmw/main.cpp
	apps/openmw/mwgui/windowmanagerimp.cpp
	apps/openmw/mwmechanics/character.cpp
	components/CMakeLists.txt
pull/459/head
David Cernat 7 years ago
commit 6cb5ac6e63

@ -34,6 +34,8 @@ MacOS:
tags:
- macos
- xcode
except:
- branches # because our CI VMs are not public, MRs can't use them and timeout
stage: build
allow_failure: true
script:
@ -44,3 +46,24 @@ MacOS:
artifacts:
paths:
- build/OpenMW-*.dmg
Windows:
tags:
- win10
- msvc2017
except:
- branches # because our CI VMs are not public, MRs can't use them and timeout
stage: build
allow_failure: true
script:
# - env # turn on for debugging
- sh %CI_PROJECT_DIR%/CI/before_script.msvc.sh -c Release -p x64 -v 2017 -V
- SET msBuildLocation="C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\msbuild.exe"
- call %msBuildLocation% MSVC2017_64\OpenMW.sln /t:Build /p:Configuration=Release /m:%NUMBER_OF_PROCESSORS%
- 7z a OpenMW_MSVC2017_64_%CI_BUILD_REF_NAME%_%CI_BUILD_ID%.zip %CI_PROJECT_DIR%\MSVC2017_64\Release\
cache:
paths:
- deps
artifacts:
paths:
- "*.zip"

@ -143,6 +143,7 @@ Programmers
Rohit Nirmal
Roman Melnik (Kromgart)
Roman Proskuryakov (kpp)
Roman Siromakha (elsid)
Sandy Carter (bwrsandman)
Scott Howard
scrawl

@ -2,6 +2,7 @@
------
Bug #1990: Sunrise/sunset not set correct
Bug #2131: Lustidrike's spell misses the player every time
Bug #2222: Fatigue's effect on selling price is backwards
Bug #2326: After a bound item expires the last equipped item of that type is not automatically re-equipped
Bug #2455: Creatures attacks degrade armor
@ -11,6 +12,7 @@
Bug #2852: No murder bounty when a player follower commits murder
Bug #2862: [macOS] Can't quit launcher using Command-Q or OpenMW->Quit
Bug #2971: Compiler did not reject lines with naked expressions beginning with x.y
Bug #3249: Fixed revert function not updating views properly
Bug #3374: Touch spells not hitting kwama foragers
Bug #3486: [Mod] NPC Commands does not work
Bug #3591: Angled hit distance too low
@ -22,15 +24,18 @@
Bug #3997: Almalexia doesn't pace
Bug #4036: Weird behaviour of AI packages if package target has non-unique ID
Bug #4047: OpenMW not reporting its version number in MacOS; OpenMW-CS not doing it fully
Bug #4110: Fixed undo / redo menu text losing the assigned shortcuts
Bug #4125: OpenMW logo cropped on bugtracker
Bug #4215: OpenMW shows book text after last EOL tag
Bug #4221: Characters get stuck in V-shaped terrain
Bug #4251: Stationary NPCs do not return to their position after combat
Bug #4274: Pre-0.43 death animations are not forward-compatible with 0.43+
Bug #4286: Scripted animations can be interrupted
Bug #4291: Non-persistent actors that started the game as dead do not play death animations
Bug #4293: Faction members are not aware of faction ownerships in barter
Bug #4307: World cleanup should remove dead bodies only if death animation is finished
Bug #4327: Missing animations during spell/weapon stance switching
Bug #4358: Running animation is interrupted when magic mode is toggled
Bug #4368: Settings window ok button doesn't have key focus by default
Bug #4393: NPCs walk back to where they were after using ResetActors
Bug #4416: Handle exception if we try to play non-music file
@ -47,12 +52,23 @@
Bug #4457: Item without CanCarry flag prevents shield autoequipping in dark areas
Bug #4458: AiWander console command handles idle chances incorrectly
Bug #4459: NotCell dialogue condition doesn't support partial matches
Bug #4460: Script function "Equip" doesn't bypass beast restrictions
Bug #4461: "Open" spell from non-player caster isn't a crime
Bug #4464: OpenMW keeps AiState cached storages even after we cancel AI packages
Bug #4469: Abot Silt Striders Model turn 90 degrees on horizontal
Bug #4474: No fallback when getVampireHead fails
Bug #4475: Scripted animations should not cause movement
Bug #4479: "Game" category on Advanced page is getting too long
Bug #4480: Segfalt in QuickKeysMenu when item no longer in inventory
Bug #4480: Segfault in QuickKeysMenu when item no longer in inventory
Bug #4489: Goodbye doesn't block dialogue hyperlinks
Bug #4490: PositionCell on player gives "Error: tried to add local script twice"
Bug #4491: Training cap based off Base Skill instead of Modified Skill
Bug #4495: Crossbow animations blending is buggy
Bug #4496: SpellTurnLeft and SpellTurnRight animation groups are unused
Bug #4497: File names starting with x or X are not classified as animation
Bug #4503: Cast and ExplodeSpell commands increase alteration skill
Feature #2606: Editor: Implemented (optional) case sensitive global search
Feature #3083: Play animation when NPC is casting spell via script
Feature #3276: Editor: Search- Show number of (remaining) search results and indicate a search without any results
Feature #3641: Editor: Limit FPS in 3d preview window
Feature #4222: 360° screenshots
@ -62,6 +78,8 @@
Feature #4404: Editor: All EnumDelegate fields should have their items sorted alphabetically
Feature #4444: Per-group KF-animation files support
Feature #4466: Editor: Add option to ignore "Base" records when running verifier
Feature #4012: Editor: Write a log file if OpenCS crashes
Task #2490: Don't open command prompt window on Release-mode builds automatically
0.44.0
------

@ -1,4 +1,5 @@
#!/bin/bash
# set -x # turn-on for debugging
MISSINGTOOLS=0
@ -76,7 +77,6 @@ while [ $# -gt 0 ]; do
h )
cat <<EOF
Usage: $0 [-cdehkpuvV]
Options:
-c <Release/Debug>
Set the configuration, can also be set with environment variable CONFIGURATION.
@ -232,10 +232,9 @@ fi
case $VS_VERSION in
15|15.0|2017 )
GENERATOR="Visual Studio 15 2017"
TOOLSET="vc140"
TOOLSET_REAL="vc141"
TOOLSET="vc141"
MSVC_REAL_VER="15"
MSVC_VER="14"
MSVC_VER="14.1"
MSVC_YEAR="2015"
MSVC_DISPLAY_YEAR="2017"
;;
@ -243,9 +242,8 @@ case $VS_VERSION in
14|14.0|2015 )
GENERATOR="Visual Studio 14 2015"
TOOLSET="vc140"
TOOLSET_REAL="vc140"
MSVC_REAL_VER="14"
MSVC_VER="14"
MSVC_VER="14.0"
MSVC_YEAR="2015"
MSVC_DISPLAY_YEAR="2015"
;;
@ -253,9 +251,8 @@ case $VS_VERSION in
12|12.0|2013 )
GENERATOR="Visual Studio 12 2013"
TOOLSET="vc120"
TOOLSET_REAL="vc120"
MSVC_REAL_VER="12"
MSVC_VER="12"
MSVC_VER="12.0"
MSVC_YEAR="2013"
MSVC_DISPLAY_YEAR="2013"
;;
@ -325,9 +322,9 @@ if [ -z $SKIP_DOWNLOAD ]; then
# Boost
if [ -z $APPVEYOR ]; then
download "Boost 1.61.0" \
"https://sourceforge.net/projects/boost/files/boost-binaries/1.61.0/boost_1_61_0-msvc-${MSVC_VER}.0-${BITS}.exe" \
"boost-1.61.0-msvc${MSVC_YEAR}-win${BITS}.exe"
download "Boost 1.67.0" \
"https://sourceforge.net/projects/boost/files/boost-binaries/1.67.0/boost_1_67_0-msvc-${MSVC_VER}-${BITS}.exe" \
"boost-1.67.0-msvc${MSVC_YEAR}-win${BITS}.exe"
fi
# Bullet
@ -365,8 +362,8 @@ if [ -z $SKIP_DOWNLOAD ]; then
QT_SUFFIX=""
fi
download "Qt 5.7.2" \
"https://download.qt.io/official_releases/qt/5.7/5.7.0/qt-opensource-windows-x86-msvc${MSVC_YEAR}${QT_SUFFIX}-5.7.0.exe" \
download "Qt 5.7.0" \
"https://download.qt.io/archive/qt/5.7/5.7.0/qt-opensource-windows-x86-msvc${MSVC_YEAR}${QT_SUFFIX}-5.7.0.exe" \
"qt-5.7.0-msvc${MSVC_YEAR}-win${BITS}.exe" \
"https://www.lysator.liu.se/~ace/OpenMW/deps/qt-5-install.qs" \
"qt-5-install.qs"
@ -403,9 +400,9 @@ echo
# Boost
if [ -z $APPVEYOR ]; then
printf "Boost 1.61.0... "
printf "Boost 1.67.0... "
else
if [ $MSVC_VER -eq 12 ]; then
if [ $MSVC_VER -eq 12.0 ]; then
printf "Boost 1.58.0 AppVeyor... "
else
printf "Boost 1.67.0 AppVeyor... "
@ -417,17 +414,27 @@ fi
BOOST_SDK="$(real_pwd)/Boost"
if [ -d Boost ] && grep "BOOST_VERSION 106100" Boost/boost/version.hpp > /dev/null; then
# Boost's installer is still based on ms-dos API that doesn't support larger than 260 char path names
# We work around this by installing to root of the current working drive and then move it to our deps
# get the current working drive's root, we'll install to that temporarily
CWD_DRIVE_ROOT="$(powershell -command '(get-location).Drive.Root')Boost_temp"
CWD_DRIVE_ROOT_BASH=$(echo "$CWD_DRIVE_ROOT" | sed "s,\\\\,/,g" | sed "s,\(.\):,/\\1,")
if [ -d CWD_DRIVE_ROOT_BASH ]; then
printf "Cannot continue, ${CWD_DRIVE_ROOT_BASH} aka ${CWD_DRIVE_ROOT} already exists. Please remove before re-running. ";
exit 1;
fi
if [ -d ${BOOST_SDK} ] && grep "BOOST_VERSION 106700" Boost/boost/version.hpp > /dev/null; then
printf "Exists. "
elif [ -z $SKIP_EXTRACT ]; then
rm -rf Boost
"${DEPS}/boost-1.61.0-msvc${MSVC_YEAR}-win${BITS}.exe" //dir="$(echo $BOOST_SDK | sed s,/,\\\\,g)" //verysilent
[ -n "$CI" ] && CI_EXTRA_INNO_OPTIONS="//SUPPRESSMSGBOXES //LOG='boost_install.log'"
"${DEPS}/boost-1.67.0-msvc${MSVC_YEAR}-win${BITS}.exe" //DIR="${CWD_DRIVE_ROOT}" //VERYSILENT //NORESTART ${CI_EXTRA_INNO_OPTIONS}
mv "${CWD_DRIVE_ROOT_BASH}" "${BOOST_SDK}"
fi
add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \
-DBOOST_LIBRARYDIR="${BOOST_SDK}/lib${BITS}-msvc-${MSVC_VER}.0"
-DBOOST_LIBRARYDIR="${BOOST_SDK}/lib${BITS}-msvc-${MSVC_VER}"
add_cmake_opts -DBoost_COMPILER="-${TOOLSET}"
echo Done.
else
# Appveyor unstable has all the boost we need already
@ -444,19 +451,17 @@ fi
add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \
-DBOOST_LIBRARYDIR="${BOOST_SDK}/lib${BITS}-msvc-${MSVC_VER}.${LIB_SUFFIX}"
add_cmake_opts -DBoost_COMPILER="-${TOOLSET_REAL}"
add_cmake_opts -DBoost_COMPILER="-${TOOLSET}"
echo Done.
fi
}
cd $DEPS
echo
# Bullet
printf "Bullet 2.86... "
{
cd $DEPS_INSTALL
if [ -d Bullet ]; then
printf -- "Exists. (No version checking) "
elif [ -z $SKIP_EXTRACT ]; then
@ -464,49 +469,38 @@ printf "Bullet 2.86... "
eval 7z x -y "${DEPS}/Bullet-2.86-msvc${MSVC_YEAR}-win${BITS}.7z" $STRIP
mv "Bullet-2.86-msvc${MSVC_YEAR}-win${BITS}" Bullet
fi
export BULLET_ROOT="$(real_pwd)/Bullet"
echo Done.
}
cd $DEPS
echo
# FFmpeg
printf "FFmpeg 3.2.4... "
{
cd $DEPS_INSTALL
if [ -d FFmpeg ] && grep "FFmpeg version: 3.2.4" FFmpeg/README.txt > /dev/null; then
printf "Exists. "
elif [ -z $SKIP_EXTRACT ]; then
rm -rf FFmpeg
eval 7z x -y "${DEPS}/ffmpeg-3.2.4-win${BITS}.zip" $STRIP
eval 7z x -y "${DEPS}/ffmpeg-3.2.4-dev-win${BITS}.zip" $STRIP
mv "ffmpeg-3.2.4-win${BITS}-shared" FFmpeg
cp -r "ffmpeg-3.2.4-win${BITS}-dev/"* FFmpeg/
rm -rf "ffmpeg-3.2.4-win${BITS}-dev"
fi
export FFMPEG_HOME="$(real_pwd)/FFmpeg"
add_runtime_dlls "$(pwd)/FFmpeg/bin/"{avcodec-57,avformat-57,avutil-55,swresample-2,swscale-4}.dll
if [ $BITS -eq 32 ]; then
add_cmake_opts "-DCMAKE_EXE_LINKER_FLAGS=\"/machine:X86 /safeseh:no\""
fi
echo Done.
}
cd $DEPS
echo
# MyGUI
printf "MyGUI 3.2.2... "
{
cd $DEPS_INSTALL
if [ -d MyGUI ] && \
grep "MYGUI_VERSION_MAJOR 3" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null && \
grep "MYGUI_VERSION_MINOR 2" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null && \
@ -518,21 +512,17 @@ printf "MyGUI 3.2.2... "
eval 7z x -y "${DEPS}/MyGUI-3.2.2-msvc${MSVC_YEAR}-win${BITS}.7z" $STRIP
mv "MyGUI-3.2.2-msvc${MSVC_YEAR}-win${BITS}" MyGUI
fi
export MYGUI_HOME="$(real_pwd)/MyGUI"
if [ $CONFIGURATION == "Debug" ]; then
SUFFIX="_d"
else
SUFFIX=""
fi
add_runtime_dlls "$(pwd)/MyGUI/bin/${CONFIGURATION}/MyGUIEngine${SUFFIX}.dll"
echo Done.
}
cd $DEPS
echo
# OpenAL
printf "OpenAL-Soft 1.17.2... "
{
@ -542,24 +532,18 @@ printf "OpenAL-Soft 1.17.2... "
rm -rf openal-soft-1.17.2-bin
eval 7z x -y OpenAL-Soft-1.17.2.zip $STRIP
fi
OPENAL_SDK="$(real_pwd)/openal-soft-1.17.2-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.17.2-bin/bin/WIN${BITS}/soft_oal.dll:OpenAL32.dll"
echo Done.
}
cd $DEPS
echo
# OSG
printf "OSG 3.4.1-scrawl... "
{
cd $DEPS_INSTALL
if [ -d OSG ] && \
grep "OPENSCENEGRAPH_MAJOR_VERSION 3" OSG/include/osg/Version > /dev/null && \
grep "OPENSCENEGRAPH_MINOR_VERSION 4" OSG/include/osg/Version > /dev/null && \
@ -571,28 +555,21 @@ printf "OSG 3.4.1-scrawl... "
eval 7z x -y "${DEPS}/OSG-3.4.1-scrawl-msvc${MSVC_YEAR}-win${BITS}.7z" $STRIP
mv "OSG-3.4.1-scrawl-msvc${MSVC_YEAR}-win${BITS}" OSG
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}${SUFFIX}.dll
add_osg_dlls "$(pwd)/OSG/bin/osgPlugins-3.4.1/osgdb_"{bmp,dds,jpeg,osg,png,tga}${SUFFIX}.dll
add_osg_dlls "$(pwd)/OSG/bin/osgPlugins-3.4.1/osgdb_serializers_osg"{,animation,fx,ga,particle,text,util,viewer}${SUFFIX}.dll
echo Done.
}
cd $DEPS
echo
# Qt
if [ -z $APPVEYOR ]; then
printf "Qt 5.7.0... "
@ -605,71 +582,53 @@ fi
else
SUFFIX=""
fi
if [ -z $APPVEYOR ]; then
cd $DEPS_INSTALL
QT_SDK="$(real_pwd)/Qt/5.7/msvc${MSVC_YEAR}${SUFFIX}"
if [ -d Qt ] && head -n2 Qt/InstallationLog.txt | grep "5.7.0" > /dev/null; then
printf "Exists. "
elif [ -z $SKIP_EXTRACT ]; then
rm -rf Qt
cp "${DEPS}/qt-5-install.qs" qt-install.qs
sed -i "s|INSTALL_DIR|$(real_pwd)/Qt|" qt-install.qs
sed -i "s/qt.VERSION.winBITS_msvcYEAR/qt.57.win${BITS}_msvc${MSVC_YEAR}${SUFFIX}/" qt-install.qs
printf -- "(Installation might take a while) "
"${DEPS}/qt-5.7.0-msvc${MSVC_YEAR}-win${BITS}.exe" --script qt-install.qs --silent
mv qt-install.qs Qt/
echo Done.
printf " Cleaning up extraneous data... "
rm -r "$(real_pwd)/Qt/"{dist,Docs,Examples,Tools,vcredist,components.xml,MaintenanceTool.dat,MaintenanceTool.exe,MaintenanceTool.ini,network.xml,qt-install.qs}
fi
cd $QT_SDK
add_cmake_opts -DDESIRED_QT_VERSION=5 \
-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"
echo Done.
else
QT_SDK="C:/Qt/5.10/msvc${MSVC_DISPLAY_YEAR}${SUFFIX}"
add_cmake_opts -DDESIRED_QT_VERSION=5 \
-DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" \
-DCMAKE_PREFIX_PATH="$QT_SDK"
if [ $CONFIGURATION == "Debug" ]; then
SUFFIX="d"
else
SUFFIX=""
fi
DIR=$(echo "${QT_SDK}" | sed "s,\\\\,/,g" | sed "s,\(.\):,/\\1,")
add_runtime_dlls "${DIR}/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${SUFFIX}.dll
add_qt_platform_dlls "${DIR}/plugins/platforms/qwindows${SUFFIX}.dll"
echo Done.
fi
}
cd $DEPS
echo
# SDL2
printf "SDL 2.0.7... "
{
@ -679,26 +638,18 @@ printf "SDL 2.0.7... "
rm -rf SDL2-2.0.7
eval 7z x -y SDL2-2.0.7.zip $STRIP
fi
export SDL2DIR="$(real_pwd)/SDL2-2.0.7"
add_runtime_dlls "$(pwd)/SDL2-2.0.7/lib/x${ARCHSUFFIX}/SDL2.dll"
echo Done.
}
echo
cd $DEPS_INSTALL/..
echo
echo "Setting up OpenMW build..."
add_cmake_opts -DBUILD_BSATOOL=no \
-DBUILD_ESMTOOL=no \
-DBUILD_MYGUI_PLUGIN=no \
-DOPENMW_MP_BUILD=on
if [ ! -z $CI ]; then
case $STEP in
components )
@ -710,7 +661,6 @@ if [ ! -z $CI ]; then
-DBUILD_OPENMW=no \
-DBUILD_WIZARD=no
;;
openmw )
echo " Building subproject: OpenMW."
add_cmake_opts -DBUILD_ESSIMPORTER=no \
@ -719,7 +669,6 @@ if [ ! -z $CI ]; then
-DBUILD_OPENCS=no \
-DBUILD_WIZARD=no
;;
opencs )
echo " Building subproject: OpenCS."
add_cmake_opts -DBUILD_ESSIMPORTER=no \
@ -728,7 +677,6 @@ if [ ! -z $CI ]; then
-DBUILD_OPENMW=no \
-DBUILD_WIZARD=no
;;
misc )
echo " Building subprojects: Misc."
add_cmake_opts -DBUILD_OPENCS=no \
@ -736,7 +684,6 @@ if [ ! -z $CI ]; then
;;
esac
fi
# NOTE: Disable this when/if we want to run test cases
#if [ -z $CI ]; then
echo "- Copying Runtime DLLs..."
@ -745,16 +692,13 @@ fi
TARGET="$(basename "$DLL")"
if [[ "$DLL" == *":"* ]]; then
IFS=':'; SPLIT=( ${DLL} ); unset IFS
DLL=${SPLIT[0]}
TARGET=${SPLIT[1]}
fi
echo " ${TARGET}."
cp "$DLL" "$BUILD_CONFIG/$TARGET"
done
echo
echo "- OSG Plugin DLLs..."
mkdir -p $BUILD_CONFIG/osgPlugins-3.4.1
for DLL in $OSG_PLUGINS; do
@ -762,7 +706,6 @@ fi
cp "$DLL" $BUILD_CONFIG/osgPlugins-3.4.1
done
echo
echo "- Qt Platform DLLs..."
mkdir -p ${BUILD_CONFIG}/platforms
for DLL in $QT_PLATFORMS; do
@ -771,16 +714,13 @@ fi
done
echo
#fi
if [ -z $VERBOSE ]; then
printf -- "- Configuring... "
else
echo "- cmake .. $CMAKE_OPTS"
fi
run_cmd cmake .. $CMAKE_OPTS
RET=$?
if [ -z $VERBOSE ]; then
if [ $RET -eq 0 ]; then
echo Done.
@ -788,5 +728,4 @@ if [ -z $VERBOSE ]; then
echo Failed.
fi
fi
exit $RET

@ -666,10 +666,10 @@ if (WIN32)
endif()
if (BUILD_OPENMW)
# Release builds use the debug console
set_target_properties(tes3mp PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:CONSOLE")
set_target_properties(tes3mp PROPERTIES COMPILE_DEFINITIONS_RELEASE "_CONSOLE")
set_target_properties(tes3mp PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:CONSOLE")
# Release builds don't use the debug console
set_target_properties(tes3mp PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS")
set_target_properties(tes3mp PROPERTIES COMPILE_DEFINITIONS_RELEASE "_WINDOWS")
set_target_properties(tes3mp PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:WINDOWS")
endif()
# Play a bit with the warning levels
@ -680,7 +680,8 @@ if (WIN32)
# Warnings that aren't enabled normally and don't need to be enabled
# They're unneeded and sometimes completely retarded warnings that /Wall enables
# Not going to bother commenting them as they tend to warn on every standard library file
4061 4263 4264 4266 4350 4371 4435 4514 4548 4571 4610 4619 4623 4625 4626 4628 4640 4668 4710 4711 4820 4826 4917 4946
4061 4263 4264 4266 4350 4371 4435 4514 4548 4571 4610 4619 4623 4625
4626 4628 4640 4668 4710 4711 4768 4820 4826 4917 4946 5032 5039 5045
# Warnings that are thrown on standard libraries and not OpenMW
4347 # Non-template function with same name and parameter count as template function
@ -701,6 +702,7 @@ if (WIN32)
# caused by MyGUI
4275 # non dll-interface class 'std::exception' used as base for dll-interface class 'MyGUI::Exception'
4297 # function assumed not to throw an exception but does
# OpenMW specific warnings
4099 # Type mismatch, declared class or struct is defined with other type

@ -874,7 +874,6 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, co
{
std::vector<std::pair<std::time_t, boost::filesystem::path>> contentFiles;
std::string baseGameFile("Game Files:GameFile");
std::string gameFile("");
std::time_t defaultTime = 0;
ToUTF8::Utf8Encoder encoder(mEncoding);
@ -890,7 +889,7 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, co
multistrmap::const_iterator it = ini.begin();
for (int i=0; it != ini.end(); i++)
{
gameFile = baseGameFile;
std::string gameFile = baseGameFile;
gameFile.append(std::to_string(i));
it = ini.find(gameFile);

@ -5,6 +5,9 @@
#include <QLocalSocket>
#include <QMessageBox>
#include <components/crashcatcher/crashcatcher.hpp>
#include <components/fallback/validate.hpp>
#include <components/nifosg/nifloader.hpp>
@ -18,12 +21,16 @@
using namespace Fallback;
CS::Editor::Editor ()
CS::Editor::Editor (int argc, char **argv)
: mSettingsState (mCfgMgr), mDocumentManager (mCfgMgr),
mViewManager (mDocumentManager), mPid(""),
mLock(), mMerge (mDocumentManager),
mIpcServerName ("org.openmw.OpenCS"), mServer(NULL), mClientSocket(NULL)
{
// install the crash handler as soon as possible. note that the log path
// does not depend on config being read.
crashCatcherInstall(argc, argv, (mCfgMgr.getLogPath() / "openmw-cs-crash.log").string());
std::pair<Files::PathContainer, std::vector<std::string> > config = readConfig();
setupDataFiles (config.first);

@ -66,7 +66,7 @@ namespace CS
public:
Editor ();
Editor (int argc, char **argv);
~Editor ();
bool makeIPCServer();

@ -67,7 +67,7 @@ int main(int argc, char *argv[])
application.setWindowIcon (QIcon (":./openmw-cs.png"));
CS::Editor editor;
CS::Editor editor(argc, argv);
if(!editor.makeIPCServer())
{

@ -22,7 +22,8 @@ void CSMTools::Search::searchTextCell (const CSMWorld::IdTableBase *model,
int pos = 0;
while ((pos = text.indexOf (search, pos, Qt::CaseInsensitive))!=-1)
Qt::CaseSensitivity caseSensitivity = mCase ? Qt::CaseSensitive : Qt::CaseInsensitive;
while ((pos = text.indexOf (search, pos, caseSensitivity))!=-1)
{
std::ostringstream hint;
hint
@ -120,25 +121,26 @@ QString CSMTools::Search::flatten (const QString& text) const
return flat;
}
CSMTools::Search::Search() : mType (Type_None), mValue (0), mIdColumn (0), mTypeColumn (0),
CSMTools::Search::Search() : mType (Type_None), mValue (0), mCase (false), mIdColumn (0), mTypeColumn (0),
mPaddingBefore (10), mPaddingAfter (10) {}
CSMTools::Search::Search (Type type, const std::string& value)
: mType (type), mText (value), mValue (0), mIdColumn (0), mTypeColumn (0), mPaddingBefore (10), mPaddingAfter (10)
CSMTools::Search::Search (Type type, bool caseSensitive, const std::string& value)
: mType (type), mText (value), mValue (0), mCase (caseSensitive), mIdColumn (0), mTypeColumn (0), mPaddingBefore (10), mPaddingAfter (10)
{
if (type!=Type_Text && type!=Type_Id)
throw std::logic_error ("Invalid search parameter (string)");
}
CSMTools::Search::Search (Type type, const QRegExp& value)
: mType (type), mRegExp (value), mValue (0), mIdColumn (0), mTypeColumn (0), mPaddingBefore (10), mPaddingAfter (10)
CSMTools::Search::Search (Type type, bool caseSensitive, const QRegExp& value)
: mType (type), mRegExp (value), mValue (0), mCase (caseSensitive), mIdColumn (0), mTypeColumn (0), mPaddingBefore (10), mPaddingAfter (10)
{
mRegExp.setCaseSensitivity(mCase ? Qt::CaseSensitive : Qt::CaseInsensitive);
if (type!=Type_TextRegEx && type!=Type_IdRegEx)
throw std::logic_error ("Invalid search parameter (RegExp)");
}
CSMTools::Search::Search (Type type, int value)
: mType (type), mValue (value), mIdColumn (0), mTypeColumn (0), mPaddingBefore (10), mPaddingAfter (10)
CSMTools::Search::Search (Type type, bool caseSensitive, int value)
: mType (type), mValue (value), mCase (caseSensitive), mIdColumn (0), mTypeColumn (0), mPaddingBefore (10), mPaddingAfter (10)
{
if (type!=Type_RecordState)
throw std::logic_error ("invalid search parameter (int)");

@ -43,6 +43,7 @@ namespace CSMTools
std::string mText;
QRegExp mRegExp;
int mValue;
bool mCase;
std::set<int> mColumns;
int mIdColumn;
int mTypeColumn;
@ -67,11 +68,11 @@ namespace CSMTools
Search();
Search (Type type, const std::string& value);
Search (Type type, bool caseSensitive, const std::string& value);
Search (Type type, const QRegExp& value);
Search (Type type, bool caseSensitive, const QRegExp& value);
Search (Type type, int value);
Search (Type type, bool caseSensitive, int value);
// Configure search for the specified model.
void configure (const CSMWorld::IdTableBase *model);

@ -136,7 +136,7 @@ namespace CSMWorld
struct VarTypeColumn : public Column<ESXRecordT>
{
VarTypeColumn (ColumnBase::Display display)
: Column<ESXRecordT> (Columns::ColumnId_ValueType, display)
: Column<ESXRecordT> (Columns::ColumnId_ValueType, display, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh)
{}
virtual QVariant get (const Record<ESXRecordT>& record) const
@ -161,7 +161,7 @@ namespace CSMWorld
template<typename ESXRecordT>
struct VarValueColumn : public Column<ESXRecordT>
{
VarValueColumn() : Column<ESXRecordT> (Columns::ColumnId_Value, ColumnBase::Display_Var) {}
VarValueColumn() : Column<ESXRecordT> (Columns::ColumnId_Value, ColumnBase::Display_Var, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh) {}
virtual QVariant get (const Record<ESXRecordT>& record) const
{

@ -84,15 +84,28 @@ bool CSMWorld::IdTable::setData (const QModelIndex &index, const QVariant &value
if (mIdCollection->getColumn (index.column()).isEditable() && role==Qt::EditRole)
{
mIdCollection->setData (index.row(), index.column(), value);
emit dataChanged(index, index);
// Modifying a value can also change the Modified status of a record.
int stateColumn = searchColumnIndex(Columns::ColumnId_Modification);
if (stateColumn != -1)
{
QModelIndex stateIndex = this->index(index.row(), stateColumn);
emit dataChanged(stateIndex, stateIndex);
}
if (index.column() == stateColumn)
{
// modifying the state column can modify other values. we need to tell
// views that the whole row has changed.
emit dataChanged(this->index(index.row(), 0),
this->index(index.row(), columnCount(index.parent())));
} else
{
emit dataChanged(index, index);
// Modifying a value can also change the Modified status of a record.
QModelIndex stateIndex = this->index(index.row(), stateColumn);
emit dataChanged(stateIndex, stateIndex);
}
} else
emit dataChanged(index, index);
return true;
}

@ -101,15 +101,39 @@ void CSVDoc::View::setupFileMenu()
file->addAction(exit);
}
namespace
{
void updateUndoRedoAction(QAction *action, const std::string &settingsKey)
{
QKeySequence seq;
CSMPrefs::State::get().getShortcutManager().getSequence(settingsKey, seq);
action->setShortcut(seq);
}
}
void CSVDoc::View::undoActionChanged()
{
updateUndoRedoAction(mUndo, "document-edit-undo");
}
void CSVDoc::View::redoActionChanged()
{
updateUndoRedoAction(mRedo, "document-edit-redo");
}
void CSVDoc::View::setupEditMenu()
{
QMenu *edit = menuBar()->addMenu (tr ("Edit"));
mUndo = mDocument->getUndoStack().createUndoAction (this, tr("Undo"));
setupShortcut("document-edit-undo", mUndo);
connect(mUndo, SIGNAL (changed ()), this, SLOT (undoActionChanged ()));
edit->addAction (mUndo);
mRedo= mDocument->getUndoStack().createRedoAction (this, tr("Redo"));
mRedo = mDocument->getUndoStack().createRedoAction (this, tr("Redo"));
connect(mRedo, SIGNAL (changed ()), this, SLOT (redoActionChanged ()));
setupShortcut("document-edit-redo", mRedo);
edit->addAction (mRedo);

@ -152,6 +152,10 @@ namespace CSVDoc
void settingChanged (const CSMPrefs::Setting *setting);
void undoActionChanged();
void redoActionChanged();
void newView();
void save();

@ -372,10 +372,10 @@ osg::ref_ptr<osg::Node> CSVRender::Object::makeRotateMarker (int axis)
indices[j] -= VertexCount;
}
size_t offset = i * IndicesPerSegment;
size_t elementOffset = i * IndicesPerSegment;
for (size_t j = 0; j < IndicesPerSegment; ++j)
{
primitives->setElement(offset++, indices[IndexPattern[j]]);
primitives->setElement(elementOffset++, indices[IndexPattern[j]]);
}
}

@ -651,7 +651,6 @@ void CSVRender::WorldspaceWidget::mouseMoveEvent (QMouseEvent *event)
if (mDragMode == InteractionType_PrimaryEdit)
{
EditMode& editMode = dynamic_cast<CSVRender::EditMode&> (*mEditMode->getCurrent());
editMode.drag (event->pos(), mDragX, mDragY, mDragFactor); // note: terraintexturemode only uses pos
}
}

@ -35,7 +35,7 @@ void CSVTools::SearchBox::updateSearchButton()
}
CSVTools::SearchBox::SearchBox (QWidget *parent)
: QWidget (parent), mSearch ("Search"), mSearchEnabled (false), mReplace ("Replace All")
: QWidget (parent), mSearch (tr("Search")), mSearchEnabled (false), mReplace (tr("Replace All"))
{
mLayout = new QGridLayout (this);
@ -48,29 +48,26 @@ CSVTools::SearchBox::SearchBox (QWidget *parent)
++iter)
mRecordState.addItem (QString::fromUtf8 (iter->c_str()));
mMode.addItem ("Text");
mMode.addItem ("Text (RegEx)");
mMode.addItem ("ID");
mMode.addItem ("ID (RegEx)");
mMode.addItem ("Record State");
mMode.addItem (tr("Text"));
mMode.addItem (tr("Text (RegEx)"));
mMode.addItem (tr("ID"));
mMode.addItem (tr("ID (RegEx)"));
mMode.addItem (tr("Record State"));
connect (&mMode, SIGNAL (activated (int)), this, SLOT (modeSelected (int)));
mLayout->addWidget (&mMode, 0, 0);
mLayout->addWidget (&mSearch, 0, 3);
connect (&mText, SIGNAL (textChanged (const QString&)), this, SLOT (textChanged (const QString&)));
connect (&mText, SIGNAL (returnPressed()), this, SLOT (startSearch()));
mInput.insertWidget (0, &mText);
mInput.insertWidget (1, &mRecordState);
mInput.insertWidget (1, &mRecordState);
mLayout->addWidget (&mInput, 0, 1);
connect (&mMode, SIGNAL (activated (int)), this, SLOT (modeSelected (int)));
connect (&mText, SIGNAL (textChanged (const QString&)),
this, SLOT (textChanged (const QString&)));
mCaseSensitive.setText (tr ("Case"));
mLayout->addWidget (&mCaseSensitive, 0, 2);
connect (&mSearch, SIGNAL (clicked (bool)), this, SLOT (startSearch (bool)));
connect (&mText, SIGNAL (returnPressed()), this, SLOT (startSearch()));
mLayout->addWidget (&mSearch, 0, 3);
// replace panel
mReplaceInput.insertWidget (0, &mReplaceText);
@ -103,22 +100,23 @@ void CSVTools::SearchBox::setSearchMode (bool enabled)
CSMTools::Search CSVTools::SearchBox::getSearch() const
{
CSMTools::Search::Type type = static_cast<CSMTools::Search::Type> (mMode.currentIndex());
bool caseSensitive = mCaseSensitive.isChecked();
switch (type)
{
case CSMTools::Search::Type_Text:
case CSMTools::Search::Type_Id:
return CSMTools::Search (type, std::string (mText.text().toUtf8().data()));
return CSMTools::Search (type, caseSensitive, std::string (mText.text().toUtf8().data()));
case CSMTools::Search::Type_TextRegEx:
case CSMTools::Search::Type_IdRegEx:
return CSMTools::Search (type, QRegExp (mText.text().toUtf8().data(), Qt::CaseInsensitive));
return CSMTools::Search (type, caseSensitive, QRegExp (mText.text().toUtf8().data(), Qt::CaseInsensitive));
case CSMTools::Search::Type_RecordState:
return CSMTools::Search (type, mRecordState.currentIndex());
return CSMTools::Search (type, caseSensitive, mRecordState.currentIndex());
case CSMTools::Search::Type_None:

@ -4,6 +4,7 @@
#include <QWidget>
#include <QLineEdit>
#include <QComboBox>
#include <QCheckBox>
#include <QStackedWidget>
#include <QPushButton>
#include <QLabel>
@ -24,6 +25,7 @@ namespace CSVTools
QStackedWidget mInput;
QLineEdit mText;
QComboBox mRecordState;
QCheckBox mCaseSensitive;
QPushButton mSearch;
QGridLayout *mLayout;
QComboBox mMode;

@ -11,12 +11,10 @@ if (ANDROID)
set(GAME ${GAME} android_main.c)
endif()
if(NOT WIN32 AND NOT ANDROID)
set(GAME ${GAME} crashcatcher.cpp)
endif()
set(GAME_HEADER
engine.hpp
)
source_group(game FILES ${GAME} ${GAME_HEADER})
add_openmw_dir (mwrender
@ -83,9 +81,9 @@ add_openmw_dir (mwclass
add_openmw_dir (mwmechanics
mechanicsmanagerimp stat creaturestats magiceffects movement actorutil
drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aibreathe
aiescort aiactivate aicombat repair enchanting pathfinding pathgrid security spellsuccess spellcasting
aicast aiescort aiface aiactivate aicombat repair enchanting pathfinding pathgrid security spellsuccess spellcasting
disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning
character actors objects aistate coordinateconverter trading aiface weaponpriority spellpriority
character actors objects aistate coordinateconverter trading weaponpriority spellpriority
)
add_openmw_dir (mwstate

@ -757,28 +757,19 @@ void OMW::Engine::go()
std::cout << "OSG version: " << osgGetVersion() << std::endl;
mViewer = new osgViewer::Viewer;
mViewer->setReleaseContextAtEndOfFrameHint(false);
osg::ref_ptr<osgViewer::StatsHandler> statshandler = new osgViewer::StatsHandler;
statshandler->setKeyEventTogglesOnScreenStats(osgGA::GUIEventAdapter::KEY_F3);
statshandler->addUserStatsLine("Script", osg::Vec4f(1.f, 1.f, 1.f, 1.f), osg::Vec4f(1.f, 1.f, 1.f, 1.f),
"script_time_taken", 1000.0, true, false, "script_time_begin", "script_time_end", 10000);
statshandler->addUserStatsLine("Mechanics", osg::Vec4f(1.f, 1.f, 1.f, 1.f), osg::Vec4f(1.f, 1.f, 1.f, 1.f),
"mechanics_time_taken", 1000.0, true, false, "mechanics_time_begin", "mechanics_time_end", 10000);
statshandler->addUserStatsLine("Physics", osg::Vec4f(1.f, 1.f, 1.f, 1.f), osg::Vec4f(1.f, 1.f, 1.f, 1.f),
"physics_time_taken", 1000.0, true, false, "physics_time_begin", "physics_time_end", 10000);
mViewer->addEventHandler(statshandler);
mViewer->addEventHandler(new Resource::StatsHandler);
// Load settings
Settings::Manager settings;
std::string settingspath;
settingspath = loadSettings (settings);
// Create encoder
ToUTF8::Utf8Encoder encoder (mEncoding);
mEncoder = &encoder;
// Setup viewer
mViewer = new osgViewer::Viewer;
mViewer->setReleaseContextAtEndOfFrameHint(false);
mScreenCaptureOperation = new WriteScreenshotToFileOperation(mCfgMgr.getUserDataPath().string(),
Settings::Manager::getString("screenshot format", "General"));
@ -788,10 +779,6 @@ void OMW::Engine::go()
mEnvironment.setFrameRateLimit(Settings::Manager::getFloat("framerate limit", "Video"));
// Create encoder
ToUTF8::Utf8Encoder encoder (mEncoding);
mEncoder = &encoder;
prepareEngine (settings);
/*
@ -814,6 +801,22 @@ void OMW::Engine::go()
End of tes3mp change (major)
*/
// Setup profiler
osg::ref_ptr<Resource::Profiler> statshandler = new Resource::Profiler;
statshandler->addUserStatsLine("Script", osg::Vec4f(1.f, 1.f, 1.f, 1.f), osg::Vec4f(1.f, 1.f, 1.f, 1.f),
"script_time_taken", 1000.0, true, false, "script_time_begin", "script_time_end", 10000);
statshandler->addUserStatsLine("Mechanics", osg::Vec4f(1.f, 1.f, 1.f, 1.f), osg::Vec4f(1.f, 1.f, 1.f, 1.f),
"mechanics_time_taken", 1000.0, true, false, "mechanics_time_begin", "mechanics_time_end", 10000);
statshandler->addUserStatsLine("Physics", osg::Vec4f(1.f, 1.f, 1.f, 1.f), osg::Vec4f(1.f, 1.f, 1.f, 1.f),
"physics_time_taken", 1000.0, true, false, "physics_time_begin", "physics_time_end", 10000);
mViewer->addEventHandler(statshandler);
osg::ref_ptr<Resource::StatsHandler> resourceshandler = new Resource::StatsHandler;
mViewer->addEventHandler(resourceshandler);
// Start the game
if (!mSaveGameFile.empty())
{
mEnvironment.getStateManager()->loadGame(mSaveGameFile);

@ -1,6 +1,7 @@
#include <iostream>
#include <components/version/version.hpp>
#include <components/crashcatcher/crashcatcher.hpp>
#include <components/files/configurationmanager.hpp>
#include <components/files/escape.hpp>
#include <components/fallback/validate.hpp>
@ -34,18 +35,6 @@
#include <unistd.h>
#endif
#if (defined(__APPLE__) || (defined(__linux) && !defined(ANDROID)) || (defined(__unix) && !defined(ANDROID)) || defined(__posix))
#define USE_CRASH_CATCHER 1
#else
#define USE_CRASH_CATCHER 0
#endif
#if USE_CRASH_CATCHER
#include <csignal>
extern int cc_install_handlers(int argc, char **argv, int num_signals, int *sigs, const char *logfile, int (*user_info)(char*, char*));
extern int is_debugger_attached(void);
#endif
/*
Start of tes3mp addition
@ -419,18 +408,7 @@ int main(int argc, char**argv)
End of tes3mp addition
*/
#if USE_CRASH_CATCHER
// Unix crash catcher
if ((argc == 2 && strcmp(argv[1], "--cc-handle-crash") == 0) || !is_debugger_attached())
{
int s[5] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGABRT };
cc_install_handlers(argc, argv, 5, s, (cfgMgr.getLogPath() / "crash.log").string().c_str(), NULL);
std::cout << "Installing crash catcher" << std::endl;
}
else
std::cout << "Running in a debugger, not installing crash catcher" << std::endl;
#endif
crashCatcherInstall(argc, argv, (cfgMgr.getLogPath() / "crash.log").string());
#ifdef __APPLE__
boost::filesystem::path binary_path = boost::filesystem::system_complete(boost::filesystem::path(argv[0]));

@ -235,9 +235,12 @@ namespace MWBase
/// Resurrects the player if necessary
virtual void keepPlayerAlive() = 0;
virtual bool isCastingSpell (const MWWorld::Ptr& ptr) const = 0;
virtual bool isReadyToBlock (const MWWorld::Ptr& ptr) const = 0;
virtual bool isAttackingOrSpell(const MWWorld::Ptr &ptr) const = 0;
virtual void castSpell(const MWWorld::Ptr& ptr, const std::string spellId, bool manualSpell) = 0;
/// Check if the target actor was detected by an observer
/// If the observer is a non-NPC, check all actors in AI processing distance as observers
virtual bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) = 0;

@ -157,7 +157,7 @@ namespace MWBase
*/
/// Make the player use an item, while updating GUI state accordingly
virtual void useItem(const MWWorld::Ptr& item) = 0;
virtual void useItem(const MWWorld::Ptr& item, bool force=false) = 0;
virtual void updateSpellWindow() = 0;

@ -583,7 +583,7 @@ namespace MWBase
*/
virtual bool startSpellCast (const MWWorld::Ptr& actor) = 0;
virtual void castSpell (const MWWorld::Ptr& actor) = 0;
virtual void castSpell (const MWWorld::Ptr& actor, bool manualSpell=false) = 0;
virtual void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) = 0;
virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile,

@ -157,10 +157,9 @@ namespace MWClass
return info;
}
std::shared_ptr<MWWorld::Action> Apparatus::use (const MWWorld::Ptr& ptr) const
std::shared_ptr<MWWorld::Action> Apparatus::use (const MWWorld::Ptr& ptr, bool force) const
{
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionAlchemy());
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionAlchemy(force));
}
MWWorld::Ptr Apparatus::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const

@ -50,8 +50,7 @@ namespace MWClass
virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const;
///< Return name of inventory icon.
virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr)
const;
virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr, bool force=false) const;
///< Generate action for using via inventory menu
virtual std::string getModel(const MWWorld::ConstPtr &ptr) const;

@ -387,9 +387,9 @@ namespace MWClass
return std::make_pair(1,"");
}
std::shared_ptr<MWWorld::Action> Armor::use (const MWWorld::Ptr& ptr) const
std::shared_ptr<MWWorld::Action> Armor::use (const MWWorld::Ptr& ptr, bool force) const
{
std::shared_ptr<MWWorld::Action> action(new MWWorld::ActionEquip(ptr));
std::shared_ptr<MWWorld::Action> action(new MWWorld::ActionEquip(ptr, force));
action->setSound(getUpSoundId(ptr));

@ -73,8 +73,7 @@ namespace MWClass
///< Return 0 if player cannot equip item. 1 if can equip. 2 if it's twohanded weapon. 3 if twohanded weapon conflicts with that. \n
/// Second item in the pair specifies the error message
virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr)
const;
virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr, bool force=false) const;
///< Generate action for using via inventory menu
virtual std::string getModel(const MWWorld::ConstPtr &ptr) const;

@ -195,7 +195,7 @@ namespace MWClass
return record->mId;
}
std::shared_ptr<MWWorld::Action> Book::use (const MWWorld::Ptr& ptr) const
std::shared_ptr<MWWorld::Action> Book::use (const MWWorld::Ptr& ptr, bool force) const
{
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionRead(ptr));
}

@ -53,7 +53,7 @@ namespace MWClass
virtual std::string applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const;
///< Creates a new record using \a ptr as template, with the given name and the given enchantment applied to it.
virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr) const;
virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr, bool force=false) const;
///< Generate action for using via inventory menu
virtual std::string getModel(const MWWorld::ConstPtr &ptr) const;

@ -276,9 +276,9 @@ namespace MWClass
return std::make_pair (1, "");
}
std::shared_ptr<MWWorld::Action> Clothing::use (const MWWorld::Ptr& ptr) const
std::shared_ptr<MWWorld::Action> Clothing::use (const MWWorld::Ptr& ptr, bool force) const
{
std::shared_ptr<MWWorld::Action> action(new MWWorld::ActionEquip(ptr));
std::shared_ptr<MWWorld::Action> action(new MWWorld::ActionEquip(ptr, force));
action->setSound(getUpSoundId(ptr));

@ -65,8 +65,7 @@ namespace MWClass
///< Return 0 if player cannot equip item. 1 if can equip. 2 if it's twohanded weapon. 3 if twohanded weapon conflicts with that.
/// Second item in the pair specifies the error message
virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr)
const;
virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr, bool force=false) const;
///< Generate action for using via inventory menu
virtual std::string getModel(const MWWorld::ConstPtr &ptr) const;

@ -108,7 +108,7 @@ namespace MWClass
}
std::shared_ptr<MWWorld::Action> Ingredient::use (const MWWorld::Ptr& ptr) const
std::shared_ptr<MWWorld::Action> Ingredient::use (const MWWorld::Ptr& ptr, bool force) const
{
std::shared_ptr<MWWorld::Action> action (new MWWorld::ActionEat (ptr));

@ -36,8 +36,7 @@ namespace MWClass
virtual int getValue (const MWWorld::ConstPtr& ptr) const;
///< Return trade value of the object. Throws an exception, if the object can't be traded.
virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr)
const;
virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr, bool force=false) const;
///< Generate action for using via inventory menu
static void registerSelf();

@ -186,9 +186,9 @@ namespace MWClass
return Class::showsInInventory(ptr);
}
std::shared_ptr<MWWorld::Action> Light::use (const MWWorld::Ptr& ptr) const
std::shared_ptr<MWWorld::Action> Light::use (const MWWorld::Ptr& ptr, bool force) const
{
std::shared_ptr<MWWorld::Action> action(new MWWorld::ActionEquip(ptr));
std::shared_ptr<MWWorld::Action> action(new MWWorld::ActionEquip(ptr, force));
action->setSound(getUpSoundId(ptr));

@ -55,8 +55,7 @@ namespace MWClass
virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const;
///< Return name of inventory icon.
virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr)
const;
virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr, bool force=false) const;
///< Generate action for using via inventory menu
virtual void setRemainingUsageTime (const MWWorld::Ptr& ptr, float duration) const;

@ -140,9 +140,9 @@ namespace MWClass
return info;
}
std::shared_ptr<MWWorld::Action> Lockpick::use (const MWWorld::Ptr& ptr) const
std::shared_ptr<MWWorld::Action> Lockpick::use (const MWWorld::Ptr& ptr, bool force) const
{
std::shared_ptr<MWWorld::Action> action(new MWWorld::ActionEquip(ptr));
std::shared_ptr<MWWorld::Action> action(new MWWorld::ActionEquip(ptr, force));
action->setSound(getUpSoundId(ptr));

@ -53,8 +53,7 @@ namespace MWClass
virtual std::pair<int, std::string> canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const;
virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr)
const;
virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr, bool force=false) const;
///< Generate action for using via inventory menu
virtual std::string getModel(const MWWorld::ConstPtr &ptr) const;

@ -259,7 +259,7 @@ namespace MWClass
return newPtr;
}
std::shared_ptr<MWWorld::Action> Miscellaneous::use (const MWWorld::Ptr& ptr) const
std::shared_ptr<MWWorld::Action> Miscellaneous::use (const MWWorld::Ptr& ptr, bool force) const
{
if (ptr.getCellRef().getSoul().empty() || !MWBase::Environment::get().getWorld()->getStore().get<ESM::Creature>().search(ptr.getCellRef().getSoul()))
return std::shared_ptr<MWWorld::Action>(new MWWorld::NullAction());

@ -49,8 +49,7 @@ namespace MWClass
virtual std::string getModel(const MWWorld::ConstPtr &ptr) const;
virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr)
const;
virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr, bool force=false) const;
///< Generate action for using via inventory menu
virtual float getWeight (const MWWorld::ConstPtr& ptr) const;

@ -172,7 +172,7 @@ namespace MWClass
return info;
}
std::shared_ptr<MWWorld::Action> Potion::use (const MWWorld::Ptr& ptr) const
std::shared_ptr<MWWorld::Action> Potion::use (const MWWorld::Ptr& ptr, bool force) const
{
MWWorld::LiveCellRef<ESM::Potion> *ref =
ptr.get<ESM::Potion>();

@ -36,7 +36,7 @@ namespace MWClass
virtual int getValue (const MWWorld::ConstPtr& ptr) const;
///< Return trade value of the object. Throws an exception, if the object can't be traded.
virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr) const;
virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr, bool force=false) const;
///< Generate action for using via inventory menu
static void registerSelf();

@ -140,9 +140,9 @@ namespace MWClass
return info;
}
std::shared_ptr<MWWorld::Action> Probe::use (const MWWorld::Ptr& ptr) const
std::shared_ptr<MWWorld::Action> Probe::use (const MWWorld::Ptr& ptr, bool force) const
{
std::shared_ptr<MWWorld::Action> action(new MWWorld::ActionEquip(ptr));
std::shared_ptr<MWWorld::Action> action(new MWWorld::ActionEquip(ptr, force));
action->setSound(getUpSoundId(ptr));

@ -53,8 +53,7 @@ namespace MWClass
virtual std::pair<int, std::string> canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const;
virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr)
const;
virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr, bool force=false) const;
///< Generate action for using via inventory menu
virtual std::string getModel(const MWWorld::ConstPtr &ptr) const;

@ -182,9 +182,9 @@ namespace MWClass
return MWWorld::Ptr(cell.insert(ref), &cell);
}
std::shared_ptr<MWWorld::Action> Repair::use (const MWWorld::Ptr& ptr) const
std::shared_ptr<MWWorld::Action> Repair::use (const MWWorld::Ptr& ptr, bool force) const
{
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionRepair(ptr));
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionRepair(ptr, force));
}
bool Repair::canSell (const MWWorld::ConstPtr& item, int npcServices) const

@ -49,7 +49,7 @@ namespace MWClass
virtual std::string getModel(const MWWorld::ConstPtr &ptr) const;
virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr)
virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr, bool force=false)
const;
///< Generate action for using via inventory menu (default implementation: return a
/// null action).

@ -435,9 +435,9 @@ namespace MWClass
return std::make_pair(1, "");
}
std::shared_ptr<MWWorld::Action> Weapon::use (const MWWorld::Ptr& ptr) const
std::shared_ptr<MWWorld::Action> Weapon::use (const MWWorld::Ptr& ptr, bool force) const
{
std::shared_ptr<MWWorld::Action> action(new MWWorld::ActionEquip(ptr));
std::shared_ptr<MWWorld::Action> action(new MWWorld::ActionEquip(ptr, force));
action->setSound(getUpSoundId(ptr));

@ -72,8 +72,7 @@ namespace MWClass
///< Return 0 if player cannot equip item. 1 if can equip. 2 if it's twohanded weapon. 3 if twohanded weapon conflicts with that.
/// Second item in the pair specifies the error message
virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr)
const;
virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr, bool force=false) const;
///< Generate action for using via inventory menu
virtual std::string getModel(const MWWorld::ConstPtr &ptr) const;

@ -629,6 +629,9 @@ namespace MWGui
void DialogueWindow::onTopicActivated(const std::string &topicId)
{
if (mGoodbye)
return;
MWBase::Environment::get().getDialogueManager()->keywordSelected(topicId, mCallback.get());
updateTopics();
}

@ -39,7 +39,7 @@
#include "../mwworld/inventorystore.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/action.hpp"
#include "../mwworld/actionequip.hpp"
#include "../mwscript/interpretercontext.hpp"
#include "../mwmechanics/actorutil.hpp"
@ -488,7 +488,7 @@ namespace MWGui
MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Inventory);
}
void InventoryWindow::useItem(const MWWorld::Ptr &ptr)
void InventoryWindow::useItem(const MWWorld::Ptr &ptr, bool force)
{
const std::string& script = ptr.getClass().getScript(ptr);
@ -497,13 +497,24 @@ namespace MWGui
// early-out for items that need to be equipped, but can't be equipped: we don't want to set OnPcEquip in that case
if (!ptr.getClass().getEquipmentSlots(ptr).first.empty())
{
std::pair<int, std::string> canEquip = ptr.getClass().canBeEquipped(ptr, player);
if (canEquip.first == 0)
if (ptr.getClass().hasItemHealth(ptr) && ptr.getCellRef().getCharge() == 0)
{
MWBase::Environment::get().getWindowManager()->messageBox(canEquip.second);
MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage1}");
updateItemView();
return;
}
if (!force)
{
std::pair<int, std::string> canEquip = ptr.getClass().canBeEquipped(ptr, player);
if (canEquip.first == 0)
{
MWBase::Environment::get().getWindowManager()->messageBox(canEquip.second);
updateItemView();
return;
}
}
}
// If the item has a script, set its OnPcEquip to 1
@ -526,9 +537,8 @@ namespace MWGui
{
if (script.empty() || ptr.getRefData().getLocals().getIntVar(script, "pcskipequip") == 0)
{
std::shared_ptr<MWWorld::Action> action = ptr.getClass().use(ptr);
action->execute (player);
std::shared_ptr<MWWorld::Action> action = ptr.getClass().use(ptr, force);
action->execute(player);
}
else
mSkippedToEquip = ptr;

@ -58,7 +58,7 @@ namespace MWGui
void clear();
void useItem(const MWWorld::Ptr& ptr);
void useItem(const MWWorld::Ptr& ptr, bool force=false);
void setGuiMode(GuiMode mode);

@ -91,7 +91,7 @@ namespace MWGui
for (int i=0; i<ESM::Skill::Length; ++i)
{
int value = npcStats.getSkill (i).getBase ();
int value = npcStats.getSkill (i).getModified ();
skills.push_back(std::make_pair(i, value));
}

@ -1423,10 +1423,10 @@ namespace MWGui
End of tes3mp addition
*/
void WindowManager::useItem(const MWWorld::Ptr &item)
void WindowManager::useItem(const MWWorld::Ptr &item, bool bypassBeastRestrictions)
{
if (mInventoryWindow)
mInventoryWindow->useItem(item);
mInventoryWindow->useItem(item, bypassBeastRestrictions);
}
bool WindowManager::isAllowed (GuiWindow wnd) const

@ -196,7 +196,7 @@ namespace MWGui
*/
/// Make the player use an item, while updating GUI state accordingly
virtual void useItem(const MWWorld::Ptr& item);
virtual void useItem(const MWWorld::Ptr& item, bool bypassBeastRestrictions=false);
virtual void updateSpellWindow();

@ -4,7 +4,6 @@
namespace MWMechanics
{
Actor::Actor(const MWWorld::Ptr &ptr, MWRender::Animation *animation)
{
mCharacterController.reset(new CharacterController(ptr, animation));
@ -19,10 +18,4 @@ namespace MWMechanics
{
return mCharacterController.get();
}
AiState& Actor::getAiState()
{
return mAiState;
}
}

@ -3,8 +3,6 @@
#include <memory>
#include "aistate.hpp"
namespace MWRender
{
class Animation;
@ -29,12 +27,8 @@ namespace MWMechanics
CharacterController* getCharacterController();
AiState& getAiState();
private:
std::unique_ptr<CharacterController> mCharacterController;
AiState mAiState;
};
}

@ -1253,6 +1253,13 @@ namespace MWMechanics
}
}
void Actors::castSpell(const MWWorld::Ptr& ptr, const std::string spellId, bool manualSpell)
{
PtrActorMap::iterator iter = mActors.find(ptr);
if(iter != mActors.end())
iter->second->getCharacterController()->castSpell(spellId, manualSpell);
}
bool Actors::isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer)
{
if (!actor.getClass().isActor())
@ -1523,7 +1530,7 @@ namespace MWMechanics
{
CreatureStats &stats = iter->first.getClass().getCreatureStats(iter->first);
if (isConscious(iter->first))
stats.getAiSequence().execute(iter->first, *iter->second->getCharacterController(), iter->second->getAiState(), duration);
stats.getAiSequence().execute(iter->first, *iter->second->getCharacterController(), duration);
}
}
/*
@ -2195,6 +2202,15 @@ namespace MWMechanics
return it->second->getCharacterController()->isReadyToBlock();
}
bool Actors::isCastingSpell(const MWWorld::Ptr &ptr) const
{
PtrActorMap::const_iterator it = mActors.find(ptr);
if (it == mActors.end())
return false;
return it->second->getCharacterController()->isCastingSpell();
}
bool Actors::isAttackingOrSpell(const MWWorld::Ptr& ptr) const
{
PtrActorMap::const_iterator it = mActors.find(ptr);
@ -2220,7 +2236,7 @@ namespace MWMechanics
|| ptr.getClass().getCreatureStats(ptr).isParalyzed())
continue;
MWMechanics::AiSequence& seq = ptr.getClass().getCreatureStats(ptr).getAiSequence();
seq.fastForward(ptr, it->second->getAiState());
seq.fastForward(ptr);
}
}
}

@ -77,6 +77,8 @@ namespace MWMechanics
///
/// \note Ignored, if \a ptr is not a registered actor.
void castSpell(const MWWorld::Ptr& ptr, const std::string spellId, bool manualSpell=false);
void updateActor(const MWWorld::Ptr &old, const MWWorld::Ptr& ptr);
///< Updates an actor with a new Ptr
@ -171,6 +173,7 @@ namespace MWMechanics
void clear(); // Clear death counter
bool isCastingSpell(const MWWorld::Ptr& ptr) const;
bool isReadyToBlock(const MWWorld::Ptr& ptr) const;
bool isAttackingOrSpell(const MWWorld::Ptr& ptr) const;

@ -0,0 +1,84 @@
#include "aicast.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwworld/class.hpp"
#include "aicombataction.hpp"
#include "creaturestats.hpp"
#include "spellcasting.hpp"
#include "steering.hpp"
MWMechanics::AiCast::AiCast(const std::string& targetId, const std::string& spellId, bool manualSpell)
: mTargetId(targetId), mSpellId(spellId), mCasting(false), mManual(manualSpell), mDistance(0)
{
ActionSpell action = ActionSpell(spellId);
bool isRanged;
mDistance = action.getCombatRange(isRanged);
}
MWMechanics::AiPackage *MWMechanics::AiCast::clone() const
{
return new AiCast(*this);
}
bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::CharacterController& characterController, MWMechanics::AiState& state, float duration)
{
MWWorld::Ptr target;
if (actor.getCellRef().getRefId() == mTargetId)
{
// If the target has the same ID as caster, consider that actor casts spell with Self range.
target = actor;
}
else
{
target = getTarget();
if (!target)
return true;
if (!mManual && !pathTo(actor, target.getRefData().getPosition().pos, duration, mDistance))
{
return false;
}
osg::Vec3f dir = target.getRefData().getPosition().asVec3() - actor.getRefData().getPosition().asVec3();
bool turned = smoothTurn(actor, getZAngleToDir(dir), 2, osg::DegreesToRadians(3.f));
turned &= smoothTurn(actor, getXAngleToDir(dir), 0, osg::DegreesToRadians(3.f));
if (!turned)
return false;
}
// Check if the actor is already casting another spell
bool isCasting = MWBase::Environment::get().getMechanicsManager()->isCastingSpell(actor);
if (isCasting && !mCasting)
return false;
if (!mCasting)
{
MWBase::Environment::get().getMechanicsManager()->castSpell(actor, mSpellId, mManual);
mCasting = true;
return false;
}
// Finish package, if actor finished spellcasting
return !isCasting;
}
MWWorld::Ptr MWMechanics::AiCast::getTarget() const
{
MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mTargetId, false);
return target;
}
int MWMechanics::AiCast::getTypeId() const
{
return AiPackage::TypeIdCast;
}
unsigned int MWMechanics::AiCast::getPriority() const
{
return 3;
}

@ -0,0 +1,37 @@
#ifndef GAME_MWMECHANICS_AICAST_H
#define GAME_MWMECHANICS_AICAST_H
#include "../mwbase/world.hpp"
#include "aipackage.hpp"
namespace MWMechanics
{
/// AiPackage which makes an actor to cast given spell.
class AiCast : public AiPackage {
public:
AiCast(const std::string& targetId, const std::string& spellId, bool manualSpell=false);
virtual AiPackage *clone() const;
virtual bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration);
virtual int getTypeId() const;
virtual MWWorld::Ptr getTarget() const;
virtual unsigned int getPriority() const;
virtual bool canCancel() const { return false; }
virtual bool shouldCancelPreviousAi() const { return false; }
private:
std::string mTargetId;
std::string mSpellId;
bool mCasting;
bool mManual;
float mDistance;
};
}
#endif

@ -48,74 +48,6 @@ namespace
namespace MWMechanics
{
/// \brief This class holds the variables AiCombat needs which are deleted if the package becomes inactive.
struct AiCombatStorage : AiTemporaryBase
{
float mAttackCooldown;
float mTimerReact;
float mTimerCombatMove;
bool mReadyToAttack;
bool mAttack;
float mAttackRange;
bool mCombatMove;
osg::Vec3f mLastTargetPos;
const MWWorld::CellStore* mCell;
std::shared_ptr<Action> mCurrentAction;
float mActionCooldown;
float mStrength;
bool mForceNoShortcut;
ESM::Position mShortcutFailPos;
MWMechanics::Movement mMovement;
enum FleeState
{
FleeState_None,
FleeState_Idle,
FleeState_RunBlindly,
FleeState_RunToDestination
};
FleeState mFleeState;
bool mLOS;
float mUpdateLOSTimer;
float mFleeBlindRunTimer;
ESM::Pathgrid::Point mFleeDest;
AiCombatStorage():
mAttackCooldown(0.0f),
mTimerReact(AI_REACTION_TIME),
mTimerCombatMove(0.0f),
mReadyToAttack(false),
mAttack(false),
mAttackRange(0.0f),
mCombatMove(false),
mLastTargetPos(0,0,0),
mCell(NULL),
mCurrentAction(),
mActionCooldown(0.0f),
mStrength(),
mForceNoShortcut(false),
mShortcutFailPos(),
mMovement(),
mFleeState(FleeState_None),
mLOS(false),
mUpdateLOSTimer(0.0f),
mFleeBlindRunTimer(0.0f)
{}
void startCombatMove(bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& actor, const MWWorld::Ptr& target);
void updateCombatMove(float duration);
void stopCombatMove();
void startAttackIfReady(const MWWorld::Ptr& actor, CharacterController& characterController,
const ESM::Weapon* weapon, bool distantCombat);
void updateAttack(CharacterController& characterController);
void stopAttack();
void startFleeing();
void stopFleeing();
bool isFleeing();
};
AiCombat::AiCombat(const MWWorld::Ptr& actor)
{
mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId();

@ -23,7 +23,72 @@ namespace MWMechanics
{
class Action;
struct AiCombatStorage;
/// \brief This class holds the variables AiCombat needs which are deleted if the package becomes inactive.
struct AiCombatStorage : AiTemporaryBase
{
float mAttackCooldown;
float mTimerReact;
float mTimerCombatMove;
bool mReadyToAttack;
bool mAttack;
float mAttackRange;
bool mCombatMove;
osg::Vec3f mLastTargetPos;
const MWWorld::CellStore* mCell;
std::shared_ptr<Action> mCurrentAction;
float mActionCooldown;
float mStrength;
bool mForceNoShortcut;
ESM::Position mShortcutFailPos;
MWMechanics::Movement mMovement;
enum FleeState
{
FleeState_None,
FleeState_Idle,
FleeState_RunBlindly,
FleeState_RunToDestination
};
FleeState mFleeState;
bool mLOS;
float mUpdateLOSTimer;
float mFleeBlindRunTimer;
ESM::Pathgrid::Point mFleeDest;
AiCombatStorage():
mAttackCooldown(0.0f),
mTimerReact(AI_REACTION_TIME),
mTimerCombatMove(0.0f),
mReadyToAttack(false),
mAttack(false),
mAttackRange(0.0f),
mCombatMove(false),
mLastTargetPos(0,0,0),
mCell(NULL),
mCurrentAction(),
mActionCooldown(0.0f),
mStrength(),
mForceNoShortcut(false),
mShortcutFailPos(),
mMovement(),
mFleeState(FleeState_None),
mLOS(false),
mUpdateLOSTimer(0.0f),
mFleeBlindRunTimer(0.0f)
{}
void startCombatMove(bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& actor, const MWWorld::Ptr& target);
void updateCombatMove(float duration);
void stopCombatMove();
void startAttackIfReady(const MWWorld::Ptr& actor, CharacterController& characterController,
const ESM::Weapon* weapon, bool distantCombat);
void updateAttack(CharacterController& characterController);
void stopAttack();
void startFleeing();
void stopFleeing();
bool isFleeing();
};
/// \brief Causes the actor to fight another actor
class AiCombat : public AiPackage

@ -17,22 +17,6 @@
namespace MWMechanics
{
struct AiFollowStorage : AiTemporaryBase
{
float mTimer;
bool mMoving;
float mTargetAngleRadians;
bool mTurnActorToTarget;
AiFollowStorage() :
mTimer(0.f),
mMoving(false),
mTargetAngleRadians(0.f),
mTurnActorToTarget(false)
{}
};
int AiFollow::mFollowIndexCounter = 0;
AiFollow::AiFollow(const std::string &actorId, float duration, float x, float y, float z)

@ -19,6 +19,21 @@ namespace AiSequence
namespace MWMechanics
{
struct AiFollowStorage : AiTemporaryBase
{
float mTimer;
bool mMoving;
float mTargetAngleRadians;
bool mTurnActorToTarget;
AiFollowStorage() :
mTimer(0.f),
mMoving(false),
mTargetAngleRadians(0.f),
mTurnActorToTarget(false)
{}
};
/// \brief AiPackage for an actor to follow another actor/the PC
/** The AI will follow the target until a condition (time, or position) are set. Both can be disabled to cause the actor to follow the other indefinitely
**/

@ -49,7 +49,8 @@ namespace MWMechanics
TypeIdAvoidDoor = 7,
TypeIdFace = 8,
TypeIdBreathe = 9,
TypeIdInternalTravel = 10
TypeIdInternalTravel = 10,
TypeIdCast = 11
};
///Default constructor

@ -180,15 +180,11 @@ bool AiSequence::isPackageDone() const
bool isActualAiPackage(int packageTypeId)
{
return (packageTypeId != AiPackage::TypeIdCombat
&& packageTypeId != AiPackage::TypeIdPursue
&& packageTypeId != AiPackage::TypeIdAvoidDoor
&& packageTypeId != AiPackage::TypeIdFace
&& packageTypeId != AiPackage::TypeIdBreathe
&& packageTypeId != AiPackage::TypeIdInternalTravel);
return (packageTypeId >= AiPackage::TypeIdWander &&
packageTypeId <= AiPackage::TypeIdActivate);
}
void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration)
void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& characterController, float duration)
{
if(actor != getPlayer())
{
@ -262,7 +258,7 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac
try
{
if (package->execute (actor,characterController,state,duration))
if (package->execute (actor, characterController, mAiState, duration))
{
// Put repeating noncombat AI packages on the end of the stack so they can be used again
if (isActualAiPackage(packageTypeId) && (mRepeat || package->getRepeat()))
@ -308,7 +304,7 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, boo
if (isActualAiPackage(package.getTypeId()))
stopCombat();
// We should return a wandering actor back after combat or pursuit.
// We should return a wandering actor back after combat, casting or pursuit.
// The same thing for actors without AI packages.
// Also there is no point to stack return packages.
int currentTypeId = getTypeId();
@ -316,7 +312,8 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, boo
if (currentTypeId <= MWMechanics::AiPackage::TypeIdWander
&& !hasPackage(MWMechanics::AiPackage::TypeIdInternalTravel)
&& (newTypeId <= MWMechanics::AiPackage::TypeIdCombat
|| newTypeId == MWMechanics::AiPackage::TypeIdPursue))
|| newTypeId == MWMechanics::AiPackage::TypeIdPursue
|| newTypeId == MWMechanics::AiPackage::TypeIdCast))
{
osg::Vec3f dest;
if (currentTypeId == MWMechanics::AiPackage::TypeIdWander)
@ -352,6 +349,13 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, boo
// insert new package in correct place depending on priority
for(std::list<AiPackage *>::iterator it = mPackages.begin(); it != mPackages.end(); ++it)
{
// We should keep current AiCast package, if we try to add a new one.
if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdCast &&
package.getTypeId() == MWMechanics::AiPackage::TypeIdCast)
{
continue;
}
if((*it)->getPriority() <= package.getPriority())
{
mPackages.insert(it,package.clone());
@ -360,6 +364,14 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, boo
}
mPackages.push_back (package.clone());
// Make sure that temporary storage is empty
if (cancelOther)
{
mAiState.moveIn(new AiCombatStorage());
mAiState.moveIn(new AiFollowStorage());
mAiState.moveIn(new AiWanderStorage());
}
}
AiPackage* MWMechanics::AiSequence::getActivePackage()
@ -494,12 +506,12 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence)
mLastAiPackage = sequence.mLastAiPackage;
}
void AiSequence::fastForward(const MWWorld::Ptr& actor, AiState& state)
void AiSequence::fastForward(const MWWorld::Ptr& actor)
{
if (!mPackages.empty())
{
MWMechanics::AiPackage* package = mPackages.front();
package->fastForward(actor, state);
package->fastForward(actor, mAiState);
}
}

@ -3,6 +3,8 @@
#include <list>
#include "aistate.hpp"
#include <components/esm/loadnpc.hpp>
namespace MWWorld
@ -47,6 +49,7 @@ namespace MWMechanics
/// The type of AI package that ran last
int mLastAiPackage;
AiState mAiState;
public:
///Default constructor
@ -104,10 +107,10 @@ namespace MWMechanics
void stopPursuit();
/// Execute current package, switching if needed.
void execute (const MWWorld::Ptr& actor, CharacterController& characterController, MWMechanics::AiState& state, float duration);
void execute (const MWWorld::Ptr& actor, CharacterController& characterController, float duration);
/// Simulate the passing of time using the currently active AI package
void fastForward(const MWWorld::Ptr &actor, AiState &state);
void fastForward(const MWWorld::Ptr &actor);
/// Remove all packages.
void clear();

@ -51,67 +51,6 @@ namespace MWMechanics
std::string("idle9"),
};
/// \brief This class holds the variables AiWander needs which are deleted if the package becomes inactive.
struct AiWanderStorage : AiTemporaryBase
{
// the z rotation angle to reach
// when mTurnActorGivingGreetingToFacePlayer is true
float mTargetAngleRadians;
bool mTurnActorGivingGreetingToFacePlayer;
float mReaction; // update some actions infrequently
AiWander::GreetingState mSaidGreeting;
int mGreetingTimer;
const MWWorld::CellStore* mCell; // for detecting cell change
// AiWander states
AiWander::WanderState mState;
bool mIsWanderingManually;
bool mCanWanderAlongPathGrid;
unsigned short mIdleAnimation;
std::vector<unsigned short> mBadIdles; // Idle animations that when called cause errors
// do we need to calculate allowed nodes based on mDistance
bool mPopulateAvailableNodes;
// allowed pathgrid nodes based on mDistance from the spawn point
// in local coordinates of mCell
std::vector<ESM::Pathgrid::Point> mAllowedNodes;
ESM::Pathgrid::Point mCurrentNode;
bool mTrimCurrentNode;
float mDoorCheckDuration;
int mStuckCount;
AiWanderStorage():
mTargetAngleRadians(0),
mTurnActorGivingGreetingToFacePlayer(false),
mReaction(0),
mSaidGreeting(AiWander::Greet_None),
mGreetingTimer(0),
mCell(NULL),
mState(AiWander::Wander_ChooseAction),
mIsWanderingManually(false),
mCanWanderAlongPathGrid(true),
mIdleAnimation(0),
mBadIdles(),
mPopulateAvailableNodes(true),
mAllowedNodes(),
mTrimCurrentNode(false),
mDoorCheckDuration(0), // TODO: maybe no longer needed
mStuckCount(0)
{};
void setState(const AiWander::WanderState wanderState, const bool isManualWander = false) {
mState = wanderState;
mIsWanderingManually = isManualWander;
}
};
AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector<unsigned char>& idle, bool repeat):
mDistance(distance), mDuration(duration), mRemainingDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle),
mRepeat(repeat), mStoredInitialActorPosition(false), mInitialActorPosition(osg::Vec3f(0, 0, 0)), mHasDestination(false), mDestination(osg::Vec3f(0, 0, 0))
@ -221,7 +160,7 @@ namespace MWMechanics
mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell()));
if (mPathFinder.isPathConstructed())
storage.setState(Wander_Walking);
storage.setState(AiWanderStorage::Wander_Walking);
}
doPerFrameActionsForState(actor, duration, storage, pos);
@ -270,7 +209,7 @@ namespace MWMechanics
if(actorCanMoveByZ && mDistance > 0) {
// Typically want to idle for a short time before the next wander
if (Misc::Rng::rollDice(100) >= 92 && storage.mState != Wander_Walking) {
if (Misc::Rng::rollDice(100) >= 92 && storage.mState != AiWanderStorage::Wander_Walking) {
wanderNearStart(actor, storage, mDistance);
}
@ -283,7 +222,7 @@ namespace MWMechanics
if (Misc::Rng::rollDice(100) >= 96) {
wanderNearStart(actor, storage, mDistance);
} else {
storage.setState(Wander_IdleNow);
storage.setState(AiWanderStorage::Wander_IdleNow);
}
} else if (storage.mAllowedNodes.empty() && !storage.mIsWanderingManually) {
storage.mCanWanderAlongPathGrid = false;
@ -299,13 +238,13 @@ namespace MWMechanics
mDistance = 0;
// Allow interrupting a walking actor to trigger a greeting
WanderState& wanderState = storage.mState;
if ((wanderState == Wander_IdleNow) || (wanderState == Wander_Walking))
AiWanderStorage::WanderState& wanderState = storage.mState;
if ((wanderState == AiWanderStorage::Wander_IdleNow) || (wanderState == AiWanderStorage::Wander_Walking))
{
playGreetingIfPlayerGetsTooClose(actor, storage);
}
if ((wanderState == Wander_MoveNow) && storage.mCanWanderAlongPathGrid)
if ((wanderState == AiWanderStorage::Wander_MoveNow) && storage.mCanWanderAlongPathGrid)
{
// Construct a new path if there isn't one
if(!mPathFinder.isPathConstructed())
@ -381,7 +320,7 @@ namespace MWMechanics
if (mPathFinder.isPathConstructed())
{
storage.setState(Wander_Walking, true);
storage.setState(AiWanderStorage::Wander_Walking, true);
mHasDestination = true;
}
return;
@ -410,26 +349,26 @@ namespace MWMechanics
void AiWander::completeManualWalking(const MWWorld::Ptr &actor, AiWanderStorage &storage) {
stopWalking(actor, storage);
mObstacleCheck.clear();
storage.setState(Wander_IdleNow);
storage.setState(AiWanderStorage::Wander_IdleNow);
}
void AiWander::doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage, ESM::Position& pos)
{
switch (storage.mState)
{
case Wander_IdleNow:
case AiWanderStorage::Wander_IdleNow:
onIdleStatePerFrameActions(actor, duration, storage);
break;
case Wander_Walking:
case AiWanderStorage::Wander_Walking:
onWalkingStatePerFrameActions(actor, duration, storage, pos);
break;
case Wander_ChooseAction:
case AiWanderStorage::Wander_ChooseAction:
onChooseActionStatePerFrameActions(actor, storage);
break;
case Wander_MoveNow:
case AiWanderStorage::Wander_MoveNow:
break; // nothing to do
default:
@ -451,7 +390,7 @@ namespace MWMechanics
if (mDistance && // actor is not intended to be stationary
proximityToDoor(actor, distance*1.6f))
{
storage.setState(Wander_MoveNow);
storage.setState(AiWanderStorage::Wander_MoveNow);
storage.mTrimCurrentNode = false; // just in case
return;
}
@ -468,13 +407,13 @@ namespace MWMechanics
}
// Check if idle animation finished
GreetingState& greetingState = storage.mSaidGreeting;
if (!checkIdle(actor, storage.mIdleAnimation) && (greetingState == Greet_Done || greetingState == Greet_None))
AiWanderStorage::GreetingState& greetingState = storage.mSaidGreeting;
if (!checkIdle(actor, storage.mIdleAnimation) && (greetingState == AiWanderStorage::Greet_Done || greetingState == AiWanderStorage::Greet_None))
{
if (mPathFinder.isPathConstructed())
storage.setState(Wander_Walking);
storage.setState(AiWanderStorage::Wander_Walking);
else
storage.setState(Wander_ChooseAction);
storage.setState(AiWanderStorage::Wander_ChooseAction);
}
}
@ -485,7 +424,7 @@ namespace MWMechanics
if ((!mPathFinder.isPathConstructed()) || pathTo(actor, ESM::Pathgrid::Point(mPathFinder.getPath().back()), duration, DESTINATION_TOLERANCE))
{
stopWalking(actor, storage);
storage.setState(Wander_ChooseAction);
storage.setState(AiWanderStorage::Wander_ChooseAction);
}
else
{
@ -502,7 +441,7 @@ namespace MWMechanics
if (!idleAnimation && mDistance)
{
storage.setState(Wander_MoveNow);
storage.setState(AiWanderStorage::Wander_MoveNow);
return;
}
if(idleAnimation)
@ -512,13 +451,13 @@ namespace MWMechanics
if(!playIdle(actor, idleAnimation))
{
storage.mBadIdles.push_back(idleAnimation);
storage.setState(Wander_ChooseAction);
storage.setState(AiWanderStorage::Wander_ChooseAction);
return;
}
}
}
storage.setState(Wander_IdleNow);
storage.setState(AiWanderStorage::Wander_IdleNow);
}
void AiWander::evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage, float duration, ESM::Position& pos)
@ -534,7 +473,7 @@ namespace MWMechanics
trimAllowedNodes(storage.mAllowedNodes, mPathFinder);
mObstacleCheck.clear();
stopWalking(actor, storage);
storage.setState(Wander_MoveNow);
storage.setState(AiWanderStorage::Wander_MoveNow);
}
storage.mStuckCount++; // TODO: maybe no longer needed
@ -545,7 +484,7 @@ namespace MWMechanics
{
mObstacleCheck.clear();
stopWalking(actor, storage);
storage.setState(Wander_ChooseAction);
storage.setState(AiWanderStorage::Wander_ChooseAction);
storage.mStuckCount = 0;
}
}
@ -596,8 +535,8 @@ namespace MWMechanics
float playerDistSqr = (playerPos - actorPos).length2();
int& greetingTimer = storage.mGreetingTimer;
GreetingState& greetingState = storage.mSaidGreeting;
if (greetingState == Greet_None)
AiWanderStorage::GreetingState& greetingState = storage.mSaidGreeting;
if (greetingState == AiWanderStorage::Greet_None)
{
if ((playerDistSqr <= helloDistance*helloDistance) &&
!player.getClass().getCreatureStats(player).isDead() && MWBase::Environment::get().getWorld()->getLOS(player, actor)
@ -606,37 +545,37 @@ namespace MWMechanics
if (greetingTimer >= GREETING_SHOULD_START)
{
greetingState = Greet_InProgress;
greetingState = AiWanderStorage::Greet_InProgress;
MWBase::Environment::get().getDialogueManager()->say(actor, "hello");
greetingTimer = 0;
}
}
if (greetingState == Greet_InProgress)
if (greetingState == AiWanderStorage::Greet_InProgress)
{
greetingTimer++;
if (storage.mState == Wander_Walking)
if (storage.mState == AiWanderStorage::Wander_Walking)
{
stopWalking(actor, storage, false);
mObstacleCheck.clear();
storage.setState(Wander_IdleNow);
storage.setState(AiWanderStorage::Wander_IdleNow);
}
turnActorToFacePlayer(actorPos, playerPos, storage);
if (greetingTimer >= GREETING_SHOULD_END)
{
greetingState = Greet_Done;
greetingState = AiWanderStorage::Greet_Done;
greetingTimer = 0;
}
}
if (greetingState == MWMechanics::AiWander::Greet_Done)
if (greetingState == AiWanderStorage::Greet_Done)
{
float resetDist = 2 * helloDistance;
if (playerDistSqr >= resetDist*resetDist)
greetingState = Greet_None;
greetingState = AiWanderStorage::Greet_None;
}
}
@ -676,7 +615,7 @@ namespace MWMechanics
storage.mAllowedNodes.push_back(storage.mCurrentNode);
storage.mCurrentNode = temp;
storage.setState(Wander_Walking);
storage.setState(AiWanderStorage::Wander_Walking);
}
// Choose a different node and delete this one from possible nodes because it is uncreachable:
else

@ -22,7 +22,80 @@ namespace ESM
namespace MWMechanics
{
struct AiWanderStorage;
/// \brief This class holds the variables AiWander needs which are deleted if the package becomes inactive.
struct AiWanderStorage : AiTemporaryBase
{
// the z rotation angle to reach
// when mTurnActorGivingGreetingToFacePlayer is true
float mTargetAngleRadians;
bool mTurnActorGivingGreetingToFacePlayer;
float mReaction; // update some actions infrequently
enum GreetingState
{
Greet_None,
Greet_InProgress,
Greet_Done
};
GreetingState mSaidGreeting;
int mGreetingTimer;
const MWWorld::CellStore* mCell; // for detecting cell change
// AiWander states
enum WanderState
{
Wander_ChooseAction,
Wander_IdleNow,
Wander_MoveNow,
Wander_Walking
};
WanderState mState;
bool mIsWanderingManually;
bool mCanWanderAlongPathGrid;
unsigned short mIdleAnimation;
std::vector<unsigned short> mBadIdles; // Idle animations that when called cause errors
// do we need to calculate allowed nodes based on mDistance
bool mPopulateAvailableNodes;
// allowed pathgrid nodes based on mDistance from the spawn point
// in local coordinates of mCell
std::vector<ESM::Pathgrid::Point> mAllowedNodes;
ESM::Pathgrid::Point mCurrentNode;
bool mTrimCurrentNode;
float mDoorCheckDuration;
int mStuckCount;
AiWanderStorage():
mTargetAngleRadians(0),
mTurnActorGivingGreetingToFacePlayer(false),
mReaction(0),
mSaidGreeting(Greet_None),
mGreetingTimer(0),
mCell(NULL),
mState(Wander_ChooseAction),
mIsWanderingManually(false),
mCanWanderAlongPathGrid(true),
mIdleAnimation(0),
mBadIdles(),
mPopulateAvailableNodes(true),
mAllowedNodes(),
mTrimCurrentNode(false),
mDoorCheckDuration(0), // TODO: maybe no longer needed
mStuckCount(0)
{};
void setState(const WanderState wanderState, const bool isManualWander = false)
{
mState = wanderState;
mIsWanderingManually = isManualWander;
}
};
/// \brief Causes the Actor to wander within a specified range
class AiWander : public AiPackage
@ -52,19 +125,6 @@ namespace MWMechanics
osg::Vec3f getDestination(const MWWorld::Ptr& actor) const;
enum GreetingState {
Greet_None,
Greet_InProgress,
Greet_Done
};
enum WanderState {
Wander_ChooseAction,
Wander_IdleNow,
Wander_MoveNow,
Wander_Walking
};
private:
// NOTE: mDistance and mDuration must be set already
void init();

@ -55,6 +55,7 @@
#include "../mwworld/esmstore.hpp"
#include "../mwworld/player.hpp"
#include "aicombataction.hpp"
#include "movement.hpp"
#include "npcstats.hpp"
#include "creaturestats.hpp"
@ -386,6 +387,10 @@ void CharacterController::refreshJumpAnims(const WeaponInfo* weap, JumpingState
{
jumpmask = MWRender::Animation::BlendMask_LowerBody;
jumpAnimName = "jump";
// For crossbow animations use 1h ones as fallback
if (mWeaponType == WeapType_Crossbow)
jumpAnimName += "1h";
}
}
}
@ -434,11 +439,18 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character
movementAnimName = movestate->groupname;
if(weap != sWeaponTypeListEnd && movementAnimName.find("swim") == std::string::npos)
{
movementAnimName += weap->shortgroup;
if (mWeaponType == WeapType_Spell && (mMovementState == CharState_TurnLeft || mMovementState == CharState_TurnRight)) // Spellcasting stance turning is a special case
movementAnimName = weap->shortgroup + movementAnimName;
else
movementAnimName += weap->shortgroup;
if(!mAnimation->hasAnimation(movementAnimName))
{
movemask = MWRender::Animation::BlendMask_LowerBody;
movementAnimName = movestate->groupname;
// For crossbow animations use 1h ones as fallback
if (mWeaponType == WeapType_Crossbow)
movementAnimName += "1h";
}
}
@ -475,9 +487,11 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character
}
}
/* If we're playing the same animation, restart from the loop start instead of the
* beginning. */
int mode = ((movementAnimName == mCurrentMovement) ? 2 : 1);
// If we're playing the same animation, start it from the point it ended
bool sameAnim = (movementAnimName == mCurrentMovement);
float startPoint = 0.f;
if (sameAnim)
mAnimation->getInfo(mCurrentMovement, &startPoint);
mMovementAnimationControlled = true;
@ -526,7 +540,7 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character
}
mAnimation->play(mCurrentMovement, Priority_Movement, movemask, false,
1.f, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul, true);
1.f, (!sameAnim ? "start" : "loop start"), "stop", startPoint, ~0ul, true);
}
}
}
@ -676,16 +690,19 @@ MWWorld::ContainerStoreIterator getActiveWeapon(CreatureStats &stats, MWWorld::I
void CharacterController::playDeath(float startpoint, CharacterState death)
{
// Make sure the character was swimming upon death for forward-compatibility
const bool wasSwimming = MWBase::Environment::get().getWorld()->isSwimming(mPtr);
switch (death)
{
case CharState_SwimDeath:
mCurrentDeath = "swimdeath";
break;
case CharState_SwimDeathKnockDown:
mCurrentDeath = "swimdeathknockdown";
mCurrentDeath = (wasSwimming ? "swimdeathknockdown" : "deathknockdown");
break;
case CharState_SwimDeathKnockOut:
mCurrentDeath = "swimdeathknockout";
mCurrentDeath = (wasSwimming ? "swimdeathknockout" : "deathknockout");
break;
case CharState_DeathKnockDown:
mCurrentDeath = "deathknockdown";
@ -808,6 +825,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim
, mSecondsOfRunning(0)
, mTurnAnimationThreshold(0)
, mAttackingOrSpell(false)
, mCastingManualSpell(false)
, mTimeUntilWake(0.f)
{
if(!mAnimation)
@ -1021,7 +1039,8 @@ void CharacterController::handleTextKey(const std::string &groupname, const std:
// the same animation for all range types, so there are 3 "release" keys on the same time, one for each range type.
&& evt.compare(off, len, mAttackType + " release") == 0)
{
MWBase::Environment::get().getWorld()->castSpell(mPtr);
MWBase::Environment::get().getWorld()->castSpell(mPtr, mCastingManualSpell);
mCastingManualSpell = false;
}
else if (groupname == "shield" && evt.compare(off, len, "block hit") == 0)
@ -1102,7 +1121,9 @@ bool CharacterController::updateCreatureState()
if (weapType == WeapType_Spell)
{
const std::string spellid = stats.getSpells().getSelectedSpell();
if (!spellid.empty() && MWBase::Environment::get().getWorld()->startSpellCast(mPtr))
bool canCast = mCastingManualSpell || MWBase::Environment::get().getWorld()->startSpellCast(mPtr);
if (!spellid.empty() && canCast)
{
/*
Start of tes3mp addition
@ -1123,7 +1144,7 @@ bool CharacterController::updateCreatureState()
End of tes3mp addition
*/
MWMechanics::CastSpell cast(mPtr, NULL);
MWMechanics::CastSpell cast(mPtr, NULL, false, mCastingManualSpell);
cast.playSpellCastingEffects(spellid);
/*
@ -1138,7 +1159,10 @@ bool CharacterController::updateCreatureState()
*/
if (!mAnimation->hasAnimation("spellcast"))
MWBase::Environment::get().getWorld()->castSpell(mPtr); // No "release" text key to use, so cast immediately
{
MWBase::Environment::get().getWorld()->castSpell(mPtr, mCastingManualSpell); // No "release" text key to use, so cast immediately
mCastingManualSpell = false;
}
else
{
const ESM::Spell *spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(spellid);
@ -1411,8 +1435,9 @@ bool CharacterController::updateWeaponState()
stats.getSpells().setSelectedSpell(selectedSpell);
}
std::string spellid = stats.getSpells().getSelectedSpell();
bool canCast = mCastingManualSpell || MWBase::Environment::get().getWorld()->startSpellCast(mPtr);
if(!spellid.empty() && MWBase::Environment::get().getWorld()->startSpellCast(mPtr))
if(!spellid.empty() && canCast)
{
/*
Start of tes3mp addition
@ -1433,7 +1458,7 @@ bool CharacterController::updateWeaponState()
End of tes3mp addition
*/
MWMechanics::CastSpell cast(mPtr, NULL);
MWMechanics::CastSpell cast(mPtr, NULL, false, mCastingManualSpell);
cast.playSpellCastingEffects(spellid);
const ESM::Spell *spell = store.get<ESM::Spell>().find(spellid);
@ -1711,16 +1736,18 @@ bool CharacterController::updateWeaponState()
break;
}
// Note: apply reload animations only for upper body since blending with movement animations can give weird result.
// Especially noticable with crossbow reload animation.
if(!start.empty())
{
mAnimation->disable(mCurrentWeapon);
if (mUpperBodyState == UpperCharState_FollowStartToFollowStop)
mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::BlendMask_All, true,
MWRender::Animation::BlendMask_UpperBody, true,
weapSpeed, start, stop, 0.0f, 0);
else
mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::BlendMask_All, false,
MWRender::Animation::BlendMask_UpperBody, false,
weapSpeed, start, stop, 0.0f, 0);
}
}
@ -2498,6 +2525,11 @@ bool CharacterController::isAttackPrepairing() const
mUpperBodyState == UpperCharState_MinAttackToMaxAttack;
}
bool CharacterController::isCastingSpell() const
{
return mCastingManualSpell || mUpperBodyState == UpperCharState_CastingSpell;
}
bool CharacterController::isReadyToBlock() const
{
return updateCarriedLeftVisible(mWeaponType);
@ -2561,6 +2593,14 @@ void CharacterController::setAttackingOrSpell(bool attackingOrSpell)
mAttackingOrSpell = attackingOrSpell;
}
void CharacterController::castSpell(const std::string spellId, bool manualSpell)
{
mAttackingOrSpell = true;
mCastingManualSpell = manualSpell;
ActionSpell action = ActionSpell(spellId);
action.prepare(mPtr);
}
void CharacterController::setAIAttackType(const std::string& attackType)
{
mAttackType = attackType;

@ -204,6 +204,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener
std::string mAttackType; // slash, chop or thrust
bool mAttackingOrSpell;
bool mCastingManualSpell;
float mTimeUntilWake;
@ -276,6 +277,7 @@ public:
void forceStateUpdate();
bool isAttackPrepairing() const;
bool isCastingSpell() const;
bool isReadyToBlock() const;
bool isKnockedDown() const;
bool isKnockedOut() const;
@ -286,6 +288,7 @@ public:
bool isAttackingOrSpell() const;
void setAttackingOrSpell(bool attackingOrSpell);
void castSpell(const std::string spellId, bool manualSpell=false);
void setAIAttackType(const std::string& attackType);
static void setAttackTypeRandomly(std::string& attackType);

@ -266,6 +266,12 @@ namespace MWMechanics
mObjects.addObject(ptr);
}
void MechanicsManager::castSpell(const MWWorld::Ptr& ptr, const std::string spellId, bool manualSpell)
{
if(ptr.getClass().isActor())
mActors.castSpell(ptr, spellId, manualSpell);
}
void MechanicsManager::remove(const MWWorld::Ptr& ptr)
{
if(ptr == mWatched)
@ -1824,6 +1830,11 @@ namespace MWMechanics
stats.resurrect();
}
bool MechanicsManager::isCastingSpell(const MWWorld::Ptr &ptr) const
{
return mActors.isCastingSpell(ptr);
}
bool MechanicsManager::isReadyToBlock(const MWWorld::Ptr &ptr) const
{
return mActors.isReadyToBlock(ptr);

@ -200,10 +200,14 @@ namespace MWMechanics
virtual void keepPlayerAlive();
virtual bool isCastingSpell (const MWWorld::Ptr& ptr) const;
virtual bool isReadyToBlock (const MWWorld::Ptr& ptr) const;
/// Is \a ptr casting spell or using weapon now?
virtual bool isAttackingOrSpell(const MWWorld::Ptr &ptr) const;
virtual void castSpell(const MWWorld::Ptr& ptr, const std::string spellId, bool manualSpell=false);
/// Check if the target actor was detected by an observer
/// If the observer is a non-NPC, check all actors in AI processing distance as observers
virtual bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer);

@ -332,13 +332,14 @@ namespace MWMechanics
return true;
}
CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target, const bool fromProjectile)
CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target, const bool fromProjectile, const bool isScripted)
: mCaster(caster)
, mTarget(target)
, mStack(false)
, mHitPosition(0,0,0)
, mAlwaysSucceed(false)
, mFromProjectile(fromProjectile)
, mIsScripted(isScripted)
{
}
@ -961,7 +962,7 @@ namespace MWMechanics
bool godmode = mCaster == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
if (mCaster.getClass().isActor() && !mAlwaysSucceed)
if (mCaster.getClass().isActor() && !mAlwaysSucceed && !mIsScripted)
{
school = getSpellSchool(spell, mCaster);
@ -1025,7 +1026,7 @@ namespace MWMechanics
stats.getSpells().usePower(spell);
}
if (mCaster == getPlayer() && spellIncreasesSkill(spell))
if (mCaster == getPlayer() && spellIncreasesSkill())
mCaster.getClass().skillUsageSucceeded(mCaster,
spellSchoolToSkill(school), 0);
@ -1149,6 +1150,14 @@ namespace MWMechanics
}
}
bool CastSpell::spellIncreasesSkill()
{
if (mIsScripted)
return false;
return MWMechanics::spellIncreasesSkill(mId);
}
int getEffectiveEnchantmentCastCost(float castCost, const MWWorld::Ptr &actor)
{
/*

@ -88,9 +88,10 @@ namespace MWMechanics
osg::Vec3f mHitPosition; // Used for spawning area orb
bool mAlwaysSucceed; // Always succeed spells casted by NPCs/creatures regardless of their chance (default: false)
bool mFromProjectile; // True if spell is cast by enchantment of some projectile (arrow, bolt or thrown weapon)
bool mIsScripted; // True if spell is casted from script and ignores some checks (mana level, success chance, etc.)
public:
CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const bool fromProjectile=false);
CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const bool fromProjectile=false, const bool isScripted=false);
bool cast (const ESM::Spell* spell);
@ -108,6 +109,8 @@ namespace MWMechanics
void playSpellCastingEffects(const std::string &spellid);
bool spellIncreasesSkill();
/// Launch a bolt with the given effects.
void launchMagicBolt ();

@ -31,6 +31,7 @@
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/actionequip.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/containerstore.hpp"
#include "../mwworld/inventorystore.hpp"
@ -215,12 +216,11 @@ namespace MWScript
"to fulfil requirements of Equip instruction" << std::endl;
}
if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr())
MWBase::Environment::get().getWindowManager()->useItem(*it);
if (ptr == MWMechanics::getPlayer())
MWBase::Environment::get().getWindowManager()->useItem(*it, true);
else
{
std::shared_ptr<MWWorld::Action> action = it->getClass().use(*it);
// No equip sound for actors other than the player
std::shared_ptr<MWWorld::Action> action = it->getClass().use(*it, true);
action->execute(ptr, true);
}
}

@ -37,6 +37,7 @@
#include "../mwworld/esmstore.hpp"
#include "../mwworld/cellstore.hpp"
#include "../mwmechanics/aicast.hpp"
#include "../mwmechanics/npcstats.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/spellcasting.hpp"
@ -1153,15 +1154,31 @@ namespace MWScript
{
MWWorld::Ptr ptr = R()(runtime);
std::string spell = runtime.getStringLiteral (runtime[0].mInteger);
std::string spellId = runtime.getStringLiteral (runtime[0].mInteger);
runtime.pop();
std::string targetId = ::Misc::StringUtils::lowerCase(runtime.getStringLiteral (runtime[0].mInteger));
runtime.pop();
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find (spellId);
if (spell && spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power)
{
runtime.getContext().report("spellcasting failed: you can cast only spells and powers.");
return;
}
// Obviously we can not use casting animation for player here
if (ptr.getClass().isActor() && ptr != MWMechanics::getPlayer())
{
MWMechanics::AiCast castPackage(targetId, spellId, true);
ptr.getClass().getCreatureStats (ptr).getAiSequence().stack(castPackage, ptr);
return;
}
MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr (targetId, false);
MWMechanics::CastSpell cast(ptr, target);
MWMechanics::CastSpell cast(ptr, target, false, true);
cast.mHitPosition = target.getRefData().getPosition().asVec3();
cast.mAlwaysSucceed = true;
cast.cast(spell);
@ -1179,7 +1196,7 @@ namespace MWScript
std::string spell = runtime.getStringLiteral (runtime[0].mInteger);
runtime.pop();
MWMechanics::CastSpell cast(ptr, ptr);
MWMechanics::CastSpell cast(ptr, ptr, false, true);
cast.mHitPosition = ptr.getRefData().getPosition().asVec3();
cast.mAlwaysSucceed = true;
cast.cast(spell);

@ -9,12 +9,19 @@
namespace MWWorld
{
ActionAlchemy::ActionAlchemy(bool force)
: Action (false)
, mForce(force)
{
}
void ActionAlchemy::executeImp (const Ptr& actor)
{
if (actor != MWMechanics::getPlayer())
return;
if(MWMechanics::isPlayerInCombat()) { //Ensure we're not in combat
if(!mForce && MWMechanics::isPlayerInCombat())
{ //Ensure we're not in combat
MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage3}");
return;
}

@ -7,7 +7,11 @@ namespace MWWorld
{
class ActionAlchemy : public Action
{
virtual void executeImp (const Ptr& actor);
bool mForce;
virtual void executeImp (const Ptr& actor);
public:
ActionAlchemy(bool force=false);
};
}

@ -14,7 +14,9 @@
namespace MWWorld
{
ActionEquip::ActionEquip (const MWWorld::Ptr& object) : Action (false, object)
ActionEquip::ActionEquip (const MWWorld::Ptr& object, bool force)
: Action (false, object)
, mForce(force)
{
}
@ -23,18 +25,29 @@ namespace MWWorld
MWWorld::Ptr object = getTarget();
MWWorld::InventoryStore& invStore = actor.getClass().getInventoryStore(actor);
std::pair <int, std::string> result = object.getClass().canBeEquipped (object, actor);
if (object.getClass().hasItemHealth(object) && object.getCellRef().getCharge() == 0)
{
if (actor == MWMechanics::getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage1}");
// display error message if the player tried to equip something
if (!result.second.empty() && actor == MWMechanics::getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox(result.second);
return;
}
switch(result.first)
if (!mForce)
{
case 0:
return;
default:
break;
std::pair <int, std::string> result = object.getClass().canBeEquipped (object, actor);
// display error message if the player tried to equip something
if (!result.second.empty() && actor == MWMechanics::getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox(result.second);
switch(result.first)
{
case 0:
return;
default:
break;
}
}
// slots that this item can be equipped in

@ -2,17 +2,18 @@
#define GAME_MWWORLD_ACTIONEQUIP_H
#include "action.hpp"
#include "ptr.hpp"
namespace MWWorld
{
class ActionEquip : public Action
{
virtual void executeImp (const Ptr& actor);
bool mForce;
public:
/// @param item to equip
ActionEquip (const Ptr& object);
virtual void executeImp (const Ptr& actor);
public:
/// @param item to equip
ActionEquip (const Ptr& object, bool force=false);
};
}

@ -8,8 +8,9 @@
namespace MWWorld
{
ActionRepair::ActionRepair(const Ptr &item)
: Action(false, item)
ActionRepair::ActionRepair(const Ptr& item, bool force)
: Action (false, item)
, mForce(force)
{
}
@ -18,7 +19,8 @@ namespace MWWorld
if (actor != MWMechanics::getPlayer())
return;
if(MWMechanics::isPlayerInCombat()) {
if(!mForce && MWMechanics::isPlayerInCombat())
{
MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage2}");
return;
}

@ -7,10 +7,13 @@ namespace MWWorld
{
class ActionRepair : public Action
{
virtual void executeImp (const Ptr& actor);
bool mForce;
virtual void executeImp (const Ptr& actor);
public:
ActionRepair(const MWWorld::Ptr& item);
/// @param item repair hammer
ActionRepair(const Ptr& item, bool force=false);
};
}

@ -113,7 +113,7 @@ namespace MWWorld
return std::shared_ptr<Action> (new NullAction);
}
std::shared_ptr<Action> Class::use (const Ptr& ptr) const
std::shared_ptr<Action> Class::use (const Ptr& ptr, bool force) const
{
return std::shared_ptr<Action> (new NullAction);
}

@ -141,7 +141,7 @@ namespace MWWorld
virtual std::shared_ptr<Action> activate (const Ptr& ptr, const Ptr& actor) const;
///< Generate action for activation (default implementation: return a null action).
virtual std::shared_ptr<Action> use (const Ptr& ptr)
virtual std::shared_ptr<Action> use (const Ptr& ptr, bool force=false)
const;
///< Generate action for using via inventory menu (default implementation: return a
/// null action).

@ -1299,7 +1299,10 @@ namespace MWWorld
if (isPlayer)
{
if (!newCell->isExterior())
{
changeToInteriorCell(Misc::StringUtils::lowerCase(newCell->getCell()->mName), pos, false);
removeContainerScripts(getPlayerPtr());
}
else
{
if (mWorldScene->isCellActive(*newCell))
@ -1320,7 +1323,8 @@ namespace MWWorld
mWorldScene->addObjectToScene(newPtr);
std::string script = newPtr.getClass().getScript(newPtr);
if (!script.empty()) {
if (!script.empty())
{
mLocalScripts.add(script, newPtr);
}
addContainerScripts(newPtr, newCell);
@ -3032,13 +3036,13 @@ namespace MWWorld
return !fail;
}
void World::castSpell(const Ptr &actor)
void World::castSpell(const Ptr &actor, bool manualSpell)
{
MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
// For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result.
std::vector<MWWorld::Ptr> targetActors;
if (!actor.isEmpty() && actor != MWMechanics::getPlayer())
if (!actor.isEmpty() && actor != MWMechanics::getPlayer() && !manualSpell)
actor.getClass().getCreatureStats(actor).getAiSequence().getCombatTargets(targetActors);
const float fCombatDistance = getStore().get<ESM::GameSetting>().find("fCombatDistance")->getFloat();
@ -3056,51 +3060,71 @@ namespace MWWorld
if (target.isEmpty())
{
// For actor targets, we want to use hit contact with bounding boxes.
// This is to give a slight tolerance for errors, especially with creatures like the Skeleton that would be very hard to aim at otherwise.
// For object targets, we want the detailed shapes (rendering raycast).
// If we used the bounding boxes for static objects, then we would not be able to target e.g. objects lying on a shelf.
std::pair<MWWorld::Ptr,osg::Vec3f> result1 = getHitContact(actor, fCombatDistance, targetActors);
// For scripted spells we should not use hit contact
if (manualSpell)
{
// Actors that are targeted by this actor's Follow or Escort packages also side with them
if (actor != MWMechanics::getPlayer())
{
const MWMechanics::CreatureStats &stats = actor.getClass().getCreatureStats(actor);
for (std::list<MWMechanics::AiPackage*>::const_iterator it = stats.getAiSequence().begin(); it != stats.getAiSequence().end(); ++it)
{
if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdCast)
{
target = (*it)->getTarget();
break;
}
}
}
}
else
{
// For actor targets, we want to use hit contact with bounding boxes.
// This is to give a slight tolerance for errors, especially with creatures like the Skeleton that would be very hard to aim at otherwise.
// For object targets, we want the detailed shapes (rendering raycast).
// If we used the bounding boxes for static objects, then we would not be able to target e.g. objects lying on a shelf.
std::pair<MWWorld::Ptr,osg::Vec3f> result1 = getHitContact(actor, fCombatDistance, targetActors);
// Get the target to use for "on touch" effects, using the facing direction from Head node
osg::Vec3f origin = getActorHeadTransform(actor).getTrans();
// Get the target to use for "on touch" effects, using the facing direction from Head node
osg::Vec3f origin = getActorHeadTransform(actor).getTrans();
osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1,0,0))
* osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0,0,-1));
osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1,0,0))
* osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0,0,-1));
osg::Vec3f direction = orient * osg::Vec3f(0,1,0);
float distance = getMaxActivationDistance();
osg::Vec3f dest = origin + direction * distance;
osg::Vec3f direction = orient * osg::Vec3f(0,1,0);
float distance = getMaxActivationDistance();
osg::Vec3f dest = origin + direction * distance;
MWRender::RenderingManager::RayResult result2 = mRendering->castRay(origin, dest, true, true);
MWRender::RenderingManager::RayResult result2 = mRendering->castRay(origin, dest, true, true);
float dist1 = std::numeric_limits<float>::max();
float dist2 = std::numeric_limits<float>::max();
float dist1 = std::numeric_limits<float>::max();
float dist2 = std::numeric_limits<float>::max();
if (!result1.first.isEmpty() && result1.first.getClass().isActor())
dist1 = (origin - result1.second).length();
if (result2.mHit)
dist2 = (origin - result2.mHitPointWorld).length();
if (!result1.first.isEmpty() && result1.first.getClass().isActor())
dist1 = (origin - result1.second).length();
if (result2.mHit)
dist2 = (origin - result2.mHitPointWorld).length();
if (!result1.first.isEmpty() && result1.first.getClass().isActor())
{
target = result1.first;
hitPosition = result1.second;
if (dist1 > getMaxActivationDistance())
target = NULL;
}
else if (result2.mHit)
{
target = result2.mHitObject;
hitPosition = result2.mHitPointWorld;
if (dist2 > getMaxActivationDistance() && !target.isEmpty() && !target.getClass().canBeActivated(target))
target = NULL;
if (!result1.first.isEmpty() && result1.first.getClass().isActor())
{
target = result1.first;
hitPosition = result1.second;
if (dist1 > getMaxActivationDistance())
target = NULL;
}
else if (result2.mHit)
{
target = result2.mHitObject;
hitPosition = result2.mHitPointWorld;
if (dist2 > getMaxActivationDistance() && !target.isEmpty() && !target.getClass().canBeActivated(target))
target = NULL;
}
}
}
std::string selectedSpell = stats.getSpells().getSelectedSpell();
MWMechanics::CastSpell cast(actor, target);
MWMechanics::CastSpell cast(actor, target, false, manualSpell);
cast.mHitPosition = hitPosition;
if (!selectedSpell.empty())

@ -696,7 +696,7 @@ namespace MWWorld
* @brief Cast the actual spell, should be called mid-animation
* @param actor
*/
void castSpell (const MWWorld::Ptr& actor) override;
void castSpell (const MWWorld::Ptr& actor, bool manualSpell=false) override;
void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) override;
void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile,

@ -210,6 +210,11 @@ add_component_dir (fallback
fallback validate
)
if(NOT WIN32 AND NOT ANDROID)
add_component_dir (crashcatcher
crashcatcher
)
endif()
set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui
)

@ -14,7 +14,11 @@
#include <stdbool.h>
#include <sys/ptrace.h>
#include <string>
#include <iostream>
#include <boost/filesystem/fstream.hpp>
namespace bfs = boost::filesystem;
#include <SDL_messagebox.h>
@ -402,7 +406,7 @@ static void crash_handler(const char *logfile)
exit(0);
}
int cc_install_handlers(int argc, char **argv, int num_signals, int *signals, const char *logfile, int (*user_info)(char*, char*))
int crashCatcherInstallHandlers(int argc, char **argv, int num_signals, int *signals, const char *logfile, int (*user_info)(char*, char*))
{
struct sigaction sa;
stack_t altss;
@ -454,19 +458,31 @@ int cc_install_handlers(int argc, char **argv, int num_signals, int *signals, co
return retval;
}
// gdb apparently opens FD(s) 3,4,5 (whereas a typical prog uses only stdin=0, stdout=1,stderr=2)
bool
is_debugger_attached(void)
static bool is_debugger_present()
{
bool rc = false;
FILE *fd = fopen("/tmp", "r");
if (fileno(fd) > 5)
bfs::ifstream file((bfs::path("/proc/self/status")));
while (!file.eof())
{
rc = true;
std::string word;
file >> word;
if (word == "TracerPid:")
{
file >> word;
return word != "0";
}
}
return false;
}
fclose(fd);
return rc;
void crashCatcherInstall(int argc, char **argv, const std::string &crashLogPath)
{
if ((argc == 2 && strcmp(argv[1], "--cc-handle-crash") == 0) || !is_debugger_present())
{
int s[5] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGABRT };
if (crashCatcherInstallHandlers(argc, argv, 5, s, crashLogPath.c_str(), NULL) == -1)
{
std::cerr << "Installing crash handler failed" << std::endl;
} else
std::cout << "Crash handler installed" << std::endl;
}
}

@ -0,0 +1,20 @@
#ifndef CRASHCATCHER_H
#define CRASHCATCHER_H
#include <string>
#if (defined(__APPLE__) || (defined(__linux) && !defined(ANDROID)) || (defined(__unix) && !defined(ANDROID)) || defined(__posix))
#define USE_CRASH_CATCHER 1
#else
#define USE_CRASH_CATCHER 0
#endif
#if USE_CRASH_CATCHER
extern void crashCatcherInstall(int argc, char **argv, const std::string &crashLogPath);
#else
inline void crashCatcherInstall(int, char **, const std::string &crashLogPath)
{
}
#endif
#endif

@ -4,10 +4,24 @@
namespace Nif
{
osg::Quat NIFStream::getQuaternion()
{
float f[4];
readLittleEndianBufferOfType<4, float,uint32_t>(inp, (float*)&f);
osg::Quat quat;
quat.w() = f[0];
quat.x() = f[1];
quat.y() = f[2];
quat.z() = f[3];
return quat;
}
//Private functions
//Public functions
Transformation NIFStream::getTrafo()
{
Transformation t;
t.pos = getVector3();
t.rotation = getMatrix3();
t.scale = getFloat();
return t;
}
}

@ -80,8 +80,8 @@ template<typename type, typename IntegerT> type inline readLittleEndianType(File
return val;
}
class NIFStream {
class NIFStream
{
/// Input stream
Files::IStreamPtr inp;
@ -97,67 +97,67 @@ public:
{
return readLittleEndianType<char,char>(inp);
}
short getShort()
{
return readLittleEndianType<short,short>(inp);
}
unsigned short getUShort()
{
return readLittleEndianType<unsigned short,unsigned short>(inp);
}
int getInt()
{
return readLittleEndianType<int,int>(inp);
}
unsigned int getUInt()
{
return readLittleEndianType<unsigned int,unsigned int>(inp);
}
float getFloat()
{
return readLittleEndianType<float,uint32_t>(inp);
}
osg::Vec2f getVector2() {
osg::Vec2f getVector2()
{
osg::Vec2f vec;
readLittleEndianBufferOfType<2,float,uint32_t>(inp, (float*)&vec._v[0]);
return vec;
}
osg::Vec3f getVector3() {
osg::Vec3f getVector3()
{
osg::Vec3f vec;
readLittleEndianBufferOfType<3, float,uint32_t>(inp, (float*)&vec._v[0]);
return vec;
}
osg::Vec4f getVector4() {
osg::Vec4f getVector4()
{
osg::Vec4f vec;
readLittleEndianBufferOfType<4, float,uint32_t>(inp, (float*)&vec._v[0]);
return vec;
}
Matrix3 getMatrix3() {
Matrix3 getMatrix3()
{
Matrix3 mat;
readLittleEndianBufferOfType<9, float,uint32_t>(inp, (float*)&mat.mValues);
return mat;
}
osg::Quat getQuaternion() {
float f[4];
readLittleEndianBufferOfType<4, float,uint32_t>(inp, (float*)&f);
osg::Quat quat;
quat.w() = f[0];
quat.x() = f[1];
quat.y() = f[2];
quat.z() = f[3];
return quat;
}
Transformation getTrafo() {
Transformation t;
t.pos = getVector3();
t.rotation = getMatrix3();
t.scale = getFloat();
return t;
}
osg::Quat getQuaternion();
Transformation getTrafo();
///Read in a string of the given length
std::string getString(size_t length) {
std::string getString(size_t length)
{
std::vector<char> str(length + 1, 0);
inp->read(&str[0], length);
@ -165,41 +165,54 @@ public:
return &str[0];
}
///Read in a string of the length specified in the file
std::string getString() {
std::string getString()
{
size_t size = readLittleEndianType<uint32_t,uint32_t>(inp);
return getString(size);
}
///This is special since the version string doesn't start with a number, and ends with "\n"
std::string getVersionString() {
std::string getVersionString()
{
std::string result;
std::getline(*inp, result);
return result;
}
void getUShorts(std::vector<unsigned short> &vec, size_t size) {
void getUShorts(std::vector<unsigned short> &vec, size_t size)
{
vec.resize(size);
readLittleEndianDynamicBufferOfType<unsigned short,unsigned short>(inp, &vec.front(), size);
}
void getFloats(std::vector<float> &vec, size_t size) {
void getFloats(std::vector<float> &vec, size_t size)
{
vec.resize(size);
readLittleEndianDynamicBufferOfType<float,uint32_t>(inp, &vec.front(), size);
}
void getVector2s(std::vector<osg::Vec2f> &vec, size_t size) {
void getVector2s(std::vector<osg::Vec2f> &vec, size_t size)
{
vec.resize(size);
/* The packed storage of each Vec2f is 2 floats exactly */
readLittleEndianDynamicBufferOfType<float,uint32_t>(inp,(float*) &vec.front(), size*2);
}
void getVector3s(std::vector<osg::Vec3f> &vec, size_t size) {
void getVector3s(std::vector<osg::Vec3f> &vec, size_t size)
{
vec.resize(size);
/* The packed storage of each Vec3f is 3 floats exactly */
readLittleEndianDynamicBufferOfType<float,uint32_t>(inp, (float*) &vec.front(), size*3);
}
void getVector4s(std::vector<osg::Vec4f> &vec, size_t size) {
void getVector4s(std::vector<osg::Vec4f> &vec, size_t size)
{
vec.resize(size);
/* The packed storage of each Vec4f is 4 floats exactly */
readLittleEndianDynamicBufferOfType<float,uint32_t>(inp, (float*) &vec.front(), size*4);
}
void getQuaternions(std::vector<osg::Quat> &quat, size_t size) {
void getQuaternions(std::vector<osg::Quat> &quat, size_t size)
{
quat.resize(size);
for (size_t i = 0;i < quat.size();i++)
quat[i] = getQuaternion();

@ -34,6 +34,13 @@ btVector3 getbtVector(const osg::Vec3f &v)
return btVector3(v.x(), v.y(), v.z());
}
bool pathFileNameStartsWithX(const std::string& path)
{
const std::size_t slashpos = path.find_last_of("/\\");
const std::size_t letterPos = slashpos == std::string::npos ? 0 : slashpos + 1;
return letterPos < path.size() && (path[letterPos] == 'x' || path[letterPos] == 'X');
}
}
namespace NifBullet
@ -88,18 +95,10 @@ osg::ref_ptr<Resource::BulletShape> BulletNifLoader::load(const Nif::NIFFilePtr&
else
{
bool autogenerated = hasAutoGeneratedCollision(node);
bool isAnimated = false;
// files with the name convention xmodel.nif usually have keyframes stored in a separate file xmodel.kf (see Animation::addAnimSource).
// assume all nodes in the file will be animated
std::string filename = nif->getFilename();
size_t slashpos = filename.find_last_of("/\\");
if (slashpos == std::string::npos)
slashpos = 0;
if (slashpos+1 < filename.size() && (filename[slashpos+1] == 'x' || filename[slashpos+1] == 'X'))
{
isAnimated = true;
}
const bool isAnimated = pathFileNameStartsWithX(nif->getFilename());
handleNode(node, 0, autogenerated, isAnimated, autogenerated);

@ -10,6 +10,8 @@
#include <osgViewer/Viewer>
#include <osgViewer/Renderer>
#include <components/myguiplatform/myguidatamanager.hpp>
namespace Resource
{
@ -19,7 +21,7 @@ StatsHandler::StatsHandler():
_statsType(false),
_statsWidth(1280.0f),
_statsHeight(1024.0f),
_font("fonts/arial.ttf"),
_font(""),
_characterSize(20.0f)
{
_camera = new osg::Camera;
@ -28,6 +30,15 @@ StatsHandler::StatsHandler():
_camera->setProjectionResizePolicy(osg::Camera::FIXED);
_resourceStatsChildNum = 0;
_font = osgMyGUI::DataManager::getInstance().getDataPath("DejaVuLGCSansMono.ttf");
}
Profiler::Profiler()
{
_font = osgMyGUI::DataManager::getInstance().getDataPath("DejaVuLGCSansMono.ttf");
setKeyEventTogglesOnScreenStats(osgGA::GUIEventAdapter::KEY_F3);
}
bool StatsHandler::handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &aa)
@ -77,7 +88,6 @@ void StatsHandler::setWindowSize(int width, int height)
{
_camera->setProjectionMatrix(osg::Matrix::ortho2D(0.0,_statsWidth,_statsHeight-height*_statsWidth/width,_statsHeight));
}
}
void StatsHandler::toggle(osgViewer::ViewerBase *viewer)

@ -1,7 +1,7 @@
#ifndef OPENMW_COMPONENTS_RESOURCE_STATS_H
#define OPENMW_COMPONENTS_RESOURCE_STATS_H
#include <osgGA/GUIEventHandler>
#include <osgViewer/ViewerEventHandlers>
namespace osgViewer
{
@ -15,6 +15,11 @@ namespace osg
namespace Resource
{
class Profiler : public osgViewer::StatsHandler
{
public:
Profiler();
};
class StatsHandler : public osgGA::GUIEventHandler
{

@ -1,2 +1,2 @@
parse_cmake
sphinx
sphinx>=1.7.0

@ -23,7 +23,7 @@ sys.path.insert(0, project_root)
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
needs_sphinx = '1.7'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
@ -34,8 +34,11 @@ extensions = [
'sphinx.ext.todo',
'sphinx.ext.coverage',
'sphinx.ext.viewcode',
'sphinx.ext.autosectionlabel',
]
#autosectionlabel_prefix_document = True
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

@ -1,11 +1,9 @@
Welcome to OpenMW's Documentation!
==================================
Sections
--------
.. toctree::
:maxdepth: 3
:caption: Table of Contents
:maxdepth: 3
manuals/index
reference/index
manuals/index
reference/index

@ -0,0 +1,50 @@
###############
Common Problems
###############
ERROR: Unknown fallback name: FontColor_color_header
####################################################
:Symptoms:
OpenMW crashes at startup with
``ERROR: Unknown fallback name: FontColor_color_header``
message at the end of ``openmw.log``, located `here </docs/source/reference/modding/paths>`_.
:Cause:
The OpenMW `configuration file </docs/source/reference/modding/paths>`_ ``openmw.cfg``
is severely lacking and missing fallback values
because "Settings Importer" was not run correctly.
:Fix:
Re-run "Settings Importer" from the OpenMW launcher.
Installing game files via Steam on macOS: DISK WRITE ERROR
##########################################################
:Symptoms:
Steam stages the download for Morrowind, but does not proceed.
The download will read "Paused: DISK WRITE ERROR".
:Cause:
The OpenMW `configuration file </docs/source/reference/modding/paths>`_ ``openmw.cfg``
is severely lacking and missing fallback values
because "Settings Importer" was not run correctly.
:Fix:
Open appmanifest_22320.acf in your favorite text editor.
Locate or create an entry under the "StateFlags" entry titled "installdir"
and give it the value "Morrowind".
Your file should now look something like this::
"AppState"
{
"appid" "22320"
"Universe" "1"
"name" "The Elder Scrolls III: Morrowind"
"StateFlags" "4"
"installdir" "Morrowind"
[other entries]
}
Restart the Steam client. The download should now proceed.

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save