Merge upstream/master

pull/541/head
AnyOldName3 7 years ago
commit cc23a968d1

@ -1,26 +1,26 @@
# use the official gcc image, based on debian
# can use verions as well, like gcc:5.2
# see https://hub.docker.com/_/gcc/
image: gcc
cache:
key: apt-cache
paths:
- apt-cache/
before_script:
- export APT_CACHE_DIR=`pwd`/apt-cache && mkdir -pv $APT_CACHE_DIR
- apt-get update -yq
- apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libsdl2-dev libqt4-dev libopenal-dev libopenscenegraph-3.4-dev libunshield-dev libtinyxml-dev
# - apt-get install -y libmygui-dev libbullet-dev # to be updated to latest below because stretch is too old
- curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet-dev_2.87+dfsg-2_amd64.deb -o libbullet-dev_2.87+dfsg-2_amd64.deb
- curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet2.87_2.87+dfsg-2_amd64.deb -o libbullet2.87_2.87+dfsg-2_amd64.deb
- curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb -o libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb
- curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb -o libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb
- curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui-dev_3.2.2+dfsg-1_amd64.deb -o libmygui-dev_3.2.2+dfsg-1_amd64.deb
- dpkg --ignore-depends=libmygui.ogreplatform0debian1v5 -i *.deb
build:
stages:
- build
Debian:
tags:
- docker
- linux
image: gcc
cache:
key: apt-cache
paths:
- apt-cache/
before_script:
- export APT_CACHE_DIR=`pwd`/apt-cache && mkdir -pv $APT_CACHE_DIR
- apt-get update -yq
- apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libsdl2-dev libqt4-dev libopenal-dev libopenscenegraph-3.4-dev libunshield-dev libtinyxml-dev
# - apt-get install -y libmygui-dev libbullet-dev # to be updated to latest below because stretch is too old
- curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet-dev_2.87+dfsg-2_amd64.deb -o libbullet-dev_2.87+dfsg-2_amd64.deb
- curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet2.87_2.87+dfsg-2_amd64.deb -o libbullet2.87_2.87+dfsg-2_amd64.deb
- curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb -o libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb
- curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb -o libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb
- curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui-dev_3.2.2+dfsg-1_amd64.deb -o libmygui-dev_3.2.2+dfsg-1_amd64.deb
- dpkg --ignore-depends=libmygui.ogreplatform0debian1v5 -i *.deb
stage: build
script:
- cores_to_use=$((`nproc`-2)); if (( $cores_to_use < 1 )); then cores_to_use=1; fi
@ -30,13 +30,40 @@ build:
artifacts:
paths:
- build/artifacts/
# depending on your build setup it's most likely a good idea to cache outputs to reduce the build time
cache:
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:
- rm -fr build/* # remove anything in the build directory
- CI/before_install.osx.sh
- CI/before_script.osx.sh
- cd build; make -j2 package
artifacts:
paths:
- "*.o"
- build/OpenMW-*.dmg
# TODO: run tests using the binary built before
#test:
# stage: test
# script:
# - ls
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"

@ -15,7 +15,6 @@ env:
# The next declaration is the encrypted COVERITY_SCAN_TOKEN, created
# via the "travis encrypt" command using the project repo's public key
- secure: "jybGzAdUbqt9vWR/GEnRd96BgAi/7Zd1+2HK68j/i/8+/1YH2XxLOy4Jv/DUBhBlJIkxs/Xv8dRcUlFOclZDHX1d/9Qnsqd3oUVkD7k1y7cTOWy9TBQaE/v/kZo3LpzA3xPwwthrb0BvqIbOfIELi5fS5s8ba85WFRg3AX70wWE="
- macos_qt_formula=qt
addons:
apt:
sources:

@ -22,6 +22,7 @@ Programmers
alexanderkjall
Alexander Nadeau (wareya)
Alexander Olofsson (Ace)
Alex S (docwest)
Allofich
Andrei Kortunov (akortunov)
AnyOldName3
@ -63,6 +64,7 @@ Programmers
Evgeniy Mineev (sandstranger)
Federico Guerra (FedeWar)
Fil Krynicki (filkry)
Finbar Crago(finbar-crago)
Florian Weber (Florianjw)
Gašper Sedej
gugus/gus
@ -141,6 +143,7 @@ Programmers
Rohit Nirmal
Roman Melnik (Kromgart)
Roman Proskuryakov (kpp)
Roman Siromakha (elsid)
Sandy Carter (bwrsandman)
Scott Howard
scrawl

@ -2,14 +2,18 @@
------
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
Bug #2562: Forcing AI to activate a teleport door sometimes causes a crash
Bug #2626: Resurrecting the player does not resume the game
Bug #2772: Non-existing class or faction freezes the game
Bug #2835: Player able to slowly move when overencumbered
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
@ -17,19 +21,24 @@
Bug #3876: Landscape texture painting is misaligned
Bug #3897: Have Goodbye give all choices the effects of Goodbye
Bug #3911: [macOS] Typing in the "Content List name" dialog box produces double characters
Bug #3950: FLATTEN_STATIC_TRANSFORMS optimization breaks animated collision shapes
Bug #3993: Terrain texture blending map is not upscaled
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 <BR> tag
Bug #4215: OpenMW shows book text after last EOL tag
Bug #4221: Characters get stuck in V-shaped terrain
Bug #4230: AiTravel package issues break some Tribunal quests
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
@ -46,16 +55,40 @@
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 #4471: Retrieve SDL window settings instead of using magic numbers
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: 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 #4494: 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
Bug #4510: Division by zero in MWMechanics::CreatureStats::setAttribute
Bug #4519: Knockdown does not discard movement in the 1st-person mode
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
Feature #4256: Implement ToggleBorders (TB) console command
Feature #4324: Add CFBundleIdentifier in Info.plist to allow for macOS function key shortcuts
Feature #4345: Add equivalents for the command line commands to Launcher
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 #4488: Make water shader rougher during rain
Feature #4012: Editor: Write a log file if OpenCS crashes
Feature #4509: Show count of enchanted items in stack in the spells list
Feature #4512: Editor: Use markers for lights and creatures levelled lists
Task #2490: Don't open command prompt window on Release-mode builds automatically
Task #4545: Enable is_pod string test
0.44.0
------

@ -4,7 +4,7 @@ brew update
brew outdated cmake || brew upgrade cmake
brew outdated pkgconfig || brew upgrade pkgconfig
brew install $macos_qt_formula
brew install qt
curl https://downloads.openmw.org/osx/dependencies/openmw-deps-100d2e0.zip -o ~/openmw-deps.zip
unzip ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null
curl -fSL -R -J https://downloads.openmw.org/osx/dependencies/openmw-deps-100d2e0.zip -o ~/openmw-deps.zip
unzip -o ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null

@ -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,28 @@ 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
CI_EXTRA_INNO_OPTIONS=""
[ -n "$CI" ] && CI_EXTRA_INNO_OPTIONS="//SUPPRESSMSGBOXES //LOG='boost_install.log'"
"${DEPS}/boost-1.67.0-msvc${MSVC_YEAR}-win${BITS}.exe" //DIR="${CWD_DRIVE_ROOT}" //VERYSILENT //NORESTART ${CI_EXTRA_INNO_OPTIONS}
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 +452,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 +470,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 +513,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 +533,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 +556,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,Shadow}${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,shadow}${SUFFIX}.dll
echo Done.
}
cd $DEPS
echo
# Qt
if [ -z $APPVEYOR ]; then
printf "Qt 5.7.0... "
@ -605,71 +583,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 +639,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 +662,6 @@ if [ ! -z $CI ]; then
-DBUILD_OPENMW=no \
-DBUILD_WIZARD=no
;;
openmw )
echo " Building subproject: OpenMW."
add_cmake_opts -DBUILD_ESSIMPORTER=no \
@ -719,7 +670,6 @@ if [ ! -z $CI ]; then
-DBUILD_OPENCS=no \
-DBUILD_WIZARD=no
;;
opencs )
echo " Building subproject: OpenCS."
add_cmake_opts -DBUILD_ESSIMPORTER=no \
@ -728,7 +678,6 @@ if [ ! -z $CI ]; then
-DBUILD_OPENMW=no \
-DBUILD_WIZARD=no
;;
misc )
echo " Building subprojects: Misc."
add_cmake_opts -DBUILD_OPENCS=no \
@ -736,7 +685,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 +693,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 +707,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 +715,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 +729,4 @@ if [ -z $VERBOSE ]; then
echo Failed.
fi
fi
exit $RET

@ -4,7 +4,7 @@ export CXX=clang++
export CC=clang
DEPENDENCIES_ROOT="/private/tmp/openmw-deps/openmw-deps"
QT_PATH=`brew --prefix $macos_qt_formula`
QT_PATH=`brew --prefix qt`
mkdir build
cd build

@ -610,10 +610,10 @@ if (WIN32)
endif()
if (BUILD_OPENMW)
# Release builds use the debug console
set_target_properties(openmw PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:CONSOLE")
set_target_properties(openmw PROPERTIES COMPILE_DEFINITIONS_RELEASE "_CONSOLE")
set_target_properties(openmw PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:CONSOLE")
# Release builds don't use the debug console
set_target_properties(openmw PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS")
set_target_properties(openmw PROPERTIES COMPILE_DEFINITIONS_RELEASE "_WINDOWS")
set_target_properties(openmw PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:WINDOWS")
endif()
# Play a bit with the warning levels
@ -624,7 +624,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
@ -645,6 +646,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

@ -3,7 +3,7 @@ How to contribute to OpenMW
Not sure what to do with all your free time? Pick out a task from here:
https://bugs.openmw.org/
https://gitlab.com/OpenMW/openmw/issues
Currently, we are focused on completing the MW game experience and general polishing. Features out of this scope may be approved in some cases, but you should probably start a discussion first.

@ -43,7 +43,6 @@ void Launcher::AdvancedPage::on_runScriptAfterStartupBrowseButton_clicked()
QDir::currentPath(),
QString(tr("Text file (*.txt)")));
if (scriptFile.isEmpty())
return;
@ -53,7 +52,7 @@ void Launcher::AdvancedPage::on_runScriptAfterStartupBrowseButton_clicked()
return;
const QString path(QDir::toNativeSeparators(info.absoluteFilePath()));
runScriptAfterStartupField->setText(path);
}
bool Launcher::AdvancedPage::loadSettings()
@ -73,18 +72,8 @@ bool Launcher::AdvancedPage::loadSettings()
loadSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game");
loadSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game");
loadSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game");
loadSettingBool(showEffectDurationCheckBox, "show effect duration", "Game");
loadSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game");
loadSettingBool(showMeleeInfoCheckBox, "show melee info", "Game");
loadSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game");
loadSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game");
// Expected values are (0, 1, 2, 3)
int showOwnedIndex = mEngineSettings.getInt("show owned", "Game");
// Match the index with the option. Will default to 0 if invalid.
if (showOwnedIndex >= 0 && showOwnedIndex <= 3)
showOwnedComboBox->setCurrentIndex(showOwnedIndex);
// Input Settings
loadSettingBool(allowThirdPersonZoomCheckBox, "allow third person zoom", "Input");
loadSettingBool(grabCursorCheckBox, "grab cursor", "Input");
@ -94,6 +83,16 @@ bool Launcher::AdvancedPage::loadSettings()
loadSettingBool(timePlayedCheckbox, "timeplayed", "Saves");
maximumQuicksavesComboBox->setValue(mEngineSettings.getInt("max quicksaves", "Saves"));
// User Interface Settings
loadSettingBool(showEffectDurationCheckBox, "show effect duration", "Game");
loadSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game");
loadSettingBool(showMeleeInfoCheckBox, "show melee info", "Game");
loadSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game");
int showOwnedIndex = mEngineSettings.getInt("show owned", "Game");
// Match the index with the option (only 0, 1, 2, or 3 are valid). Will default to 0 if invalid.
if (showOwnedIndex >= 0 && showOwnedIndex <= 3)
showOwnedComboBox->setCurrentIndex(showOwnedIndex);
// Other Settings
QString screenshotFormatString = QString::fromStdString(mEngineSettings.getString("screenshot format", "General")).toUpper();
if (screenshotFormatComboBox->findText(screenshotFormatString) == -1)
@ -125,16 +124,8 @@ void Launcher::AdvancedPage::saveSettings()
saveSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game");
saveSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game");
saveSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game");
saveSettingBool(showEffectDurationCheckBox, "show effect duration", "Game");
saveSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game");
saveSettingBool(showMeleeInfoCheckBox, "show melee info", "Game");
saveSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game");
saveSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game");
int showOwnedCurrentIndex = showOwnedComboBox->currentIndex();
if (showOwnedCurrentIndex != mEngineSettings.getInt("show owned", "Game"))
mEngineSettings.setInt("show owned", "Game", showOwnedCurrentIndex);
// Input Settings
saveSettingBool(allowThirdPersonZoomCheckBox, "allow third person zoom", "Input");
saveSettingBool(grabCursorCheckBox, "grab cursor", "Input");
@ -147,6 +138,15 @@ void Launcher::AdvancedPage::saveSettings()
mEngineSettings.setInt("max quicksaves", "Saves", maximumQuicksaves);
}
// User Interface Settings
saveSettingBool(showEffectDurationCheckBox, "show effect duration", "Game");
saveSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game");
saveSettingBool(showMeleeInfoCheckBox, "show melee info", "Game");
saveSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game");
int showOwnedCurrentIndex = showOwnedComboBox->currentIndex();
if (showOwnedCurrentIndex != mEngineSettings.getInt("show owned", "Game"))
mEngineSettings.setInt("show owned", "Game", showOwnedCurrentIndex);
// Other Settings
std::string screenshotFormatString = screenshotFormatComboBox->currentText().toLower().toStdString();
if (screenshotFormatString != mEngineSettings.getString("screenshot format", "General"))
@ -167,4 +167,4 @@ void Launcher::AdvancedPage::saveSettingBool(QCheckBox *checkbox, const std::str
void Launcher::AdvancedPage::slotLoadedCellsChanged(QStringList cellNames)
{
loadCellsForAutocomplete(cellNames);
}
}

@ -90,19 +90,19 @@ void Launcher::MainDialog::createIcons()
dataFilesButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
QListWidgetItem *graphicsButton = new QListWidgetItem(iconWidget);
graphicsButton->setIcon(QIcon::fromTheme("video-display"));
graphicsButton->setIcon(QIcon(":/images/preferences-video.png"));
graphicsButton->setText(tr("Graphics"));
graphicsButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom | Qt::AlignAbsolute);
graphicsButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
QListWidgetItem *settingsButton = new QListWidgetItem(iconWidget);
settingsButton->setIcon(QIcon::fromTheme("preferences-system"));
settingsButton->setIcon(QIcon(":/images/preferences.png"));
settingsButton->setText(tr("Settings"));
settingsButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom);
settingsButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
QListWidgetItem *advancedButton = new QListWidgetItem(iconWidget);
advancedButton->setIcon(QIcon::fromTheme("emblem-system"));
advancedButton->setIcon(QIcon(":/images/preferences-advanced.png"));
advancedButton->setText(tr("Advanced"));
advancedButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom);
advancedButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);

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

@ -48,7 +48,7 @@ int main(int argc, char *argv[])
#endif
try
{
{
// To allow background thread drawing in OSG
QApplication::setAttribute(Qt::AA_X11InitThreads, true);
@ -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())
{

@ -201,6 +201,9 @@ void CSMPrefs::State::declare()
declareDouble ("rotate-factor", "Free rotation factor", 0.007).setPrecision(4).setRange(0.0001, 0.1);
declareCategory ("Rendering");
declareInt ("framerate-limit", "FPS limit", 60).
setTooltip("Framerate limit in 3D preview windows. Zero value means \"unlimited\".").
setRange(0, 10000);
declareInt ("camera-fov", "Camera FOV", 90).setRange(10, 170);
declareBool ("camera-ortho", "Orthographic projection for camera", false);
declareInt ("camera-ortho-size", "Orthographic projection size parameter", 100).

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

@ -156,4 +156,4 @@ namespace CSMWorld
}
}
#endif
#endif

@ -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);
@ -622,6 +646,7 @@ void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::strin
}
assert(view);
view->setParent(this);
view->setEditLock (mDocument->getState() & CSMDoc::State_Locked);
mSubViews.append(view); // only after assert
int minWidth = windows["minimum-width"].toInt();

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

@ -102,6 +102,14 @@ void CSVRender::Object::update()
if (recordType == CSMWorld::UniversalId::Type_Light)
{
light = &dynamic_cast<const CSMWorld::Record<ESM::Light>& >(referenceables.getRecord(index)).get();
if (model.empty())
model = "marker_light.nif";
}
if (recordType == CSMWorld::UniversalId::Type_CreatureLevelledList)
{
if (model.empty())
model = "marker_creature.nif";
}
if (model.empty())
@ -372,10 +380,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]]);
}
}

@ -151,6 +151,9 @@ CompositeViewer::CompositeViewer()
connect( &mTimer, SIGNAL(timeout()), this, SLOT(update()) );
mTimer.start( 10 );
int frameRateLimit = CSMPrefs::get()["Rendering"]["framerate-limit"].toInt();
setRunMaxFrameRate(frameRateLimit);
}
CompositeViewer &CompositeViewer::get()
@ -168,6 +171,12 @@ void CompositeViewer::update()
mSimulationTime += dt;
frame(mSimulationTime);
double minFrameTime = _runMaxFrameRate > 0.0 ? 1.0 / _runMaxFrameRate : 0.0;
if (dt < minFrameTime)
{
OpenThreads::Thread::microSleep(1000*1000*(minFrameTime-dt));
}
}
// ---------------------------------------------------
@ -376,6 +385,10 @@ void SceneWidget::settingChanged (const CSMPrefs::Setting *setting)
{
mOrbitCamControl->setConstRoll(setting->isTrue());
}
else if (*setting=="Rendering/framerate-limit")
{
CompositeViewer::get().setRunMaxFrameRate(setting->toInt());
}
else if (*setting=="Rendering/camera-fov" ||
*setting=="Rendering/camera-ortho" ||
*setting=="Rendering/camera-ortho-size")

@ -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);
mLayout->addWidget (&mInput, 0, 1);
connect (&mMode, SIGNAL (activated (int)), this, SLOT (modeSelected (int)));
mInput.insertWidget (1, &mRecordState);
mLayout->addWidget (&mInput, 0, 1);
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);
@ -102,23 +99,24 @@ void CSVTools::SearchBox::setSearchMode (bool enabled)
CSMTools::Search CSVTools::SearchBox::getSearch() const
{
CSMTools::Search::Type type = static_cast<CSMTools::Search::Type> (mMode.currentIndex());
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;

@ -32,7 +32,7 @@ void CSVWorld::DataDisplayDelegate::buildPixmaps ()
while (it != mIcons.end())
{
mPixmaps.push_back (std::make_pair (it->first, it->second.pixmap (mIconSize) ) );
mPixmaps.push_back (std::make_pair (it->mValue, it->mIcon.pixmap (mIconSize) ) );
++it;
}
}
@ -142,9 +142,23 @@ void CSVWorld::DataDisplayDelegate::settingChanged (const CSMPrefs::Setting *set
void CSVWorld::DataDisplayDelegateFactory::add (int enumValue, const QString& enumName, const QString& iconFilename)
{
mIcons.push_back (std::make_pair(enumValue, QIcon(iconFilename)));
EnumDelegateFactory::add(enumValue, enumName);
Icon icon;
icon.mValue = enumValue;
icon.mName = enumName;
icon.mIcon = QIcon(iconFilename);
for (auto it=mIcons.begin(); it!=mIcons.end(); ++it)
{
if (it->mName > enumName)
{
mIcons.insert(it, icon);
return;
}
}
mIcons.push_back(icon);
}
CSVWorld::CommandDelegate *CSVWorld::DataDisplayDelegateFactory::makeDelegate (

@ -11,11 +11,18 @@ namespace CSMPrefs
namespace CSVWorld
{
struct Icon
{
int mValue;
QIcon mIcon;
QString mName;
};
class DataDisplayDelegate : public EnumDelegate
{
public:
typedef std::vector < std::pair < int, QIcon > > IconList;
typedef std::vector<Icon> IconList;
typedef std::vector<std::pair<int, QString> > ValueList;
protected:

@ -110,7 +110,11 @@ void CSVWorld::EnumDelegate::paint (QPainter *painter, const QStyleOptionViewIte
int valueIndex = getValueIndex(index);
if (valueIndex != -1)
{
#if QT_VERSION >= QT_VERSION_CHECK(5,7,0)
QStyleOptionViewItem itemOption(option);
#else
QStyleOptionViewItemV4 itemOption(option);
#endif
itemOption.text = mValues.at(valueIndex).second;
QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &itemOption, painter);
}
@ -171,5 +175,16 @@ CSVWorld::CommandDelegate *CSVWorld::EnumDelegateFactory::makeDelegate (
void CSVWorld::EnumDelegateFactory::add (int value, const QString& name)
{
mValues.push_back (std::make_pair (value, name));
auto pair = std::make_pair (value, name);
for (auto it=mValues.begin(); it!=mValues.end(); ++it)
{
if (it->second > name)
{
mValues.insert(it, pair);
return;
}
}
mValues.push_back(std::make_pair (value, name));
}

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

@ -381,33 +381,24 @@ void OMW::Engine::createWindow(Settings::Manager& settings)
setWindowIcon();
osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
int redSize;
int greenSize;
int blueSize;
int depthSize;
int stencilSize;
int doubleBuffer;
SDL_GL_GetAttribute(SDL_GL_RED_SIZE, &redSize);
SDL_GL_GetAttribute(SDL_GL_GREEN_SIZE, &greenSize);
SDL_GL_GetAttribute(SDL_GL_BLUE_SIZE, &blueSize);
SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &depthSize);
SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &stencilSize);
SDL_GL_GetAttribute(SDL_GL_DOUBLEBUFFER, &doubleBuffer);
SDL_GetWindowPosition(mWindow, &traits->x, &traits->y);
SDL_GetWindowSize(mWindow, &traits->width, &traits->height);
traits->red = redSize;
traits->green = greenSize;
traits->blue = blueSize;
traits->depth = depthSize;
traits->stencil = stencilSize;
traits->doubleBuffer = doubleBuffer;
traits->windowName = SDL_GetWindowTitle(mWindow);
traits->windowDecoration = !(SDL_GetWindowFlags(mWindow)&SDL_WINDOW_BORDERLESS);
traits->screenNum = SDL_GetWindowDisplayIndex(mWindow);
traits->vsync = vsync;
// We tried to get rid of the hardcoding but failed: https://github.com/OpenMW/openmw/pull/1771
// Here goes kcat's quote:
// It's ultimately a chicken and egg problem, and the reason why the code is like it was in the first place.
// It needs a context to get the current attributes, but it needs the attributes to set up the context.
// So it just specifies the same values that were given to SDL in the hopes that it's good enough to what the window eventually gets.
traits->red = 8;
traits->green = 8;
traits->blue = 8;
traits->alpha = 0; // set to 0 to stop ScreenCaptureHandler reading the alpha channel
traits->depth = 24;
traits->stencil = 8;
traits->vsync = vsync;
traits->doubleBuffer = true;
traits->inheritedWindowData = new SDLUtil::GraphicsWindowSDL2::WindowData(mWindow);
osg::ref_ptr<SDLUtil::GraphicsWindowSDL2> graphicsWindow = new SDLUtil::GraphicsWindowSDL2(traits);
@ -646,28 +637,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"));
@ -677,12 +659,24 @@ void OMW::Engine::go()
mEnvironment.setFrameRateLimit(Settings::Manager::getFloat("framerate limit", "Video"));
// Create encoder
ToUTF8::Utf8Encoder encoder (mEncoding);
mEncoder = &encoder;
prepareEngine (settings);
// 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>
@ -24,18 +25,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
/**
* Workaround for problems with whitespaces in paths in older versions of Boost library
*/
@ -339,18 +328,7 @@ int main(int argc, char**argv)
std::cerr.rdbuf (&cerrsb);
#endif
#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]));

@ -225,9 +225,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;

@ -55,6 +55,8 @@ namespace MWBase
virtual void endGame() = 0;
virtual void resumeGame() = 0;
virtual void deleteGame (const MWState::Character *character, const MWState::Slot *slot) = 0;
virtual void saveGame (const std::string& description, const MWState::Slot *slot = 0) = 0;

@ -146,7 +146,7 @@ namespace MWBase
virtual MWGui::TradeWindow* getTradeWindow() = 0;
/// 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;

@ -297,7 +297,7 @@ namespace MWBase
///< Queues movement for \a ptr (in local space), to be applied in the next call to
/// doPhysics.
virtual bool castRay (float x1, float y1, float z1, float x2, float y2, float z2) = 0;
virtual bool castRay (float x1, float y1, float z1, float x2, float y2, float z2, bool ignoreDoors=false) = 0;
///< cast a Ray and return true if there is an object in the ray path.
virtual bool toggleCollisionMode() = 0;
@ -487,7 +487,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,

@ -124,10 +124,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;

@ -354,9 +354,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;

@ -162,7 +162,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;

@ -243,9 +243,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;

@ -519,6 +519,9 @@ namespace MWClass
float Creature::getSpeed(const MWWorld::Ptr &ptr) const
{
MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead())
return 0.f;
const GMST& gmst = getGmst();
float walkSpeed = gmst.fMinWalkSpeedCreature->getFloat() + 0.01f * stats.getAttribute(ESM::Attribute::Speed).getModified()
@ -611,7 +614,7 @@ namespace MWClass
float Creature::getCapacity (const MWWorld::Ptr& ptr) const
{
const MWMechanics::CreatureStats& stats = getCreatureStats (ptr);
return static_cast<float>(stats.getAttribute(0).getModified() * 5);
return static_cast<float>(stats.getAttribute(ESM::Attribute::Strength).getModified() * 5);
}
int Creature::getServices(const MWWorld::ConstPtr &actor) const

@ -75,7 +75,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;

@ -226,7 +226,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;

@ -773,22 +773,24 @@ namespace MWClass
float x = damage / (damage + getArmorRating(ptr));
damage *= std::max(gmst.fCombatArmorMinMult->getFloat(), x);
int damageDiff = static_cast<int>(unmitigatedDamage - damage);
if (damage < 1)
damage = 1;
damage = std::max(1.f, damage);
damageDiff = std::max(1, damageDiff);
MWWorld::InventoryStore &inv = getInventoryStore(ptr);
MWWorld::ContainerStoreIterator armorslot = inv.getSlot(hitslot);
MWWorld::Ptr armor = ((armorslot != inv.end()) ? *armorslot : MWWorld::Ptr());
if(!armor.isEmpty() && armor.getTypeName() == typeid(ESM::Armor).name())
{
int armorhealth = armor.getClass().getItemHealth(armor);
armorhealth -= std::min(std::max(1, damageDiff),
armorhealth);
armor.getCellRef().setCharge(armorhealth);
if (attacker.isEmpty() || (!attacker.isEmpty() && !(object.isEmpty() && !attacker.getClass().isNpc()))) // Unarmed creature attacks don't affect armor condition
{
int armorhealth = armor.getClass().getItemHealth(armor);
armorhealth -= std::min(damageDiff, armorhealth);
armor.getCellRef().setCharge(armorhealth);
// Armor broken? unequip it
if (armorhealth == 0)
armor = *inv.unequipItem(armor, ptr);
// Armor broken? unequip it
if (armorhealth == 0)
armor = *inv.unequipItem(armor, ptr);
}
if (ptr == MWMechanics::getPlayer())
skillUsageSucceeded(ptr, armor.getClass().getEquipmentSkill(armor), 0);
@ -923,6 +925,10 @@ namespace MWClass
float Npc::getSpeed(const MWWorld::Ptr& ptr) const
{
const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead())
return 0.f;
const MWBase::World *world = MWBase::Environment::get().getWorld();
const GMST& gmst = getGmst();
@ -931,8 +937,8 @@ namespace MWClass
const float normalizedEncumbrance = getNormalizedEncumbrance(ptr);
bool sneaking = ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Sneak);
bool running = ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run);
bool sneaking = stats.getStance(MWMechanics::CreatureStats::Stance_Sneak);
bool running = stats.getStance(MWMechanics::CreatureStats::Stance_Run);
float walkSpeed = gmst.fMinWalkSpeed->getFloat() + 0.01f*npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified()*
(gmst.fMaxWalkSpeed->getFloat() - gmst.fMinWalkSpeed->getFloat());
@ -1073,7 +1079,7 @@ namespace MWClass
{
const MWMechanics::CreatureStats& stats = getCreatureStats (ptr);
static const float fEncumbranceStrMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fEncumbranceStrMult")->getFloat();
return stats.getAttribute(0).getModified()*fEncumbranceStrMult;
return stats.getAttribute(ESM::Attribute::Strength).getModified()*fEncumbranceStrMult;
}
float Npc::getEncumbrance (const MWWorld::Ptr& ptr) const

@ -139,7 +139,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;

@ -149,9 +149,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).

@ -401,9 +401,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();
}

@ -31,13 +31,16 @@ namespace MWGui
boost::algorithm::replace_all(mText, "\r", "");
// vanilla game does not show any text after last <BR> tag.
// vanilla game does not show any text after the last EOL tag.
const std::string lowerText = Misc::StringUtils::lowerCase(mText);
int index = lowerText.rfind("<br>");
if (index == -1)
int brIndex = lowerText.rfind("<br>");
int pIndex = lowerText.rfind("<p>");
if (brIndex == pIndex)
mText = "";
else if (brIndex > pIndex)
mText = mText.substr(0, brIndex+4);
else
mText = mText.substr(0, index+4);
mText = mText.substr(0, pIndex+3);
registerTag("br", Event_BrTag);
registerTag("p", Event_PTag);

@ -158,7 +158,9 @@ namespace MWGui
getWidget(mCrosshair, "Crosshair");
LocalMapBase::init(mMinimap, mCompass, Settings::Manager::getInt("local map hud widget size", "Map"), Settings::Manager::getInt("local map cell distance", "Map"));
int mapSize = std::max(1, Settings::Manager::getInt("local map hud widget size", "Map"));
int cellDistance = std::max(1, Settings::Manager::getInt("local map cell distance", "Map"));
LocalMapBase::init(mMinimap, mCompass, mapSize, cellDistance);
mMainWidget->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onWorldClicked);
mMainWidget->eventMouseMove += MyGUI::newDelegate(this, &HUD::onWorldMouseOver);

@ -25,7 +25,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"
@ -474,7 +474,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);
@ -483,13 +483,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
@ -512,9 +523,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);

@ -679,7 +679,9 @@ namespace MWGui
mEventBoxLocal->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart);
mEventBoxLocal->eventMouseButtonDoubleClick += MyGUI::newDelegate(this, &MapWindow::onMapDoubleClicked);
LocalMapBase::init(mLocalMap, mPlayerArrowLocal, Settings::Manager::getInt("local map widget size", "Map"), Settings::Manager::getInt("local map cell distance", "Map"));
int mapSize = std::max(1, Settings::Manager::getInt("local map widget size", "Map"));
int cellDistance = std::max(1, Settings::Manager::getInt("local map cell distance", "Map"));
LocalMapBase::init(mLocalMap, mPlayerArrowLocal, mapSize, cellDistance);
mGlobalMap->setVisible(mGlobal);
mLocalMap->setVisible(!mGlobal);

@ -81,7 +81,7 @@ namespace MWGui
MyGUI::Widget* getDefaultKeyFocus() override;
virtual bool exit() { return false; }
virtual bool exit() override { return false; }
bool mMarkedToDelete;

@ -33,44 +33,41 @@ namespace MWGui
QuickKeysMenu::QuickKeysMenu()
: WindowBase("openmw_quickkeys_menu.layout")
, mKey(std::vector<keyData>(10))
, mSelected(nullptr)
, mActivated(nullptr)
, mAssignDialog(0)
, mItemSelectionDialog(0)
, mMagicSelectionDialog(0)
, mSelectedIndex(-1)
, mActivatedIndex(-1)
{
getWidget(mOkButton, "OKButton");
getWidget(mInstructionLabel, "InstructionLabel");
mMainWidget->setSize(mMainWidget->getWidth(),
mMainWidget->getHeight() + (mInstructionLabel->getTextSize().height - mInstructionLabel->getHeight()));
mMainWidget->getHeight() +
(mInstructionLabel->getTextSize().height - mInstructionLabel->getHeight()));
mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &QuickKeysMenu::onOkButtonClicked);
center();
for (int i = 0; i < 10; ++i)
{
ItemWidget* button;
getWidget(button, "QuickKey" + MyGUI::utility::toString(i+1));
button->eventMouseButtonClick += MyGUI::newDelegate(this, &QuickKeysMenu::onQuickKeyButtonClicked);
mQuickKeyButtons.push_back(button);
mAssigned.push_back(Type_Unassigned);
mKey[i].index = i+1;
getWidget(mKey[i].button, "QuickKey" + MyGUI::utility::toString(i+1));
mKey[i].button->eventMouseButtonClick += MyGUI::newDelegate(this, &QuickKeysMenu::onQuickKeyButtonClicked);
unassign(button, i);
unassign(&mKey[i]);
}
}
void QuickKeysMenu::clear()
{
mActivatedIndex = -1;
mActivated = nullptr;
for (int i=0; i<10; ++i)
{
unassign(mQuickKeyButtons[i], i);
unassign(&mKey[i]);
}
}
@ -91,10 +88,7 @@ namespace MWGui
// Check if quick keys are still valid
for (int i=0; i<10; ++i)
{
ItemWidget* button = mQuickKeyButtons[i];
int type = mAssigned[i];
switch (type)
switch (mKey[i].type)
{
case Type_Unassigned:
case Type_HandToHand:
@ -103,49 +97,55 @@ namespace MWGui
case Type_Item:
case Type_MagicItem:
{
MWWorld::Ptr item = *button->getUserData<MWWorld::Ptr>();
MWWorld::Ptr item = *mKey[i].button->getUserData<MWWorld::Ptr>();
// Make sure the item is available and is not broken
if (item.getRefData().getCount() < 1 ||
if (!item || item.getRefData().getCount() < 1 ||
(item.getClass().hasItemHealth(item) &&
item.getClass().getItemHealth(item) <= 0))
{
// Try searching for a compatible replacement
std::string id = item.getCellRef().getRefId();
item = store.findReplacement(mKey[i].id);
if (item)
mKey[i].button->setUserData(MWWorld::Ptr(item));
item = store.findReplacement(id);
button->setUserData(MWWorld::Ptr(item));
break;
}
}
}
}
}
void QuickKeysMenu::unassign(ItemWidget* key, int index)
void QuickKeysMenu::unassign(keyData* key)
{
key->clearUserStrings();
key->setItem(MWWorld::Ptr());
while (key->getChildCount()) // Destroy number label
MyGUI::Gui::getInstance().destroyWidget(key->getChildAt(0));
key->button->clearUserStrings();
key->button->setItem(MWWorld::Ptr());
if (index == 9)
while (key->button->getChildCount()) // Destroy number label
MyGUI::Gui::getInstance().destroyWidget(key->button->getChildAt(0));
if (key->index == 10)
{
mAssigned[index] = Type_HandToHand;
key->type = Type_HandToHand;
MyGUI::ImageBox* image = key->createWidget<MyGUI::ImageBox>("ImageBox",
MyGUI::ImageBox* image = key->button->createWidget<MyGUI::ImageBox>("ImageBox",
MyGUI::IntCoord(14, 13, 32, 32), MyGUI::Align::Default);
image->setImageTexture("icons\\k\\stealth_handtohand.dds");
image->setNeedMouseFocus(false);
}
else
{
mAssigned[index] = Type_Unassigned;
key->type = Type_Unassigned;
key->id = "";
key->name = "";
MyGUI::TextBox* textBox = key->button->createWidgetReal<MyGUI::TextBox>("SandText",
MyGUI::FloatCoord(0,0,1,1), MyGUI::Align::Default);
MyGUI::TextBox* textBox = key->createWidgetReal<MyGUI::TextBox>("SandText", MyGUI::FloatCoord(0,0,1,1), MyGUI::Align::Default);
textBox->setTextAlign (MyGUI::Align::Center);
textBox->setCaption (MyGUI::utility::toString(index+1));
textBox->setNeedMouseFocus (false);
textBox->setTextAlign(MyGUI::Align::Center);
textBox->setCaption(MyGUI::utility::toString(key->index));
textBox->setNeedMouseFocus(false);
}
}
@ -154,19 +154,24 @@ namespace MWGui
int index = -1;
for (int i = 0; i < 10; ++i)
{
if (sender == mQuickKeyButtons[i] || sender->getParent () == mQuickKeyButtons[i])
if (sender == mKey[i].button || sender->getParent() == mKey[i].button)
{
index = i;
break;
}
}
assert(index != -1);
mSelectedIndex = index;
mSelected = &mKey[index];
// prevent reallocation of zero key from Type_HandToHand
if(mSelected->index == 10)
return;
// open assign dialog
if (!mAssignDialog)
mAssignDialog = new QuickKeysMenuAssign(this);
mAssignDialog->setVisible (true);
mAssignDialog->setVisible(true);
}
void QuickKeysMenu::onOkButtonClicked (MyGUI::Widget *sender)
@ -174,10 +179,9 @@ namespace MWGui
MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_QuickKeysMenu);
}
void QuickKeysMenu::onItemButtonClicked(MyGUI::Widget* sender)
{
if (!mItemSelectionDialog )
if (!mItemSelectionDialog)
{
mItemSelectionDialog = new ItemSelectionDialog("#{sQuickMenu6}");
mItemSelectionDialog->eventItemSelected += MyGUI::newDelegate(this, &QuickKeysMenu::onAssignItem);
@ -187,43 +191,45 @@ namespace MWGui
mItemSelectionDialog->openContainer(MWMechanics::getPlayer());
mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyUsableItems);
mAssignDialog->setVisible (false);
mAssignDialog->setVisible(false);
}
void QuickKeysMenu::onMagicButtonClicked(MyGUI::Widget* sender)
{
if (!mMagicSelectionDialog )
if (!mMagicSelectionDialog)
{
mMagicSelectionDialog = new MagicSelectionDialog(this);
}
mMagicSelectionDialog->setVisible(true);
mAssignDialog->setVisible (false);
mAssignDialog->setVisible(false);
}
void QuickKeysMenu::onUnassignButtonClicked(MyGUI::Widget* sender)
{
unassign(mQuickKeyButtons[mSelectedIndex], mSelectedIndex);
mAssignDialog->setVisible (false);
unassign(mSelected);
mAssignDialog->setVisible(false);
}
void QuickKeysMenu::onCancelButtonClicked(MyGUI::Widget* sender)
{
mAssignDialog->setVisible (false);
mAssignDialog->setVisible(false);
}
void QuickKeysMenu::onAssignItem(MWWorld::Ptr item)
{
assert (mSelectedIndex >= 0);
ItemWidget* button = mQuickKeyButtons[mSelectedIndex];
while (button->getChildCount()) // Destroy number label
MyGUI::Gui::getInstance().destroyWidget(button->getChildAt(0));
assert(mSelected);
while (mSelected->button->getChildCount()) // Destroy number label
MyGUI::Gui::getInstance().destroyWidget(mSelected->button->getChildAt(0));
mAssigned[mSelectedIndex] = Type_Item;
mSelected->type = Type_Item;
mSelected->id = item.getCellRef().getRefId();
mSelected->name = item.getClass().getName(item);
button->setItem(item, ItemWidget::Barter);
button->setUserString ("ToolTipType", "ItemPtr");
button->setUserData(MWWorld::Ptr(item));
mSelected->button->setItem(item, ItemWidget::Barter);
mSelected->button->setUserString("ToolTipType", "ItemPtr");
mSelected->button->setUserData(item);
if (mItemSelectionDialog)
mItemSelectionDialog->setVisible(false);
@ -234,40 +240,39 @@ namespace MWGui
mItemSelectionDialog->setVisible(false);
}
void QuickKeysMenu::onAssignMagicItem (MWWorld::Ptr item)
void QuickKeysMenu::onAssignMagicItem(MWWorld::Ptr item)
{
assert (mSelectedIndex >= 0);
ItemWidget* button = mQuickKeyButtons[mSelectedIndex];
while (button->getChildCount()) // Destroy number label
MyGUI::Gui::getInstance().destroyWidget(button->getChildAt(0));
assert(mSelected);
mAssigned[mSelectedIndex] = Type_MagicItem;
while (mSelected->button->getChildCount()) // Destroy number label
MyGUI::Gui::getInstance().destroyWidget(mSelected->button->getChildAt(0));
button->setFrame("textures\\menu_icon_select_magic_magic.dds", MyGUI::IntCoord(2, 2, 40, 40));
button->setIcon(item);
mSelected->type = Type_MagicItem;
button->setUserString ("ToolTipType", "ItemPtr");
button->setUserData(MWWorld::Ptr(item));
mSelected->button->setFrame("textures\\menu_icon_select_magic_magic.dds", MyGUI::IntCoord(2, 2, 40, 40));
mSelected->button->setIcon(item);
mSelected->button->setUserString("ToolTipType", "ItemPtr");
mSelected->button->setUserData(MWWorld::Ptr(item));
if (mMagicSelectionDialog)
mMagicSelectionDialog->setVisible(false);
}
void QuickKeysMenu::onAssignMagic (const std::string& spellId)
void QuickKeysMenu::onAssignMagic(const std::string& spellId)
{
assert (mSelectedIndex >= 0);
ItemWidget* button = mQuickKeyButtons[mSelectedIndex];
while (button->getChildCount()) // Destroy number label
MyGUI::Gui::getInstance().destroyWidget(button->getChildAt(0));
assert(mSelected);
while (mSelected->button->getChildCount()) // Destroy number label
MyGUI::Gui::getInstance().destroyWidget(mSelected->button->getChildAt(0));
mAssigned[mSelectedIndex] = Type_Magic;
mSelected->type = Type_Magic;
mSelected->id = spellId;
button->setItem(MWWorld::Ptr());
button->setUserString ("ToolTipType", "Spell");
button->setUserString ("Spell", spellId);
mSelected->button->setItem(MWWorld::Ptr());
mSelected->button->setUserString("ToolTipType", "Spell");
mSelected->button->setUserString("Spell", spellId);
const MWWorld::ESMStore &esmStore =
MWBase::Environment::get().getWorld()->getStore();
const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore();
// use the icon of the first effect
const ESM::Spell* spell = esmStore.get<ESM::Spell>().find(spellId);
@ -280,14 +285,14 @@ namespace MWGui
path.insert(slashPos+1, "b_");
path = MWBase::Environment::get().getWindowManager()->correctIconPath(path);
button->setFrame("textures\\menu_icon_select_magic.dds", MyGUI::IntCoord(2, 2, 40, 40));
button->setIcon(path);
mSelected->button->setFrame("textures\\menu_icon_select_magic.dds", MyGUI::IntCoord(2, 2, 40, 40));
mSelected->button->setIcon(path);
if (mMagicSelectionDialog)
mMagicSelectionDialog->setVisible(false);
}
void QuickKeysMenu::onAssignMagicCancel ()
void QuickKeysMenu::onAssignMagicCancel()
{
mMagicSelectionDialog->setVisible(false);
}
@ -295,18 +300,17 @@ namespace MWGui
void QuickKeysMenu::updateActivatedQuickKey()
{
// there is no delayed action, nothing to do.
if (mActivatedIndex < 0)
if (!mActivated)
return;
activateQuickKey(mActivatedIndex);
activateQuickKey(mActivated->index);
}
void QuickKeysMenu::activateQuickKey(int index)
{
assert (index-1 >= 0);
ItemWidget* button = mQuickKeyButtons[index-1];
assert(index >= 1 && index <= 10);
QuickKeyType type = mAssigned[index-1];
keyData *key = &mKey[index-1];
MWWorld::Ptr player = MWMechanics::getPlayer();
MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player);
@ -319,117 +323,112 @@ namespace MWGui
|| playerStats.getHitRecovery();
bool isReturnNeeded = playerStats.isParalyzed() || playerStats.isDead();
if (isReturnNeeded && type != Type_Item)
if (isReturnNeeded && key->type != Type_Item)
{
return;
}
if (isDelayNeeded && type != Type_Item)
else if (isDelayNeeded && key->type != Type_Item)
{
mActivatedIndex = index;
mActivated = key;
return;
}
else
mActivatedIndex = -1;
{
mActivated = nullptr;
}
if (type == Type_Item || type == Type_MagicItem)
if (key->type == Type_Item || key->type == Type_MagicItem)
{
MWWorld::Ptr item = *button->getUserData<MWWorld::Ptr>();
// Make sure the item is available and is not broken
if (item.getRefData().getCount() < 1 ||
(item.getClass().hasItemHealth(item) &&
item.getClass().getItemHealth(item) <= 0))
MWWorld::Ptr item = *key->button->getUserData<MWWorld::Ptr>();
MWWorld::ContainerStoreIterator it = store.begin();
for (; it != store.end(); ++it)
{
// Try searching for a compatible replacement
std::string id = item.getCellRef().getRefId();
if (*it == item)
break;
}
if (it == store.end())
item = nullptr;
item = store.findReplacement(id);
button->setUserData(MWWorld::Ptr(item));
// check the item is available and not broken
if (!item || item.getRefData().getCount() < 1 ||
(item.getClass().hasItemHealth(item) && item.getClass().getItemHealth(item) <= 0))
{
item = store.findReplacement(key->id);
if (item.getRefData().getCount() < 1)
if (!item || item.getRefData().getCount() < 1)
{
// No replacement was found
MWBase::Environment::get().getWindowManager ()->messageBox (
"#{sQuickMenu5} " + item.getClass().getName(item));
MWBase::Environment::get().getWindowManager()->messageBox(
"#{sQuickMenu5} " + key->name);
return;
}
}
}
if (type == Type_Magic)
{
std::string spellId = button->getUserString("Spell");
// Make sure the player still has this spell
MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player);
MWMechanics::Spells& spells = stats.getSpells();
if (!spells.hasSpell(spellId))
if (key->type == Type_Item)
{
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(spellId);
MWBase::Environment::get().getWindowManager()->messageBox (
"#{sQuickMenu5} " + spell->mName);
return;
}
store.setSelectedEnchantItem(store.end());
MWBase::Environment::get().getWindowManager()->setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, player)));
MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Spell);
}
else if (type == Type_Item)
{
MWWorld::Ptr item = *button->getUserData<MWWorld::Ptr>();
bool isWeapon = item.getTypeName() == typeid(ESM::Weapon).name();
bool isTool = item.getTypeName() == typeid(ESM::Probe).name() || item.getTypeName() == typeid(ESM::Lockpick).name();
bool isWeapon = item.getTypeName() == typeid(ESM::Weapon).name();
bool isTool = item.getTypeName() == typeid(ESM::Probe).name() ||
item.getTypeName() == typeid(ESM::Lockpick).name();
// delay weapon switching if player is busy
if (isDelayNeeded && (isWeapon || isTool))
{
mActivatedIndex = index;
return;
}
// delay weapon switching if player is busy
if (isDelayNeeded && (isWeapon || isTool))
{
mActivated = key;
return;
}
else if (isReturnNeeded && (isWeapon || isTool))
{
return;
}
// disable weapon switching if player is dead or paralyzed
if (isReturnNeeded && (isWeapon || isTool))
{
return;
MWBase::Environment::get().getWindowManager()->useItem(item);
MWWorld::ConstContainerStoreIterator rightHand = store.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
// change draw state only if the item is in player's right hand
if (rightHand != store.end() && item == *rightHand)
{
MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon);
}
}
MWBase::Environment::get().getWindowManager()->useItem(item);
MWWorld::ConstContainerStoreIterator rightHand = store.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
// change draw state only if the item is in player's right hand
if (rightHand != store.end() && item == *rightHand)
else if (key->type == Type_MagicItem)
{
MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon);
// equip, if it can be equipped
if (!item.getClass().getEquipmentSlots(item).first.empty())
{
MWBase::Environment::get().getWindowManager()->useItem(item);
// make sure that item was successfully equipped
if (!store.isEquipped(item))
return;
}
store.setSelectedEnchantItem(it);
MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Spell);
}
}
else if (type == Type_MagicItem)
else if (key->type == Type_Magic)
{
MWWorld::Ptr item = *button->getUserData<MWWorld::Ptr>();
std::string spellId = key->id;
// retrieve ContainerStoreIterator to the item
MWWorld::ContainerStoreIterator it = store.begin();
for (; it != store.end(); ++it)
{
if (*it == item)
{
break;
}
}
assert(it != store.end());
// Make sure the player still has this spell
MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player);
MWMechanics::Spells& spells = stats.getSpells();
// equip, if it can be equipped
if (!item.getClass().getEquipmentSlots(item).first.empty())
if (!spells.hasSpell(spellId))
{
MWBase::Environment::get().getWindowManager()->useItem(item);
// make sure that item was successfully equipped
if (!store.isEquipped(item))
return;
const ESM::Spell* spell =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(spellId);
MWBase::Environment::get().getWindowManager()->messageBox("#{sQuickMenu5} " + spell->mName);
return;
}
store.setSelectedEnchantItem(it);
store.setSelectedEnchantItem(store.end());
MWBase::Environment::get().getWindowManager()
->setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, player)));
MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Spell);
}
else if (type == Type_HandToHand)
else if (key->type == Type_HandToHand)
{
store.unequipSlot(MWWorld::InventoryStore::Slot_CarriedRight, player);
MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon);
@ -491,9 +490,9 @@ namespace MWGui
for (int i=0; i<10; ++i)
{
ItemWidget* button = mQuickKeyButtons[i];
ItemWidget* button = mKey[i].button;
int type = mAssigned[i];
int type = mKey[i].type;
ESM::QuickKeys::QuickKey key;
key.mType = type;
@ -541,30 +540,29 @@ namespace MWGui
if (i >= 10)
return;
mSelectedIndex = i;
int keyType = it->mType;
std::string id = it->mId;
ItemWidget* button = mQuickKeyButtons[i];
mSelected = &mKey[i];
mSelected->type = (QuickKeysMenu::QuickKeyType) it->mType;
mSelected->id = it->mId;
switch (keyType)
switch (mSelected->type)
{
case Type_Magic:
if (MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(id))
onAssignMagic(id);
if (MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(mSelected->id))
onAssignMagic(mSelected->id);
break;
case Type_Item:
case Type_MagicItem:
{
// Find the item by id
MWWorld::Ptr item = store.findReplacement(id);
MWWorld::Ptr item = store.findReplacement(mSelected->id);
if (item.isEmpty())
unassign(button, i);
unassign(&mKey[i]);
else
{
if (keyType == Type_Item)
if (mSelected->type == Type_Item)
onAssignItem(item);
else if (keyType == Type_MagicItem)
else if (mSelected->type == Type_MagicItem)
onAssignMagicItem(item);
}
@ -572,7 +570,7 @@ namespace MWGui
}
case Type_Unassigned:
case Type_HandToHand:
unassign(button, i);
unassign(&mKey[i]);
break;
}

@ -55,23 +55,31 @@ namespace MWGui
private:
struct keyData {
int index;
ItemWidget* button;
QuickKeysMenu::QuickKeyType type;
std::string id;
std::string name;
keyData(): index(-1), button(nullptr), type(Type_Unassigned), id(""), name("") {}
};
std::vector<keyData> mKey;
keyData* mSelected;
keyData* mActivated;
MyGUI::EditBox* mInstructionLabel;
MyGUI::Button* mOkButton;
std::vector<ItemWidget*> mQuickKeyButtons;
std::vector<QuickKeyType> mAssigned;
QuickKeysMenuAssign* mAssignDialog;
ItemSelectionDialog* mItemSelectionDialog;
MagicSelectionDialog* mMagicSelectionDialog;
int mSelectedIndex;
int mActivatedIndex;
void onQuickKeyButtonClicked(MyGUI::Widget* sender);
void onOkButtonClicked(MyGUI::Widget* sender);
void unassign(ItemWidget* key, int index);
void unassign(keyData* key);
};
class QuickKeysMenuAssign : public WindowModal

@ -80,6 +80,7 @@ namespace MWGui
newSpell.mSelected = (MWBase::Environment::get().getWindowManager()->getSelectedSpell() == spell->mId);
newSpell.mActive = true;
newSpell.mCount = 1;
mSpells.push_back(newSpell);
}
@ -109,6 +110,7 @@ namespace MWGui
newSpell.mItem = item;
newSpell.mId = item.getCellRef().getRefId();
newSpell.mName = item.getClass().getName(item);
newSpell.mCount = item.getRefData().getCount();
newSpell.mType = Spell::Type_EnchantedItem;
newSpell.mSelected = invStore.getSelectedEnchantItem() == it;

@ -20,6 +20,7 @@ namespace MWGui
std::string mCostColumn; // Cost/chance or Cost/charge
std::string mId; // Item ID or spell ID
MWWorld::Ptr mItem; // Only for Type_EnchantedItem
int mCount; // Only for Type_EnchantedItem
bool mSelected; // Is this the currently selected spell/item (only one can be selected at a time)
bool mActive; // (Items only) is the item equipped?

@ -7,6 +7,8 @@
#include <components/widgets/sharedstatebutton.hpp>
#include "tooltips.hpp"
namespace MWGui
{
@ -103,11 +105,12 @@ namespace MWGui
}
const std::string skin = spell.mActive ? "SandTextButton" : "SpellTextUnequipped";
const std::string captionSuffix = MWGui::ToolTips::getCountString(spell.mCount);
Gui::SharedStateButton* t = mScrollView->createWidget<Gui::SharedStateButton>(skin,
MyGUI::IntCoord(0, 0, 0, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top);
t->setNeedKeyFocus(true);
t->setCaption(spell.mName);
t->setCaption(spell.mName + captionSuffix);
t->setTextAlign(MyGUI::Align::Left);
adjustSpellWidget(spell, i, t);
@ -163,7 +166,8 @@ namespace MWGui
// more checking for major change.
const Spell& spell = mModel->getItem(spellIndex);
if (nameButton->getCaption() != spell.mName)
const std::string captionSuffix = MWGui::ToolTips::getCountString(spell.mCount);
if (nameButton->getCaption() != (spell.mName + captionSuffix))
{
fullUpdateRequired = true;
break;

@ -191,6 +191,9 @@ namespace MWGui
else if (type == "ItemPtr")
{
mFocusObject = *focus->getUserData<MWWorld::Ptr>();
if (!mFocusObject)
return;
tooltipSize = getToolTipViaPtr(mFocusObject.getRefData().getCount(), false, checkOwned());
}
else if (type == "ItemModelIndex")

@ -80,7 +80,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));
}
@ -143,7 +143,7 @@ namespace MWGui
return;
MWMechanics::NpcStats& npcStats = mPtr.getClass().getNpcStats (mPtr);
if (npcStats.getSkill (skillId).getBase () <= pcStats.getSkill (skillId).getBase ())
if (npcStats.getSkill (skillId).getModified () <= pcStats.getSkill (skillId).getBase ())
{
MWBase::Environment::get().getWindowManager()->messageBox ("#{sServiceTrainingWords}");
return;

@ -56,15 +56,15 @@ namespace MWGui
/*
* "Modal" windows cause the rest of the interface to be unaccessible while they are visible
* "Modal" windows cause the rest of the interface to be inaccessible while they are visible
*/
class WindowModal : public WindowBase
{
public:
WindowModal(const std::string& parLayout);
virtual void onOpen();
virtual void onClose();
virtual bool exit() {return true;}
virtual void onOpen() override;
virtual void onClose() override;
virtual bool exit() override {return true;}
};
/// A window that cannot be the target of a drag&drop action.

@ -1328,10 +1328,10 @@ namespace MWGui
MWGui::ConfirmationDialog* WindowManager::getConfirmationDialog() { return mConfirmationDialog; }
MWGui::TradeWindow* WindowManager::getTradeWindow() { return mTradeWindow; }
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

@ -185,7 +185,7 @@ namespace MWGui
virtual MWGui::TradeWindow* getTradeWindow();
/// 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;
};
}

@ -766,6 +766,8 @@ namespace MWMechanics
for (ActiveSpells::TIterator it = spells.begin(); it != spells.end(); ++it)
{
bool actorKilled = false;
const ActiveSpells::ActiveSpellParams& spell = it->second;
MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spell.mCasterActorId);
for (std::vector<ActiveSpells::ActiveEffect>::const_iterator effectIt = spell.mEffects.begin();
@ -793,10 +795,14 @@ namespace MWMechanics
caster.getClass().getNpcStats(caster).addWerewolfKill();
MWBase::Environment::get().getMechanicsManager()->actorKilled(ptr, player);
actorKilled = true;
break;
}
}
}
if (actorKilled)
break;
}
}
@ -1157,6 +1163,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())
@ -1365,7 +1378,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);
}
}
@ -1968,6 +1981,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);
@ -1993,7 +2015,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
@ -161,6 +163,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

@ -35,74 +35,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();
@ -115,7 +47,7 @@ namespace MWMechanics
void AiCombat::init()
{
}
/*

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

@ -53,7 +53,23 @@ namespace MWMechanics
if (!isWithinMaxRange(osg::Vec3f(mX, mY, mZ), pos.asVec3()))
return false;
if (pathTo(actor, ESM::Pathgrid::Point(static_cast<int>(mX), static_cast<int>(mY), static_cast<int>(mZ)), duration))
// Unfortunately, with vanilla assets destination is sometimes blocked by other actor.
// If we got close to target, check for actors nearby. If they are, finish AI package.
int destinationTolerance = 64;
ESM::Pathgrid::Point dest(static_cast<int>(mX), static_cast<int>(mY), static_cast<int>(mZ));
if (distance(pos.pos, dest) <= destinationTolerance)
{
std::vector<MWWorld::Ptr> targetActors;
std::pair<MWWorld::Ptr, osg::Vec3f> result = MWBase::Environment::get().getWorld()->getHitContact(actor, destinationTolerance, targetActors);
if (!result.first.isEmpty())
{
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
return true;
}
}
if (pathTo(actor, dest, duration))
{
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
return true;

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

@ -21,8 +21,81 @@ 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();

@ -39,6 +39,7 @@
#include "../mwworld/esmstore.hpp"
#include "../mwworld/player.hpp"
#include "aicombataction.hpp"
#include "movement.hpp"
#include "npcstats.hpp"
#include "creaturestats.hpp"
@ -370,6 +371,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";
}
}
}
@ -418,11 +423,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";
}
}
@ -459,9 +471,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;
@ -510,7 +524,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);
}
}
}
@ -660,16 +674,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";
@ -778,6 +795,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim
, mSecondsOfRunning(0)
, mTurnAnimationThreshold(0)
, mAttackingOrSpell(false)
, mCastingManualSpell(false)
, mTimeUntilWake(0.f)
{
if(!mAnimation)
@ -991,7 +1009,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)
@ -1072,13 +1091,18 @@ 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)
{
MWMechanics::CastSpell cast(mPtr, NULL);
MWMechanics::CastSpell cast(mPtr, NULL, false, mCastingManualSpell);
cast.playSpellCastingEffects(spellid);
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);
@ -1205,12 +1229,17 @@ bool CharacterController::updateWeaponState()
&& mUpperBodyState != UpperCharState_UnEquipingWeap
&& !isStillWeapon)
{
// Note: we do not disable unequipping animation automatically to avoid body desync
getWeaponGroup(mWeaponType, weapgroup);
mAnimation->play(weapgroup, priorityWeapon,
MWRender::Animation::BlendMask_All, false,
1.0f, "unequip start", "unequip stop", 0.0f, 0);
mUpperBodyState = UpperCharState_UnEquipingWeap;
// We can not play un-equip animation when we switch to HtH
// because we already un-equipped weapon
if (weaptype != WeapType_HandToHand || mWeaponType == WeapType_Spell)
{
// Note: we do not disable unequipping animation automatically to avoid body desync
getWeaponGroup(mWeaponType, weapgroup);
mAnimation->play(weapgroup, priorityWeapon,
MWRender::Animation::BlendMask_All, false,
1.0f, "unequip start", "unequip stop", 0.0f, 0);
mUpperBodyState = UpperCharState_UnEquipingWeap;
}
if(!downSoundId.empty())
{
@ -1351,10 +1380,11 @@ 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)
{
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);
@ -1622,16 +1652,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);
}
}
@ -2141,7 +2173,7 @@ void CharacterController::unpersistAnimationState()
bool loopfallback = (mAnimQueue.front().mGroup.compare(0,4,"idle") == 0);
mAnimation->play(anim.mGroup,
Priority_Default, MWRender::Animation::BlendMask_All, false, 1.0f,
Priority_Persistent, MWRender::Animation::BlendMask_All, false, 1.0f,
"start", "stop", complete, anim.mLoopCount, loopfallback);
}
}
@ -2359,6 +2391,11 @@ bool CharacterController::isAttackPrepairing() const
mUpperBodyState == UpperCharState_MinAttackToMaxAttack;
}
bool CharacterController::isCastingSpell() const
{
return mCastingManualSpell || mUpperBodyState == UpperCharState_CastingSpell;
}
bool CharacterController::isReadyToBlock() const
{
return updateCarriedLeftVisible(mWeaponType);
@ -2422,6 +2459,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);

@ -115,14 +115,16 @@ namespace MWMechanics
if (Misc::Rng::roll0to99() < x)
{
// Reduce shield durability by incoming damage
int shieldhealth = shield->getClass().getItemHealth(*shield);
shieldhealth -= std::min(shieldhealth, int(damage));
shield->getCellRef().setCharge(shieldhealth);
if (shieldhealth == 0)
inv.unequipItem(*shield, blocker);
if (!(weapon.isEmpty() && !attacker.getClass().isNpc())) // Unarmed creature attacks don't affect armor condition
{
// Reduce shield durability by incoming damage
int shieldhealth = shield->getClass().getItemHealth(*shield);
shieldhealth -= std::min(shieldhealth, int(damage));
shield->getCellRef().setCharge(shieldhealth);
if (shieldhealth == 0)
inv.unequipItem(*shield, blocker);
}
// Reduce blocker fatigue
const float fFatigueBlockBase = gmst.find("fFatigueBlockBase")->getFloat();
const float fFatigueBlockMult = gmst.find("fFatigueBlockMult")->getFloat();

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

Loading…
Cancel
Save