Merge branch 'master' into screenshot360

0.6.3
Miloslav Číž 7 years ago committed by GitHub
commit d629c30fdb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,42 @@
# 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:
stage: build
script:
- cores_to_use=$((`nproc`-2)); if (( $cores_to_use < 1 )); then cores_to_use=1; fi
- mkdir build; cd build; cmake -DCMAKE_BUILD_TYPE=MinSizeRel ../
- make -j$cores_to_use
- DESTDIR=artifacts make install
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:
paths:
- "*.o"
# TODO: run tests using the binary built before
#test:
# stage: test
# script:
# - ls

@ -15,6 +15,7 @@ Programmers
Adam Hogan (aurix) Adam Hogan (aurix)
Aesylwinn Aesylwinn
aegis aegis
AHSauge
Aleksandar Jovanov Aleksandar Jovanov
Alex Haddad (rainChu) Alex Haddad (rainChu)
Alex McKibben Alex McKibben
@ -36,12 +37,15 @@ Programmers
Britt Mathis (galdor557) Britt Mathis (galdor557)
Capostrophic Capostrophic
cc9cii cc9cii
Cédric Mocquillon
Chris Boyce (slothlife) Chris Boyce (slothlife)
Chris Robinson (KittyCat) Chris Robinson (KittyCat)
Cory F. Cohen (cfcohen) Cory F. Cohen (cfcohen)
Cris Mihalache (Mirceam) Cris Mihalache (Mirceam)
crussell187 crussell187
DanielVukelich
darkf darkf
David Cernat (davidcernat)
devnexen devnexen
Dieho Dieho
Dmitry Shkurskiy (endorph) Dmitry Shkurskiy (endorph)
@ -59,6 +63,7 @@ Programmers
Evgeniy Mineev (sandstranger) Evgeniy Mineev (sandstranger)
Federico Guerra (FedeWar) Federico Guerra (FedeWar)
Fil Krynicki (filkry) Fil Krynicki (filkry)
Florian Weber (Florianjw)
Gašper Sedej Gašper Sedej
gugus/gus gugus/gus
Hallfaer Tuilinn Hallfaer Tuilinn
@ -155,6 +160,8 @@ Programmers
terrorfisch terrorfisch
thegriglat thegriglat
Thomas Luppi (Digmaster) Thomas Luppi (Digmaster)
tri4ng1e
unelsson
Will Herrmann (Thunderforge) Will Herrmann (Thunderforge)
Tom Mason (wheybags) Tom Mason (wheybags)
Torben Leif Carrington (TorbenC) Torben Leif Carrington (TorbenC)
@ -224,7 +231,7 @@ Artwork
Necrod - OpenMW Logo Necrod - OpenMW Logo
Mickey Lyle (raevol) - Wordpress Theme Mickey Lyle (raevol) - Wordpress Theme
Tom Koenderink (Okulo), SirHerrbatka, crysthala, Shnatsel - OpenMW Editor Icons Tom Koenderink (Okulo), SirHerrbatka, crysthala, Shnatsel, Lamoot - OpenMW Editor Icons
Inactive Contributors Inactive Contributors
--------------------- ---------------------

@ -1,5 +1,22 @@
#!/bin/bash #!/bin/bash
MISSINGTOOLS=0
command -v 7z >/dev/null 2>&1 || { echo "Error: 7z (7zip) is not on the path."; MISSINGTOOLS=1; }
command -v cmake >/dev/null 2>&1 || { echo "Error: cmake (CMake) is not on the path."; MISSINGTOOLS=1; }
if [ $MISSINGTOOLS -ne 0 ]; then
exit 1
fi
WORKINGDIR="$(pwd)"
case "$WORKINGDIR" in
*[[:space:]]*)
echo "Error: Working directory contains spaces."
exit 1
;;
esac
set -euo pipefail set -euo pipefail
APPVEYOR=${APPVEYOR:-} APPVEYOR=${APPVEYOR:-}
@ -216,6 +233,8 @@ case $VS_VERSION in
15|15.0|2017 ) 15|15.0|2017 )
GENERATOR="Visual Studio 15 2017" GENERATOR="Visual Studio 15 2017"
TOOLSET="vc140" TOOLSET="vc140"
TOOLSET_REAL="vc141"
MSVC_REAL_VER="15"
MSVC_VER="14" MSVC_VER="14"
MSVC_YEAR="2015" MSVC_YEAR="2015"
MSVC_DISPLAY_YEAR="2017" MSVC_DISPLAY_YEAR="2017"
@ -224,6 +243,8 @@ case $VS_VERSION in
14|14.0|2015 ) 14|14.0|2015 )
GENERATOR="Visual Studio 14 2015" GENERATOR="Visual Studio 14 2015"
TOOLSET="vc140" TOOLSET="vc140"
TOOLSET_REAL="vc140"
MSVC_REAL_VER="14"
MSVC_VER="14" MSVC_VER="14"
MSVC_YEAR="2015" MSVC_YEAR="2015"
MSVC_DISPLAY_YEAR="2015" MSVC_DISPLAY_YEAR="2015"
@ -232,6 +253,8 @@ case $VS_VERSION in
12|12.0|2013 ) 12|12.0|2013 )
GENERATOR="Visual Studio 12 2013" GENERATOR="Visual Studio 12 2013"
TOOLSET="vc120" TOOLSET="vc120"
TOOLSET_REAL="vc120"
MSVC_REAL_VER="12"
MSVC_VER="12" MSVC_VER="12"
MSVC_YEAR="2013" MSVC_YEAR="2013"
MSVC_DISPLAY_YEAR="2013" MSVC_DISPLAY_YEAR="2013"
@ -303,25 +326,25 @@ if [ -z $SKIP_DOWNLOAD ]; then
# Boost # Boost
if [ -z $APPVEYOR ]; then if [ -z $APPVEYOR ]; then
download "Boost 1.61.0" \ download "Boost 1.61.0" \
"http://sourceforge.net/projects/boost/files/boost-binaries/1.61.0/boost_1_61_0-msvc-${MSVC_VER}.0-${BITS}.exe" \ "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" "boost-1.61.0-msvc${MSVC_YEAR}-win${BITS}.exe"
fi fi
# Bullet # Bullet
download "Bullet 2.86" \ download "Bullet 2.86" \
"http://www.lysator.liu.se/~ace/OpenMW/deps/Bullet-2.86-msvc${MSVC_YEAR}-win${BITS}.7z" \ "https://www.lysator.liu.se/~ace/OpenMW/deps/Bullet-2.86-msvc${MSVC_YEAR}-win${BITS}.7z" \
"Bullet-2.86-msvc${MSVC_YEAR}-win${BITS}.7z" "Bullet-2.86-msvc${MSVC_YEAR}-win${BITS}.7z"
# FFmpeg # FFmpeg
download "FFmpeg 3.2.4" \ download "FFmpeg 3.2.4" \
"http://ffmpeg.zeranoe.com/builds/win${BITS}/shared/ffmpeg-3.2.4-win${BITS}-shared.zip" \ "https://ffmpeg.zeranoe.com/builds/win${BITS}/shared/ffmpeg-3.2.4-win${BITS}-shared.zip" \
"ffmpeg-3.2.4-win${BITS}.zip" \ "ffmpeg-3.2.4-win${BITS}.zip" \
"http://ffmpeg.zeranoe.com/builds/win${BITS}/dev/ffmpeg-3.2.4-win${BITS}-dev.zip" \ "https://ffmpeg.zeranoe.com/builds/win${BITS}/dev/ffmpeg-3.2.4-win${BITS}-dev.zip" \
"ffmpeg-3.2.4-dev-win${BITS}.zip" "ffmpeg-3.2.4-dev-win${BITS}.zip"
# MyGUI # MyGUI
download "MyGUI 3.2.2" \ download "MyGUI 3.2.2" \
"http://www.lysator.liu.se/~ace/OpenMW/deps/MyGUI-3.2.2-msvc${MSVC_YEAR}-win${BITS}.7z" \ "https://www.lysator.liu.se/~ace/OpenMW/deps/MyGUI-3.2.2-msvc${MSVC_YEAR}-win${BITS}.7z" \
"MyGUI-3.2.2-msvc${MSVC_YEAR}-win${BITS}.7z" "MyGUI-3.2.2-msvc${MSVC_YEAR}-win${BITS}.7z"
# OpenAL # OpenAL
@ -331,7 +354,7 @@ if [ -z $SKIP_DOWNLOAD ]; then
# OSG # OSG
download "OpenSceneGraph 3.4.1-scrawl" \ download "OpenSceneGraph 3.4.1-scrawl" \
"http://www.lysator.liu.se/~ace/OpenMW/deps/OSG-3.4.1-scrawl-msvc${MSVC_YEAR}-win${BITS}.7z" \ "https://www.lysator.liu.se/~ace/OpenMW/deps/OSG-3.4.1-scrawl-msvc${MSVC_YEAR}-win${BITS}.7z" \
"OSG-3.4.1-scrawl-msvc${MSVC_YEAR}-win${BITS}.7z" "OSG-3.4.1-scrawl-msvc${MSVC_YEAR}-win${BITS}.7z"
# Qt # Qt
@ -343,9 +366,9 @@ if [ -z $SKIP_DOWNLOAD ]; then
fi fi
download "Qt 5.7.2" \ download "Qt 5.7.2" \
"http://download.qt.io/official_releases/qt/5.7/5.7.0/qt-opensource-windows-x86-msvc${MSVC_YEAR}${QT_SUFFIX}-5.7.0.exe" \ "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" \
"qt-5.7.0-msvc${MSVC_YEAR}-win${BITS}.exe" \ "qt-5.7.0-msvc${MSVC_YEAR}-win${BITS}.exe" \
"http://www.lysator.liu.se/~ace/OpenMW/deps/qt-5-install.qs" \ "https://www.lysator.liu.se/~ace/OpenMW/deps/qt-5-install.qs" \
"qt-5-install.qs" "qt-5-install.qs"
fi fi
@ -385,7 +408,7 @@ else
if [ $MSVC_VER -eq 12 ]; then if [ $MSVC_VER -eq 12 ]; then
printf "Boost 1.58.0 AppVeyor... " printf "Boost 1.58.0 AppVeyor... "
else else
printf "Boost 1.60.0 AppVeyor... " printf "Boost 1.67.0 AppVeyor... "
fi fi
fi fi
{ {
@ -408,14 +431,20 @@ fi
echo Done. echo Done.
else else
# Appveyor unstable has all the boost we need already # Appveyor unstable has all the boost we need already
if [ $MSVC_VER -eq 12 ]; then if [ $MSVC_REAL_VER -eq 12 ]; then
BOOST_SDK="c:/Libraries/boost_1_58_0" BOOST_SDK="c:/Libraries/boost_1_58_0"
else else
BOOST_SDK="c:/Libraries/boost_1_60_0" BOOST_SDK="c:/Libraries/boost_1_67_0"
fi fi
if [ $MSVC_REAL_VER -eq 15 ]; then
LIB_SUFFIX="1"
else
LIB_SUFFIX="0"
fi
add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \ 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}.${LIB_SUFFIX}"
add_cmake_opts -DBoost_COMPILER="-${TOOLSET}" add_cmake_opts -DBoost_COMPILER="-${TOOLSET_REAL}"
echo Done. echo Done.
fi fi
@ -568,7 +597,7 @@ echo
if [ -z $APPVEYOR ]; then if [ -z $APPVEYOR ]; then
printf "Qt 5.7.0... " printf "Qt 5.7.0... "
else else
printf "Qt 5.7 AppVeyor... " printf "Qt 5.10 AppVeyor... "
fi fi
{ {
if [ $BITS -eq 64 ]; then if [ $BITS -eq 64 ]; then
@ -613,18 +642,28 @@ fi
SUFFIX="" SUFFIX=""
fi fi
add_runtime_dlls "$(pwd)/bin/lib"{EGL,GLESv2}${SUFFIX}.dll \ add_runtime_dlls "$(pwd)/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${SUFFIX}.dll
"$(pwd)/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${SUFFIX}.dll
add_qt_platform_dlls "$(pwd)/plugins/platforms/qwindows${SUFFIX}.dll" add_qt_platform_dlls "$(pwd)/plugins/platforms/qwindows${SUFFIX}.dll"
echo Done. echo Done.
else else
QT_SDK="C:/Qt/5.7/msvc${MSVC_YEAR}${SUFFIX}" QT_SDK="C:/Qt/5.10/msvc${MSVC_DISPLAY_YEAR}${SUFFIX}"
add_cmake_opts -DDESIRED_QT_VERSION=5 \ add_cmake_opts -DDESIRED_QT_VERSION=5 \
-DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" \ -DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" \
-DCMAKE_PREFIX_PATH="$QT_SDK" -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. echo Done.
fi fi
} }
@ -699,7 +738,7 @@ if [ ! -z $CI ]; then
fi fi
# NOTE: Disable this when/if we want to run test cases # NOTE: Disable this when/if we want to run test cases
if [ -z $CI ]; then #if [ -z $CI ]; then
echo "- Copying Runtime DLLs..." echo "- Copying Runtime DLLs..."
mkdir -p $BUILD_CONFIG mkdir -p $BUILD_CONFIG
for DLL in $RUNTIME_DLLS; do for DLL in $RUNTIME_DLLS; do
@ -731,7 +770,7 @@ if [ -z $CI ]; then
cp "$DLL" "${BUILD_CONFIG}/platforms" cp "$DLL" "${BUILD_CONFIG}/platforms"
done done
echo echo
fi #fi
if [ -z $VERBOSE ]; then if [ -z $VERBOSE ]; then
printf -- "- Configuring... " printf -- "- Configuring... "

@ -6,4 +6,6 @@ DATE=`date +'%d%m%Y'`
SHORT_COMMIT=`git rev-parse --short ${TRAVIS_COMMIT}` SHORT_COMMIT=`git rev-parse --short ${TRAVIS_COMMIT}`
TARGET_FILENAME="OpenMW-${DATE}-${SHORT_COMMIT}.dmg" TARGET_FILENAME="OpenMW-${DATE}-${SHORT_COMMIT}.dmg"
curl --ssl --ftp-create-dirs -T *.dmg -u $OSX_FTP_USER:$OSX_FTP_PASSWORD "${OSX_FTP_URL}${TARGET_FILENAME}" if ! curl --ssl -u $OSX_FTP_USER:$OSX_FTP_PASSWORD "${OSX_FTP_URL}" --silent | grep $SHORT_COMMIT > /dev/null; then
curl --ssl --ftp-create-dirs -T *.dmg -u $OSX_FTP_USER:$OSX_FTP_PASSWORD "${OSX_FTP_URL}${TARGET_FILENAME}"
fi

@ -54,7 +54,7 @@ endif()
message(STATUS "Configuring OpenMW...") message(STATUS "Configuring OpenMW...")
set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MAJOR 0)
set(OPENMW_VERSION_MINOR 43) set(OPENMW_VERSION_MINOR 44)
set(OPENMW_VERSION_RELEASE 0) set(OPENMW_VERSION_RELEASE 0)
set(OPENMW_VERSION_COMMITHASH "") set(OPENMW_VERSION_COMMITHASH "")
@ -135,6 +135,8 @@ if (WIN32)
endif() endif()
# Dependencies # Dependencies
find_package(OpenGL REQUIRED)
if (USE_QT) if (USE_QT)
message(STATUS "Using Qt${DESIRED_QT_VERSION}") message(STATUS "Using Qt${DESIRED_QT_VERSION}")
@ -182,12 +184,6 @@ if (NOT WIN32 AND BUILD_WIZARD) # windows users can just run the morrowind insta
set(OPENMW_USE_UNSHIELD TRUE) set(OPENMW_USE_UNSHIELD TRUE)
endif() endif()
option(OPENGL_ES "enable opengl es support" FALSE )
if (OPENGL_ES)
add_definitions(-DOPENGL_ES)
endif(OPENGL_ES)
# Fix for not visible pthreads functions for linker with glibc 2.15 # Fix for not visible pthreads functions for linker with glibc 2.15
if (UNIX AND NOT APPLE) if (UNIX AND NOT APPLE)
find_package (Threads) find_package (Threads)
@ -505,8 +501,8 @@ if(WIN32)
SET(CPACK_PACKAGE_DESCRIPTION_FILE "${OpenMW_SOURCE_DIR}/README.md") SET(CPACK_PACKAGE_DESCRIPTION_FILE "${OpenMW_SOURCE_DIR}/README.md")
SET(CPACK_NSIS_EXECUTABLES_DIRECTORY ".") SET(CPACK_NSIS_EXECUTABLES_DIRECTORY ".")
SET(CPACK_NSIS_DISPLAY_NAME "OpenMW ${OPENMW_VERSION}") SET(CPACK_NSIS_DISPLAY_NAME "OpenMW ${OPENMW_VERSION}")
SET(CPACK_NSIS_HELP_LINK "http:\\\\\\\\www.openmw.org") SET(CPACK_NSIS_HELP_LINK "https:\\\\\\\\www.openmw.org")
SET(CPACK_NSIS_URL_INFO_ABOUT "http:\\\\\\\\www.openmw.org") SET(CPACK_NSIS_URL_INFO_ABOUT "https:\\\\\\\\www.openmw.org")
SET(CPACK_NSIS_INSTALLED_ICON_NAME "openmw-launcher.exe") SET(CPACK_NSIS_INSTALLED_ICON_NAME "openmw-launcher.exe")
SET(CPACK_NSIS_MUI_FINISHPAGE_RUN "openmw-launcher.exe") SET(CPACK_NSIS_MUI_FINISHPAGE_RUN "openmw-launcher.exe")
SET(CPACK_NSIS_MUI_ICON "${OpenMW_SOURCE_DIR}/files/windows/openmw.ico") SET(CPACK_NSIS_MUI_ICON "${OpenMW_SOURCE_DIR}/files/windows/openmw.ico")
@ -549,11 +545,6 @@ endif()
# Components # Components
add_subdirectory (components) add_subdirectory (components)
# Plugins
#if (BUILD_MYGUI_PLUGIN)
# add_subdirectory(plugins/mygui_resource_plugin)
#endif()
# Apps and tools # Apps and tools
if (BUILD_OPENMW) if (BUILD_OPENMW)
add_subdirectory( apps/openmw ) add_subdirectory( apps/openmw )
@ -714,8 +705,7 @@ if (WIN32)
endif() endif()
if (BUILD_OPENMW) if (BUILD_OPENMW)
# Very specific issue this, only needed on 32-bit VS2015 during unity builds. if (OPENMW_UNITY_BUILD)
if (MSVC_VERSION GREATER 1800 AND CMAKE_SIZEOF_VOID_P EQUAL 4 AND OPENMW_UNITY_BUILD)
set_target_properties(openmw PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD} /bigobj") set_target_properties(openmw PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD} /bigobj")
else() else()
set_target_properties(openmw PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") set_target_properties(openmw PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")

@ -3,15 +3,14 @@ How to contribute to OpenMW
Not sure what to do with all your free time? Pick out a task from here: Not sure what to do with all your free time? Pick out a task from here:
http://bugs.openmw.org/ https://bugs.openmw.org/
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. 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.
Note: Note:
- Tasks set to 'openmw-future' are usually out of the current scope of the project and can't be started yet. * Tasks set to 'openmw-future' are usually out of the current scope of the project and can't be started yet.
- Bugs that are not 'Confirmed' should be confirmed first. * Bugs that are not 'Confirmed' should be confirmed first.
- Larger Features should have a discussion before you start implementing. * Often, it's best to start a discussion about possible solutions before you jump into coding, especially for larger features.
- In many cases, it's best to have a discussion about possible solutions before you jump into coding.
Aside from coding, you can also help by triaging the issues list. Check for bugs that are 'Unconfirmed' and try to confirm them on your end, working out any details that may be necessary. Check for bugs that do not conform to [Bug reporting guidelines](https://wiki.openmw.org/index.php?title=Bug_Reporting_Guidelines) and improve them to do so! Aside from coding, you can also help by triaging the issues list. Check for bugs that are 'Unconfirmed' and try to confirm them on your end, working out any details that may be necessary. Check for bugs that do not conform to [Bug reporting guidelines](https://wiki.openmw.org/index.php?title=Bug_Reporting_Guidelines) and improve them to do so!
@ -20,20 +19,21 @@ There are various [Tools](https://wiki.openmw.org/index.php?title=Tools) to faci
Pull Request Guidelines Pull Request Guidelines
======================= =======================
Thought of a change? Great! To facilitate the review process, your pull request description should include the following (if applicable): To facilitate the review process, your pull request description should include the following, if applicable:
* A link back to the bug report or forum discussion that prompted the change * A link back to the bug report or forum discussion that prompted the change. Note: when linking bugs, use the syntax ```[Bug #xyz](https://bugs.openmw.org/issues/#xyz)``` to create a clickable link. Writing only 'Bug #xyz' will unfortunately create a link to the Github pull request with that number instead.
* Summary of the changes made * Summary of the changes made
* Reasoning / motivation behind the change * Reasoning / motivation behind the change
* What testing you have carried out to verify the change * What testing you have carried out to verify the change
Furthermore, we advise to: Furthermore, we advise to:
* Separate your work into multiple pull requests whenever possible. As a rule of thumb, each feature and each bugfix should go into a separate PR, unless they are closely related or dependent upon each other. Small pull requests are easier to review, and are less likely to require further changes before we can merge them. A "mega" pull request with lots of unrelated commits in it is likely to get held up in review for a long time. * Avoid stuffing unrelated commits into one pull request. As a rule of thumb, each feature and each bugfix should go into a separate PR, unless they are closely related or dependent upon each other. Small pull requests are easier to review, and are less likely to require further changes before we can merge them. A "mega" pull request with lots of unrelated commits in it is likely to get held up in review for a long time.
* Feel free to submit incomplete pull requests. Even if the work can not be merged yet, pull requests are a great place to collect early feedback. Just make sure to mark it as *[Incomplete]* or *[Do not merge yet]* in the title. * Feel free to submit incomplete pull requests. Even if the work can not be merged yet, pull requests are a great place to collect early feedback. Just make sure to mark it as *[Incomplete]* or *[Do not merge yet]* in the title.
* If you plan on contributing often, please read the [Developer Reference](https://wiki.openmw.org/index.php?title=Developer_Reference) on our wiki, especially the [Policies and Standards](https://wiki.openmw.org/index.php?title=Policies_and_Standards). * If you plan on contributing often, please read the [Developer Reference](https://wiki.openmw.org/index.php?title=Developer_Reference) on our wiki, especially the [Policies and Standards](https://wiki.openmw.org/index.php?title=Policies_and_Standards).
* Make sure each of your changes has a clear objective. Unnecessary changes may lead to merge conflicts, clutter the commit history and slow down review. Code formatting 'fixes' should be avoided, unless you were already changing that particular line anyway. * Make sure each of your changes has a clear objective. Unnecessary changes may lead to merge conflicts, clutter the commit history and slow down review. Code formatting 'fixes' should be avoided, unless you were already changing that particular line anyway.
* Reference the bug / feature ticket(s) in your commit message (e.g. 'Bug #123') to make it easier to keep track of what we changed for what reason. Our bugtracker will show those commits next to the ticket. If your commit message includes 'Fixes #123', that bug/feature will automatically be set to 'Closed' when your commit is merged. * Reference the bug / feature ticket(s) in your commit message (e.g. 'Bug #123') to make it easier to keep track of what we changed for what reason. Our bugtracker will show those commits next to the ticket. If your commit message includes 'Fixes #123', that bug/feature will automatically be set to 'Closed' when your commit is merged.
* When pulling changes from master, prefer rebase over merge. Consider using a merge if there are conflicts or for long-running PRs.
Guidelines for original engine "fixes" Guidelines for original engine "fixes"
================================= =================================
@ -47,7 +47,7 @@ Unfortunately, the definition of what is a "bug" is not so clear. Consider that
* Many people will actually <i>like</i> these "bugs" because that is what they remember the game for. * Many people will actually <i>like</i> these "bugs" because that is what they remember the game for.
* Exploits may be part of the fun of an open-world game - they reward knowledge with power. There are too many of them to plug them all, anyway. * Exploits may be part of the fun of an open-world game - they reward knowledge with power. There are too many of them to plug them all, anyway.
OpenMW, in its default configuration, is meant to be a faithful reimplementation of Morrowind, minus things like crash bugs, stability issues and design errors. However, we try to avoid touching anything that affects the core gameplay, the balancing of the game or introduces incompatibilities with existing mod content. OpenMW, in its default configuration, is meant to be a faithful reimplementation of Morrowind, minus things like crash bugs, stability issues and severe design errors. However, we try to avoid touching anything that affects the core gameplay, the balancing of the game or introduces incompatibilities with existing mod content.
That said, we may sometimes evaluate such issues on an individual basis. Common exceptions to the above would be: That said, we may sometimes evaluate such issues on an individual basis. Common exceptions to the above would be:
@ -62,10 +62,54 @@ We get it, you have waited so long for feature XYZ to be available in Morrowind
Unfortunately, since maintaining features comes at a cost and our resources are limited, we have to be a little selective in what features we allow into the main repository. Generally: Unfortunately, since maintaining features comes at a cost and our resources are limited, we have to be a little selective in what features we allow into the main repository. Generally:
- Features should be as generic and non-redundant as possible. * Features should be as generic and non-redundant as possible.
- Any feature that is also possible with modding should be done as a mod instead. * Any feature that is also possible with modding should be done as a mod instead.
- In the future, OpenMW plans to expand the scope of what is possible with modding, e.g. by moving certain game logic into editable scripts. * In the future, OpenMW plans to expand the scope of what is possible with modding, e.g. by moving certain game logic into editable scripts.
- Currently, modders can edit OpenMW's GUI skins and layout XML files, although there are still a few missing hooks (e.g. scripting support) in order to make this into a powerful way of modding. * Currently, modders can edit OpenMW's GUI skins and layout XML files, although there are still a few missing hooks (e.g. scripting support) in order to make this into a powerful way of modding.
- If a feature introduces new game UI strings, that reduces its chance of being accepted because we do not currently have any way of localizing these to the user's Morrowind installation language. * If a feature introduces new game UI strings, that reduces its chance of being accepted because we do not currently have any way of localizing these to the user's Morrowind installation language.
If you are in doubt of your feature being within our scope, it is probably best to start a forum discussion first. See the [settings documentation](https://openmw.readthedocs.io/en/stable/reference/modding/settings/index.html) and [Features list](https://wiki.openmw.org/index.php?title=Features) for some examples of features that were deemed acceptable. If you are in doubt of your feature being within our scope, it is probably best to start a forum discussion first. See the [settings documentation](https://openmw.readthedocs.io/en/stable/reference/modding/settings/index.html) and [Features list](https://wiki.openmw.org/index.php?title=Features) for some examples of features that were deemed acceptable.
Reviewing pull requests
=======================
We welcome any help in reviewing open PRs. You don't need to be a developer to comment on new features. We also encourage ["junior" developers to review senior's work](https://pagefault.blog/2018/04/08/why-junior-devs-should-review-seniors-commits/).
This review process is divided into two sections because complaining about code or style issues hardly makes sense until the functionality of the PR is deemed OK. Anyone can help with the Functionality Review while most parts of the Code Review require you to have programming experience.
In addition to the checklist below, make sure to check that the Pull Request Guidelines (first half of this document) were followed.
First review
============
1. Ask for missing information or clarifications. Compare against the project's design goals and roadmap.
2. Check if the automated tests are passing. If they are not, make the PR author aware of the issue and potentially link to the error line on Travis CI or Appveyor. If the error appears unrelated to the PR and/or the master branch is failing with the same error, our CI has broken and needs to be fixed independently of any open PRs. Raise this issue on the forums, bug tracker or with the relevant maintainer. The PR can be merged in this case as long as you've built it yourself to make sure it does build.
3. Make sure that the new code has been tested thoroughly, either by asking the author or, preferably, testing yourself. In a complex project like OpenMW, it is easy to make mistakes, typos, etc. Therefore, prefer testing all code changes, no matter how trivial they look. When you have tested a PR that no one has tested so far, post a comment letting us know.
4. On long running PRs, request the author to update its description with the current state or a checklist of things left to do.
Code Review
===========
1. Carefully review each line for issues the author may not have thought of, paying special attention to 'special' cases. Often, people build their code with a particular mindset and forget about other configurations or unexpected interactions.
2. If any changes are workarounds for an issue in an upstream library, make sure the issue was reported upstream so we can eventually drop the workaround when the issue is fixed and the new version of that library is a build dependency.
3. Make sure PRs do not turn into arguments about hardly related issues. If the PR author disagrees with an established part of the project (e.g. supported build environments), they should open a forum discussion or bug report and in the meantime adjust the PR to adhere to the established way, rather than leaving the PR hanging on a dispute.
4. Check if the code matches our style guidelines.
5. Check to make sure the commit history is clean. Squashing should be considered if the review process has made the commit history particularly long. Commits that don't build should be avoided because they are a nuisance for ```git bisect```.
Merging
=======
To be able to merge PRs, commit priviledges are required. If you do not have the priviledges, just ping someone that does have them with a short comment like "Looks good to me @user".
The person to merge the PR may either use github's Merge button or if using the command line, use the ```--no-ff``` flag (so a merge commit is created, just like with Github's merge button) and include the pull request number in the commit description.
Dealing with regressions
========================
The master branch should always be in a working state that is not worse than the previous release in any way. If a regression is found, the first and foremost priority should be to get the regression fixed quickly, either by reverting the change that caused it or finding a better solution. Please avoid leaving the project in the 'broken' state for an extensive period of time while proper solutions are found. If the solution takes more than a day or so then it is usually better to revert the offending change first and reapply it later when fixed.
Other resources
===============
[GitHub blog - how to write the perfect pull request](https://blog.github.com/2015-01-21-how-to-write-the-perfect-pull-request/)

@ -1,7 +1,7 @@
GNU GENERAL PUBLIC LICENSE GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007 Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed. of this license document, but changing it is not allowed.
@ -645,7 +645,7 @@ the "copyright" line and a pointer to where the full notice is found.
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail. Also add information on how to contact you by electronic and paper mail.
@ -664,11 +664,11 @@ might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school, You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary. if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>. <https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>. <https://www.gnu.org/philosophy/why-not-lgpl.html>.

@ -1,15 +1,15 @@
OpenMW OpenMW
====== ======
[![Build Status](https://api.travis-ci.org/OpenMW/openmw.svg)](https://travis-ci.org/OpenMW/openmw) [![Build status](https://ci.appveyor.com/api/projects/status/e6bqw8oouy8ufd46?svg=true)](https://ci.appveyor.com/project/scrawl/openmw) [![Coverity Scan Build Status](https://scan.coverity.com/projects/3740/badge.svg)](https://scan.coverity.com/projects/3740) [![Build Status](https://api.travis-ci.org/OpenMW/openmw.svg)](https://travis-ci.org/OpenMW/openmw) [![Build status](https://ci.appveyor.com/api/projects/status/github/openmw/openmw?svg=true)](https://ci.appveyor.com/project/psi29a/openmw) [![Coverity Scan Build Status](https://scan.coverity.com/projects/3740/badge.svg)](https://scan.coverity.com/projects/3740) [![pipeline status](https://gitlab.com/OpenMW/openmw/badges/master/pipeline.svg)](https://gitlab.com/OpenMW/openmw/commits/master)
OpenMW is a recreation of the engine for the popular role-playing game Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work. OpenMW is an open-source game engine that supports playing Morrowind by Bethesda Softworks. You need to own the game for OpenMW to play Morrowind.
OpenMW also comes with OpenMW-CS, a replacement for Morrowind's TES Construction Set. OpenMW also comes with OpenMW-CS, a replacement for Bethesda's Construction Set.
* Version: 0.43.0 * Version: 0.44.0
* License: GPLv3 (see [LICENSE](https://github.com/OpenMW/openmw/blob/master/LICENSE) for more information) * License: GPLv3 (see [LICENSE](https://github.com/OpenMW/openmw/blob/master/LICENSE) for more information)
* Website: http://www.openmw.org * Website: https://www.openmw.org
* IRC: #openmw on irc.freenode.net * IRC: #openmw on irc.freenode.net
Font Licenses: Font Licenses:
@ -30,8 +30,8 @@ Getting Started
* [Build from source](https://wiki.openmw.org/index.php?title=Development_Environment_Setup) * [Build from source](https://wiki.openmw.org/index.php?title=Development_Environment_Setup)
* [Testing the game](https://wiki.openmw.org/index.php?title=Testing) * [Testing the game](https://wiki.openmw.org/index.php?title=Testing)
* [How to contribute](https://wiki.openmw.org/index.php?title=Contribution_Wanted) * [How to contribute](https://wiki.openmw.org/index.php?title=Contribution_Wanted)
* [Report a bug](http://bugs.openmw.org/projects/openmw) - read the [guidelines](https://wiki.openmw.org/index.php?title=Bug_Reporting_Guidelines) before submitting your first bug! * [Report a bug](https://bugs.openmw.org/projects/openmw) - read the [guidelines](https://wiki.openmw.org/index.php?title=Bug_Reporting_Guidelines) before submitting your first bug!
* [Known issues](http://bugs.openmw.org/projects/openmw/issues?utf8=%E2%9C%93&set_filter=1&f%5B%5D=status_id&op%5Bstatus_id%5D=%3D&v%5Bstatus_id%5D%5B%5D=7&f%5B%5D=tracker_id&op%5Btracker_id%5D=%3D&v%5Btracker_id%5D%5B%5D=1&f%5B%5D=&c%5B%5D=project&c%5B%5D=tracker&c%5B%5D=status&c%5B%5D=priority&c%5B%5D=subject&c%5B%5D=assigned_to&c%5B%5D=updated_on&group_by=tracker) * [Known issues](https://bugs.openmw.org/projects/openmw/issues?utf8=%E2%9C%93&set_filter=1&f%5B%5D=status_id&op%5Bstatus_id%5D=%3D&v%5Bstatus_id%5D%5B%5D=7&f%5B%5D=tracker_id&op%5Btracker_id%5D=%3D&v%5Btracker_id%5D%5B%5D=1&f%5B%5D=&c%5B%5D=project&c%5B%5D=tracker&c%5B%5D=status&c%5B%5D=priority&c%5B%5D=subject&c%5B%5D=assigned_to&c%5B%5D=updated_on&group_by=tracker)
The data path The data path
------------- -------------

@ -694,7 +694,7 @@ void Record<ESM::Dialogue>::print()
// loads, rather than loading and then dumping. :-( Anyone mind if // loads, rather than loading and then dumping. :-( Anyone mind if
// I change this? // I change this?
ESM::Dialogue::InfoContainer::iterator iit; ESM::Dialogue::InfoContainer::iterator iit;
for (iit = mData.mInfo.begin(); iit != mData.mInfo.end(); iit++) for (iit = mData.mInfo.begin(); iit != mData.mInfo.end(); ++iit)
std::cout << "INFO!" << iit->mId << std::endl; std::cout << "INFO!" << iit->mId << std::endl;
} }
@ -1040,45 +1040,47 @@ void Record<ESM::NPC>::print()
if (mData.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) if (mData.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
{ {
std::cout << " Level: " << mData.mNpdt12.mLevel << std::endl; std::cout << " Level: " << mData.mNpdt.mLevel << std::endl;
std::cout << " Reputation: " << (int)mData.mNpdt12.mReputation << std::endl; std::cout << " Reputation: " << (int)mData.mNpdt.mReputation << std::endl;
std::cout << " Disposition: " << (int)mData.mNpdt12.mDisposition << std::endl; std::cout << " Disposition: " << (int)mData.mNpdt.mDisposition << std::endl;
std::cout << " Rank: " << (int)mData.mNpdt12.mRank << std::endl; std::cout << " Rank: " << (int)mData.mNpdt.mRank << std::endl;
std::cout << " Unknown1: " //Why do we want to print these fields? They are padding in the struct and contain
<< (unsigned int)((unsigned char)mData.mNpdt12.mUnknown1) << std::endl; // nothing of real value. Now we don't deal with NPDTstruct12 in runtime either...
std::cout << " Unknown2: " //std::cout << " Unknown1: "
<< (unsigned int)((unsigned char)mData.mNpdt12.mUnknown2) << std::endl; // << (unsigned int)((unsigned char)mData.mNpdt12.mUnknown1) << std::endl;
std::cout << " Unknown3: " //std::cout << " Unknown2: "
<< (unsigned int)((unsigned char)mData.mNpdt12.mUnknown3) << std::endl; // << (unsigned int)((unsigned char)mData.mNpdt12.mUnknown2) << std::endl;
std::cout << " Gold: " << mData.mNpdt12.mGold << std::endl; //std::cout << " Unknown3: "
// << (unsigned int)((unsigned char)mData.mNpdt12.mUnknown3) << std::endl;
std::cout << " Gold: " << mData.mNpdt.mGold << std::endl;
} }
else { else {
std::cout << " Level: " << mData.mNpdt52.mLevel << std::endl; std::cout << " Level: " << mData.mNpdt.mLevel << std::endl;
std::cout << " Reputation: " << (int)mData.mNpdt52.mReputation << std::endl; std::cout << " Reputation: " << (int)mData.mNpdt.mReputation << std::endl;
std::cout << " Disposition: " << (int)mData.mNpdt52.mDisposition << std::endl; std::cout << " Disposition: " << (int)mData.mNpdt.mDisposition << std::endl;
std::cout << " Rank: " << (int)mData.mNpdt52.mRank << std::endl; std::cout << " Rank: " << (int)mData.mNpdt.mRank << std::endl;
std::cout << " FactionID: " << (int)mData.mNpdt52.mFactionID << std::endl; std::cout << " FactionID: " << (int)mData.mNpdt.mFactionID << std::endl;
std::cout << " Attributes:" << std::endl; std::cout << " Attributes:" << std::endl;
std::cout << " Strength: " << (int)mData.mNpdt52.mStrength << std::endl; std::cout << " Strength: " << (int)mData.mNpdt.mStrength << std::endl;
std::cout << " Intelligence: " << (int)mData.mNpdt52.mIntelligence << std::endl; std::cout << " Intelligence: " << (int)mData.mNpdt.mIntelligence << std::endl;
std::cout << " Willpower: " << (int)mData.mNpdt52.mWillpower << std::endl; std::cout << " Willpower: " << (int)mData.mNpdt.mWillpower << std::endl;
std::cout << " Agility: " << (int)mData.mNpdt52.mAgility << std::endl; std::cout << " Agility: " << (int)mData.mNpdt.mAgility << std::endl;
std::cout << " Speed: " << (int)mData.mNpdt52.mSpeed << std::endl; std::cout << " Speed: " << (int)mData.mNpdt.mSpeed << std::endl;
std::cout << " Endurance: " << (int)mData.mNpdt52.mEndurance << std::endl; std::cout << " Endurance: " << (int)mData.mNpdt.mEndurance << std::endl;
std::cout << " Personality: " << (int)mData.mNpdt52.mPersonality << std::endl; std::cout << " Personality: " << (int)mData.mNpdt.mPersonality << std::endl;
std::cout << " Luck: " << (int)mData.mNpdt52.mLuck << std::endl; std::cout << " Luck: " << (int)mData.mNpdt.mLuck << std::endl;
std::cout << " Skills:" << std::endl; std::cout << " Skills:" << std::endl;
for (int i = 0; i != ESM::Skill::Length; i++) for (int i = 0; i != ESM::Skill::Length; i++)
std::cout << " " << skillLabel(i) << ": " std::cout << " " << skillLabel(i) << ": "
<< (int)(mData.mNpdt52.mSkills[i]) << std::endl; << (int)(mData.mNpdt.mSkills[i]) << std::endl;
std::cout << " Health: " << mData.mNpdt52.mHealth << std::endl; std::cout << " Health: " << mData.mNpdt.mHealth << std::endl;
std::cout << " Magicka: " << mData.mNpdt52.mMana << std::endl; std::cout << " Magicka: " << mData.mNpdt.mMana << std::endl;
std::cout << " Fatigue: " << mData.mNpdt52.mFatigue << std::endl; std::cout << " Fatigue: " << mData.mNpdt.mFatigue << std::endl;
std::cout << " Unknown: " << (int)mData.mNpdt52.mUnknown << std::endl; std::cout << " Unknown: " << (int)mData.mNpdt.mUnknown << std::endl;
std::cout << " Gold: " << mData.mNpdt52.mGold << std::endl; std::cout << " Gold: " << mData.mNpdt.mGold << std::endl;
} }
std::vector<ESM::ContItem>::iterator cit; std::vector<ESM::ContItem>::iterator cit;
@ -1123,7 +1125,7 @@ void Record<ESM::Pathgrid>::print()
int i = 0; int i = 0;
ESM::Pathgrid::PointList::iterator pit; ESM::Pathgrid::PointList::iterator pit;
for (pit = mData.mPoints.begin(); pit != mData.mPoints.end(); pit++) for (pit = mData.mPoints.begin(); pit != mData.mPoints.end(); ++pit)
{ {
std::cout << " Point[" << i << "]:" << std::endl; std::cout << " Point[" << i << "]:" << std::endl;
std::cout << " Coordinates: (" << pit->mX << "," std::cout << " Coordinates: (" << pit->mX << ","
@ -1135,7 +1137,7 @@ void Record<ESM::Pathgrid>::print()
} }
i = 0; i = 0;
ESM::Pathgrid::EdgeList::iterator eit; ESM::Pathgrid::EdgeList::iterator eit;
for (eit = mData.mEdges.begin(); eit != mData.mEdges.end(); eit++) for (eit = mData.mEdges.begin(); eit != mData.mEdges.end(); ++eit)
{ {
std::cout << " Edge[" << i << "]: " << eit->mV0 << " -> " << eit->mV1 << std::endl; std::cout << " Edge[" << i << "]: " << eit->mV0 << " -> " << eit->mV1 << std::endl;
if (eit->mV0 >= mData.mData.mS2 || eit->mV1 >= mData.mData.mS2) if (eit->mV0 >= mData.mData.mS2 || eit->mV1 >= mData.mData.mS2)

@ -122,7 +122,7 @@ public:
} }
else else
{ {
mContext->mPlayer.mObject.mCreatureStats.mLevel = npc.mNpdt52.mLevel; mContext->mPlayer.mObject.mCreatureStats.mLevel = npc.mNpdt.mLevel;
mContext->mPlayerBase = npc; mContext->mPlayerBase = npc;
ESM::SpellState::SpellParams empty; ESM::SpellState::SpellParams empty;
// FIXME: player start spells and birthsign spells aren't listed here, // FIXME: player start spells and birthsign spells aren't listed here,

@ -377,7 +377,7 @@ namespace ESSImport
profile.mPlayerClassName = context.mCustomPlayerClassName; profile.mPlayerClassName = context.mCustomPlayerClassName;
else else
profile.mPlayerClassId = context.mPlayerBase.mClass; profile.mPlayerClassId = context.mPlayerBase.mClass;
profile.mPlayerLevel = context.mPlayerBase.mNpdt52.mLevel; profile.mPlayerLevel = context.mPlayerBase.mNpdt.mLevel;
profile.mPlayerName = header.mGameData.mPlayerName.toString(); profile.mPlayerName = header.mGameData.mPlayerName.toString();
writeScreenshot(header, profile); writeScreenshot(header, profile);

@ -8,6 +8,7 @@ set(LAUNCHER
settingspage.cpp settingspage.cpp
advancedpage.cpp advancedpage.cpp
utils/cellnameloader.cpp
utils/profilescombobox.cpp utils/profilescombobox.cpp
utils/textinputdialog.cpp utils/textinputdialog.cpp
utils/lineedit.cpp utils/lineedit.cpp
@ -24,6 +25,7 @@ set(LAUNCHER_HEADER
settingspage.hpp settingspage.hpp
advancedpage.hpp advancedpage.hpp
utils/cellnameloader.hpp
utils/profilescombobox.hpp utils/profilescombobox.hpp
utils/textinputdialog.hpp utils/textinputdialog.hpp
utils/lineedit.hpp utils/lineedit.hpp
@ -39,6 +41,7 @@ set(LAUNCHER_HEADER_MOC
settingspage.hpp settingspage.hpp
advancedpage.hpp advancedpage.hpp
utils/cellnameloader.hpp
utils/textinputdialog.hpp utils/textinputdialog.hpp
utils/profilescombobox.hpp utils/profilescombobox.hpp
utils/lineedit.hpp utils/lineedit.hpp
@ -106,7 +109,7 @@ if (DESIRED_QT_VERSION MATCHES 4)
target_link_libraries(openmw-launcher ${QT_QTMAIN_LIBRARY}) target_link_libraries(openmw-launcher ${QT_QTMAIN_LIBRARY})
endif(WIN32) endif(WIN32)
else() else()
qt5_use_modules(openmw-launcher Widgets Core) target_link_libraries(openmw-launcher Qt5::Widgets Qt5::Core)
endif() endif()
if (BUILD_WITH_CODE_COVERAGE) if (BUILD_WITH_CODE_COVERAGE)

@ -1,10 +1,18 @@
#include "advancedpage.hpp" #include "advancedpage.hpp"
#include <components/files/configurationmanager.hpp> #include <components/config/gamesettings.hpp>
#include <components/config/launchersettings.hpp>
Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent) #include <QFileDialog>
#include <QCompleter>
#include <components/contentselector/view/contentselector.hpp>
#include <components/contentselector/model/esmfile.hpp>
Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg,
Config::GameSettings &gameSettings,
Settings::Manager &engineSettings, QWidget *parent)
: QWidget(parent) : QWidget(parent)
, mCfgMgr(cfg) , mCfgMgr(cfg)
, mGameSettings(gameSettings)
, mEngineSettings(engineSettings) , mEngineSettings(engineSettings)
{ {
setObjectName ("AdvancedPage"); setObjectName ("AdvancedPage");
@ -13,8 +21,54 @@ Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg, Settings:
loadSettings(); loadSettings();
} }
void Launcher::AdvancedPage::loadCellsForAutocomplete(QStringList cellNames) {
// Set up an auto-completer for the "Start default character at" field
auto *completer = new QCompleter(cellNames);
completer->setCompletionMode(QCompleter::PopupCompletion);
completer->setCaseSensitivity(Qt::CaseSensitivity::CaseInsensitive);
startDefaultCharacterAtField->setCompleter(completer);
}
void Launcher::AdvancedPage::on_skipMenuCheckBox_stateChanged(int state) {
startDefaultCharacterAtLabel->setEnabled(state == Qt::Checked);
startDefaultCharacterAtField->setEnabled(state == Qt::Checked);
}
void Launcher::AdvancedPage::on_runScriptAfterStartupBrowseButton_clicked()
{
QString scriptFile = QFileDialog::getOpenFileName(
this,
QObject::tr("Select script file"),
QDir::currentPath(),
QString(tr("Text file (*.txt)")));
if (scriptFile.isEmpty())
return;
QFileInfo info(scriptFile);
if (!info.exists() || !info.isReadable())
return;
const QString path(QDir::toNativeSeparators(info.absoluteFilePath()));
}
bool Launcher::AdvancedPage::loadSettings() bool Launcher::AdvancedPage::loadSettings()
{ {
// Testing
bool skipMenu = mGameSettings.value("skip-menu").toInt() == 1;
if (skipMenu) {
skipMenuCheckBox->setCheckState(Qt::Checked);
}
startDefaultCharacterAtLabel->setEnabled(skipMenu);
startDefaultCharacterAtField->setEnabled(skipMenu);
startDefaultCharacterAtField->setText(mGameSettings.value("start"));
runScriptAfterStartupField->setText(mGameSettings.value("script-run"));
// Game Settings // Game Settings
loadSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game"); loadSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game");
loadSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game"); loadSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game");
@ -23,6 +77,7 @@ bool Launcher::AdvancedPage::loadSettings()
loadSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game"); loadSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game");
loadSettingBool(showMeleeInfoCheckBox, "show melee info", "Game"); loadSettingBool(showMeleeInfoCheckBox, "show melee info", "Game");
loadSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game"); loadSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game");
loadSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game");
// Expected values are (0, 1, 2, 3) // Expected values are (0, 1, 2, 3)
int showOwnedIndex = mEngineSettings.getInt("show owned", "Game"); int showOwnedIndex = mEngineSettings.getInt("show owned", "Game");
@ -35,9 +90,11 @@ bool Launcher::AdvancedPage::loadSettings()
loadSettingBool(grabCursorCheckBox, "grab cursor", "Input"); loadSettingBool(grabCursorCheckBox, "grab cursor", "Input");
loadSettingBool(toggleSneakCheckBox, "toggle sneak", "Input"); loadSettingBool(toggleSneakCheckBox, "toggle sneak", "Input");
// Other Settings // Saves Settings
loadSettingBool(timePlayedCheckbox, "timeplayed", "Saves"); loadSettingBool(timePlayedCheckbox, "timeplayed", "Saves");
maximumQuicksavesComboBox->setValue(mEngineSettings.getInt("max quicksaves", "Saves"));
// Other Settings
QString screenshotFormatString = QString::fromStdString(mEngineSettings.getString("screenshot format", "General")).toUpper(); QString screenshotFormatString = QString::fromStdString(mEngineSettings.getString("screenshot format", "General")).toUpper();
if (screenshotFormatComboBox->findText(screenshotFormatString) == -1) if (screenshotFormatComboBox->findText(screenshotFormatString) == -1)
screenshotFormatComboBox->addItem(screenshotFormatString); screenshotFormatComboBox->addItem(screenshotFormatString);
@ -51,6 +108,19 @@ void Launcher::AdvancedPage::saveSettings()
// Ensure we only set the new settings if they changed. This is to avoid cluttering the // Ensure we only set the new settings if they changed. This is to avoid cluttering the
// user settings file (which by definition should only contain settings the user has touched) // user settings file (which by definition should only contain settings the user has touched)
// Testing
int skipMenu = skipMenuCheckBox->checkState() == Qt::Checked;
if (skipMenu != mGameSettings.value("skip-menu").toInt())
mGameSettings.setValue("skip-menu", QString::number(skipMenu));
QString startCell = startDefaultCharacterAtField->text();
if (startCell != mGameSettings.value("start")) {
mGameSettings.setValue("start", startCell);
}
QString scriptRun = runScriptAfterStartupField->text();
if (scriptRun != mGameSettings.value("script-run"))
mGameSettings.setValue("script-run", scriptRun);
// Game Settings // Game Settings
saveSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game"); saveSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game");
saveSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game"); saveSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game");
@ -59,6 +129,7 @@ void Launcher::AdvancedPage::saveSettings()
saveSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game"); saveSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game");
saveSettingBool(showMeleeInfoCheckBox, "show melee info", "Game"); saveSettingBool(showMeleeInfoCheckBox, "show melee info", "Game");
saveSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game"); saveSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game");
saveSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game");
int showOwnedCurrentIndex = showOwnedComboBox->currentIndex(); int showOwnedCurrentIndex = showOwnedComboBox->currentIndex();
if (showOwnedCurrentIndex != mEngineSettings.getInt("show owned", "Game")) if (showOwnedCurrentIndex != mEngineSettings.getInt("show owned", "Game"))
@ -69,9 +140,14 @@ void Launcher::AdvancedPage::saveSettings()
saveSettingBool(grabCursorCheckBox, "grab cursor", "Input"); saveSettingBool(grabCursorCheckBox, "grab cursor", "Input");
saveSettingBool(toggleSneakCheckBox, "toggle sneak", "Input"); saveSettingBool(toggleSneakCheckBox, "toggle sneak", "Input");
// Other Settings // Saves Settings
saveSettingBool(timePlayedCheckbox, "timeplayed", "Saves"); saveSettingBool(timePlayedCheckbox, "timeplayed", "Saves");
int maximumQuicksaves = maximumQuicksavesComboBox->value();
if (maximumQuicksaves != mEngineSettings.getInt("max quicksaves", "Saves")) {
mEngineSettings.setInt("max quicksaves", "Saves", maximumQuicksaves);
}
// Other Settings
std::string screenshotFormatString = screenshotFormatComboBox->currentText().toLower().toStdString(); std::string screenshotFormatString = screenshotFormatComboBox->currentText().toLower().toStdString();
if (screenshotFormatString != mEngineSettings.getString("screenshot format", "General")) if (screenshotFormatString != mEngineSettings.getString("screenshot format", "General"))
mEngineSettings.setString("screenshot format", "General", screenshotFormatString); mEngineSettings.setString("screenshot format", "General", screenshotFormatString);
@ -86,4 +162,9 @@ void Launcher::AdvancedPage::saveSettingBool(QCheckBox *checkbox, const std::str
bool cValue = checkbox->checkState(); bool cValue = checkbox->checkState();
if (cValue != mEngineSettings.getBool(setting, group)) if (cValue != mEngineSettings.getBool(setting, group))
mEngineSettings.setBool(setting, group, cValue); mEngineSettings.setBool(setting, group, cValue);
}
void Launcher::AdvancedPage::slotLoadedCellsChanged(QStringList cellNames)
{
loadCellsForAutocomplete(cellNames);
} }

@ -8,6 +8,7 @@
#include <components/settings/settings.hpp> #include <components/settings/settings.hpp>
namespace Files { struct ConfigurationManager; } namespace Files { struct ConfigurationManager; }
namespace Config { class GameSettings; }
namespace Launcher namespace Launcher
{ {
@ -16,15 +17,29 @@ namespace Launcher
Q_OBJECT Q_OBJECT
public: public:
AdvancedPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent = 0); AdvancedPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings,
Settings::Manager &engineSettings, QWidget *parent = 0);
bool loadSettings(); bool loadSettings();
void saveSettings(); void saveSettings();
public slots:
void slotLoadedCellsChanged(QStringList cellNames);
private slots:
void on_skipMenuCheckBox_stateChanged(int state);
void on_runScriptAfterStartupBrowseButton_clicked();
private: private:
Files::ConfigurationManager &mCfgMgr; Files::ConfigurationManager &mCfgMgr;
Config::GameSettings &mGameSettings;
Settings::Manager &mEngineSettings; Settings::Manager &mEngineSettings;
/**
* Load the cells associated with the given content files for use in autocomplete
* @param filePaths the file paths of the content files to be examined
*/
void loadCellsForAutocomplete(QStringList filePaths);
void loadSettingBool(QCheckBox *checkbox, const std::string& setting, const std::string& group); void loadSettingBool(QCheckBox *checkbox, const std::string& setting, const std::string& group);
void saveSettingBool(QCheckBox *checkbox, const std::string& setting, const std::string& group); void saveSettingBool(QCheckBox *checkbox, const std::string& setting, const std::string& group);
}; };

@ -7,7 +7,10 @@
#include <QCheckBox> #include <QCheckBox>
#include <QMenu> #include <QMenu>
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
#include <thread>
#include <mutex>
#include <apps/launcher/utils/cellnameloader.hpp>
#include <components/files/configurationmanager.hpp> #include <components/files/configurationmanager.hpp>
#include <components/contentselector/model/esmfile.hpp> #include <components/contentselector/model/esmfile.hpp>
@ -16,6 +19,7 @@
#include <components/config/gamesettings.hpp> #include <components/config/gamesettings.hpp>
#include <components/config/launchersettings.hpp> #include <components/config/launchersettings.hpp>
#include <iostream>
#include "utils/textinputdialog.hpp" #include "utils/textinputdialog.hpp"
#include "utils/profilescombobox.hpp" #include "utils/profilescombobox.hpp"
@ -40,6 +44,13 @@ Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config:
buildView(); buildView();
loadSettings(); loadSettings();
// Connect signal and slot after the settings have been loaded. We only care about the user changing
// the addons and don't want to get signals of the system doing it during startup.
connect(mSelector, SIGNAL(signalAddonDataChanged(QModelIndex,QModelIndex)),
this, SLOT(slotAddonDataChanged()));
// Call manually to indicate all changes to addon data during startup.
slotAddonDataChanged();
} }
void Launcher::DataFilesPage::buildView() void Launcher::DataFilesPage::buildView()
@ -142,6 +153,17 @@ void Launcher::DataFilesPage::saveSettings(const QString &profile)
mGameSettings.setContentList(fileNames); mGameSettings.setContentList(fileNames);
} }
QStringList Launcher::DataFilesPage::selectedFilePaths()
{
//retrieve the files selected for the profile
ContentSelectorModel::ContentFileList items = mSelector->selectedFiles();
QStringList filePaths;
foreach(const ContentSelectorModel::EsmFile *item, items) {
filePaths.append(item->filePath());
}
return filePaths;
}
void Launcher::DataFilesPage::removeProfile(const QString &profile) void Launcher::DataFilesPage::removeProfile(const QString &profile)
{ {
mLauncherSettings.removeContentList(profile); mLauncherSettings.removeContentList(profile);
@ -308,3 +330,31 @@ bool Launcher::DataFilesPage::showDeleteMessageBox (const QString &text)
return (msgBox.clickedButton() == deleteButton); return (msgBox.clickedButton() == deleteButton);
} }
void Launcher::DataFilesPage::slotAddonDataChanged()
{
QStringList selectedFiles = selectedFilePaths();
if (previousSelectedFiles != selectedFiles) {
previousSelectedFiles = selectedFiles;
// Loading cells for core Morrowind + Expansions takes about 0.2 seconds, which is enough to cause a
// barely perceptible UI lag. Splitting into its own thread to alleviate that.
std::thread loadCellsThread(&DataFilesPage::reloadCells, this, selectedFiles);
loadCellsThread.detach();
}
}
// Mutex lock to run reloadCells synchronously.
std::mutex _reloadCellsMutex;
void Launcher::DataFilesPage::reloadCells(QStringList selectedFiles)
{
// Use a mutex lock so that we can prevent two threads from executing the rest of this code at the same time
// Based on https://stackoverflow.com/a/5429695/531762
std::unique_lock<std::mutex> lock(_reloadCellsMutex);
// The following code will run only if there is not another thread currently running it
CellNameLoader cellNameLoader;
QStringList cellNamesList = QStringList::fromSet(cellNameLoader.getCellNames(selectedFiles));
std::sort(cellNamesList.begin(), cellNamesList.end());
emit signalLoadedCellsChanged(cellNamesList);
}

@ -7,6 +7,7 @@
#include <QDir> #include <QDir>
#include <QFile> #include <QFile>
#include <QStringList>
class QSortFilterProxyModel; class QSortFilterProxyModel;
class QAbstractItemModel; class QAbstractItemModel;
@ -41,8 +42,15 @@ namespace Launcher
void saveSettings(const QString &profile = ""); void saveSettings(const QString &profile = "");
bool loadSettings(); bool loadSettings();
/**
* Returns the file paths of all selected content files
* @return the file paths of all selected content files
*/
QStringList selectedFilePaths();
signals: signals:
void signalProfileChanged (int index); void signalProfileChanged (int index);
void signalLoadedCellsChanged(QStringList selectedFiles);
public slots: public slots:
void slotProfileChanged (int index); void slotProfileChanged (int index);
@ -52,6 +60,7 @@ namespace Launcher
void slotProfileChangedByUser(const QString &previous, const QString &current); void slotProfileChangedByUser(const QString &previous, const QString &current);
void slotProfileRenamed(const QString &previous, const QString &current); void slotProfileRenamed(const QString &previous, const QString &current);
void slotProfileDeleted(const QString &item); void slotProfileDeleted(const QString &item);
void slotAddonDataChanged ();
void updateOkButton(const QString &text); void updateOkButton(const QString &text);
@ -72,7 +81,7 @@ namespace Launcher
Config::LauncherSettings &mLauncherSettings; Config::LauncherSettings &mLauncherSettings;
QString mPreviousProfile; QString mPreviousProfile;
QStringList previousSelectedFiles;
QString mDataLocal; QString mDataLocal;
void setPluginsCheckstates(Qt::CheckState state); void setPluginsCheckstates(Qt::CheckState state);
@ -87,6 +96,7 @@ namespace Launcher
void addProfile (const QString &profile, bool setAsCurrent); void addProfile (const QString &profile, bool setAsCurrent);
void checkForDefaultProfile(); void checkForDefaultProfile();
void populateFileViews(const QString& contentModelName); void populateFileViews(const QString& contentModelName);
void reloadCells(QStringList selectedFiles);
class PathIterator class PathIterator
{ {

@ -119,7 +119,7 @@ void Launcher::MainDialog::createPages()
mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this); mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this);
mGraphicsPage = new GraphicsPage(mCfgMgr, mEngineSettings, this); mGraphicsPage = new GraphicsPage(mCfgMgr, mEngineSettings, this);
mSettingsPage = new SettingsPage(mCfgMgr, mGameSettings, mLauncherSettings, this); mSettingsPage = new SettingsPage(mCfgMgr, mGameSettings, mLauncherSettings, this);
mAdvancedPage = new AdvancedPage(mCfgMgr, mEngineSettings, this); mAdvancedPage = new AdvancedPage(mCfgMgr, mGameSettings, mEngineSettings, this);
// Set the combobox of the play page to imitate the combobox on the datafilespage // Set the combobox of the play page to imitate the combobox on the datafilespage
mPlayPage->setProfilesModel(mDataFilesPage->profilesModel()); mPlayPage->setProfilesModel(mDataFilesPage->profilesModel());
@ -139,6 +139,8 @@ void Launcher::MainDialog::createPages()
connect(mPlayPage, SIGNAL(signalProfileChanged(int)), mDataFilesPage, SLOT(slotProfileChanged(int))); connect(mPlayPage, SIGNAL(signalProfileChanged(int)), mDataFilesPage, SLOT(slotProfileChanged(int)));
connect(mDataFilesPage, SIGNAL(signalProfileChanged(int)), mPlayPage, SLOT(setProfilesIndex(int))); connect(mDataFilesPage, SIGNAL(signalProfileChanged(int)), mPlayPage, SLOT(setProfilesIndex(int)));
// Using Qt::QueuedConnection because signal is emitted in a subthread and slot is in the main thread
connect(mDataFilesPage, SIGNAL(signalLoadedCellsChanged(QStringList)), mAdvancedPage, SLOT(slotLoadedCellsChanged(QStringList)), Qt::QueuedConnection);
} }

@ -0,0 +1,48 @@
#include "cellnameloader.hpp"
#include <components/esm/loadcell.hpp>
#include <components/contentselector/view/contentselector.hpp>
QSet<QString> CellNameLoader::getCellNames(QStringList &contentPaths)
{
QSet<QString> cellNames;
ESM::ESMReader esmReader;
// Loop through all content files
for (auto &contentPath : contentPaths) {
esmReader.open(contentPath.toStdString());
// Loop through all records
while(esmReader.hasMoreRecs())
{
ESM::NAME recordName = esmReader.getRecName();
esmReader.getRecHeader();
if (isCellRecord(recordName)) {
QString cellName = getCellName(esmReader);
if (!cellName.isEmpty()) {
cellNames.insert(cellName);
}
}
// Stop loading content for this record and continue to the next
esmReader.skipRecord();
}
}
return cellNames;
}
bool CellNameLoader::isCellRecord(ESM::NAME &recordName)
{
return recordName.intval == ESM::REC_CELL;
}
QString CellNameLoader::getCellName(ESM::ESMReader &esmReader)
{
ESM::Cell cell;
bool isDeleted = false;
cell.loadNameAndData(esmReader, isDeleted);
return QString::fromStdString(cell.mName);
}

@ -0,0 +1,41 @@
#ifndef OPENMW_CELLNAMELOADER_H
#define OPENMW_CELLNAMELOADER_H
#include <QComboBox>
#include <QSet>
#include <QString>
#include <components/esm/esmreader.hpp>
namespace ESM {class ESMReader; struct Cell;}
namespace ContentSelectorView {class ContentSelector;}
class CellNameLoader {
public:
/**
* Returns the names of all cells contained within the given content files
* @param contentPaths the file paths of each content file to be examined
* @return the names of all cells
*/
QSet<QString> getCellNames(QStringList &contentPaths);
private:
/**
* Returns whether or not the given record is of type "Cell"
* @param name The name associated with the record
* @return whether or not the given record is of type "Cell"
*/
bool isCellRecord(ESM::NAME &name);
/**
* Returns the name of the cell
* @param esmReader the reader currently pointed to a loaded cell
* @return the name of the cell
*/
QString getCellName(ESM::ESMReader &esmReader);
};
#endif //OPENMW_CELLNAMELOADER_H

@ -3,6 +3,7 @@
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <components/misc/stringops.hpp> #include <components/misc/stringops.hpp>
#include <components/esm/esmreader.hpp>
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp> #include <boost/filesystem/fstream.hpp>
@ -653,12 +654,6 @@ void MwIniImporter::setVerbose(bool verbose) {
mVerbose = verbose; mVerbose = verbose;
} }
std::string MwIniImporter::numberToString(int n) {
std::stringstream str;
str << n;
return str.str();
}
MwIniImporter::multistrmap MwIniImporter::loadIniFile(const boost::filesystem::path& filename) const { MwIniImporter::multistrmap MwIniImporter::loadIniFile(const boost::filesystem::path& filename) const {
std::cout << "load ini file: " << filename << std::endl; std::cout << "load ini file: " << filename << std::endl;
@ -800,7 +795,7 @@ void MwIniImporter::importArchives(multistrmap &cfg, const multistrmap &ini) con
multistrmap::const_iterator it = ini.begin(); multistrmap::const_iterator it = ini.begin();
for(int i=0; it != ini.end(); i++) { for(int i=0; it != ini.end(); i++) {
archive = baseArchive; archive = baseArchive;
archive.append(this->numberToString(i)); archive.append(std::to_string(i));
it = ini.find(archive); it = ini.find(archive);
if(it == ini.end()) { if(it == ini.end()) {
@ -824,33 +819,105 @@ void MwIniImporter::importArchives(multistrmap &cfg, const multistrmap &ini) con
} }
} }
void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, const boost::filesystem::path& iniFilename) const { void MwIniImporter::dependencySortStep(std::string& element, MwIniImporter::dependencyList& source, std::vector<std::string>& result)
std::vector<std::pair<std::time_t, std::string> > contentFiles; {
auto iter = std::find_if(
source.begin(),
source.end(),
[&element](std::pair< std::string, std::vector<std::string> >& sourceElement)
{
return sourceElement.first == element;
}
);
if (iter != source.end())
{
auto foundElement = std::move(*iter);
source.erase(iter);
for (auto name : foundElement.second)
{
MwIniImporter::dependencySortStep(name, source, result);
}
result.push_back(std::move(foundElement.first));
}
}
std::vector<std::string> MwIniImporter::dependencySort(MwIniImporter::dependencyList source)
{
std::vector<std::string> result;
while (!source.empty())
{
MwIniImporter::dependencySortStep(source.begin()->first, source, result);
}
return result;
}
std::vector<std::string>::iterator MwIniImporter::findString(std::vector<std::string>& source, const std::string& string)
{
return std::find_if(source.begin(), source.end(), [&string](const std::string& sourceString)
{
return Misc::StringUtils::ciEqual(sourceString, string);
});
}
void MwIniImporter::addPaths(std::vector<boost::filesystem::path>& output, std::vector<std::string> input) {
for (auto& path : input) {
if (path.front() == '"')
{
path.erase(path.begin());
path.erase(path.end() - 1);
}
output.emplace_back(path);
}
}
void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, const boost::filesystem::path& iniFilename) const
{
std::vector<std::pair<std::time_t, boost::filesystem::path>> contentFiles;
std::string baseGameFile("Game Files:GameFile"); std::string baseGameFile("Game Files:GameFile");
std::string gameFile(""); std::string gameFile("");
std::time_t defaultTime = 0; std::time_t defaultTime = 0;
ToUTF8::Utf8Encoder encoder(mEncoding);
std::vector<boost::filesystem::path> dataPaths;
if (cfg.count("data"))
addPaths(dataPaths, cfg["data"]);
// assume the Game Files are all in a "Data Files" directory under the directory holding Morrowind.ini if (cfg.count("data-local"))
const boost::filesystem::path gameFilesDir(iniFilename.parent_path() /= "Data Files"); addPaths(dataPaths, cfg["data-local"]);
dataPaths.push_back(iniFilename.parent_path() /= "Data Files");
multistrmap::const_iterator it = ini.begin(); multistrmap::const_iterator it = ini.begin();
for(int i=0; it != ini.end(); i++) { for (int i=0; it != ini.end(); i++)
{
gameFile = baseGameFile; gameFile = baseGameFile;
gameFile.append(this->numberToString(i)); gameFile.append(std::to_string(i));
it = ini.find(gameFile); it = ini.find(gameFile);
if(it == ini.end()) { if(it == ini.end())
break; break;
}
for(std::vector<std::string>::const_iterator entry = it->second.begin(); entry!=it->second.end(); ++entry) { for(std::vector<std::string>::const_iterator entry = it->second.begin(); entry!=it->second.end(); ++entry)
{
std::string filetype(entry->substr(entry->length()-3)); std::string filetype(entry->substr(entry->length()-3));
Misc::StringUtils::lowerCaseInPlace(filetype); Misc::StringUtils::lowerCaseInPlace(filetype);
if(filetype.compare("esm") == 0 || filetype.compare("esp") == 0) { if(filetype.compare("esm") == 0 || filetype.compare("esp") == 0)
boost::filesystem::path filepath(gameFilesDir); {
filepath /= *entry; bool found = false;
contentFiles.push_back(std::make_pair(lastWriteTime(filepath, defaultTime), *entry)); for (auto & dataPath : dataPaths)
{
boost::filesystem::path path = dataPath / *entry;
std::time_t time = lastWriteTime(path, defaultTime);
if (time != defaultTime)
{
contentFiles.push_back({time, path});
found = true;
break;
}
}
if (!found)
std::cout << "Warning: " << *entry << " not found, ignoring" << std::endl;
} }
} }
} }
@ -858,11 +925,46 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, co
cfg.erase("content"); cfg.erase("content");
cfg.insert( std::make_pair("content", std::vector<std::string>() ) ); cfg.insert( std::make_pair("content", std::vector<std::string>() ) );
// this will sort files by time order first, then alphabetical (maybe), I suspect non ASCII filenames will be stuffed. // sort by timestamp
sort(contentFiles.begin(), contentFiles.end()); sort(contentFiles.begin(), contentFiles.end());
for(std::vector<std::pair<std::time_t, std::string> >::const_iterator iter=contentFiles.begin(); iter!=contentFiles.end(); ++iter) {
cfg["content"].push_back(iter->second); MwIniImporter::dependencyList unsortedFiles;
ESM::ESMReader reader;
reader.setEncoder(&encoder);
for (auto& file : contentFiles)
{
reader.open(file.second.string());
std::vector<std::string> dependencies;
for (auto& gameFile : reader.getGameFiles())
{
dependencies.push_back(gameFile.name);
}
unsortedFiles.emplace_back(boost::filesystem::path(reader.getName()).filename().string(), dependencies);
reader.close();
}
auto sortedFiles = dependencySort(unsortedFiles);
// hard-coded dependency Morrowind - Tribunal - Bloodmoon
if(findString(sortedFiles, "Morrowind.esm") != sortedFiles.end())
{
auto tribunalIter = findString(sortedFiles, "Tribunal.esm");
auto bloodmoonIter = findString(sortedFiles, "Bloodmoon.esm");
if (bloodmoonIter != sortedFiles.end() && tribunalIter != sortedFiles.end())
{
size_t bloodmoonIndex = std::distance(sortedFiles.begin(), bloodmoonIter);
size_t tribunalIndex = std::distance(sortedFiles.begin(), tribunalIter);
if (bloodmoonIndex < tribunalIndex)
tribunalIndex++;
sortedFiles.insert(bloodmoonIter, *tribunalIter);
sortedFiles.erase(sortedFiles.begin() + tribunalIndex);
}
} }
for (auto& file : sortedFiles)
cfg["content"].push_back(file);
} }
void MwIniImporter::writeToFile(std::ostream &out, const multistrmap &cfg) { void MwIniImporter::writeToFile(std::ostream &out, const multistrmap &cfg) {
@ -901,9 +1003,5 @@ std::time_t MwIniImporter::lastWriteTime(const boost::filesystem::path& filename
std::cout << "content file: " << resolved << " timestamp = (" << writeTime << std::cout << "content file: " << resolved << " timestamp = (" << writeTime <<
") " << timeStrBuffer << std::endl; ") " << timeStrBuffer << std::endl;
} }
else
{
std::cout << "content file: " << filename << " not found" << std::endl;
}
return writeTime; return writeTime;
} }

@ -14,6 +14,7 @@ class MwIniImporter {
public: public:
typedef std::map<std::string, std::string> strmap; typedef std::map<std::string, std::string> strmap;
typedef std::map<std::string, std::vector<std::string> > multistrmap; typedef std::map<std::string, std::vector<std::string> > multistrmap;
typedef std::vector< std::pair< std::string, std::vector<std::string> > > dependencyList;
MwIniImporter(); MwIniImporter();
void setInputEncoding(const ToUTF8::FromType& encoding); void setInputEncoding(const ToUTF8::FromType& encoding);
@ -22,14 +23,19 @@ class MwIniImporter {
static multistrmap loadCfgFile(const boost::filesystem::path& filename); static multistrmap loadCfgFile(const boost::filesystem::path& filename);
void merge(multistrmap &cfg, const multistrmap &ini) const; void merge(multistrmap &cfg, const multistrmap &ini) const;
void mergeFallback(multistrmap &cfg, const multistrmap &ini) const; void mergeFallback(multistrmap &cfg, const multistrmap &ini) const;
void importGameFiles(multistrmap &cfg, const multistrmap &ini, void importGameFiles(multistrmap &cfg, const multistrmap &ini,
const boost::filesystem::path& iniFilename) const; const boost::filesystem::path& iniFilename) const;
void importArchives(multistrmap &cfg, const multistrmap &ini) const; void importArchives(multistrmap &cfg, const multistrmap &ini) const;
static void writeToFile(std::ostream &out, const multistrmap &cfg); static void writeToFile(std::ostream &out, const multistrmap &cfg);
static std::vector<std::string> dependencySort(MwIniImporter::dependencyList source);
private: private:
static void dependencySortStep(std::string& element, MwIniImporter::dependencyList& source, std::vector<std::string>& result);
static std::vector<std::string>::iterator findString(std::vector<std::string>& source, const std::string& string);
static void insertMultistrmap(multistrmap &cfg, const std::string& key, const std::string& value); static void insertMultistrmap(multistrmap &cfg, const std::string& key, const std::string& value);
static std::string numberToString(int n); static void addPaths(std::vector<boost::filesystem::path>& output, std::vector<std::string> input);
/// \return file's "last modified time", used in original MW to determine plug-in load order /// \return file's "last modified time", used in original MW to determine plug-in load order
static std::time_t lastWriteTime(const boost::filesystem::path& filename, std::time_t defaultTime); static std::time_t lastWriteTime(const boost::filesystem::path& filename, std::time_t defaultTime);
@ -40,5 +46,4 @@ class MwIniImporter {
ToUTF8::FromType mEncoding; ToUTF8::FromType mEncoding;
}; };
#endif #endif

@ -33,12 +33,12 @@ bool hasExtension(std::string filename, std::string extensionToFind)
} }
///See if the file has the "nif" extension. ///See if the file has the "nif" extension.
bool isNIF(std::string filename) bool isNIF(const std::string & filename)
{ {
return hasExtension(filename,"nif"); return hasExtension(filename,"nif");
} }
///See if the file has the "bsa" extension. ///See if the file has the "bsa" extension.
bool isBSA(std::string filename) bool isBSA(const std::string & filename)
{ {
return hasExtension(filename,"bsa"); return hasExtension(filename,"bsa");
} }

@ -81,14 +81,14 @@ opencs_units_noqt (view/world
opencs_units (view/widget opencs_units (view/widget
scenetoolbar scenetool scenetoolmode pushbutton scenetooltoggle scenetoolrun modebutton scenetoolbar scenetool scenetoolmode pushbutton scenetooltoggle scenetoolrun modebutton
scenetooltoggle2 completerpopup coloreditor colorpickerpopup droplineedit scenetooltoggle2 scenetooltexturebrush completerpopup coloreditor colorpickerpopup droplineedit
) )
opencs_units (view/render opencs_units (view/render
scenewidget worldspacewidget pagedworldspacewidget unpagedworldspacewidget scenewidget worldspacewidget pagedworldspacewidget unpagedworldspacewidget
previewwidget editmode instancemode instanceselectionmode instancemovemode previewwidget editmode instancemode instanceselectionmode instancemovemode
orbitcameramode pathgridmode selectionmode pathgridselectionmode cameracontroller orbitcameramode pathgridmode selectionmode pathgridselectionmode cameracontroller
cellwater cellwater terraintexturemode
) )
opencs_units_noqt (view/render opencs_units_noqt (view/render
@ -246,7 +246,7 @@ if (DESIRED_QT_VERSION MATCHES 4)
target_link_libraries(openmw-cs ${QT_QTMAIN_LIBRARY}) target_link_libraries(openmw-cs ${QT_QTMAIN_LIBRARY})
endif() endif()
else() else()
qt5_use_modules(openmw-cs Widgets Core Network OpenGL) target_link_libraries(openmw-cs Qt5::Widgets Qt5::Core Qt5::Network Qt5::OpenGL)
endif() endif()
if (WIN32) if (WIN32)

@ -320,12 +320,13 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration,
connect (&mUndoStack, SIGNAL (cleanChanged (bool)), this, SLOT (modificationStateChanged (bool))); connect (&mUndoStack, SIGNAL (cleanChanged (bool)), this, SLOT (modificationStateChanged (bool)));
connect (&mTools, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int))); connect (&mTools, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int)));
connect (&mTools, SIGNAL (done (int, bool)), this, SLOT (operationDone (int, bool))); connect (&mTools, SIGNAL (done (int, bool)), this, SIGNAL (operationDone (int, bool)));
connect (&mTools, SIGNAL (done (int, bool)), this, SLOT (operationDone2 (int, bool)));
connect (&mTools, SIGNAL (mergeDone (CSMDoc::Document*)), connect (&mTools, SIGNAL (mergeDone (CSMDoc::Document*)),
this, SIGNAL (mergeDone (CSMDoc::Document*))); this, SIGNAL (mergeDone (CSMDoc::Document*)));
connect (&mSaving, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int))); connect (&mSaving, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int)));
connect (&mSaving, SIGNAL (done (int, bool)), this, SLOT (operationDone (int, bool))); connect (&mSaving, SIGNAL (done (int, bool)), this, SLOT (operationDone2 (int, bool)));
connect ( connect (
&mSaving, SIGNAL (reportMessage (const CSMDoc::Message&, int)), &mSaving, SIGNAL (reportMessage (const CSMDoc::Message&, int)),
@ -437,7 +438,7 @@ void CSMDoc::Document::reportMessage (const CSMDoc::Message& message, int type)
std::cout << message.mMessage << std::endl; std::cout << message.mMessage << std::endl;
} }
void CSMDoc::Document::operationDone (int type, bool failed) void CSMDoc::Document::operationDone2 (int type, bool failed)
{ {
if (type==CSMDoc::State_Saving && !failed) if (type==CSMDoc::State_Saving && !failed)
mDirty = false; mDirty = false;

@ -168,13 +168,15 @@ namespace CSMDoc
/// document. This signal must be handled to avoid a leak. /// document. This signal must be handled to avoid a leak.
void mergeDone (CSMDoc::Document *document); void mergeDone (CSMDoc::Document *document);
void operationDone (int type, bool failed);
private slots: private slots:
void modificationStateChanged (bool clean); void modificationStateChanged (bool clean);
void reportMessage (const CSMDoc::Message& message, int type); void reportMessage (const CSMDoc::Message& message, int type);
void operationDone (int type, bool failed); void operationDone2 (int type, bool failed);
void runStateChanged(); void runStateChanged();

@ -579,7 +579,7 @@ bool CSMFilter::Parser::parse (const std::string& filter, bool allowPredefined)
} }
// We do not use isString() here, because there could be a pre-defined filter with an ID that is // We do not use isString() here, because there could be a pre-defined filter with an ID that is
// equal a filter keyword. // equal a filter keyword.
else if (token.mType==Token::Type_String && allowPredefined) else if (token.mType==Token::Type_String)
{ {
if (getNextToken()!=Token (Token::Type_EOS)) if (getNextToken()!=Token (Token::Type_EOS))
{ {

@ -49,7 +49,7 @@ namespace CSMPrefs
ShortcutMap::iterator shortcutListIt = mWidgetShortcuts.find(widget); ShortcutMap::iterator shortcutListIt = mWidgetShortcuts.find(widget);
if (shortcutListIt != mWidgetShortcuts.end()) if (shortcutListIt != mWidgetShortcuts.end())
{ {
std::remove(shortcutListIt->second.begin(), shortcutListIt->second.end(), shortcut); shortcutListIt->second.erase(std::remove(shortcutListIt->second.begin(), shortcutListIt->second.end(), shortcut), shortcutListIt->second.end());
} }
} }

@ -222,7 +222,15 @@ void CSMPrefs::State::declare()
EnumValues insertOutsideVisibleCell; EnumValues insertOutsideVisibleCell;
insertOutsideVisibleCell.add (showAndInsert).add (dontInsert).add (insertAnyway); insertOutsideVisibleCell.add (showAndInsert).add (dontInsert).add (insertAnyway);
declareCategory ("Scene Drops"); EnumValue createAndLandEdit ("Create cell and land, then edit");
EnumValue showAndLandEdit ("Show cell and edit");
EnumValue dontLandEdit ("Discard");
EnumValues landeditOutsideCell;
landeditOutsideCell.add (createAndLandEdit).add (dontLandEdit);
EnumValues landeditOutsideVisibleCell;
landeditOutsideVisibleCell.add (showAndLandEdit).add (dontLandEdit);
declareCategory ("3D Scene Editing");
declareInt ("distance", "Drop Distance", 50). declareInt ("distance", "Drop Distance", 50).
setTooltip ("If an instance drop can not be placed against another object at the " setTooltip ("If an instance drop can not be placed against another object at the "
"insert point, it will be placed by this distance from the insert point instead"); "insert point, it will be placed by this distance from the insert point instead");
@ -230,6 +238,12 @@ void CSMPrefs::State::declare()
addValues (insertOutsideCell); addValues (insertOutsideCell);
declareEnum ("outside-visible-drop", "Handling drops outside of visible cells", showAndInsert). declareEnum ("outside-visible-drop", "Handling drops outside of visible cells", showAndInsert).
addValues (insertOutsideVisibleCell); addValues (insertOutsideVisibleCell);
declareEnum ("outside-landedit", "Handling land edit outside of cells", createAndLandEdit).
addValues (landeditOutsideCell);
declareEnum ("outside-visible-landedit", "Handling land edit outside of visible cells", showAndLandEdit).
addValues (landeditOutsideVisibleCell);
declareInt ("texturebrush-maximumsize", "Maximum texture brush size", 50).
setMin (1);
declareCategory ("Key Bindings"); declareCategory ("Key Bindings");

@ -28,12 +28,13 @@ void CSMTools::ClassCheckStage::perform (int stage, CSMDoc::Messages& messages)
CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Class, class_.mId); CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Class, class_.mId);
// test for empty name and description // A class should have a name
if (class_.mName.empty()) if (class_.mName.empty())
messages.push_back (std::make_pair (id, class_.mId + " has an empty name")); messages.push_back (std::make_pair (id, class_.mId + " doesn't have a name"));
if (class_.mDescription.empty()) // A playable class should have a description
messages.push_back (std::make_pair (id, class_.mId + " has an empty description")); if (class_.mData.mIsPlayable != 0 && class_.mDescription.empty())
messages.push_back (std::make_pair (id, class_.mId + " doesn't have a description and it's playable"));
// test for invalid attributes // test for invalid attributes
for (int i=0; i<2; ++i) for (int i=0; i<2; ++i)

@ -29,7 +29,7 @@ void CSMTools::PathgridCheckStage::perform (int stage, CSMDoc::Messages& message
CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Pathgrid, pathgrid.mId); CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Pathgrid, pathgrid.mId);
// check the number of pathgrid points // check the number of pathgrid points
if (pathgrid.mData.mS2 > static_cast<int>(pathgrid.mPoints.size())) if (pathgrid.mData.mS2 < static_cast<int>(pathgrid.mPoints.size()))
messages.add (id, pathgrid.mId + " has less points than expected", "", CSMDoc::Message::Severity_Error); messages.add (id, pathgrid.mId + " has less points than expected", "", CSMDoc::Message::Severity_Error);
else if (pathgrid.mData.mS2 > static_cast<int>(pathgrid.mPoints.size())) else if (pathgrid.mData.mS2 > static_cast<int>(pathgrid.mPoints.size()))
messages.add (id, pathgrid.mId + " has more points than expected", "", CSMDoc::Message::Severity_Error); messages.add (id, pathgrid.mId + " has more points than expected", "", CSMDoc::Message::Severity_Error);

@ -239,9 +239,7 @@ void CSMTools::ReferenceableCheckStage::bookCheck(
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
if (baseRecord.isDeleted()) if (baseRecord.isDeleted())
{
return; return;
}
const ESM::Book& book = (dynamic_cast<const CSMWorld::Record<ESM::Book>& >(baseRecord)).get(); const ESM::Book& book = (dynamic_cast<const CSMWorld::Record<ESM::Book>& >(baseRecord)).get();
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Book, book.mId); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Book, book.mId);
@ -260,9 +258,7 @@ void CSMTools::ReferenceableCheckStage::activatorCheck(
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
if (baseRecord.isDeleted()) if (baseRecord.isDeleted())
{
return; return;
}
const ESM::Activator& activator = (dynamic_cast<const CSMWorld::Record<ESM::Activator>& >(baseRecord)).get(); const ESM::Activator& activator = (dynamic_cast<const CSMWorld::Record<ESM::Activator>& >(baseRecord)).get();
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Activator, activator.mId); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Activator, activator.mId);
@ -283,9 +279,7 @@ void CSMTools::ReferenceableCheckStage::potionCheck(
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
if (baseRecord.isDeleted()) if (baseRecord.isDeleted())
{
return; return;
}
const ESM::Potion& potion = (dynamic_cast<const CSMWorld::Record<ESM::Potion>& >(baseRecord)).get(); const ESM::Potion& potion = (dynamic_cast<const CSMWorld::Record<ESM::Potion>& >(baseRecord)).get();
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Potion, potion.mId); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Potion, potion.mId);
@ -306,9 +300,7 @@ void CSMTools::ReferenceableCheckStage::apparatusCheck(
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
if (baseRecord.isDeleted()) if (baseRecord.isDeleted())
{
return; return;
}
const ESM::Apparatus& apparatus = (dynamic_cast<const CSMWorld::Record<ESM::Apparatus>& >(baseRecord)).get(); const ESM::Apparatus& apparatus = (dynamic_cast<const CSMWorld::Record<ESM::Apparatus>& >(baseRecord)).get();
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Apparatus, apparatus.mId); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Apparatus, apparatus.mId);
@ -329,9 +321,7 @@ void CSMTools::ReferenceableCheckStage::armorCheck(
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
if (baseRecord.isDeleted()) if (baseRecord.isDeleted())
{
return; return;
}
const ESM::Armor& armor = (dynamic_cast<const CSMWorld::Record<ESM::Armor>& >(baseRecord)).get(); const ESM::Armor& armor = (dynamic_cast<const CSMWorld::Record<ESM::Armor>& >(baseRecord)).get();
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Armor, armor.mId); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Armor, armor.mId);
@ -358,9 +348,7 @@ void CSMTools::ReferenceableCheckStage::clothingCheck(
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
if (baseRecord.isDeleted()) if (baseRecord.isDeleted())
{
return; return;
}
const ESM::Clothing& clothing = (dynamic_cast<const CSMWorld::Record<ESM::Clothing>& >(baseRecord)).get(); const ESM::Clothing& clothing = (dynamic_cast<const CSMWorld::Record<ESM::Clothing>& >(baseRecord)).get();
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Clothing, clothing.mId); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Clothing, clothing.mId);
@ -378,9 +366,7 @@ void CSMTools::ReferenceableCheckStage::containerCheck(
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
if (baseRecord.isDeleted()) if (baseRecord.isDeleted())
{
return; return;
}
const ESM::Container& container = (dynamic_cast<const CSMWorld::Record<ESM::Container>& >(baseRecord)).get(); const ESM::Container& container = (dynamic_cast<const CSMWorld::Record<ESM::Container>& >(baseRecord)).get();
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Container, container.mId); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Container, container.mId);
@ -512,9 +498,7 @@ void CSMTools::ReferenceableCheckStage::ingredientCheck(
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
if (baseRecord.isDeleted()) if (baseRecord.isDeleted())
{
return; return;
}
const ESM::Ingredient& ingredient = (dynamic_cast<const CSMWorld::Record<ESM::Ingredient>& >(baseRecord)).get(); const ESM::Ingredient& ingredient = (dynamic_cast<const CSMWorld::Record<ESM::Ingredient>& >(baseRecord)).get();
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Ingredient, ingredient.mId); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Ingredient, ingredient.mId);
@ -577,13 +561,8 @@ void CSMTools::ReferenceableCheckStage::lightCheck(
messages.push_back (std::make_pair (id, light.mId + " has negative light radius")); messages.push_back (std::make_pair (id, light.mId + " has negative light radius"));
if (light.mData.mFlags & ESM::Light::Carry) if (light.mData.mFlags & ESM::Light::Carry)
{
inventoryItemCheck<ESM::Light>(light, messages, id.toString()); inventoryItemCheck<ESM::Light>(light, messages, id.toString());
if (light.mData.mTime == 0)
messages.push_back (std::make_pair (id, light.mId + " has zero duration"));
}
// Check that mentioned scripts exist // Check that mentioned scripts exist
scriptCheck<ESM::Light>(light, messages, id.toString()); scriptCheck<ESM::Light>(light, messages, id.toString());
} }
@ -596,9 +575,7 @@ void CSMTools::ReferenceableCheckStage::lockpickCheck(
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
if (baseRecord.isDeleted()) if (baseRecord.isDeleted())
{
return; return;
}
const ESM::Lockpick& lockpick = (dynamic_cast<const CSMWorld::Record<ESM::Lockpick>& >(baseRecord)).get(); const ESM::Lockpick& lockpick = (dynamic_cast<const CSMWorld::Record<ESM::Lockpick>& >(baseRecord)).get();
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Lockpick, lockpick.mId); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Lockpick, lockpick.mId);
@ -619,9 +596,7 @@ void CSMTools::ReferenceableCheckStage::miscCheck(
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
if (baseRecord.isDeleted()) if (baseRecord.isDeleted())
{
return; return;
}
const ESM::Miscellaneous& miscellaneous = (dynamic_cast<const CSMWorld::Record<ESM::Miscellaneous>& >(baseRecord)).get(); const ESM::Miscellaneous& miscellaneous = (dynamic_cast<const CSMWorld::Record<ESM::Miscellaneous>& >(baseRecord)).get();
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Miscellaneous, miscellaneous.mId); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Miscellaneous, miscellaneous.mId);
@ -644,12 +619,12 @@ void CSMTools::ReferenceableCheckStage::npcCheck (
const ESM::NPC& npc = (dynamic_cast<const CSMWorld::Record<ESM::NPC>& >(baseRecord)).get(); const ESM::NPC& npc = (dynamic_cast<const CSMWorld::Record<ESM::NPC>& >(baseRecord)).get();
CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Npc, npc.mId); CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Npc, npc.mId);
short level(npc.mNpdt52.mLevel); short level(npc.mNpdt.mLevel);
char disposition(npc.mNpdt52.mDisposition); char disposition(npc.mNpdt.mDisposition);
char reputation(npc.mNpdt52.mReputation); char reputation(npc.mNpdt.mReputation);
char rank(npc.mNpdt52.mRank); char rank(npc.mNpdt.mRank);
//Don't know what unknown is for //Don't know what unknown is for
int gold(npc.mNpdt52.mGold); int gold(npc.mNpdt.mGold);
//Detect if player is present //Detect if player is present
if (Misc::StringUtils::ciEqual(npc.mId, "player")) //Happy now, scrawl? if (Misc::StringUtils::ciEqual(npc.mId, "player")) //Happy now, scrawl?
@ -663,36 +638,36 @@ void CSMTools::ReferenceableCheckStage::npcCheck (
return; return;
} }
level = npc.mNpdt12.mLevel; level = npc.mNpdt.mLevel;
disposition = npc.mNpdt12.mDisposition; disposition = npc.mNpdt.mDisposition;
reputation = npc.mNpdt12.mReputation; reputation = npc.mNpdt.mReputation;
rank = npc.mNpdt12.mRank; rank = npc.mNpdt.mRank;
gold = npc.mNpdt12.mGold; gold = npc.mNpdt.mGold;
} }
else else
{ {
if (npc.mNpdt52.mAgility == 0) if (npc.mNpdt.mAgility == 0)
messages.push_back (std::make_pair (id, npc.mId + " agility has zero value")); messages.push_back (std::make_pair (id, npc.mId + " agility has zero value"));
if (npc.mNpdt52.mEndurance == 0) if (npc.mNpdt.mEndurance == 0)
messages.push_back (std::make_pair (id, npc.mId + " endurance has zero value")); messages.push_back (std::make_pair (id, npc.mId + " endurance has zero value"));
if (npc.mNpdt52.mIntelligence == 0) if (npc.mNpdt.mIntelligence == 0)
messages.push_back (std::make_pair (id, npc.mId + " intelligence has zero value")); messages.push_back (std::make_pair (id, npc.mId + " intelligence has zero value"));
if (npc.mNpdt52.mLuck == 0) if (npc.mNpdt.mLuck == 0)
messages.push_back (std::make_pair (id, npc.mId + " luck has zero value")); messages.push_back (std::make_pair (id, npc.mId + " luck has zero value"));
if (npc.mNpdt52.mPersonality == 0) if (npc.mNpdt.mPersonality == 0)
messages.push_back (std::make_pair (id, npc.mId + " personality has zero value")); messages.push_back (std::make_pair (id, npc.mId + " personality has zero value"));
if (npc.mNpdt52.mStrength == 0) if (npc.mNpdt.mStrength == 0)
messages.push_back (std::make_pair (id, npc.mId + " strength has zero value")); messages.push_back (std::make_pair (id, npc.mId + " strength has zero value"));
if (npc.mNpdt52.mSpeed == 0) if (npc.mNpdt.mSpeed == 0)
messages.push_back (std::make_pair (id, npc.mId + " speed has zero value")); messages.push_back (std::make_pair (id, npc.mId + " speed has zero value"));
if (npc.mNpdt52.mWillpower == 0) if (npc.mNpdt.mWillpower == 0)
messages.push_back (std::make_pair (id, npc.mId + " willpower has zero value")); messages.push_back (std::make_pair (id, npc.mId + " willpower has zero value"));
} }
@ -706,22 +681,14 @@ void CSMTools::ReferenceableCheckStage::npcCheck (
messages.push_back (std::make_pair (id, npc.mId + " has any empty name")); messages.push_back (std::make_pair (id, npc.mId + " has any empty name"));
if (npc.mClass.empty()) if (npc.mClass.empty())
{ messages.push_back (std::make_pair (id, npc.mId + " has an empty class"));
messages.push_back (std::make_pair (id, npc.mId + " has any empty class"));
}
else if (mClasses.searchId (npc.mClass) == -1) else if (mClasses.searchId (npc.mClass) == -1)
{
messages.push_back (std::make_pair (id, npc.mId + " has invalid class")); messages.push_back (std::make_pair (id, npc.mId + " has invalid class"));
}
if (npc.mRace.empty()) if (npc.mRace.empty())
{ messages.push_back (std::make_pair (id, npc.mId + " has an empty race"));
messages.push_back (std::make_pair (id, npc.mId + " has any empty race"));
}
else if (mRaces.searchId (npc.mRace) == -1) else if (mRaces.searchId (npc.mRace) == -1)
{
messages.push_back (std::make_pair (id, npc.mId + " has invalid race")); messages.push_back (std::make_pair (id, npc.mId + " has invalid race"));
}
if (disposition < 0) if (disposition < 0)
messages.push_back (std::make_pair (id, npc.mId + " has negative disposition")); messages.push_back (std::make_pair (id, npc.mId + " has negative disposition"));
@ -823,7 +790,7 @@ void CSMTools::ReferenceableCheckStage::weaponCheck(
{ {
//checking of health //checking of health
if (weapon.mData.mHealth <= 0) if (weapon.mData.mHealth <= 0)
messages.push_back (std::make_pair (id, weapon.mId + " has non-positivie health")); messages.push_back (std::make_pair (id, weapon.mId + " has non-positive health"));
if (weapon.mData.mReach < 0) if (weapon.mData.mReach < 0)
messages.push_back (std::make_pair (id, weapon.mId + " has negative reach")); messages.push_back (std::make_pair (id, weapon.mId + " has negative reach"));
@ -842,9 +809,7 @@ void CSMTools::ReferenceableCheckStage::probeCheck(
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
if (baseRecord.isDeleted()) if (baseRecord.isDeleted())
{
return; return;
}
const ESM::Probe& probe = (dynamic_cast<const CSMWorld::Record<ESM::Probe>& >(baseRecord)).get(); const ESM::Probe& probe = (dynamic_cast<const CSMWorld::Record<ESM::Probe>& >(baseRecord)).get();
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Probe, probe.mId); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Probe, probe.mId);

@ -27,7 +27,7 @@ void CSMTools::SoundCheckStage::perform (int stage, CSMDoc::Messages& messages)
CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Sound, sound.mId); CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Sound, sound.mId);
if (sound.mData.mMinRange>sound.mData.mMaxRange) if (sound.mData.mMinRange>sound.mData.mMaxRange)
messages.push_back (std::make_pair (id, "Maximum range larger than minimum range")); messages.push_back (std::make_pair (id, "Minimum range larger than maximum range"));
/// \todo check, if the sound file exists /// \todo check, if the sound file exists
} }

@ -61,12 +61,7 @@ CSMDoc::OperationHolder *CSMTools::Tools::getVerifier()
connect (&mVerifier, SIGNAL (reportMessage (const CSMDoc::Message&, int)), connect (&mVerifier, SIGNAL (reportMessage (const CSMDoc::Message&, int)),
this, SLOT (verifierMessage (const CSMDoc::Message&, int))); this, SLOT (verifierMessage (const CSMDoc::Message&, int)));
std::vector<std::string> mandatoryIds; // I want C++11, damn it! std::vector<std::string> mandatoryIds {"Day", "DaysPassed", "GameHour", "Month", "PCRace"};
mandatoryIds.push_back ("Day");
mandatoryIds.push_back ("DaysPassed");
mandatoryIds.push_back ("GameHour");
mandatoryIds.push_back ("Month");
mandatoryIds.push_back ("PCRace");
mVerifierOperation->appendStage (new MandatoryIdStage (mData.getGlobals(), mVerifierOperation->appendStage (new MandatoryIdStage (mData.getGlobals(),
CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Globals), mandatoryIds)); CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Globals), mandatoryIds));

@ -88,6 +88,7 @@ namespace CSMWorld
Display_UnsignedInteger8, Display_UnsignedInteger8,
Display_Integer, Display_Integer,
Display_Float, Display_Float,
Display_Double,
Display_Var, Display_Var,
Display_GmstVarType, Display_GmstVarType,
Display_GlobalVarType, Display_GlobalVarType,

@ -1371,7 +1371,7 @@ namespace CSMWorld
RotColumn (ESM::Position ESXRecordT::* position, int index, bool door) RotColumn (ESM::Position ESXRecordT::* position, int index, bool door)
: Column<ESXRecordT> ( : Column<ESXRecordT> (
(door ? Columns::ColumnId_DoorPositionXRot : Columns::ColumnId_PositionXRot)+index, (door ? Columns::ColumnId_DoorPositionXRot : Columns::ColumnId_PositionXRot)+index,
ColumnBase::Display_Float), mPosition (position), mIndex (index) {} ColumnBase::Display_Double), mPosition (position), mIndex (index) {}
virtual QVariant get (const Record<ESXRecordT>& record) const virtual QVariant get (const Record<ESXRecordT>& record) const
{ {

@ -66,7 +66,7 @@ namespace CSMWorld
{ ColumnId_SleepForbidden, "Sleep Forbidden" }, { ColumnId_SleepForbidden, "Sleep Forbidden" },
{ ColumnId_InteriorWater, "Interior Water" }, { ColumnId_InteriorWater, "Interior Water" },
{ ColumnId_InteriorSky, "Interior Sky" }, { ColumnId_InteriorSky, "Interior Sky" },
{ ColumnId_Model, "Model" }, { ColumnId_Model, "Model/Animation" },
{ ColumnId_Script, "Script" }, { ColumnId_Script, "Script" },
{ ColumnId_Icon, "Icon" }, { ColumnId_Icon, "Icon" },
{ ColumnId_Weight, "Weight" }, { ColumnId_Weight, "Weight" },
@ -105,7 +105,7 @@ namespace CSMWorld
{ ColumnId_Respawn, "Respawn" }, { ColumnId_Respawn, "Respawn" },
{ ColumnId_CreatureType, "Creature Type" }, { ColumnId_CreatureType, "Creature Type" },
{ ColumnId_SoulPoints, "Soul Points" }, { ColumnId_SoulPoints, "Soul Points" },
{ ColumnId_OriginalCreature, "Original Creature" }, { ColumnId_ParentCreature, "Parent Creature" },
{ ColumnId_Biped, "Biped" }, { ColumnId_Biped, "Biped" },
{ ColumnId_HasWeapon, "Has Weapon" }, { ColumnId_HasWeapon, "Has Weapon" },
{ ColumnId_Swims, "Swims" }, { ColumnId_Swims, "Swims" },

@ -99,7 +99,7 @@ namespace CSMWorld
ColumnId_Respawn = 84, ColumnId_Respawn = 84,
ColumnId_CreatureType = 85, ColumnId_CreatureType = 85,
ColumnId_SoulPoints = 86, ColumnId_SoulPoints = 86,
ColumnId_OriginalCreature = 87, ColumnId_ParentCreature = 87,
ColumnId_Biped = 88, ColumnId_Biped = 88,
ColumnId_HasWeapon = 89, ColumnId_HasWeapon = 89,
// unused // unused

@ -962,6 +962,29 @@ int CSMWorld::Data::startLoading (const boost::filesystem::path& path, bool base
return mReader->getRecordCount(); return mReader->getRecordCount();
} }
void CSMWorld::Data::loadFallbackEntries()
{
// Load default marker definitions, if game files do not have them for some reason
std::pair<std::string, std::string> markers[] = {
std::make_pair("divinemarker", "marker_divine.nif"),
std::make_pair("doormarker", "marker_arrow.nif"),
std::make_pair("northmarker", "marker_north.nif"),
std::make_pair("templemarker", "marker_temple.nif"),
std::make_pair("travelmarker", "marker_travel.nif")
};
for (const std::pair<std::string, std::string> marker : markers)
{
if (mReferenceables.searchId (marker.first)==-1)
{
CSMWorld::Record<ESM::Static> record;
record.mBase = ESM::Static(marker.first, marker.second);
record.mState = CSMWorld::RecordBase::State_BaseOnly;
mReferenceables.appendRecord (record, CSMWorld::UniversalId::Type_Static);
}
}
}
bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages)
{ {
if (!mReader) if (!mReader)
@ -983,6 +1006,9 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages)
mReader = 0; mReader = 0;
mDialogue = 0; mDialogue = 0;
loadFallbackEntries();
return true; return true;
} }

@ -144,6 +144,8 @@ namespace CSMWorld
static int count (RecordBase::State state, const CollectionBase& collection); static int count (RecordBase::State state, const CollectionBase& collection);
void loadFallbackEntries();
public: public:
Data (ToUTF8::FromType encoding, bool fsStrict, const Files::PathContainer& dataPaths, Data (ToUTF8::FromType encoding, bool fsStrict, const Files::PathContainer& dataPaths,

@ -11,11 +11,6 @@ namespace CSMWorld
bool LandTextureTableProxyModel::filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) const bool LandTextureTableProxyModel::filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) const
{ {
int columnIndex = mSourceModel->findColumnIndex(Columns::ColumnId_Modification);
QModelIndex index = mSourceModel->index(sourceRow, columnIndex);
if (mSourceModel->data(index).toInt() != RecordBase::State_ModifiedOnly)
return false;
return IdTableProxyModel::filterAcceptsRow(sourceRow, sourceParent); return IdTableProxyModel::filterAcceptsRow(sourceRow, sourceParent);
} }
} }

@ -914,7 +914,7 @@ void CSMWorld::NpcAttributesRefIdAdapter::setNestedTable (const RefIdColumn* col
ESM::NPC npc = record.get(); ESM::NPC npc = record.get();
// store the whole struct // store the whole struct
npc.mNpdt52 = npc.mNpdt =
static_cast<const NestedTableWrapper<std::vector<ESM::NPC::NPDTstruct52> > &>(nestedTable).mNestedTable.at(0); static_cast<const NestedTableWrapper<std::vector<ESM::NPC::NPDTstruct52> > &>(nestedTable).mNestedTable.at(0);
record.setModified (npc); record.setModified (npc);
@ -928,7 +928,7 @@ CSMWorld::NestedTableWrapperBase* CSMWorld::NpcAttributesRefIdAdapter::nestedTab
// return the whole struct // return the whole struct
std::vector<ESM::NPC::NPDTstruct52> wrap; std::vector<ESM::NPC::NPDTstruct52> wrap;
wrap.push_back(record.get().mNpdt52); wrap.push_back(record.get().mNpdt);
// deleted by dtor of NestedTableStoring // deleted by dtor of NestedTableStoring
return new NestedTableWrapper<std::vector<ESM::NPC::NPDTstruct52> >(wrap); return new NestedTableWrapper<std::vector<ESM::NPC::NPDTstruct52> >(wrap);
} }
@ -939,7 +939,7 @@ QVariant CSMWorld::NpcAttributesRefIdAdapter::getNestedData (const RefIdColumn *
const Record<ESM::NPC>& record = const Record<ESM::NPC>& record =
static_cast<const Record<ESM::NPC>&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); static_cast<const Record<ESM::NPC>&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc)));
const ESM::NPC::NPDTstruct52& npcStruct = record.get().mNpdt52; const ESM::NPC::NPDTstruct52& npcStruct = record.get().mNpdt;
if (subColIndex == 0) if (subColIndex == 0)
return subRowIndex; return subRowIndex;
@ -966,7 +966,7 @@ void CSMWorld::NpcAttributesRefIdAdapter::setNestedData (const RefIdColumn *colu
Record<ESM::NPC>& record = Record<ESM::NPC>& record =
static_cast<Record<ESM::NPC>&> (data.getRecord (RefIdData::LocalIndex (row, UniversalId::Type_Npc))); static_cast<Record<ESM::NPC>&> (data.getRecord (RefIdData::LocalIndex (row, UniversalId::Type_Npc)));
ESM::NPC npc = record.get(); ESM::NPC npc = record.get();
ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt52; ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt;
if (subColIndex == 1) if (subColIndex == 1)
switch(subRowIndex) switch(subRowIndex)
@ -1021,7 +1021,7 @@ void CSMWorld::NpcSkillsRefIdAdapter::setNestedTable (const RefIdColumn* column,
ESM::NPC npc = record.get(); ESM::NPC npc = record.get();
// store the whole struct // store the whole struct
npc.mNpdt52 = npc.mNpdt =
static_cast<const NestedTableWrapper<std::vector<ESM::NPC::NPDTstruct52> > &>(nestedTable).mNestedTable.at(0); static_cast<const NestedTableWrapper<std::vector<ESM::NPC::NPDTstruct52> > &>(nestedTable).mNestedTable.at(0);
record.setModified (npc); record.setModified (npc);
@ -1035,7 +1035,7 @@ CSMWorld::NestedTableWrapperBase* CSMWorld::NpcSkillsRefIdAdapter::nestedTable (
// return the whole struct // return the whole struct
std::vector<ESM::NPC::NPDTstruct52> wrap; std::vector<ESM::NPC::NPDTstruct52> wrap;
wrap.push_back(record.get().mNpdt52); wrap.push_back(record.get().mNpdt);
// deleted by dtor of NestedTableStoring // deleted by dtor of NestedTableStoring
return new NestedTableWrapper<std::vector<ESM::NPC::NPDTstruct52> >(wrap); return new NestedTableWrapper<std::vector<ESM::NPC::NPDTstruct52> >(wrap);
} }
@ -1046,7 +1046,7 @@ QVariant CSMWorld::NpcSkillsRefIdAdapter::getNestedData (const RefIdColumn *colu
const Record<ESM::NPC>& record = const Record<ESM::NPC>& record =
static_cast<const Record<ESM::NPC>&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); static_cast<const Record<ESM::NPC>&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc)));
const ESM::NPC::NPDTstruct52& npcStruct = record.get().mNpdt52; const ESM::NPC::NPDTstruct52& npcStruct = record.get().mNpdt;
if (subRowIndex < 0 || subRowIndex >= ESM::Skill::Length) if (subRowIndex < 0 || subRowIndex >= ESM::Skill::Length)
throw std::runtime_error ("index out of range"); throw std::runtime_error ("index out of range");
@ -1065,7 +1065,7 @@ void CSMWorld::NpcSkillsRefIdAdapter::setNestedData (const RefIdColumn *column,
Record<ESM::NPC>& record = Record<ESM::NPC>& record =
static_cast<Record<ESM::NPC>&> (data.getRecord (RefIdData::LocalIndex (row, UniversalId::Type_Npc))); static_cast<Record<ESM::NPC>&> (data.getRecord (RefIdData::LocalIndex (row, UniversalId::Type_Npc)));
ESM::NPC npc = record.get(); ESM::NPC npc = record.get();
ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt52; ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt;
if (subRowIndex < 0 || subRowIndex >= ESM::Skill::Length) if (subRowIndex < 0 || subRowIndex >= ESM::Skill::Length)
throw std::runtime_error ("index out of range"); throw std::runtime_error ("index out of range");
@ -1130,30 +1130,30 @@ QVariant CSMWorld::NpcMiscRefIdAdapter::getNestedData (const RefIdColumn *column
if (autoCalc) if (autoCalc)
switch (subColIndex) switch (subColIndex)
{ {
case 0: return static_cast<int>(record.get().mNpdt12.mLevel); case 0: return static_cast<int>(record.get().mNpdt.mLevel);
case 1: return QVariant(QVariant::UserType); case 1: return QVariant(QVariant::UserType);
case 2: return QVariant(QVariant::UserType); case 2: return QVariant(QVariant::UserType);
case 3: return QVariant(QVariant::UserType); case 3: return QVariant(QVariant::UserType);
case 4: return QVariant(QVariant::UserType); case 4: return QVariant(QVariant::UserType);
case 5: return static_cast<int>(record.get().mNpdt12.mDisposition); case 5: return static_cast<int>(record.get().mNpdt.mDisposition);
case 6: return static_cast<int>(record.get().mNpdt12.mReputation); case 6: return static_cast<int>(record.get().mNpdt.mReputation);
case 7: return static_cast<int>(record.get().mNpdt12.mRank); case 7: return static_cast<int>(record.get().mNpdt.mRank);
case 8: return record.get().mNpdt12.mGold; case 8: return record.get().mNpdt.mGold;
case 9: return record.get().mPersistent == true; case 9: return record.get().mPersistent == true;
default: return QVariant(); // throw an exception here? default: return QVariant(); // throw an exception here?
} }
else else
switch (subColIndex) switch (subColIndex)
{ {
case 0: return static_cast<int>(record.get().mNpdt52.mLevel); case 0: return static_cast<int>(record.get().mNpdt.mLevel);
case 1: return static_cast<int>(record.get().mNpdt52.mFactionID); case 1: return static_cast<int>(record.get().mNpdt.mFactionID);
case 2: return static_cast<int>(record.get().mNpdt52.mHealth); case 2: return static_cast<int>(record.get().mNpdt.mHealth);
case 3: return static_cast<int>(record.get().mNpdt52.mMana); case 3: return static_cast<int>(record.get().mNpdt.mMana);
case 4: return static_cast<int>(record.get().mNpdt52.mFatigue); case 4: return static_cast<int>(record.get().mNpdt.mFatigue);
case 5: return static_cast<int>(record.get().mNpdt52.mDisposition); case 5: return static_cast<int>(record.get().mNpdt.mDisposition);
case 6: return static_cast<int>(record.get().mNpdt52.mReputation); case 6: return static_cast<int>(record.get().mNpdt.mReputation);
case 7: return static_cast<int>(record.get().mNpdt52.mRank); case 7: return static_cast<int>(record.get().mNpdt.mRank);
case 8: return record.get().mNpdt52.mGold; case 8: return record.get().mNpdt.mGold;
case 9: return record.get().mPersistent == true; case 9: return record.get().mPersistent == true;
default: return QVariant(); // throw an exception here? default: return QVariant(); // throw an exception here?
} }
@ -1171,30 +1171,30 @@ void CSMWorld::NpcMiscRefIdAdapter::setNestedData (const RefIdColumn *column,
if (autoCalc) if (autoCalc)
switch(subColIndex) switch(subColIndex)
{ {
case 0: npc.mNpdt12.mLevel = static_cast<short>(value.toInt()); break; case 0: npc.mNpdt.mLevel = static_cast<short>(value.toInt()); break;
case 1: return; case 1: return;
case 2: return; case 2: return;
case 3: return; case 3: return;
case 4: return; case 4: return;
case 5: npc.mNpdt12.mDisposition = static_cast<signed char>(value.toInt()); break; case 5: npc.mNpdt.mDisposition = static_cast<signed char>(value.toInt()); break;
case 6: npc.mNpdt12.mReputation = static_cast<signed char>(value.toInt()); break; case 6: npc.mNpdt.mReputation = static_cast<signed char>(value.toInt()); break;
case 7: npc.mNpdt12.mRank = static_cast<signed char>(value.toInt()); break; case 7: npc.mNpdt.mRank = static_cast<signed char>(value.toInt()); break;
case 8: npc.mNpdt12.mGold = value.toInt(); break; case 8: npc.mNpdt.mGold = value.toInt(); break;
case 9: npc.mPersistent = value.toBool(); break; case 9: npc.mPersistent = value.toBool(); break;
default: return; // throw an exception here? default: return; // throw an exception here?
} }
else else
switch(subColIndex) switch(subColIndex)
{ {
case 0: npc.mNpdt52.mLevel = static_cast<short>(value.toInt()); break; case 0: npc.mNpdt.mLevel = static_cast<short>(value.toInt()); break;
case 1: npc.mNpdt52.mFactionID = static_cast<char>(value.toInt()); break; case 1: npc.mNpdt.mFactionID = static_cast<char>(value.toInt()); break;
case 2: npc.mNpdt52.mHealth = static_cast<unsigned short>(value.toInt()); break; case 2: npc.mNpdt.mHealth = static_cast<unsigned short>(value.toInt()); break;
case 3: npc.mNpdt52.mMana = static_cast<unsigned short>(value.toInt()); break; case 3: npc.mNpdt.mMana = static_cast<unsigned short>(value.toInt()); break;
case 4: npc.mNpdt52.mFatigue = static_cast<unsigned short>(value.toInt()); break; case 4: npc.mNpdt.mFatigue = static_cast<unsigned short>(value.toInt()); break;
case 5: npc.mNpdt52.mDisposition = static_cast<signed char>(value.toInt()); break; case 5: npc.mNpdt.mDisposition = static_cast<signed char>(value.toInt()); break;
case 6: npc.mNpdt52.mReputation = static_cast<signed char>(value.toInt()); break; case 6: npc.mNpdt.mReputation = static_cast<signed char>(value.toInt()); break;
case 7: npc.mNpdt52.mRank = static_cast<signed char>(value.toInt()); break; case 7: npc.mNpdt.mRank = static_cast<signed char>(value.toInt()); break;
case 8: npc.mNpdt52.mGold = value.toInt(); break; case 8: npc.mNpdt.mGold = value.toInt(); break;
case 9: npc.mPersistent = value.toBool(); break; case 9: npc.mPersistent = value.toBool(); break;
default: return; // throw an exception here? default: return; // throw an exception here?
} }
@ -1368,15 +1368,15 @@ QVariant CSMWorld::CreatureAttackRefIdAdapter::getNestedData (const RefIdColumn
const ESM::Creature& creature = record.get(); const ESM::Creature& creature = record.get();
if (subRowIndex < 0 || subRowIndex > 2 || subColIndex < 0 || subColIndex > 2) if (subRowIndex < 0 || subRowIndex > 2)
throw std::runtime_error ("index out of range"); throw std::runtime_error ("index out of range");
if (subColIndex == 0) if (subColIndex == 0)
return subRowIndex + 1; return subRowIndex + 1;
else if (subColIndex < 3) // 1 or 2 else if (subColIndex == 1 || subColIndex == 2)
return creature.mData.mAttack[(subRowIndex * 2) + (subColIndex - 1)]; return creature.mData.mAttack[(subRowIndex * 2) + (subColIndex - 1)];
else else
return QVariant(); // throw an exception here? throw std::runtime_error ("index out of range");
} }
void CSMWorld::CreatureAttackRefIdAdapter::setNestedData (const RefIdColumn *column, void CSMWorld::CreatureAttackRefIdAdapter::setNestedData (const RefIdColumn *column,

@ -184,11 +184,11 @@ CSMWorld::RefIdCollection::RefIdCollection()
mColumns.back().addColumn( mColumns.back().addColumn(
new RefIdColumn (Columns::ColumnId_PosZ, CSMWorld::ColumnBase::Display_Float)); new RefIdColumn (Columns::ColumnId_PosZ, CSMWorld::ColumnBase::Display_Float));
mColumns.back().addColumn( mColumns.back().addColumn(
new RefIdColumn (Columns::ColumnId_RotX, CSMWorld::ColumnBase::Display_Float)); new RefIdColumn (Columns::ColumnId_RotX, CSMWorld::ColumnBase::Display_Double));
mColumns.back().addColumn( mColumns.back().addColumn(
new RefIdColumn (Columns::ColumnId_RotY, CSMWorld::ColumnBase::Display_Float)); new RefIdColumn (Columns::ColumnId_RotY, CSMWorld::ColumnBase::Display_Double));
mColumns.back().addColumn( mColumns.back().addColumn(
new RefIdColumn (Columns::ColumnId_RotZ, CSMWorld::ColumnBase::Display_Float)); new RefIdColumn (Columns::ColumnId_RotZ, CSMWorld::ColumnBase::Display_Double));
// Nested table // Nested table
mColumns.push_back(RefIdColumn (Columns::ColumnId_AiPackageList, mColumns.push_back(RefIdColumn (Columns::ColumnId_AiPackageList,
@ -331,7 +331,7 @@ CSMWorld::RefIdCollection::RefIdCollection()
creatureColumns.mType = &mColumns.back(); creatureColumns.mType = &mColumns.back();
mColumns.push_back (RefIdColumn (Columns::ColumnId_Scale, ColumnBase::Display_Float)); mColumns.push_back (RefIdColumn (Columns::ColumnId_Scale, ColumnBase::Display_Float));
creatureColumns.mScale = &mColumns.back(); creatureColumns.mScale = &mColumns.back();
mColumns.push_back (RefIdColumn (Columns::ColumnId_OriginalCreature, ColumnBase::Display_Creature)); mColumns.push_back (RefIdColumn (Columns::ColumnId_ParentCreature, ColumnBase::Display_Creature));
creatureColumns.mOriginal = &mColumns.back(); creatureColumns.mOriginal = &mColumns.back();
static const struct static const struct

@ -64,21 +64,21 @@ namespace
static const TypeData sIdArg[] = static const TypeData sIdArg[] =
{ {
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Global, "Global Variable", ":./globvar.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Global, "Global Variable", ":./global-variable.png" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Gmst, "Game Setting", ":./GMST.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Gmst, "Game Setting", ":./gmst.png" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Skill, "Skill", ":./skill.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Skill, "Skill", ":./skill.png" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Class, "Class", ":./class.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Class, "Class", ":./class.png" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Faction, "Faction", ":./faction.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Faction, "Faction", ":./faction.png" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Race, "Race", ":./race.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Race, "Race", ":./race.png" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Sound, "Sound", ":./sound.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Sound, "Sound", ":./sound.png" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Script, "Script", ":./script.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Script, "Script", ":./script.png" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Region, "Region", ":./land.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Region, "Region", ":./region.png" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Birthsign, "Birthsign", ":./birthsign.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Birthsign, "Birthsign", ":./birthsign.png" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Spell, "Spell", ":./spell.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Spell, "Spell", ":./spell.png" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Topic, "Topic", 0 }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Topic, "Topic", ":./dialogue-topics.png" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Journal, "Journal", 0 }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Journal, "Journal", ":./journal-topics.png" },
{ CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_TopicInfo, "TopicInfo", 0 }, { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_TopicInfo, "TopicInfo", ":./dialogue-topic-infos.png" },
{ CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_JournalInfo, "JournalInfo", 0 }, { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_JournalInfo, "JournalInfo", ":./journal-topic-infos.png" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell, "Cell", ":./cell.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell, "Cell", ":./cell.png" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell_Missing, "Cell", ":./cell.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell_Missing, "Cell", ":./cell.png" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Referenceable, "Object", 0 }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Referenceable, "Object", 0 },
@ -93,7 +93,7 @@ namespace
{ CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Door, "Door", ":./door.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Door, "Door", ":./door.png" },
{ CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Ingredient, "Ingredient", ":./ingredient.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Ingredient, "Ingredient", ":./ingredient.png" },
{ CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_CreatureLevelledList, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_CreatureLevelledList,
"Creature Levelled List", ":./creature.png" }, "Creature Levelled List", ":./leveled-creature.png" },
{ CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_ItemLevelledList, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_ItemLevelledList,
"Item Levelled List", ":./leveled-item.png" }, "Item Levelled List", ":./leveled-item.png" },
{ CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Light, "Light", ":./light.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Light, "Light", ":./light.png" },
@ -109,20 +109,20 @@ namespace
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Filter, "Filter", ":./filter.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Filter, "Filter", ":./filter.png" },
{ CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Scene, "Scene", 0 }, { CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Scene, "Scene", 0 },
{ CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Preview, "Preview", 0 }, { CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Preview, "Preview", 0 },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Enchantment, "Enchantment", 0 }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Enchantment, "Enchantment", ":./enchantment.png" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_BodyPart, "Body Part", 0 }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_BodyPart, "Body Part", ":./body-part.png" },
{ CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Mesh, "Mesh", 0 }, { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Mesh, "Mesh", ":resources-mesh"},
{ CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Icon, "Icon", 0 }, { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Icon, "Icon", ":resources-icon"},
{ CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Music, "Music", 0 }, { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Music, "Music", ":resources-music" },
{ CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_SoundRes, "Sound File", 0 }, { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_SoundRes, "Sound File", ":resources-sound" },
{ CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Texture, "Texture", 0 }, { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Texture, "Texture", ":resources-texture"},
{ CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Video, "Video", 0 }, { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Video, "Video", ":resources-video"},
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_DebugProfile, "Debug Profile", 0 }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_DebugProfile, "Debug Profile", 0 },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_SoundGen, "Sound Generator", 0 }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_SoundGen, "Sound Generator", ":./sound-generator.png" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_MagicEffect, "Magic Effect", 0 }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_MagicEffect, "Magic Effect", ":./magic-effect.png" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Land, "Land", 0 }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Land, "Land", ":./land-heightmap.png" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_LandTexture, "LandTexture", 0 }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_LandTexture, "LandTexture", ":./land-texture.png" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Pathgrid, "Pathgrid", 0 }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Pathgrid, "Pathgrid", ":./pathgrid.png" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_StartScript, "Start Script", 0 }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_StartScript, "Start Script", 0 },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_MetaData, "Meta Data", 0 }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_MetaData, "Meta Data", 0 },

@ -167,7 +167,7 @@ void CSVDoc::Loader::loadingStopped (CSMDoc::Document *document, bool completed,
delete iter->second; delete iter->second;
mDocuments.erase (iter); mDocuments.erase (iter);
} }
else if (!completed && !error.empty()) else
{ {
iter->second->abort (error); iter->second->abort (error);
// Leave the window open for now (wait for the user to close it) // Leave the window open for now (wait for the user to close it)

@ -106,7 +106,7 @@ CSVDoc::StartupDialogue::StartupDialogue() : mWidth (0), mColumn (2)
/// \todo remove this label once we are feature complete and convinced that this thing is /// \todo remove this label once we are feature complete and convinced that this thing is
/// working properly. /// working properly.
QLabel *warning = new QLabel ("<font color=Red>WARNING: OpenMW-CS is in alpha stage.<p>The editor is not feature complete and not sufficiently tested.<br>In theory your data should be safe. But we strongly advice to make backups regularly if you are working with live data.</font color>"); QLabel *warning = new QLabel ("<font color=Red>WARNING: OpenMW-CS is in alpha stage.<p>The editor is not feature complete and not sufficiently tested.<br>In theory your data should be safe. But we strongly advise to make backups regularly if you are working with live data.</font color>");
QFont font; QFont font;
font.setPointSize (12); font.setPointSize (12);

@ -92,7 +92,7 @@ osg::Vec3f CSVRender::InstanceMode::getScreenCoords(const osg::Vec3f& pos)
} }
CSVRender::InstanceMode::InstanceMode (WorldspaceWidget *worldspaceWidget, QWidget *parent) CSVRender::InstanceMode::InstanceMode (WorldspaceWidget *worldspaceWidget, QWidget *parent)
: EditMode (worldspaceWidget, QIcon (":placeholder"), Mask_Reference | Mask_Terrain, "Instance editing", : EditMode (worldspaceWidget, QIcon (":scenetoolbar/editing-instance"), Mask_Reference | Mask_Terrain, "Instance editing",
parent), mSubMode (0), mSubModeId ("move"), mSelectionMode (0), mDragMode (DragMode_None), parent), mSubMode (0), mSubModeId ("move"), mSelectionMode (0), mDragMode (DragMode_None),
mDragAxis (-1), mLocked (false), mUnitScaleDist(1) mDragAxis (-1), mLocked (false), mUnitScaleDist(1)
{ {
@ -104,14 +104,14 @@ void CSVRender::InstanceMode::activate (CSVWidget::SceneToolbar *toolbar)
{ {
mSubMode = new CSVWidget::SceneToolMode (toolbar, "Edit Sub-Mode"); mSubMode = new CSVWidget::SceneToolMode (toolbar, "Edit Sub-Mode");
mSubMode->addButton (new InstanceMoveMode (this), "move"); mSubMode->addButton (new InstanceMoveMode (this), "move");
mSubMode->addButton (":placeholder", "rotate", mSubMode->addButton (":scenetoolbar/transform-rotate", "rotate",
"Rotate selected instances" "Rotate selected instances"
"<ul><li>Use {scene-edit-primary} to rotate instances freely</li>" "<ul><li>Use {scene-edit-primary} to rotate instances freely</li>"
"<li>Use {scene-edit-secondary} to rotate instances within the grid</li>" "<li>Use {scene-edit-secondary} to rotate instances within the grid</li>"
"<li>The center of the view acts as the axis of rotation</li>" "<li>The center of the view acts as the axis of rotation</li>"
"</ul>" "</ul>"
"<font color=Red>Grid rotate not implemented yet</font color>"); "<font color=Red>Grid rotate not implemented yet</font color>");
mSubMode->addButton (":placeholder", "scale", mSubMode->addButton (":scenetoolbar/transform-scale", "scale",
"Scale selected instances" "Scale selected instances"
"<ul><li>Use {scene-edit-primary} to scale instances freely</li>" "<ul><li>Use {scene-edit-primary} to scale instances freely</li>"
"<li>Use {scene-edit-secondary} to scale instances along the grid</li>" "<li>Use {scene-edit-secondary} to scale instances along the grid</li>"
@ -551,7 +551,7 @@ void CSVRender::InstanceMode::dropEvent (QDropEvent* event)
if (noCell) if (noCell)
{ {
std::string mode = CSMPrefs::get()["Scene Drops"]["outside-drop"].toString(); std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-drop"].toString();
// target cell does not exist // target cell does not exist
if (mode=="Discard") if (mode=="Discard")
@ -585,7 +585,7 @@ void CSVRender::InstanceMode::dropEvent (QDropEvent* event)
{ {
// target cell exists, but is not shown // target cell exists, but is not shown
std::string mode = std::string mode =
CSMPrefs::get()["Scene Drops"]["outside-visible-drop"].toString(); CSMPrefs::get()["3D Scene Editing"]["outside-visible-drop"].toString();
if (mode=="Discard") if (mode=="Discard")
return; return;

@ -2,7 +2,7 @@
#include "instancemovemode.hpp" #include "instancemovemode.hpp"
CSVRender::InstanceMoveMode::InstanceMoveMode (QWidget *parent) CSVRender::InstanceMoveMode::InstanceMoveMode (QWidget *parent)
: ModeButton (QIcon (QPixmap (":placeholder")), : ModeButton (QIcon (QPixmap (":scenetoolbar/transform-move")),
"Move selected instances" "Move selected instances"
"<ul><li>Use {scene-edit-primary} to move instances around freely</li>" "<ul><li>Use {scene-edit-primary} to move instances around freely</li>"
"<li>Use {scene-edit-secondary} to move instances around within the grid</li>" "<li>Use {scene-edit-secondary} to move instances around within the grid</li>"

@ -21,6 +21,7 @@
#include "mask.hpp" #include "mask.hpp"
#include "cameracontroller.hpp" #include "cameracontroller.hpp"
#include "cellarrow.hpp" #include "cellarrow.hpp"
#include "terraintexturemode.hpp"
bool CSVRender::PagedWorldspaceWidget::adjustCells() bool CSVRender::PagedWorldspaceWidget::adjustCells()
{ {
@ -136,7 +137,7 @@ void CSVRender::PagedWorldspaceWidget::addEditModeSelectorButtons (
new EditMode (this, QIcon (":placeholder"), Mask_Reference, "Terrain shape editing"), new EditMode (this, QIcon (":placeholder"), Mask_Reference, "Terrain shape editing"),
"terrain-shape"); "terrain-shape");
tool->addButton ( tool->addButton (
new EditMode (this, QIcon (":placeholder"), Mask_Reference, "Terrain texture editing"), new TerrainTextureMode (this, tool),
"terrain-texture"); "terrain-texture");
tool->addButton ( tool->addButton (
new EditMode (this, QIcon (":placeholder"), Mask_Reference, "Terrain vertex paint editing"), new EditMode (this, QIcon (":placeholder"), Mask_Reference, "Terrain vertex paint editing"),

@ -13,7 +13,7 @@ namespace CSVRender
, mWorldspaceWidget(worldspaceWidget) , mWorldspaceWidget(worldspaceWidget)
, mInteractionMask(interactionMask) , mInteractionMask(interactionMask)
{ {
addButton(":placeholder", "cube-centre", addButton(":scenetoolbar/selection-mode-cube", "cube-centre",
"Centred cube" "Centred cube"
"<ul><li>Drag with {scene-select-primary} (make instances the selection) or {scene-select-secondary} " "<ul><li>Drag with {scene-select-primary} (make instances the selection) or {scene-select-secondary} "
"(invert selection state) from the centre of the selection cube outwards</li>" "(invert selection state) from the centre of the selection cube outwards</li>"
@ -22,7 +22,7 @@ namespace CSVRender
"starting on an instance will have the same effect</li>" "starting on an instance will have the same effect</li>"
"</ul>" "</ul>"
"<font color=Red>Not implemented yet</font color>"); "<font color=Red>Not implemented yet</font color>");
addButton(":placeholder", "cube-corner", addButton(":scenetoolbar/selection-mode-cube-corner", "cube-corner",
"Cube corner to corner" "Cube corner to corner"
"<ul><li>Drag with {scene-select-primary} (make instances the selection) or {scene-select-secondary} " "<ul><li>Drag with {scene-select-primary} (make instances the selection) or {scene-select-secondary} "
"(invert selection state) from one corner of the selection cube to the opposite corner</li>" "(invert selection state) from one corner of the selection cube to the opposite corner</li>"
@ -31,7 +31,7 @@ namespace CSVRender
"starting on an instance will have the same effect</li>" "starting on an instance will have the same effect</li>"
"</ul>" "</ul>"
"<font color=Red>Not implemented yet</font color>"); "<font color=Red>Not implemented yet</font color>");
addButton(":placeholder", "sphere", addButton(":scenetoolbar/selection-mode-cube-sphere", "sphere",
"Centred sphere" "Centred sphere"
"<ul><li>Drag with {scene-select-primary} (make instances the selection) or {scene-select-secondary} " "<ul><li>Drag with {scene-select-primary} (make instances the selection) or {scene-select-secondary} "
"(invert selection state) from the centre of the selection sphere outwards</li>" "(invert selection state) from the centre of the selection sphere outwards</li>"

@ -0,0 +1,542 @@
#include "terraintexturemode.hpp"
#include <string>
#include <sstream>
#include <QWidget>
#include <QIcon>
#include <QEvent>
#include <QDropEvent>
#include <QDragEnterEvent>
#include <QDrag>
#include <components/esm/loadland.hpp>
#include "../widget/modebutton.hpp"
#include "../widget/scenetoolbar.hpp"
#include "../widget/scenetooltexturebrush.hpp"
#include "../../model/doc/document.hpp"
#include "../../model/prefs/state.hpp"
#include "../../model/world/columnbase.hpp"
#include "../../model/world/commandmacro.hpp"
#include "../../model/world/commands.hpp"
#include "../../model/world/data.hpp"
#include "../../model/world/idtable.hpp"
#include "../../model/world/idtree.hpp"
#include "../../model/world/land.hpp"
#include "../../model/world/landtexture.hpp"
#include "../../model/world/resourcetable.hpp"
#include "../../model/world/tablemimedata.hpp"
#include "../../model/world/universalid.hpp"
#include "editmode.hpp"
#include "pagedworldspacewidget.hpp"
#include "mask.hpp"
#include "object.hpp" // Something small needed regarding pointers from here ()
#include "worldspacewidget.hpp"
CSVRender::TerrainTextureMode::TerrainTextureMode (WorldspaceWidget *worldspaceWidget, QWidget *parent)
: EditMode (worldspaceWidget, QIcon {":scenetoolbar/editing-terrain-texture"}, Mask_Terrain | Mask_Reference, "Terrain texture editing", parent),
mBrushTexture("L0#0"),
mBrushSize(0),
mBrushShape(0),
mTextureBrushScenetool(0)
{
}
void CSVRender::TerrainTextureMode::activate(CSVWidget::SceneToolbar* toolbar)
{
if(!mTextureBrushScenetool)
{
mTextureBrushScenetool = new CSVWidget::SceneToolTextureBrush (toolbar, "scenetooltexturebrush", getWorldspaceWidget().getDocument());
connect(mTextureBrushScenetool, SIGNAL (clicked()), mTextureBrushScenetool, SLOT (activate()));
connect(mTextureBrushScenetool->mTextureBrushWindow, SIGNAL(passBrushSize(int)), this, SLOT(setBrushSize(int)));
connect(mTextureBrushScenetool->mTextureBrushWindow, SIGNAL(passBrushShape(int)), this, SLOT(setBrushShape(int)));
connect(mTextureBrushScenetool->mTextureBrushWindow->mSizeSliders->mBrushSizeSlider, SIGNAL(valueChanged(int)), this, SLOT(setBrushSize(int)));
connect(mTextureBrushScenetool, SIGNAL(passTextureId(std::string)), this, SLOT(setBrushTexture(std::string)));
connect(mTextureBrushScenetool->mTextureBrushWindow, SIGNAL(passTextureId(std::string)), this, SLOT(setBrushTexture(std::string)));
connect(mTextureBrushScenetool, SIGNAL(passEvent(QDropEvent*)), this, SLOT(handleDropEvent(QDropEvent*)));
connect(this, SIGNAL(passBrushTexture(std::string)), mTextureBrushScenetool->mTextureBrushWindow, SLOT(setBrushTexture(std::string)));
connect(this, SIGNAL(passBrushTexture(std::string)), mTextureBrushScenetool, SLOT(updateBrushHistory(std::string)));
}
EditMode::activate(toolbar);
toolbar->addTool (mTextureBrushScenetool);
}
void CSVRender::TerrainTextureMode::deactivate(CSVWidget::SceneToolbar* toolbar)
{
if(mTextureBrushScenetool)
{
toolbar->removeTool (mTextureBrushScenetool);
delete mTextureBrushScenetool;
mTextureBrushScenetool = 0;
}
EditMode::deactivate(toolbar);
}
void CSVRender::TerrainTextureMode::primaryEditPressed(const WorldspaceHitResult& hit) // Apply changes here
{
CSMDoc::Document& document = getWorldspaceWidget().getDocument();
CSMWorld::IdTable& landTable = dynamic_cast<CSMWorld::IdTable&> (
*document.getData().getTableModel (CSMWorld::UniversalId::Type_Land));
CSMWorld::IdTable& ltexTable = dynamic_cast<CSMWorld::IdTable&> (
*document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures));
mCellId = getWorldspaceWidget().getCellId (hit.worldPos);
QUndoStack& undoStack = document.getUndoStack();
CSMWorld::IdCollection<CSMWorld::LandTexture>& landtexturesCollection = document.getData().getLandTextures();
int index = landtexturesCollection.searchId(mBrushTexture);
if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted() && hit.hit == true)
{
undoStack.beginMacro ("Edit texture records");
if(allowLandTextureEditing(mCellId)==true)
{
undoStack.push (new CSMWorld::TouchLandCommand(landTable, ltexTable, mCellId));
editTerrainTextureGrid(hit);
}
undoStack.endMacro();
}
}
void CSVRender::TerrainTextureMode::primarySelectPressed(const WorldspaceHitResult& hit)
{
}
void CSVRender::TerrainTextureMode::secondarySelectPressed(const WorldspaceHitResult& hit)
{
}
bool CSVRender::TerrainTextureMode::primaryEditStartDrag (const QPoint& pos)
{
WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask());
CSMDoc::Document& document = getWorldspaceWidget().getDocument();
CSMWorld::IdTable& landTable = dynamic_cast<CSMWorld::IdTable&> (
*document.getData().getTableModel (CSMWorld::UniversalId::Type_Land));
CSMWorld::IdTable& ltexTable = dynamic_cast<CSMWorld::IdTable&> (
*document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures));
mCellId = getWorldspaceWidget().getCellId (hit.worldPos);
QUndoStack& undoStack = document.getUndoStack();
CSMWorld::IdCollection<CSMWorld::LandTexture>& landtexturesCollection = document.getData().getLandTextures();
int index = landtexturesCollection.searchId(mBrushTexture);
if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted())
{
undoStack.beginMacro ("Edit texture records");
if(allowLandTextureEditing(mCellId)==true && hit.hit == true)
{
undoStack.push (new CSMWorld::TouchLandCommand(landTable, ltexTable, mCellId));
editTerrainTextureGrid(hit);
}
}
return true;
}
bool CSVRender::TerrainTextureMode::secondaryEditStartDrag (const QPoint& pos)
{
return false;
}
bool CSVRender::TerrainTextureMode::primarySelectStartDrag (const QPoint& pos)
{
return false;
}
bool CSVRender::TerrainTextureMode::secondarySelectStartDrag (const QPoint& pos)
{
return false;
}
void CSVRender::TerrainTextureMode::drag (const QPoint& pos, int diffX, int diffY, double speedFactor)
{
WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask());
CSMDoc::Document& document = getWorldspaceWidget().getDocument();
CSMWorld::IdCollection<CSMWorld::LandTexture>& landtexturesCollection = document.getData().getLandTextures();
int index = landtexturesCollection.searchId(mBrushTexture);
if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted() && hit.hit == true)
{
editTerrainTextureGrid(hit);
}
}
void CSVRender::TerrainTextureMode::dragCompleted(const QPoint& pos) {
CSMDoc::Document& document = getWorldspaceWidget().getDocument();
QUndoStack& undoStack = document.getUndoStack();
CSMWorld::IdCollection<CSMWorld::LandTexture>& landtexturesCollection = document.getData().getLandTextures();
int index = landtexturesCollection.searchId(mBrushTexture);
if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted())
{
undoStack.endMacro();
}
}
void CSVRender::TerrainTextureMode::dragAborted() {
}
void CSVRender::TerrainTextureMode::dragWheel (int diff, double speedFactor) {}
void CSVRender::TerrainTextureMode::handleDropEvent (QDropEvent *event) {
const CSMWorld::TableMimeData* mime = dynamic_cast<const CSMWorld::TableMimeData*> (event->mimeData());
if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped
return;
if (mime->holdsType (CSMWorld::UniversalId::Type_LandTexture))
{
const std::vector<CSMWorld::UniversalId> ids = mime->getData();
for (const CSMWorld::UniversalId& uid : ids)
{
mBrushTexture = uid.getId();
emit passBrushTexture(mBrushTexture);
}
}
if (mime->holdsType (CSMWorld::UniversalId::Type_Texture))
{
const std::vector<CSMWorld::UniversalId> ids = mime->getData();
for (const CSMWorld::UniversalId& uid : ids)
{
std::string textureFileName = uid.toString();
createTexture(textureFileName);
emit passBrushTexture(mBrushTexture);
}
}
}
void CSVRender::TerrainTextureMode::editTerrainTextureGrid(const WorldspaceHitResult& hit)
{
CSMDoc::Document& document = getWorldspaceWidget().getDocument();
CSMWorld::IdTable& landTable = dynamic_cast<CSMWorld::IdTable&> (
*document.getData().getTableModel (CSMWorld::UniversalId::Type_Land));
mCellId = getWorldspaceWidget().getCellId (hit.worldPos);
if(allowLandTextureEditing(mCellId)==true) {}
std::pair<CSMWorld::CellCoordinates, bool> cellCoordinates_pair = CSMWorld::CellCoordinates::fromId (mCellId);
int cellX = cellCoordinates_pair.first.getX();
int cellY = cellCoordinates_pair.first.getY();
// The coordinates of hit in mCellId
int xHitInCell (float(((hit.worldPos.x() - (cellX* cellSize)) * landTextureSize / cellSize) - 0.5));
int yHitInCell (float(((hit.worldPos.y() - (cellY* cellSize)) * landTextureSize / cellSize) + 0.5));
if (xHitInCell < 0)
{
xHitInCell = xHitInCell + landTextureSize;
cellX = cellX - 1;
}
if (yHitInCell > 15)
{
yHitInCell = yHitInCell - landTextureSize;
cellY = cellY + 1;
}
mCellId = "#" + std::to_string(cellX) + " " + std::to_string(cellY);
if(allowLandTextureEditing(mCellId)==true) {}
std::string iteratedCellId;
int textureColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandTexturesIndex);
std::size_t hashlocation = mBrushTexture.find("#");
std::string mBrushTextureInt = mBrushTexture.substr (hashlocation+1);
int brushInt = stoi(mBrushTexture.substr (hashlocation+1))+1; // All indices are offset by +1
float rf = mBrushSize/2;
int r = (mBrushSize/2)+1;
float distance = 0;
if (mBrushShape == 0)
{
CSMWorld::LandTexturesColumn::DataType mPointer = landTable.data(landTable.getModelIndex(mCellId, textureColumn)).value<CSMWorld::LandTexturesColumn::DataType>();
CSMWorld::LandTexturesColumn::DataType mNew(mPointer);
if(allowLandTextureEditing(mCellId)==true)
{
mNew[yHitInCell*landTextureSize+xHitInCell] = brushInt;
pushEditToCommand(mNew, document, landTable, mCellId);
}
}
if (mBrushShape == 1)
{
int upperLeftCellX = cellX - std::floor(r / landTextureSize);
int upperLeftCellY = cellY - std::floor(r / landTextureSize);
if (xHitInCell - (r % landTextureSize) < 0) upperLeftCellX--;
if (yHitInCell - (r % landTextureSize) < 0) upperLeftCellY--;
int lowerrightCellX = cellX + std::floor(r / landTextureSize);
int lowerrightCellY = cellY + std::floor(r / landTextureSize);
if (xHitInCell + (r % landTextureSize) > landTextureSize - 1) lowerrightCellX++;
if (yHitInCell + (r % landTextureSize) > landTextureSize - 1) lowerrightCellY++;
for(int i_cell = upperLeftCellX; i_cell <= lowerrightCellX; i_cell++)
{
for(int j_cell = upperLeftCellY; j_cell <= lowerrightCellY; j_cell++)
{
iteratedCellId = "#" + std::to_string(i_cell) + " " + std::to_string(j_cell);
if(allowLandTextureEditing(iteratedCellId)==true)
{
CSMWorld::LandTexturesColumn::DataType mPointer = landTable.data(landTable.getModelIndex(iteratedCellId, textureColumn)).value<CSMWorld::LandTexturesColumn::DataType>();
CSMWorld::LandTexturesColumn::DataType mNew(mPointer);
for(int i = 0; i < landTextureSize; i++)
{
for(int j = 0; j < landTextureSize; j++)
{
if (i_cell == cellX && j_cell == cellY && abs(i-xHitInCell) < r && abs(j-yHitInCell) < r)
{
mNew[j*landTextureSize+i] = brushInt;
}
else
{
int distanceX(0);
int distanceY(0);
if (i_cell < cellX) distanceX = xHitInCell + landTextureSize * abs(i_cell-cellX) - i;
if (j_cell < cellY) distanceY = yHitInCell + landTextureSize * abs(j_cell-cellY) - j;
if (i_cell > cellX) distanceX = -xHitInCell + landTextureSize * abs(i_cell-cellX) + i;
if (j_cell > cellY) distanceY = -yHitInCell + landTextureSize * abs(j_cell-cellY) + j;
if (i_cell == cellX) distanceX = abs(i-xHitInCell);
if (j_cell == cellY) distanceY = abs(j-yHitInCell);
if (distanceX < r && distanceY < r) mNew[j*landTextureSize+i] = brushInt;
}
}
}
pushEditToCommand(mNew, document, landTable, iteratedCellId);
}
}
}
}
if (mBrushShape == 2)
{
int upperLeftCellX = cellX - std::floor(r / landTextureSize);
int upperLeftCellY = cellY - std::floor(r / landTextureSize);
if (xHitInCell - (r % landTextureSize) < 0) upperLeftCellX--;
if (yHitInCell - (r % landTextureSize) < 0) upperLeftCellY--;
int lowerrightCellX = cellX + std::floor(r / landTextureSize);
int lowerrightCellY = cellY + std::floor(r / landTextureSize);
if (xHitInCell + (r % landTextureSize) > landTextureSize - 1) lowerrightCellX++;
if (yHitInCell + (r % landTextureSize) > landTextureSize - 1) lowerrightCellY++;
for(int i_cell = upperLeftCellX; i_cell <= lowerrightCellX; i_cell++)
{
for(int j_cell = upperLeftCellY; j_cell <= lowerrightCellY; j_cell++)
{
iteratedCellId = "#" + std::to_string(i_cell) + " " + std::to_string(j_cell);
if(allowLandTextureEditing(iteratedCellId)==true)
{
CSMWorld::LandTexturesColumn::DataType mPointer = landTable.data(landTable.getModelIndex(iteratedCellId, textureColumn)).value<CSMWorld::LandTexturesColumn::DataType>();
CSMWorld::LandTexturesColumn::DataType mNew(mPointer);
for(int i = 0; i < landTextureSize; i++)
{
for(int j = 0; j < landTextureSize; j++)
{
if (i_cell == cellX && j_cell == cellY && abs(i-xHitInCell) < r && abs(j-yHitInCell) < r)
{
int distanceX(0);
int distanceY(0);
if (i_cell < cellX) distanceX = xHitInCell + landTextureSize * abs(i_cell-cellX) - i;
if (j_cell < cellY) distanceY = yHitInCell + landTextureSize * abs(j_cell-cellY) - j;
if (i_cell > cellX) distanceX = -xHitInCell + landTextureSize* abs(i_cell-cellX) + i;
if (j_cell > cellY) distanceY = -yHitInCell + landTextureSize * abs(j_cell-cellY) + j;
if (i_cell == cellX) distanceX = abs(i-xHitInCell);
if (j_cell == cellY) distanceY = abs(j-yHitInCell);
distance = std::round(sqrt(pow(distanceX, 2)+pow(distanceY, 2)));
if (distance < rf) mNew[j*landTextureSize+i] = brushInt;
}
else
{
int distanceX(0);
int distanceY(0);
if (i_cell < cellX) distanceX = xHitInCell + landTextureSize * abs(i_cell-cellX) - i;
if (j_cell < cellY) distanceY = yHitInCell + landTextureSize * abs(j_cell-cellY) - j;
if (i_cell > cellX) distanceX = -xHitInCell + landTextureSize * abs(i_cell-cellX) + i;
if (j_cell > cellY) distanceY = -yHitInCell + landTextureSize * abs(j_cell-cellY) + j;
if (i_cell == cellX) distanceX = abs(i-xHitInCell);
if (j_cell == cellY) distanceY = abs(j-yHitInCell);
distance = std::round(sqrt(pow(distanceX, 2)+pow(distanceY, 2)));
if (distance < rf) mNew[j*landTextureSize+i] = brushInt;
}
}
}
pushEditToCommand(mNew, document, landTable, iteratedCellId);
}
}
}
}
if (mBrushShape == 3)
{
// Not implemented
}
}
void CSVRender::TerrainTextureMode::pushEditToCommand(CSMWorld::LandTexturesColumn::DataType& newLandGrid, CSMDoc::Document& document,
CSMWorld::IdTable& landTable, std::string cellId)
{
CSMWorld::IdTable& ltexTable = dynamic_cast<CSMWorld::IdTable&> (
*document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures));
QVariant changedLand;
changedLand.setValue(newLandGrid);
QModelIndex index(landTable.getModelIndex (cellId, landTable.findColumnIndex (CSMWorld::Columns::ColumnId_LandTexturesIndex)));
QUndoStack& undoStack = document.getUndoStack();
undoStack.push (new CSMWorld::TouchLandCommand(landTable, ltexTable, cellId));
undoStack.push (new CSMWorld::ModifyCommand(landTable, index, changedLand));
}
void CSVRender::TerrainTextureMode::createTexture(std::string textureFileName)
{
CSMDoc::Document& document = getWorldspaceWidget().getDocument();
CSMWorld::IdTable& ltexTable = dynamic_cast<CSMWorld::IdTable&> (
*document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures));
QUndoStack& undoStack = document.getUndoStack();
std::string newId;
int counter=0;
bool freeIndexFound = false;
do {
const size_t maxCounter = std::numeric_limits<uint16_t>::max() - 1;
try
{
newId = CSMWorld::LandTexture::createUniqueRecordId(0, counter);
if (ltexTable.getRecord(newId).isDeleted() == 0) counter = (counter + 1) % maxCounter;
} catch (const std::exception& e)
{
newId = CSMWorld::LandTexture::createUniqueRecordId(0, counter);
freeIndexFound = true;
}
} while (freeIndexFound == false);
std::size_t idlocation = textureFileName.find("Texture: ");
textureFileName = textureFileName.substr (idlocation + 9);
QVariant textureNameVariant;
QVariant textureFileNameVariant;
textureFileNameVariant.setValue(QString::fromStdString(textureFileName));
undoStack.beginMacro ("Add land texture record");
undoStack.push (new CSMWorld::CreateCommand (ltexTable, newId));
QModelIndex index(ltexTable.getModelIndex (newId, ltexTable.findColumnIndex (CSMWorld::Columns::ColumnId_Texture)));
undoStack.push (new CSMWorld::ModifyCommand(ltexTable, index, textureFileNameVariant));
undoStack.endMacro();
mBrushTexture = newId;
}
bool CSVRender::TerrainTextureMode::allowLandTextureEditing(std::string cellId)
{
CSMDoc::Document& document = getWorldspaceWidget().getDocument();
CSMWorld::IdTable& landTable = dynamic_cast<CSMWorld::IdTable&> (
*document.getData().getTableModel (CSMWorld::UniversalId::Type_Land));
CSMWorld::IdTree& cellTable = dynamic_cast<CSMWorld::IdTree&> (
*document.getData().getTableModel (CSMWorld::UniversalId::Type_Cells));
bool noCell = document.getData().getCells().searchId (cellId)==-1;
bool noLand = document.getData().getLand().searchId (cellId)==-1;
if (noCell)
{
std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-landedit"].toString();
// target cell does not exist
if (mode=="Discard")
return false;
if (mode=="Create cell and land, then edit")
{
std::unique_ptr<CSMWorld::CreateCommand> createCommand (
new CSMWorld::CreateCommand (cellTable, cellId));
int parentIndex = cellTable.findColumnIndex (CSMWorld::Columns::ColumnId_Cell);
int index = cellTable.findNestedColumnIndex (parentIndex, CSMWorld::Columns::ColumnId_Interior);
createCommand->addNestedValue (parentIndex, index, false);
document.getUndoStack().push (createCommand.release());
if (CSVRender::PagedWorldspaceWidget *paged =
dynamic_cast<CSVRender::PagedWorldspaceWidget *> (&getWorldspaceWidget()))
{
CSMWorld::CellSelection selection = paged->getCellSelection();
selection.add (CSMWorld::CellCoordinates::fromId (cellId).first);
paged->setCellSelection (selection);
}
}
}
else if (CSVRender::PagedWorldspaceWidget *paged =
dynamic_cast<CSVRender::PagedWorldspaceWidget *> (&getWorldspaceWidget()))
{
CSMWorld::CellSelection selection = paged->getCellSelection();
if (!selection.has (CSMWorld::CellCoordinates::fromId (cellId).first))
{
// target cell exists, but is not shown
std::string mode =
CSMPrefs::get()["3D Scene Editing"]["outside-visible-landedit"].toString();
if (mode=="Discard")
return false;
if (mode=="Show cell and edit")
{
selection.add (CSMWorld::CellCoordinates::fromId (cellId).first);
paged->setCellSelection (selection);
}
}
}
if (noLand)
{
std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-landedit"].toString();
// target cell does not exist
if (mode=="Discard")
return false;
if (mode=="Create cell and land, then edit")
{
document.getUndoStack().push (new CSMWorld::CreateCommand (landTable, cellId));
}
}
return true;
}
void CSVRender::TerrainTextureMode::dragMoveEvent (QDragMoveEvent *event) {
}
void CSVRender::TerrainTextureMode::setBrushSize(int brushSize)
{
mBrushSize = brushSize;
}
void CSVRender::TerrainTextureMode::setBrushShape(int brushShape)
{
mBrushShape = brushShape;
}
void CSVRender::TerrainTextureMode::setBrushTexture(std::string brushTexture)
{
mBrushTexture = brushTexture;
}

@ -0,0 +1,100 @@
#ifndef CSV_RENDER_TERRAINTEXTUREMODE_H
#define CSV_RENDER_TERRAINTEXTUREMODE_H
#include "editmode.hpp"
#include <string>
#include <QWidget>
#include <QEvent>
#include "../../model/world/data.hpp"
#include "../../model/world/land.hpp"
#include "../../model/doc/document.hpp"
#include "../../model/world/commands.hpp"
#include "../../model/world/idtable.hpp"
#include "../../model/world/landtexture.hpp"
namespace CSVWidget
{
class SceneToolTextureBrush;
}
namespace CSVRender
{
class TerrainTextureMode : public EditMode
{
Q_OBJECT
public:
/// \brief Editmode for terrain texture grid
TerrainTextureMode(WorldspaceWidget*, QWidget* parent = nullptr);
/// \brief Create single command for one-click texture editing
void primaryEditPressed (const WorldspaceHitResult& hit);
/// \brief Open brush settings window
void primarySelectPressed(const WorldspaceHitResult&);
void secondarySelectPressed(const WorldspaceHitResult&);
void activate(CSVWidget::SceneToolbar*);
void deactivate(CSVWidget::SceneToolbar*);
/// \brief Start texture editing command macro
virtual bool primaryEditStartDrag (const QPoint& pos);
virtual bool secondaryEditStartDrag (const QPoint& pos);
virtual bool primarySelectStartDrag (const QPoint& pos);
virtual bool secondarySelectStartDrag (const QPoint& pos);
/// \brief Handle texture edit behavior during dragging
virtual void drag (const QPoint& pos, int diffX, int diffY, double speedFactor);
/// \brief End texture editing command macro
virtual void dragCompleted(const QPoint& pos);
virtual void dragAborted();
virtual void dragWheel (int diff, double speedFactor);
virtual void dragMoveEvent (QDragMoveEvent *event);
/// \brief Handle brush mechanics, maths regarding worldspace hit etc.
void editTerrainTextureGrid (const WorldspaceHitResult& hit);
/// \brief Push texture edits to command macro
void pushEditToCommand (CSMWorld::LandTexturesColumn::DataType& newLandGrid, CSMDoc::Document& document,
CSMWorld::IdTable& landTable, std::string cellId);
/// \brief Create new land texture record from texture asset
void createTexture(std::string textureFileName);
/// \brief Create new cell and land if needed
bool allowLandTextureEditing(std::string textureFileName);
private:
std::string mCellId;
std::string mBrushTexture;
int mBrushSize;
int mBrushShape;
CSVWidget::SceneToolTextureBrush *mTextureBrushScenetool;
const int cellSize {ESM::Land::REAL_SIZE};
const int landSize {ESM::Land::LAND_SIZE};
const int landTextureSize {ESM::Land::LAND_TEXTURE_SIZE};
signals:
void passBrushTexture(std::string brushTexture);
public slots:
void handleDropEvent(QDropEvent *event);
void setBrushSize(int brushSize);
void setBrushShape(int brushShape);
void setBrushTexture(std::string brushShape);
};
}
#endif

@ -445,7 +445,7 @@ CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick (const QPo
// Default placement // Default placement
direction.normalize(); direction.normalize();
direction *= CSMPrefs::get()["Scene Drops"]["distance"].toInt(); direction *= CSMPrefs::get()["3D Scene Editing"]["distance"].toInt();
WorldspaceHitResult hit = { false, 0, 0, 0, 0, start + direction }; WorldspaceHitResult hit = { false, 0, 0, 0, 0, start + direction };
return hit; return hit;
@ -648,6 +648,12 @@ void CSVRender::WorldspaceWidget::mouseMoveEvent (QMouseEvent *event)
mDragX = event->posF().x(); mDragX = event->posF().x();
mDragY = height() - event->posF().y(); mDragY = height() - event->posF().y();
#endif #endif
if (mDragMode == InteractionType_PrimaryEdit)
{
EditMode& editMode = dynamic_cast<CSVRender::EditMode&> (*mEditMode->getCurrent());
editMode.drag (event->pos(), mDragX, mDragY, mDragFactor); // note: terraintexturemode only uses pos
}
} }
} }
else else

@ -156,6 +156,7 @@ CSVTools::ReportTable::ReportTable (CSMDoc::Document& document,
setSelectionMode (QAbstractItemView::ExtendedSelection); setSelectionMode (QAbstractItemView::ExtendedSelection);
mProxyModel = new QSortFilterProxyModel (this); mProxyModel = new QSortFilterProxyModel (this);
mProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
mProxyModel->setSourceModel (mModel); mProxyModel->setSourceModel (mModel);
mProxyModel->setSortRole(Qt::UserRole); mProxyModel->setSortRole(Qt::UserRole);

@ -3,11 +3,15 @@
#include <QVBoxLayout> #include <QVBoxLayout>
#include "../../model/doc/document.hpp" #include "../../model/doc/document.hpp"
#include "../../model/doc/state.hpp"
#include "../../model/tools/search.hpp" #include "../../model/tools/search.hpp"
#include "../../model/tools/reportmodel.hpp" #include "../../model/tools/reportmodel.hpp"
#include "../../model/world/idtablebase.hpp" #include "../../model/world/idtablebase.hpp"
#include "../../model/prefs/state.hpp" #include "../../model/prefs/state.hpp"
#include "../world/tablebottombox.hpp"
#include "../world/creator.hpp"
#include "reporttable.hpp" #include "reporttable.hpp"
#include "searchbox.hpp" #include "searchbox.hpp"
@ -73,6 +77,9 @@ CSVTools::SearchSubView::SearchSubView (const CSMWorld::UniversalId& id, CSMDoc:
layout->addWidget (mTable = new ReportTable (document, id, true), 2); layout->addWidget (mTable = new ReportTable (document, id, true), 2);
layout->addWidget (mBottom =
new CSVWorld::TableBottomBox (CSVWorld::NullCreatorFactory(), document, id, this), 0);
QWidget *widget = new QWidget; QWidget *widget = new QWidget;
widget->setLayout (layout); widget->setLayout (layout);
@ -93,6 +100,15 @@ CSVTools::SearchSubView::SearchSubView (const CSMWorld::UniversalId& id, CSMDoc:
this, SLOT (startSearch (const CSMTools::Search&))); this, SLOT (startSearch (const CSMTools::Search&)));
connect (&mSearchBox, SIGNAL (replaceAll()), this, SLOT (replaceAllRequest())); connect (&mSearchBox, SIGNAL (replaceAll()), this, SLOT (replaceAllRequest()));
connect (document.getReport (id), SIGNAL (rowsRemoved (const QModelIndex&, int, int)),
this, SLOT (tableSizeUpdate()));
connect (document.getReport (id), SIGNAL (rowsInserted (const QModelIndex&, int, int)),
this, SLOT (tableSizeUpdate()));
connect (&document, SIGNAL (operationDone (int, bool)),
this, SLOT (operationDone (int, bool)));
} }
void CSVTools::SearchSubView::setEditLock (bool locked) void CSVTools::SearchSubView::setEditLock (bool locked)
@ -101,6 +117,11 @@ void CSVTools::SearchSubView::setEditLock (bool locked)
mSearchBox.setEditLock (locked); mSearchBox.setEditLock (locked);
} }
void CSVTools::SearchSubView::setStatusBar (bool show)
{
mBottom->setStatusBar(show);
}
void CSVTools::SearchSubView::stateChanged (int state, CSMDoc::Document *document) void CSVTools::SearchSubView::stateChanged (int state, CSMDoc::Document *document)
{ {
mSearchBox.setSearchMode (!(state & CSMDoc::State_Searching)); mSearchBox.setSearchMode (!(state & CSMDoc::State_Searching));
@ -126,3 +147,17 @@ void CSVTools::SearchSubView::replaceAllRequest()
{ {
replace (false); replace (false);
} }
void CSVTools::SearchSubView::tableSizeUpdate()
{
mBottom->tableSizeChanged (mDocument.getReport (getUniversalId())->rowCount(), 0, 0);
}
void CSVTools::SearchSubView::operationDone (int type, bool failed)
{
if (type==CSMDoc::State_Searching && !failed &&
!mDocument.getReport (getUniversalId())->rowCount())
{
mBottom->setStatusMessage ("No Results");
}
}

@ -15,6 +15,11 @@ namespace CSMDoc
class Document; class Document;
} }
namespace CSVWorld
{
class TableBottomBox;
}
namespace CSVTools namespace CSVTools
{ {
class ReportTable; class ReportTable;
@ -28,6 +33,7 @@ namespace CSVTools
CSMDoc::Document& mDocument; CSMDoc::Document& mDocument;
CSMTools::Search mSearch; CSMTools::Search mSearch;
bool mLocked; bool mLocked;
CSVWorld::TableBottomBox *mBottom;
private: private:
@ -43,6 +49,8 @@ namespace CSVTools
virtual void setEditLock (bool locked); virtual void setEditLock (bool locked);
virtual void setStatusBar (bool show);
private slots: private slots:
void stateChanged (int state, CSMDoc::Document *document); void stateChanged (int state, CSMDoc::Document *document);
@ -52,6 +60,10 @@ namespace CSVTools
void replaceRequest(); void replaceRequest();
void replaceAllRequest(); void replaceAllRequest();
void tableSizeUpdate();
void operationDone (int type, bool failed);
}; };
} }

@ -0,0 +1,379 @@
#include "scenetooltexturebrush.hpp"
#include <QFrame>
#include <QIcon>
#include <QTableWidget>
#include <QHBoxLayout>
#include <QWidget>
#include <QSpinBox>
#include <QGroupBox>
#include <QSlider>
#include <QEvent>
#include <QDropEvent>
#include <QButtonGroup>
#include <QVBoxLayout>
#include <QDragEnterEvent>
#include <QDrag>
#include <QTableWidget>
#include <QHeaderView>
#include <QApplication>
#include <QSizePolicy>
#include "scenetool.hpp"
#include "../../model/doc/document.hpp"
#include "../../model/prefs/state.hpp"
#include "../../model/world/commands.hpp"
#include "../../model/world/data.hpp"
#include "../../model/world/idcollection.hpp"
#include "../../model/world/idtable.hpp"
#include "../../model/world/landtexture.hpp"
#include "../../model/world/universalid.hpp"
CSVWidget::BrushSizeControls::BrushSizeControls(const QString &title, QWidget *parent)
: QGroupBox(title, parent)
{
mBrushSizeSlider = new QSlider(Qt::Horizontal);
mBrushSizeSlider->setTickPosition(QSlider::TicksBothSides);
mBrushSizeSlider->setTickInterval(10);
mBrushSizeSlider->setRange(1, CSMPrefs::get()["3D Scene Editing"]["texturebrush-maximumsize"].toInt());
mBrushSizeSlider->setSingleStep(1);
mBrushSizeSpinBox = new QSpinBox;
mBrushSizeSpinBox->setRange(1, CSMPrefs::get()["3D Scene Editing"]["texturebrush-maximumsize"].toInt());
mBrushSizeSpinBox->setSingleStep(1);
mLayoutSliderSize = new QHBoxLayout;
mLayoutSliderSize->addWidget(mBrushSizeSlider);
mLayoutSliderSize->addWidget(mBrushSizeSpinBox);
connect(mBrushSizeSlider, SIGNAL(valueChanged(int)), mBrushSizeSpinBox, SLOT(setValue(int)));
connect(mBrushSizeSpinBox, SIGNAL(valueChanged(int)), mBrushSizeSlider, SLOT(setValue(int)));
setLayout(mLayoutSliderSize);
}
CSVWidget::TextureBrushWindow::TextureBrushWindow(CSMDoc::Document& document, QWidget *parent)
: QFrame(parent, Qt::Popup),
mBrushShape(0),
mBrushSize(0),
mBrushTexture("L0#0"),
mDocument(document)
{
mBrushTextureLabel = "Selected texture: " + mBrushTexture + " ";
CSMWorld::IdCollection<CSMWorld::LandTexture>& landtexturesCollection = mDocument.getData().getLandTextures();
int landTextureFilename = landtexturesCollection.findColumnIndex(CSMWorld::Columns::ColumnId_Texture);
int index = landtexturesCollection.searchId(mBrushTexture);
if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted())
{
mSelectedBrush = new QLabel(QString::fromStdString(mBrushTextureLabel) + landtexturesCollection.getData(index, landTextureFilename).value<QString>());
} else
{
mBrushTextureLabel = "No selected texture or invalid texture";
mSelectedBrush = new QLabel(QString::fromStdString(mBrushTextureLabel));
}
mButtonPoint = new QPushButton(QIcon (QPixmap (":scenetoolbar/brush-point")), "", this);
mButtonSquare = new QPushButton(QIcon (QPixmap (":scenetoolbar/brush-square")), "", this);
mButtonCircle = new QPushButton(QIcon (QPixmap (":scenetoolbar/brush-circle")), "", this);
mButtonCustom = new QPushButton(QIcon (QPixmap (":scenetoolbar/brush-custom")), "", this);
mSizeSliders = new BrushSizeControls("Brush size", this);
QVBoxLayout *layoutMain = new QVBoxLayout;
layoutMain->setSpacing(0);
layoutMain->setContentsMargins(4,0,4,4);
QHBoxLayout *layoutHorizontal = new QHBoxLayout;
layoutHorizontal->setSpacing(0);
layoutHorizontal->setContentsMargins (QMargins (0, 0, 0, 0));
configureButtonInitialSettings(mButtonPoint);
configureButtonInitialSettings(mButtonSquare);
configureButtonInitialSettings(mButtonCircle);
configureButtonInitialSettings(mButtonCustom);
mButtonPoint->setToolTip (toolTipPoint);
mButtonSquare->setToolTip (toolTipSquare);
mButtonCircle->setToolTip (toolTipCircle);
mButtonCustom->setToolTip (toolTipCustom);
QButtonGroup* brushButtonGroup = new QButtonGroup(this);
brushButtonGroup->addButton(mButtonPoint);
brushButtonGroup->addButton(mButtonSquare);
brushButtonGroup->addButton(mButtonCircle);
brushButtonGroup->addButton(mButtonCustom);
brushButtonGroup->setExclusive(true);
layoutHorizontal->addWidget(mButtonPoint, 0, Qt::AlignTop);
layoutHorizontal->addWidget(mButtonSquare, 0, Qt::AlignTop);
layoutHorizontal->addWidget(mButtonCircle, 0, Qt::AlignTop);
layoutHorizontal->addWidget(mButtonCustom, 0, Qt::AlignTop);
mHorizontalGroupBox = new QGroupBox(tr(""));
mHorizontalGroupBox->setLayout(layoutHorizontal);
layoutMain->addWidget(mHorizontalGroupBox);
layoutMain->addWidget(mSizeSliders);
layoutMain->addWidget(mSelectedBrush);
setLayout(layoutMain);
connect(mButtonPoint, SIGNAL(clicked()), this, SLOT(setBrushShape()));
connect(mButtonSquare, SIGNAL(clicked()), this, SLOT(setBrushShape()));
connect(mButtonCircle, SIGNAL(clicked()), this, SLOT(setBrushShape()));
connect(mButtonCustom, SIGNAL(clicked()), this, SLOT(setBrushShape()));
}
void CSVWidget::TextureBrushWindow::configureButtonInitialSettings(QPushButton *button)
{
button->setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed));
button->setContentsMargins (QMargins (0, 0, 0, 0));
button->setIconSize (QSize (48-6, 48-6));
button->setFixedSize (48, 48);
button->setCheckable(true);
}
void CSVWidget::TextureBrushWindow::setBrushTexture(std::string brushTexture)
{
mBrushTexture = brushTexture;
CSMWorld::IdCollection<CSMWorld::LandTexture>& landtexturesCollection = mDocument.getData().getLandTextures();
int landTextureFilename = landtexturesCollection.findColumnIndex(CSMWorld::Columns::ColumnId_Texture);
int columnModification = landtexturesCollection.findColumnIndex(CSMWorld::Columns::ColumnId_Modification);
int index = landtexturesCollection.searchId(mBrushTexture);
// Check if texture exists in current plugin
if(landtexturesCollection.getData(index, columnModification).value<int>() == 0)
{
CSMWorld::IdTable& ltexTable = dynamic_cast<CSMWorld::IdTable&> (
*mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures));
QUndoStack& undoStack = mDocument.getUndoStack();
QVariant textureFileNameVariant;
textureFileNameVariant.setValue(landtexturesCollection.getData(index, landTextureFilename).value<QString>());
std::size_t hashlocation = mBrushTexture.find("#");
std::string mBrushTexturePlugin = "L0#" + mBrushTexture.substr (hashlocation+1);
int indexPlugin = landtexturesCollection.searchId(mBrushTexturePlugin);
// Reindex texture if needed
if (indexPlugin != -1 && !landtexturesCollection.getRecord(indexPlugin).isDeleted())
{
int counter=0;
bool freeIndexFound = false;
do {
const size_t maxCounter = std::numeric_limits<uint16_t>::max() - 1;
mBrushTexturePlugin = CSMWorld::LandTexture::createUniqueRecordId(0, counter);
if (landtexturesCollection.searchId(mBrushTexturePlugin) != -1 && landtexturesCollection.getRecord(mBrushTexturePlugin).isDeleted() == 0) counter = (counter + 1) % maxCounter;
else freeIndexFound = true;
} while (freeIndexFound == false);
}
undoStack.beginMacro ("Add land texture record");
undoStack.push (new CSMWorld::CloneCommand (ltexTable, mBrushTexture, mBrushTexturePlugin, CSMWorld::UniversalId::Type_LandTexture));
undoStack.endMacro();
mBrushTexture = mBrushTexturePlugin;
emit passTextureId(mBrushTexture);
}
if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted())
{
mBrushTextureLabel = "Selected texture: " + mBrushTexture + " ";
mSelectedBrush->setText(QString::fromStdString(mBrushTextureLabel) + landtexturesCollection.getData(index, landTextureFilename).value<QString>());
} else
{
mBrushTextureLabel = "No selected texture or invalid texture";
mSelectedBrush->setText(QString::fromStdString(mBrushTextureLabel));
}
emit passBrushShape(mBrushShape); // update icon
}
void CSVWidget::TextureBrushWindow::setBrushSize(int brushSize)
{
mBrushSize = brushSize;
emit passBrushSize(mBrushSize);
}
void CSVWidget::TextureBrushWindow::setBrushShape()
{
if(mButtonPoint->isChecked()) mBrushShape = 0;
if(mButtonSquare->isChecked()) mBrushShape = 1;
if(mButtonCircle->isChecked()) mBrushShape = 2;
if(mButtonCustom->isChecked()) mBrushShape = 3;
emit passBrushShape(mBrushShape);
}
void CSVWidget::SceneToolTextureBrush::adjustToolTips()
{
}
CSVWidget::SceneToolTextureBrush::SceneToolTextureBrush (SceneToolbar *parent, const QString& toolTip, CSMDoc::Document& document)
: SceneTool (parent, Type_TopAction),
mToolTip (toolTip),
mDocument (document),
mTextureBrushWindow(new TextureBrushWindow(document, this))
{
mBrushHistory.resize(1);
mBrushHistory[0] = "L0#0";
setAcceptDrops(true);
connect(mTextureBrushWindow, SIGNAL(passBrushShape(int)), this, SLOT(setButtonIcon(int)));
setButtonIcon(mTextureBrushWindow->mBrushShape);
mPanel = new QFrame (this, Qt::Popup);
QHBoxLayout *layout = new QHBoxLayout (mPanel);
layout->setContentsMargins (QMargins (0, 0, 0, 0));
mTable = new QTableWidget (0, 2, this);
mTable->setShowGrid (true);
mTable->verticalHeader()->hide();
mTable->horizontalHeader()->hide();
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
mTable->horizontalHeader()->setSectionResizeMode (0, QHeaderView::Stretch);
mTable->horizontalHeader()->setSectionResizeMode (1, QHeaderView::Stretch);
#else
mTable->horizontalHeader()->setResizeMode (0, QHeaderView::Stretch);
mTable->horizontalHeader()->setResizeMode (1, QHeaderView::Stretch);
#endif
mTable->setSelectionMode (QAbstractItemView::NoSelection);
layout->addWidget (mTable);
connect (mTable, SIGNAL (clicked (const QModelIndex&)),
this, SLOT (clicked (const QModelIndex&)));
}
void CSVWidget::SceneToolTextureBrush::setButtonIcon (int brushShape)
{
QString tooltip = "Change brush settings <p>Currently selected: ";
switch (brushShape)
{
case 0:
setIcon (QIcon (QPixmap (":scenetoolbar/brush-point")));
tooltip += mTextureBrushWindow->toolTipPoint;
break;
case 1:
setIcon (QIcon (QPixmap (":scenetoolbar/brush-square")));
tooltip += mTextureBrushWindow->toolTipSquare;
break;
case 2:
setIcon (QIcon (QPixmap (":scenetoolbar/brush-circle")));
tooltip += mTextureBrushWindow->toolTipCircle;
break;
case 3:
setIcon (QIcon (QPixmap (":scenetoolbar/brush-custom")));
tooltip += mTextureBrushWindow->toolTipCustom;
break;
}
tooltip += "<p>(right click to access of previously used brush settings)";
CSMWorld::IdCollection<CSMWorld::LandTexture>& landtexturesCollection = mDocument.getData().getLandTextures();
int landTextureFilename = landtexturesCollection.findColumnIndex(CSMWorld::Columns::ColumnId_Texture);
int index = landtexturesCollection.searchId(mTextureBrushWindow->mBrushTexture);
if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted())
{
tooltip += "<p>Selected texture: " + QString::fromStdString(mTextureBrushWindow->mBrushTexture) + " ";
tooltip += landtexturesCollection.getData(index, landTextureFilename).value<QString>();
} else
{
tooltip += "<p>No selected texture or invalid texture";
}
tooltip += "<br>(drop texture here to change)";
setToolTip (tooltip);
}
void CSVWidget::SceneToolTextureBrush::showPanel (const QPoint& position)
{
updatePanel();
mPanel->move (position);
mPanel->show();
}
void CSVWidget::SceneToolTextureBrush::updatePanel()
{
mTable->setRowCount (mBrushHistory.size());
for (int i = mBrushHistory.size()-1; i >= 0; --i)
{
CSMWorld::IdCollection<CSMWorld::LandTexture>& landtexturesCollection = mDocument.getData().getLandTextures();
int landTextureFilename = landtexturesCollection.findColumnIndex(CSMWorld::Columns::ColumnId_Texture);
int index = landtexturesCollection.searchId(mBrushHistory[i]);
if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted())
{
mTable->setItem (i, 1, new QTableWidgetItem (landtexturesCollection.getData(index, landTextureFilename).value<QString>()));
mTable->setItem (i, 0, new QTableWidgetItem (QString::fromStdString(mBrushHistory[i])));
} else
{
mTable->setItem (i, 1, new QTableWidgetItem ("Invalid/deleted texture"));
mTable->setItem (i, 0, new QTableWidgetItem (QString::fromStdString(mBrushHistory[i])));
}
}
}
void CSVWidget::SceneToolTextureBrush::updateBrushHistory (const std::string& brushTexture)
{
mBrushHistory.insert(mBrushHistory.begin(), brushTexture);
if(mBrushHistory.size() > 5) mBrushHistory.pop_back();
}
void CSVWidget::SceneToolTextureBrush::clicked (const QModelIndex& index)
{
if (index.column()==0 || index.column()==1)
{
std::string brushTexture = mBrushHistory[index.row()];
std::swap(mBrushHistory[index.row()], mBrushHistory[0]);
mTextureBrushWindow->setBrushTexture(brushTexture);
emit passTextureId(brushTexture);
updatePanel();
mPanel->hide();
}
}
void CSVWidget::SceneToolTextureBrush::activate ()
{
QPoint position = QCursor::pos();
mTextureBrushWindow->mSizeSliders->mBrushSizeSlider->setRange(1, CSMPrefs::get()["3D Scene Editing"]["texturebrush-maximumsize"].toInt());
mTextureBrushWindow->mSizeSliders->mBrushSizeSpinBox->setRange(1, CSMPrefs::get()["3D Scene Editing"]["texturebrush-maximumsize"].toInt());
mTextureBrushWindow->move (position);
mTextureBrushWindow->show();
}
void CSVWidget::SceneToolTextureBrush::dragEnterEvent (QDragEnterEvent *event)
{
emit passEvent(event);
event->accept();
}
void CSVWidget::SceneToolTextureBrush::dropEvent (QDropEvent *event)
{
emit passEvent(event);
event->accept();
}

@ -0,0 +1,133 @@
#ifndef CSV_WIDGET_SCENETOOLTEXTUREBRUSH_H
#define CSV_WIDGET_SCENETOOLTEXTUREBRUSH_H
#include <QIcon>
#include <QFrame>
#include <QModelIndex>
#include <QWidget>
#include <QLabel>
#include <QSpinBox>
#include <QGroupBox>
#include <QSlider>
#include <QEvent>
#include <QHBoxLayout>
#include <QPushButton>
#include "scenetool.hpp"
#include "../../model/doc/document.hpp"
class QTableWidget;
namespace CSVRender
{
class TerrainTextureMode;
}
namespace CSVWidget
{
class SceneToolTextureBrush;
/// \brief Layout-box for some brush button settings
class BrushSizeControls : public QGroupBox
{
Q_OBJECT
public:
BrushSizeControls(const QString &title, QWidget *parent);
private:
QHBoxLayout *mLayoutSliderSize;
QSlider *mBrushSizeSlider;
QSpinBox *mBrushSizeSpinBox;
friend class SceneToolTextureBrush;
friend class CSVRender::TerrainTextureMode;
};
class SceneToolTextureBrush;
/// \brief Brush settings window
class TextureBrushWindow : public QFrame
{
Q_OBJECT
public:
TextureBrushWindow(CSMDoc::Document& document, QWidget *parent = 0);
void configureButtonInitialSettings(QPushButton *button);
const QString toolTipPoint = "Paint single point";
const QString toolTipSquare = "Paint with square brush";
const QString toolTipCircle = "Paint with circle brush";
const QString toolTipCustom = "Paint custom selection (not implemented yet)";
private:
int mBrushShape;
int mBrushSize;
std::string mBrushTexture;
CSMDoc::Document& mDocument;
QLabel *mSelectedBrush;
QGroupBox *mHorizontalGroupBox;
std::string mBrushTextureLabel;
QPushButton *mButtonPoint;
QPushButton *mButtonSquare;
QPushButton *mButtonCircle;
QPushButton *mButtonCustom;
BrushSizeControls* mSizeSliders;
friend class SceneToolTextureBrush;
friend class CSVRender::TerrainTextureMode;
public slots:
void setBrushTexture(std::string brushTexture);
void setBrushShape();
void setBrushSize(int brushSize);
signals:
void passBrushSize (int brushSize);
void passBrushShape(int brushShape);
void passTextureId(std::string brushTexture);
};
class SceneToolTextureBrush : public SceneTool
{
Q_OBJECT
QString mToolTip;
CSMDoc::Document& mDocument;
QFrame *mPanel;
QTableWidget *mTable;
std::vector<std::string> mBrushHistory;
TextureBrushWindow *mTextureBrushWindow;
private:
void adjustToolTips();
public:
SceneToolTextureBrush (SceneToolbar *parent, const QString& toolTip, CSMDoc::Document& document);
virtual void showPanel (const QPoint& position);
void updatePanel ();
void dropEvent (QDropEvent *event);
void dragEnterEvent (QDragEnterEvent *event);
friend class CSVRender::TerrainTextureMode;
public slots:
void setButtonIcon(int brushShape);
void updateBrushHistory (const std::string& mBrushTexture);
void clicked (const QModelIndex& index);
virtual void activate();
signals:
void passEvent(QDropEvent *event);
void passEvent(QDragEnterEvent *event);
void passTextureId(std::string brushTexture);
};
}
#endif

@ -57,12 +57,12 @@ CSVWorld::RecordButtonBar::RecordButtonBar (const CSMWorld::UniversalId& id,
// left section // left section
mPrevButton = new QToolButton (this); mPrevButton = new QToolButton (this);
mPrevButton->setIcon(QIcon(":/go-previous.png")); mPrevButton->setIcon(QIcon(":record-previous"));
mPrevButton->setToolTip ("Switch to previous record"); mPrevButton->setToolTip ("Switch to previous record");
buttonsLayout->addWidget (mPrevButton, 0); buttonsLayout->addWidget (mPrevButton, 0);
mNextButton = new QToolButton (this); mNextButton = new QToolButton (this);
mNextButton->setIcon(QIcon(":/go-next.png")); mNextButton->setIcon(QIcon(":/record-next"));
mNextButton->setToolTip ("Switch to next record"); mNextButton->setToolTip ("Switch to next record");
buttonsLayout->addWidget (mNextButton, 1); buttonsLayout->addWidget (mNextButton, 1);
@ -72,7 +72,7 @@ CSVWorld::RecordButtonBar::RecordButtonBar (const CSMWorld::UniversalId& id,
if (mTable.getFeatures() & CSMWorld::IdTable::Feature_Preview) if (mTable.getFeatures() & CSMWorld::IdTable::Feature_Preview)
{ {
QToolButton* previewButton = new QToolButton (this); QToolButton* previewButton = new QToolButton (this);
previewButton->setIcon(QIcon(":/edit-preview.png")); previewButton->setIcon(QIcon(":edit-preview"));
previewButton->setToolTip ("Open a preview of this record"); previewButton->setToolTip ("Open a preview of this record");
buttonsLayout->addWidget(previewButton); buttonsLayout->addWidget(previewButton);
connect (previewButton, SIGNAL(clicked()), this, SIGNAL (showPreview())); connect (previewButton, SIGNAL(clicked()), this, SIGNAL (showPreview()));
@ -89,22 +89,22 @@ CSVWorld::RecordButtonBar::RecordButtonBar (const CSMWorld::UniversalId& id,
// right section // right section
mCloneButton = new QToolButton (this); mCloneButton = new QToolButton (this);
mCloneButton->setIcon(QIcon(":/edit-clone.png")); mCloneButton->setIcon(QIcon(":edit-clone"));
mCloneButton->setToolTip ("Clone record"); mCloneButton->setToolTip ("Clone record");
buttonsLayout->addWidget(mCloneButton); buttonsLayout->addWidget(mCloneButton);
mAddButton = new QToolButton (this); mAddButton = new QToolButton (this);
mAddButton->setIcon(QIcon(":/add.png")); mAddButton->setIcon(QIcon(":edit-add"));
mAddButton->setToolTip ("Add new record"); mAddButton->setToolTip ("Add new record");
buttonsLayout->addWidget(mAddButton); buttonsLayout->addWidget(mAddButton);
mDeleteButton = new QToolButton (this); mDeleteButton = new QToolButton (this);
mDeleteButton->setIcon(QIcon(":/edit-delete.png")); mDeleteButton->setIcon(QIcon(":edit-delete"));
mDeleteButton->setToolTip ("Delete record"); mDeleteButton->setToolTip ("Delete record");
buttonsLayout->addWidget(mDeleteButton); buttonsLayout->addWidget(mDeleteButton);
mRevertButton = new QToolButton (this); mRevertButton = new QToolButton (this);
mRevertButton->setIcon(QIcon(":/edit-undo.png")); mRevertButton->setIcon(QIcon(":edit-undo"));
mRevertButton->setToolTip ("Revert record"); mRevertButton->setToolTip ("Revert record");
buttonsLayout->addWidget(mRevertButton); buttonsLayout->addWidget(mRevertButton);

@ -27,7 +27,7 @@ CSVWorld::RecordStatusDelegateFactory::RecordStatusDelegateFactory()
static const char *sIcons[] = static const char *sIcons[] =
{ {
":./base.png", ":./modified.png", ":./added.png", ":./removed.png", ":./removed.png", 0 ":list-base", ":list-modified", ":list-added", ":list-removed", ":list-removed", 0
}; };
for (int i=0; sIcons[i]; ++i) for (int i=0; sIcons[i]; ++i)

@ -63,19 +63,9 @@ std::string CSVWorld::ReferenceCreator::getErrors() const
std::string cell = mCell->text().toUtf8().constData(); std::string cell = mCell->text().toUtf8().constData();
if (cell.empty()) if (cell.empty())
{
if (!errors.empty())
errors += "<br>";
errors += "Missing Cell ID"; errors += "Missing Cell ID";
}
else if (getData().getCells().searchId (cell)==-1) else if (getData().getCells().searchId (cell)==-1)
{
if (!errors.empty())
errors += "<br>";
errors += "Invalid Cell ID"; errors += "Invalid Cell ID";
}
return errors; return errors;
} }

@ -764,10 +764,8 @@ std::vector< CSMWorld::UniversalId > CSVWorld::Table::getDraggedRecords() const
QModelIndexList selectedRows = selectionModel()->selectedRows(); QModelIndexList selectedRows = selectionModel()->selectedRows();
std::vector<CSMWorld::UniversalId> idToDrag; std::vector<CSMWorld::UniversalId> idToDrag;
foreach (QModelIndex it, selectedRows) //I had a dream. Dream where you could use C++11 in OpenMW. for (QModelIndex& it : selectedRows)
{
idToDrag.push_back (getUniversalId (it.row())); idToDrag.push_back (getUniversalId (it.row()));
}
return idToDrag; return idToDrag;
} }

@ -28,6 +28,12 @@ void CSVWorld::TableBottomBox::updateStatus()
{ {
if (mShowStatusBar) if (mShowStatusBar)
{ {
if (!mStatusMessage.isEmpty())
{
mStatus->setText (mStatusMessage);
return;
}
static const char *sLabels[4] = { "record", "deleted", "touched", "selected" }; static const char *sLabels[4] = { "record", "deleted", "touched", "selected" };
static const char *sLabelsPlural[4] = { "records", "deleted", "touched", "selected" }; static const char *sLabelsPlural[4] = { "records", "deleted", "touched", "selected" };
@ -178,10 +184,17 @@ void CSVWorld::TableBottomBox::currentWidgetChanged(int /*index*/)
updateSize(); updateSize();
} }
void CSVWorld::TableBottomBox::setStatusMessage (const QString& message)
{
mStatusMessage = message;
updateStatus();
}
void CSVWorld::TableBottomBox::selectionSizeChanged (int size) void CSVWorld::TableBottomBox::selectionSizeChanged (int size)
{ {
if (mStatusCount[3]!=size) if (mStatusCount[3]!=size)
{ {
mStatusMessage = "";
mStatusCount[3] = size; mStatusCount[3] = size;
updateStatus(); updateStatus();
} }
@ -210,7 +223,10 @@ void CSVWorld::TableBottomBox::tableSizeChanged (int size, int deleted, int modi
} }
if (changed) if (changed)
{
mStatusMessage = "";
updateStatus(); updateStatus();
}
} }
void CSVWorld::TableBottomBox::positionChanged (int row, int column) void CSVWorld::TableBottomBox::positionChanged (int row, int column)

@ -39,6 +39,7 @@ namespace CSVWorld
bool mHasPosition; bool mHasPosition;
int mRow; int mRow;
int mColumn; int mColumn;
QString mStatusMessage;
private: private:
@ -73,6 +74,8 @@ namespace CSVWorld
/// ///
/// \note The BotomBox does not partake in the deletion of records. /// \note The BotomBox does not partake in the deletion of records.
void setStatusMessage (const QString& message);
signals: signals:
void requestFocus (const std::string& id); void requestFocus (const std::string& id);

@ -233,6 +233,15 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO
return dsb; return dsb;
} }
case CSMWorld::ColumnBase::Display_Double:
{
DialogueDoubleSpinBox *dsb = new DialogueDoubleSpinBox(parent);
dsb->setRange(-FLT_MAX, FLT_MAX);
dsb->setSingleStep(0.01f);
dsb->setDecimals(6);
return dsb;
}
case CSMWorld::ColumnBase::Display_LongString: case CSMWorld::ColumnBase::Display_LongString:
{ {
QPlainTextEdit *edit = new QPlainTextEdit(parent); QPlainTextEdit *edit = new QPlainTextEdit(parent);

@ -89,7 +89,7 @@ add_openmw_dir (mwmechanics
) )
add_openmw_dir (mwstate add_openmw_dir (mwstate
statemanagerimp charactermanager character statemanagerimp charactermanager character quicksavemanager
) )
add_openmw_dir (mwbase add_openmw_dir (mwbase
@ -222,4 +222,3 @@ endif (MSVC)
if (WIN32) if (WIN32)
INSTALL(TARGETS openmw RUNTIME DESTINATION ".") INSTALL(TARGETS openmw RUNTIME DESTINATION ".")
endif (WIN32) endif (WIN32)

@ -35,28 +35,11 @@ int Java_org_libsdl_app_SDLActivity_isMouseShown(JNIEnv *env, jclass cls, jobjec
return SDL_ShowCursor(SDL_QUERY); return SDL_ShowCursor(SDL_QUERY);
} }
int Java_org_libsdl_app_SDLActivity_nativeInit(JNIEnv* env, jclass cls, jobject obj) {
int Java_org_libsdl_app_SDLActivity_nativeInit(JNIEnv* env, jclass cls,
jobject obj) {
setenv("OPENMW_DECOMPRESS_TEXTURES", "1", 1); setenv("OPENMW_DECOMPRESS_TEXTURES", "1", 1);
SDL_Android_Init(env, cls);
SDL_SetMainReady();
// On Android, we use a virtual controller with guid="Virtual" // On Android, we use a virtual controller with guid="Virtual"
SDL_GameControllerAddMapping("5669727475616c000000000000000000,Virtual,a:b0,b:b1,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4"); SDL_GameControllerAddMapping("5669727475616c000000000000000000,Virtual,a:b0,b:b1,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4");
/* Run the application code! */ return 0;
int status;
status = main(argcData+1, argvData);
releaseArgv();
/* Do not issue an exit or the whole application will terminate instead of just the SDL thread */
/* exit(status); */
return status;
} }

@ -39,6 +39,7 @@ namespace MWBase
class ResponseCallback class ResponseCallback
{ {
public: public:
virtual ~ResponseCallback() = default;
virtual void addResponse(const std::string& title, const std::string& text) = 0; virtual void addResponse(const std::string& title, const std::string& text) = 0;
}; };

@ -239,7 +239,7 @@ namespace MWBase
virtual std::vector<std::pair<std::string, int> > getStolenItemOwners(const std::string& itemid) = 0; virtual std::vector<std::pair<std::string, int> > getStolenItemOwners(const std::string& itemid) = 0;
/// Has the player stolen this item from the given owner? /// Has the player stolen this item from the given owner?
virtual bool isItemStolenFrom(const std::string& itemid, const std::string& ownerid) = 0; virtual bool isItemStolenFrom(const std::string& itemid, const MWWorld::Ptr& ptr) = 0;
virtual bool isBoundItem(const MWWorld::Ptr& item) = 0; virtual bool isBoundItem(const MWWorld::Ptr& item) = 0;
virtual bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, MWWorld::Ptr& victim) = 0; virtual bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, MWWorld::Ptr& victim) = 0;

@ -492,12 +492,14 @@ namespace MWBase
virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile,
const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength) = 0; const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength) = 0;
virtual void applyLoopingParticles(const MWWorld::Ptr& ptr) = 0;
virtual const std::vector<std::string>& getContentFiles() const = 0; virtual const std::vector<std::string>& getContentFiles() const = 0;
virtual void breakInvisibility (const MWWorld::Ptr& actor) = 0; virtual void breakInvisibility (const MWWorld::Ptr& actor) = 0;
// Are we in an exterior or pseudo-exterior cell and it's night? // Allow NPCs to use torches?
virtual bool isDark() const = 0; virtual bool useTorches() const = 0;
virtual bool findInteriorPositionInWorldSpace(const MWWorld::CellStore* cell, osg::Vec3f& result) = 0; virtual bool findInteriorPositionInWorldSpace(const MWWorld::CellStore* cell, osg::Vec3f& result) = 0;
@ -563,6 +565,8 @@ namespace MWBase
virtual bool isPlayerInJail() const = 0; virtual bool isPlayerInJail() const = 0;
virtual void rotateWorldObject (const MWWorld::Ptr& ptr, osg::Quat rotate) = 0;
/// Return terrain height at \a worldPos position. /// Return terrain height at \a worldPos position.
virtual float getTerrainHeightAt(const osg::Vec3f& worldPos) const = 0; virtual float getTerrainHeightAt(const osg::Vec3f& worldPos) const = 0;

@ -139,24 +139,21 @@ namespace MWClass
const std::string trapActivationSound = "Disarm Trap Fail"; const std::string trapActivationSound = "Disarm Trap Fail";
MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr();
const MWWorld::InventoryStore& invStore = player.getClass().getInventoryStore(player); MWWorld::InventoryStore& invStore = player.getClass().getInventoryStore(player);
bool isLocked = ptr.getCellRef().getLockLevel() > 0; bool isLocked = ptr.getCellRef().getLockLevel() > 0;
bool isTrapped = !ptr.getCellRef().getTrap().empty(); bool isTrapped = !ptr.getCellRef().getTrap().empty();
bool hasKey = false; bool hasKey = false;
std::string keyName; std::string keyName;
// make key id lowercase const std::string keyId = ptr.getCellRef().getKey();
std::string keyId = ptr.getCellRef().getKey(); if (!keyId.empty())
Misc::StringUtils::lowerCaseInPlace(keyId);
for (MWWorld::ConstContainerStoreIterator it = invStore.cbegin(); it != invStore.cend(); ++it)
{ {
std::string refId = it->getCellRef().getRefId(); MWWorld::Ptr keyPtr = invStore.search(keyId);
Misc::StringUtils::lowerCaseInPlace(refId); if (!keyPtr.isEmpty())
if (refId == keyId)
{ {
hasKey = true; hasKey = true;
keyName = it->getClass().getName(*it); keyName = keyPtr.getClass().getName(keyPtr);
} }
} }

@ -114,7 +114,7 @@ namespace MWClass
const std::string lockedSound = "LockedDoor"; const std::string lockedSound = "LockedDoor";
const std::string trapActivationSound = "Disarm Trap Fail"; const std::string trapActivationSound = "Disarm Trap Fail";
const MWWorld::ContainerStore &invStore = actor.getClass().getContainerStore(actor); MWWorld::ContainerStore &invStore = actor.getClass().getContainerStore(actor);
bool isLocked = ptr.getCellRef().getLockLevel() > 0; bool isLocked = ptr.getCellRef().getLockLevel() > 0;
bool isTrapped = !ptr.getCellRef().getTrap().empty(); bool isTrapped = !ptr.getCellRef().getTrap().empty();
@ -135,21 +135,14 @@ namespace MWClass
animation->addSpellCastGlow(effect, 1); // 1 second glow to match the time taken for a door opening or closing animation->addSpellCastGlow(effect, 1); // 1 second glow to match the time taken for a door opening or closing
} }
// make key id lowercase const std::string keyId = ptr.getCellRef().getKey();
std::string keyId = ptr.getCellRef().getKey();
if (!keyId.empty()) if (!keyId.empty())
{ {
Misc::StringUtils::lowerCaseInPlace(keyId); MWWorld::Ptr keyPtr = invStore.search(keyId);
for (MWWorld::ConstContainerStoreIterator it = invStore.cbegin(); it != invStore.cend(); ++it) if (!keyPtr.isEmpty())
{ {
std::string refId = it->getCellRef().getRefId(); hasKey = true;
Misc::StringUtils::lowerCaseInPlace(refId); keyName = keyPtr.getClass().getName(keyPtr);
if (refId == keyId)
{
hasKey = true;
keyName = it->getClass().getName(*it);
break;
}
} }
} }

@ -233,24 +233,6 @@ namespace MWClass
if (!(ref->mBase->mData.mFlags & ESM::Light::Carry)) if (!(ref->mBase->mData.mFlags & ESM::Light::Carry))
return std::make_pair(0,""); return std::make_pair(0,"");
const MWWorld::InventoryStore& invStore = npc.getClass().getInventoryStore(npc);
MWWorld::ConstContainerStoreIterator weapon = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
if(weapon == invStore.end())
return std::make_pair(1,"");
/// \todo the 2h check is repeated many times; put it in a function
if(weapon->getTypeName() == typeid(ESM::Weapon).name() &&
(weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::LongBladeTwoHand ||
weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::BluntTwoClose ||
weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::BluntTwoWide ||
weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::SpearTwoWide ||
weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::AxeTwoHand ||
weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanBow ||
weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow))
{
return std::make_pair(3,"");
}
return std::make_pair(1,""); return std::make_pair(1,"");
} }

@ -1,6 +1,7 @@
#include "misc.hpp" #include "misc.hpp"
#include <components/esm/loadmisc.hpp> #include <components/esm/loadmisc.hpp>
#include <components/settings/settings.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
@ -83,8 +84,24 @@ namespace MWClass
if (ptr.getCellRef().getSoul() != "") if (ptr.getCellRef().getSoul() != "")
{ {
const ESM::Creature *creature = MWBase::Environment::get().getWorld()->getStore().get<ESM::Creature>().find(ref->mRef.getSoul()); const ESM::Creature *creature = MWBase::Environment::get().getWorld()->getStore().get<ESM::Creature>().search(ref->mRef.getSoul());
value *= creature->mData.mSoul; if (creature)
{
int soul = creature->mData.mSoul;
if (Settings::Manager::getBool("rebalance soul gem values", "Game"))
{
// use the 'soul gem value rebalance' formula from the Morrowind Code Patch
float soulValue = 0.0001 * pow(soul, 3) + 2 * soul;
// for Azura's star add the unfilled value
if (Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "Misc_SoulGem_Azura"))
value += soulValue;
else
value = soulValue;
}
else
value *= soul;
}
} }
return value; return value;
@ -148,8 +165,9 @@ namespace MWClass
if (ref->mRef.getSoul() != "") if (ref->mRef.getSoul() != "")
{ {
const ESM::Creature *creature = store.get<ESM::Creature>().find(ref->mRef.getSoul()); const ESM::Creature *creature = store.get<ESM::Creature>().search(ref->mRef.getSoul());
info.caption += " (" + creature->mName + ")"; if (creature)
info.caption += " (" + creature->mName + ")";
} }
std::string text; std::string text;
@ -210,7 +228,7 @@ namespace MWClass
std::shared_ptr<MWWorld::Action> Miscellaneous::use (const MWWorld::Ptr& ptr) const std::shared_ptr<MWWorld::Action> Miscellaneous::use (const MWWorld::Ptr& ptr) const
{ {
if (ptr.getCellRef().getSoul().empty()) 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()); return std::shared_ptr<MWWorld::Action>(new MWWorld::NullAction());
else else
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionSoulgem(ptr)); return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionSoulgem(ptr));

@ -312,40 +312,40 @@ namespace MWClass
int gold=0; int gold=0;
if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
{ {
gold = ref->mBase->mNpdt52.mGold; gold = ref->mBase->mNpdt.mGold;
for (unsigned int i=0; i< ESM::Skill::Length; ++i) for (unsigned int i=0; i< ESM::Skill::Length; ++i)
data->mNpcStats.getSkill (i).setBase (ref->mBase->mNpdt52.mSkills[i]); data->mNpcStats.getSkill (i).setBase (ref->mBase->mNpdt.mSkills[i]);
data->mNpcStats.setAttribute(ESM::Attribute::Strength, ref->mBase->mNpdt52.mStrength); data->mNpcStats.setAttribute(ESM::Attribute::Strength, ref->mBase->mNpdt.mStrength);
data->mNpcStats.setAttribute(ESM::Attribute::Intelligence, ref->mBase->mNpdt52.mIntelligence); data->mNpcStats.setAttribute(ESM::Attribute::Intelligence, ref->mBase->mNpdt.mIntelligence);
data->mNpcStats.setAttribute(ESM::Attribute::Willpower, ref->mBase->mNpdt52.mWillpower); data->mNpcStats.setAttribute(ESM::Attribute::Willpower, ref->mBase->mNpdt.mWillpower);
data->mNpcStats.setAttribute(ESM::Attribute::Agility, ref->mBase->mNpdt52.mAgility); data->mNpcStats.setAttribute(ESM::Attribute::Agility, ref->mBase->mNpdt.mAgility);
data->mNpcStats.setAttribute(ESM::Attribute::Speed, ref->mBase->mNpdt52.mSpeed); data->mNpcStats.setAttribute(ESM::Attribute::Speed, ref->mBase->mNpdt.mSpeed);
data->mNpcStats.setAttribute(ESM::Attribute::Endurance, ref->mBase->mNpdt52.mEndurance); data->mNpcStats.setAttribute(ESM::Attribute::Endurance, ref->mBase->mNpdt.mEndurance);
data->mNpcStats.setAttribute(ESM::Attribute::Personality, ref->mBase->mNpdt52.mPersonality); data->mNpcStats.setAttribute(ESM::Attribute::Personality, ref->mBase->mNpdt.mPersonality);
data->mNpcStats.setAttribute(ESM::Attribute::Luck, ref->mBase->mNpdt52.mLuck); data->mNpcStats.setAttribute(ESM::Attribute::Luck, ref->mBase->mNpdt.mLuck);
data->mNpcStats.setHealth (ref->mBase->mNpdt52.mHealth); data->mNpcStats.setHealth (ref->mBase->mNpdt.mHealth);
data->mNpcStats.setMagicka (ref->mBase->mNpdt52.mMana); data->mNpcStats.setMagicka (ref->mBase->mNpdt.mMana);
data->mNpcStats.setFatigue (ref->mBase->mNpdt52.mFatigue); data->mNpcStats.setFatigue (ref->mBase->mNpdt.mFatigue);
data->mNpcStats.setLevel(ref->mBase->mNpdt52.mLevel); data->mNpcStats.setLevel(ref->mBase->mNpdt.mLevel);
data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt52.mDisposition); data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt.mDisposition);
data->mNpcStats.setReputation(ref->mBase->mNpdt52.mReputation); data->mNpcStats.setReputation(ref->mBase->mNpdt.mReputation);
data->mNpcStats.setNeedRecalcDynamicStats(false); data->mNpcStats.setNeedRecalcDynamicStats(false);
} }
else else
{ {
gold = ref->mBase->mNpdt12.mGold; gold = ref->mBase->mNpdt.mGold;
for (int i=0; i<3; ++i) for (int i=0; i<3; ++i)
data->mNpcStats.setDynamic (i, 10); data->mNpcStats.setDynamic (i, 10);
data->mNpcStats.setLevel(ref->mBase->mNpdt12.mLevel); data->mNpcStats.setLevel(ref->mBase->mNpdt.mLevel);
data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt12.mDisposition); data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt.mDisposition);
data->mNpcStats.setReputation(ref->mBase->mNpdt12.mReputation); data->mNpcStats.setReputation(ref->mBase->mNpdt.mReputation);
autoCalculateAttributes(ref->mBase, data->mNpcStats); autoCalculateAttributes(ref->mBase, data->mNpcStats);
autoCalculateSkills(ref->mBase, data->mNpcStats, ptr); autoCalculateSkills(ref->mBase, data->mNpcStats, ptr);
@ -406,7 +406,7 @@ namespace MWClass
// store // store
ptr.getRefData().setCustomData (data.release()); ptr.getRefData().setCustomData (data.release());
getInventoryStore(ptr).autoEquip(ptr); getInventoryStore(ptr).autoEquip(ptr);
} }
} }
@ -988,7 +988,7 @@ namespace MWClass
const MWMechanics::MagicEffects &mageffects = npcdata->mNpcStats.getMagicEffects(); const MWMechanics::MagicEffects &mageffects = npcdata->mNpcStats.getMagicEffects();
const float encumbranceTerm = gmst.fJumpEncumbranceBase->getFloat() + const float encumbranceTerm = gmst.fJumpEncumbranceBase->getFloat() +
gmst.fJumpEncumbranceMultiplier->getFloat() * gmst.fJumpEncumbranceMultiplier->getFloat() *
(1.0f - Npc::getEncumbrance(ptr)/Npc::getCapacity(ptr)); (1.0f - Npc::getNormalizedEncumbrance(ptr));
float a = static_cast<float>(npcdata->mNpcStats.getSkill(ESM::Skill::Acrobatics).getModified()); float a = static_cast<float>(npcdata->mNpcStats.getSkill(ESM::Skill::Acrobatics).getModified());
float b = 0.0f; float b = 0.0f;
@ -1327,10 +1327,7 @@ namespace MWClass
int Npc::getBaseGold(const MWWorld::ConstPtr& ptr) const int Npc::getBaseGold(const MWWorld::ConstPtr& ptr) const
{ {
const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>(); const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) return ref->mBase->mNpdt.mGold;
return ref->mBase->mNpdt52.mGold;
else
return ref->mBase->mNpdt12.mGold;
} }
bool Npc::isClass(const MWWorld::ConstPtr& ptr, const std::string &className) const bool Npc::isClass(const MWWorld::ConstPtr& ptr, const std::string &className) const

@ -325,7 +325,10 @@ namespace MWClass
// add reach and attack speed for melee weapon // add reach and attack speed for melee weapon
if (ref->mBase->mData.mType < 9 && Settings::Manager::getBool("show melee info", "Game")) if (ref->mBase->mData.mType < 9 && Settings::Manager::getBool("show melee info", "Game"))
{ {
text += MWGui::ToolTips::getPercentString(ref->mBase->mData.mReach, "#{sRange}"); // 64 game units = 1 yard = 3 ft, display value in feet
const float combatDistance = store.get<ESM::GameSetting>().find("fCombatDistance")->getFloat() * ref->mBase->mData.mReach;
text += MWGui::ToolTips::getWeightString(combatDistance*3/64, "#{sRange}");
text += " #{sFeet}";
text += MWGui::ToolTips::getPercentString(ref->mBase->mData.mSpeed, "#{sAttributeSpeed}"); text += MWGui::ToolTips::getPercentString(ref->mBase->mData.mSpeed, "#{sAttributeSpeed}");
} }

@ -432,7 +432,6 @@ namespace MWDialogue
void DialogueManager::addChoice (const std::string& text, int choice) void DialogueManager::addChoice (const std::string& text, int choice)
{ {
mIsInChoice = true; mIsInChoice = true;
mChoices.push_back(std::make_pair(text, choice)); mChoices.push_back(std::make_pair(text, choice));
} }

@ -892,7 +892,7 @@ protected:
public: public:
typedef TypesetBookImpl::StyleImpl Style; typedef TypesetBookImpl::StyleImpl Style;
typedef std::map <TextFormat::Id, TextFormat*> ActiveTextFormats; typedef std::map <TextFormat::Id, std::unique_ptr<TextFormat>> ActiveTextFormats;
int mViewTop; int mViewTop;
int mViewBottom; int mViewBottom;
@ -1048,7 +1048,7 @@ public:
{ {
if (mNode != NULL) if (mNode != NULL)
i->second->destroyDrawItem (mNode); i->second->destroyDrawItem (mNode);
delete i->second; i->second.reset();
} }
mActiveTextFormats.clear (); mActiveTextFormats.clear ();
@ -1115,11 +1115,11 @@ public:
if (j == this_->mActiveTextFormats.end ()) if (j == this_->mActiveTextFormats.end ())
{ {
TextFormat * textFormat = new TextFormat (Font, this_); std::unique_ptr<TextFormat> textFormat(new TextFormat (Font, this_));
textFormat->mTexture = Font->getTextureFont (); textFormat->mTexture = Font->getTextureFont ();
j = this_->mActiveTextFormats.insert (std::make_pair (Font, textFormat)).first; j = this_->mActiveTextFormats.insert (std::make_pair (Font, std::move(textFormat))).first;
} }
j->second->mCountVertex += run.mPrintableChars * 6; j->second->mCountVertex += run.mPrintableChars * 6;

@ -635,6 +635,11 @@ namespace MWGui
void DialogueWindow::onChoiceActivated(int id) void DialogueWindow::onChoiceActivated(int id)
{ {
if (mGoodbye)
{
onGoodbyeActivated();
return;
}
MWBase::Environment::get().getDialogueManager()->questionAnswered(id, mCallback.get()); MWBase::Environment::get().getDialogueManager()->questionAnswered(id, mCallback.get());
updateTopics(); updateTopics();
} }

@ -339,8 +339,7 @@ namespace MWGui
for (int i=0; i<2; ++i) for (int i=0; i<2; ++i)
{ {
MWWorld::Ptr item = (i == 0) ? mEnchanting.getOldItem() : mEnchanting.getGem(); MWWorld::Ptr item = (i == 0) ? mEnchanting.getOldItem() : mEnchanting.getGem();
if (MWBase::Environment::get().getMechanicsManager()->isItemStolenFrom(item.getCellRef().getRefId(), if (MWBase::Environment::get().getMechanicsManager()->isItemStolenFrom(item.getCellRef().getRefId(), mPtr))
mPtr.getCellRef().getRefId()))
{ {
std::string msg = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sNotifyMessage49")->getString(); std::string msg = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sNotifyMessage49")->getString();
if (msg.find("%s") != std::string::npos) if (msg.find("%s") != std::string::npos)

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

@ -129,6 +129,12 @@ namespace MWGui
mItemView->setModel(mSortModel); mItemView->setModel(mSortModel);
mFilterAll->setStateSelected(true);
mFilterWeapon->setStateSelected(false);
mFilterApparel->setStateSelected(false);
mFilterMagic->setStateSelected(false);
mFilterMisc->setStateSelected(false);
mPreview->updatePtr(mPtr); mPreview->updatePtr(mPtr);
mPreview->rebuild(); mPreview->rebuild();
mPreview->update(); mPreview->update();

@ -16,8 +16,13 @@ namespace
{ {
if (count == 1) if (count == 1)
return ""; return "";
if (count > 9999)
return MyGUI::utility::toString(int(count/1000.f)) + "k"; if (count > 999999999)
return MyGUI::utility::toString(count/1000000000) + "b";
else if (count > 999999)
return MyGUI::utility::toString(count/1000000) + "m";
else if (count > 9999)
return MyGUI::utility::toString(count/1000) + "k";
else else
return MyGUI::utility::toString(count); return MyGUI::utility::toString(count);
} }

@ -244,7 +244,7 @@ BookTypesetter::Ptr JournalBooks::createLatinJournalIndex ()
const MWGui::TextColours& textColours = MWBase::Environment::get().getWindowManager()->getTextColours(); const MWGui::TextColours& textColours = MWBase::Environment::get().getWindowManager()->getTextColours();
BookTypesetter::Style* style = typesetter->createHotStyle (body, textColours.journalTopic, BookTypesetter::Style* style = typesetter->createHotStyle (body, textColours.journalTopic,
textColours.journalTopicOver, textColours.journalTopicOver,
textColours.journalTopicPressed, (uint32_t) ch); textColours.journalTopicPressed, (Utf8Stream::UnicodeChar) ch);
if (i == 13) if (i == 13)
typesetter->sectionBreak (); typesetter->sectionBreak ();
@ -274,7 +274,7 @@ BookTypesetter::Ptr JournalBooks::createCyrillicJournalIndex ()
sprintf(buffer, "( %c%c )", ch[0], ch[1]); sprintf(buffer, "( %c%c )", ch[0], ch[1]);
Utf8Stream stream ((char*) ch); Utf8Stream stream ((char*) ch);
uint32_t first = stream.peek(); Utf8Stream::UnicodeChar first = stream.peek();
const MWGui::TextColours& textColours = MWBase::Environment::get().getWindowManager()->getTextColours(); const MWGui::TextColours& textColours = MWBase::Environment::get().getWindowManager()->getTextColours();
BookTypesetter::Style* style = typesetter->createHotStyle (body, textColours.journalTopic, BookTypesetter::Style* style = typesetter->createHotStyle (body, textColours.journalTopic,

@ -7,7 +7,6 @@
#include <components/translation/translation.hpp> #include <components/translation/translation.hpp>
#include <components/misc/stringops.hpp> #include <components/misc/stringops.hpp>
#include <components/misc/utf8stream.hpp>
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/journal.hpp" #include "../mwbase/journal.hpp"
@ -307,39 +306,22 @@ struct JournalViewModelImpl : JournalViewModel
visitor (toUtf8Span (topic.getName())); visitor (toUtf8Span (topic.getName()));
} }
void visitTopicNamesStartingWith (uint32_t character, std::function < void (const std::string&) > visitor) const void visitTopicNamesStartingWith (Utf8Stream::UnicodeChar character, std::function < void (const std::string&) > visitor) const
{ {
MWBase::Journal * journal = MWBase::Environment::get().getJournal(); MWBase::Journal * journal = MWBase::Environment::get().getJournal();
for (MWBase::Journal::TTopicIter i = journal->topicBegin (); i != journal->topicEnd (); ++i) for (MWBase::Journal::TTopicIter i = journal->topicBegin (); i != journal->topicEnd (); ++i)
{ {
Utf8Stream stream (i->first.c_str()); Utf8Stream stream (i->first.c_str());
uint32_t first = toUpper(stream.peek()); Utf8Stream::UnicodeChar first = Misc::StringUtils::toLowerUtf8(stream.peek());
if (first != character) if (first != Misc::StringUtils::toLowerUtf8(character))
continue; continue;
visitor (i->second.getName()); visitor (i->second.getName());
} }
} }
static uint32_t toUpper(uint32_t ch)
{
// Russian alphabet
if (ch >= 0x0430 && ch < 0x0450)
ch -= 0x20;
// Cyrillic IO character
if (ch == 0x0451)
ch -= 0x50;
// Latin alphabet
if (ch >= 0x61 && ch < 0x80)
ch -= 0x20;
return ch;
}
struct TopicEntryImpl : BaseEntry <MWDialogue::Topic::TEntryIter, TopicEntry> struct TopicEntryImpl : BaseEntry <MWDialogue::Topic::TEntryIter, TopicEntry>
{ {
MWDialogue::Topic const & mTopic; MWDialogue::Topic const & mTopic;

@ -6,6 +6,8 @@
#include <functional> #include <functional>
#include <stdint.h> #include <stdint.h>
#include <components/misc/utf8stream.hpp>
namespace MWGui namespace MWGui
{ {
/// View-Model for the journal GUI /// View-Model for the journal GUI
@ -76,7 +78,7 @@ namespace MWGui
virtual void visitTopicName (TopicId topicId, std::function <void (Utf8Span)> visitor) const = 0; virtual void visitTopicName (TopicId topicId, std::function <void (Utf8Span)> visitor) const = 0;
/// walks over the topics whose names start with the character /// walks over the topics whose names start with the character
virtual void visitTopicNamesStartingWith (uint32_t character, std::function < void (const std::string&) > visitor) const = 0; virtual void visitTopicNamesStartingWith (Utf8Stream::UnicodeChar character, std::function < void (const std::string&) > visitor) const = 0;
/// walks over the topic entries for the topic specified by its identifier /// walks over the topic entries for the topic specified by its identifier
virtual void visitTopicEntries (TopicId topicId, std::function <void (TopicEntry const &)> visitor) const = 0; virtual void visitTopicEntries (TopicId topicId, std::function <void (TopicEntry const &)> visitor) const = 0;

@ -616,7 +616,7 @@ namespace
if (page+2 < book->pageCount()) if (page+2 < book->pageCount())
{ {
MWBase::Environment::get().getWindowManager()->playSound("book page"); MWBase::Environment::get().getWindowManager()->playSound("book page", true);
page += 2; page += 2;
updateShowingPages (); updateShowingPages ();
@ -634,7 +634,7 @@ namespace
if(page >= 2) if(page >= 2)
{ {
MWBase::Environment::get().getWindowManager()->playSound("book page"); MWBase::Environment::get().getWindowManager()->playSound("book page", true);
page -= 2; page -= 2;
updateShowingPages (); updateShowingPages ();

@ -14,6 +14,9 @@ namespace MWGui
bool shouldAcceptKeyFocus(MyGUI::Widget* w) bool shouldAcceptKeyFocus(MyGUI::Widget* w)
{ {
if (w && w->getUserString("IgnoreTabKey") == "y")
return false;
return w && !w->castType<MyGUI::Window>(false) && w->getInheritedEnabled() && w->getInheritedVisible() && w->getVisible() && w->getEnabled(); return w && !w->castType<MyGUI::Window>(false) && w->getInheritedEnabled() && w->getInheritedVisible() && w->getVisible() && w->getEnabled();
} }
@ -39,6 +42,7 @@ void getKeyFocusWidgets(MyGUI::Widget* parent, std::vector<MyGUI::Widget*>& resu
KeyboardNavigation::KeyboardNavigation() KeyboardNavigation::KeyboardNavigation()
: mCurrentFocus(nullptr) : mCurrentFocus(nullptr)
, mModalWindow(nullptr) , mModalWindow(nullptr)
, mEnabled(true)
{ {
MyGUI::WidgetManager::getInstance().registerUnlinker(this); MyGUI::WidgetManager::getInstance().registerUnlinker(this);
} }
@ -101,6 +105,9 @@ bool isRootParent(MyGUI::Widget* widget, MyGUI::Widget* root)
void KeyboardNavigation::onFrame() void KeyboardNavigation::onFrame()
{ {
if (!mEnabled)
return;
MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget();
if (focus == mCurrentFocus) if (focus == mCurrentFocus)
@ -150,6 +157,11 @@ void KeyboardNavigation::setModalWindow(MyGUI::Widget *window)
mModalWindow = window; mModalWindow = window;
} }
void KeyboardNavigation::setEnabled(bool enabled)
{
mEnabled = enabled;
}
enum Direction enum Direction
{ {
D_Left, D_Left,
@ -162,6 +174,9 @@ enum Direction
bool KeyboardNavigation::injectKeyPress(MyGUI::KeyCode key, unsigned int text) bool KeyboardNavigation::injectKeyPress(MyGUI::KeyCode key, unsigned int text)
{ {
if (!mEnabled)
return false;
switch (key.getValue()) switch (key.getValue())
{ {
case MyGUI::KeyCode::ArrowLeft: case MyGUI::KeyCode::ArrowLeft:

@ -28,6 +28,8 @@ namespace MWGui
void setModalWindow(MyGUI::Widget* window); void setModalWindow(MyGUI::Widget* window);
void setEnabled(bool enabled);
private: private:
bool switchFocus(int direction, bool wrap); bool switchFocus(int direction, bool wrap);
@ -40,6 +42,8 @@ namespace MWGui
MyGUI::Widget* mCurrentFocus; MyGUI::Widget* mCurrentFocus;
MyGUI::Widget* mModalWindow; MyGUI::Widget* mModalWindow;
bool mEnabled;
}; };
} }

@ -81,6 +81,47 @@ namespace MWGui
delete mMagicSelectionDialog; delete mMagicSelectionDialog;
} }
void QuickKeysMenu::onOpen()
{
WindowBase::onOpen();
MWWorld::Ptr player = MWMechanics::getPlayer();
MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player);
// Check if quick keys are still valid
for (int i=0; i<10; ++i)
{
ItemWidget* button = mQuickKeyButtons[i];
int type = mAssigned[i];
switch (type)
{
case Type_Unassigned:
case Type_HandToHand:
case Type_Magic:
break;
case Type_Item:
case 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))
{
// Try searching for a compatible replacement
std::string id = item.getCellRef().getRefId();
item = store.findReplacement(id);
button->setUserData(MWWorld::Ptr(item));
break;
}
}
}
}
}
void QuickKeysMenu::unassign(ItemWidget* key, int index) void QuickKeysMenu::unassign(ItemWidget* key, int index)
{ {
key->clearUserStrings(); key->clearUserStrings();
@ -122,12 +163,10 @@ namespace MWGui
assert(index != -1); assert(index != -1);
mSelectedIndex = index; mSelectedIndex = index;
{ // open assign dialog
// open assign dialog if (!mAssignDialog)
if (!mAssignDialog) mAssignDialog = new QuickKeysMenuAssign(this);
mAssignDialog = new QuickKeysMenuAssign(this); mAssignDialog->setVisible (true);
mAssignDialog->setVisible (true);
}
} }
void QuickKeysMenu::onOkButtonClicked (MyGUI::Widget *sender) void QuickKeysMenu::onOkButtonClicked (MyGUI::Widget *sender)
@ -296,21 +335,16 @@ namespace MWGui
if (type == Type_Item || type == Type_MagicItem) if (type == Type_Item || type == Type_MagicItem)
{ {
MWWorld::Ptr item = *button->getUserData<MWWorld::Ptr>(); MWWorld::Ptr item = *button->getUserData<MWWorld::Ptr>();
// make sure the item is available // Make sure the item is available and is not broken
if (item.getRefData ().getCount() < 1) if (item.getRefData().getCount() < 1 ||
(item.getClass().hasItemHealth(item) &&
item.getClass().getItemHealth(item) <= 0))
{ {
// Try searching for a compatible replacement // Try searching for a compatible replacement
std::string id = item.getCellRef().getRefId(); std::string id = item.getCellRef().getRefId();
for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) item = store.findReplacement(id);
{ button->setUserData(MWWorld::Ptr(item));
if (Misc::StringUtils::ciEqual(it->getCellRef().getRefId(), id))
{
item = *it;
button->setUserData(MWWorld::Ptr(item));
break;
}
}
if (item.getRefData().getCount() < 1) if (item.getRefData().getCount() < 1)
{ {
@ -330,7 +364,12 @@ namespace MWGui
MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player);
MWMechanics::Spells& spells = stats.getSpells(); MWMechanics::Spells& spells = stats.getSpells();
if (!spells.hasSpell(spellId)) if (!spells.hasSpell(spellId))
{
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(spellId);
MWBase::Environment::get().getWindowManager()->messageBox (
"#{sQuickMenu5} " + spell->mName);
return; return;
}
store.setSelectedEnchantItem(store.end()); store.setSelectedEnchantItem(store.end());
MWBase::Environment::get().getWindowManager()->setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, player))); MWBase::Environment::get().getWindowManager()->setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, player)));
MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Spell); MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Spell);
@ -493,6 +532,9 @@ namespace MWGui
ESM::QuickKeys keys; ESM::QuickKeys keys;
keys.load(reader); keys.load(reader);
MWWorld::Ptr player = MWMechanics::getPlayer();
MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player);
int i=0; int i=0;
for (std::vector<ESM::QuickKeys::QuickKey>::const_iterator it = keys.mKeys.begin(); it != keys.mKeys.end(); ++it) for (std::vector<ESM::QuickKeys::QuickKey>::const_iterator it = keys.mKeys.begin(); it != keys.mKeys.end(); ++it)
{ {
@ -514,22 +556,7 @@ namespace MWGui
case Type_MagicItem: case Type_MagicItem:
{ {
// Find the item by id // Find the item by id
MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::Ptr item = store.findReplacement(id);
MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player);
MWWorld::Ptr item;
for (MWWorld::ContainerStoreIterator iter = store.begin(); iter != store.end(); ++iter)
{
if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), id))
{
if (item.isEmpty() ||
// Prefer the stack with the lowest remaining uses
!item.getClass().hasItemHealth(*iter) ||
iter->getClass().getItemHealth(*iter) < item.getClass().getItemHealth(item))
{
item = *iter;
}
}
}
if (item.isEmpty()) if (item.isEmpty())
unassign(button, i); unassign(button, i);

@ -34,6 +34,7 @@ namespace MWGui
void onAssignMagicItem (MWWorld::Ptr item); void onAssignMagicItem (MWWorld::Ptr item);
void onAssignMagic (const std::string& spellId); void onAssignMagic (const std::string& spellId);
void onAssignMagicCancel (); void onAssignMagicCancel ();
void onOpen();
void activateQuickKey(int index); void activateQuickKey(int index);
void updateActivatedQuickKey(); void updateActivatedQuickKey();

@ -15,11 +15,8 @@ namespace MWGui
// check if count of the reference has become 0 // check if count of the reference has become 0
if (!mPtr.isEmpty() && mPtr.getRefData().getCount() == 0) if (!mPtr.isEmpty() && mPtr.getRefData().getCount() == 0)
{ {
if (!mPtr.isEmpty()) mPtr = MWWorld::Ptr();
{ onReferenceUnavailable();
mPtr = MWWorld::Ptr();
onReferenceUnavailable();
}
} }
} }
} }

@ -562,8 +562,9 @@ namespace MWGui
void SettingsWindow::onOpen() void SettingsWindow::onOpen()
{ {
updateControlsBox (); updateControlsBox();
resetScrollbars(); resetScrollbars();
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mOkButton);
} }
void SettingsWindow::onWindowResize(MyGUI::Window *_sender) void SettingsWindow::onWindowResize(MyGUI::Window *_sender)

@ -208,7 +208,7 @@ namespace MWGui
if ((mFilter & Filter_OnlyEnchanted) && !(item.mFlags & ItemStack::Flag_Enchanted)) if ((mFilter & Filter_OnlyEnchanted) && !(item.mFlags & ItemStack::Flag_Enchanted))
return false; return false;
if ((mFilter & Filter_OnlyChargedSoulstones) && (base.getTypeName() != typeid(ESM::Miscellaneous).name() if ((mFilter & Filter_OnlyChargedSoulstones) && (base.getTypeName() != typeid(ESM::Miscellaneous).name()
|| base.getCellRef().getSoul() == "")) || base.getCellRef().getSoul() == "" || !MWBase::Environment::get().getWorld()->getStore().get<ESM::Creature>().search(base.getCellRef().getSoul())))
return false; return false;
if ((mFilter & Filter_OnlyRepairTools) && (base.getTypeName() != typeid(ESM::Repair).name())) if ((mFilter & Filter_OnlyRepairTools) && (base.getTypeName() != typeid(ESM::Repair).name()))
return false; return false;

@ -32,10 +32,14 @@ namespace
namespace MWGui namespace MWGui
{ {
SpellModel::SpellModel(const MWWorld::Ptr &actor, const std::string& filter)
: mActor(actor), mFilter(filter)
{
}
SpellModel::SpellModel(const MWWorld::Ptr &actor) SpellModel::SpellModel(const MWWorld::Ptr &actor)
: mActor(actor) : mActor(actor)
{ {
} }
void SpellModel::update() void SpellModel::update()
@ -48,12 +52,19 @@ namespace MWGui
const MWWorld::ESMStore &esmStore = const MWWorld::ESMStore &esmStore =
MWBase::Environment::get().getWorld()->getStore(); MWBase::Environment::get().getWorld()->getStore();
std::string filter = Misc::StringUtils::lowerCaseUtf8(mFilter);
for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it) for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it)
{ {
const ESM::Spell* spell = it->first; const ESM::Spell* spell = it->first;
if (spell->mData.mType != ESM::Spell::ST_Power && spell->mData.mType != ESM::Spell::ST_Spell) if (spell->mData.mType != ESM::Spell::ST_Power && spell->mData.mType != ESM::Spell::ST_Spell)
continue; continue;
std::string name = Misc::StringUtils::lowerCaseUtf8(spell->mName);
if (name.find(filter) == std::string::npos)
continue;
Spell newSpell; Spell newSpell;
newSpell.mName = spell->mName; newSpell.mName = spell->mName;
if (spell->mData.mType == ESM::Spell::ST_Spell) if (spell->mData.mType == ESM::Spell::ST_Spell)
@ -89,6 +100,11 @@ namespace MWGui
if (enchant->mData.mType != ESM::Enchantment::WhenUsed && enchant->mData.mType != ESM::Enchantment::CastOnce) if (enchant->mData.mType != ESM::Enchantment::WhenUsed && enchant->mData.mType != ESM::Enchantment::CastOnce)
continue; continue;
std::string name = Misc::StringUtils::lowerCaseUtf8(item.getClass().getName(item));
if (name.find(filter) == std::string::npos)
continue;
Spell newSpell; Spell newSpell;
newSpell.mItem = item; newSpell.mItem = item;
newSpell.mId = item.getCellRef().getRefId(); newSpell.mId = item.getCellRef().getRefId();

@ -35,6 +35,7 @@ namespace MWGui
class SpellModel class SpellModel
{ {
public: public:
SpellModel(const MWWorld::Ptr& actor, const std::string& filter);
SpellModel(const MWWorld::Ptr& actor); SpellModel(const MWWorld::Ptr& actor);
typedef int ModelIndex; typedef int ModelIndex;
@ -50,6 +51,8 @@ namespace MWGui
MWWorld::Ptr mActor; MWWorld::Ptr mActor;
std::vector<Spell> mSpells; std::vector<Spell> mSpells;
std::string mFilter;
}; };
} }

@ -2,6 +2,7 @@
#include <boost/format.hpp> #include <boost/format.hpp>
#include <MyGUI_EditBox.h>
#include <MyGUI_InputManager.h> #include <MyGUI_InputManager.h>
#include <components/settings/settings.hpp> #include <components/settings/settings.hpp>
@ -38,8 +39,12 @@ namespace MWGui
getWidget(mSpellView, "SpellView"); getWidget(mSpellView, "SpellView");
getWidget(mEffectBox, "EffectsBox"); getWidget(mEffectBox, "EffectsBox");
getWidget(mFilterEdit, "FilterEdit");
mFilterEdit->setUserString("IgnoreTabKey", "y");
mSpellView->eventSpellClicked += MyGUI::newDelegate(this, &SpellWindow::onModelIndexSelected); mSpellView->eventSpellClicked += MyGUI::newDelegate(this, &SpellWindow::onModelIndexSelected);
mFilterEdit->eventEditTextChange += MyGUI::newDelegate(this, &SpellWindow::onFilterChanged);
setCoord(498, 300, 302, 300); setCoord(498, 300, 302, 300);
} }
@ -64,6 +69,11 @@ namespace MWGui
void SpellWindow::onOpen() void SpellWindow::onOpen()
{ {
// Reset the filter focus when opening the window
MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget();
if (focus == mFilterEdit)
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(NULL);
updateSpells(); updateSpells();
} }
@ -82,7 +92,7 @@ namespace MWGui
{ {
mSpellIcons->updateWidgets(mEffectBox, false); mSpellIcons->updateWidgets(mEffectBox, false);
mSpellView->setModel(new SpellModel(MWMechanics::getPlayer())); mSpellView->setModel(new SpellModel(MWMechanics::getPlayer(), mFilterEdit->getCaption()));
} }
void SpellWindow::onEnchantedItemSelected(MWWorld::Ptr item, bool alreadyEquipped) void SpellWindow::onEnchantedItemSelected(MWWorld::Ptr item, bool alreadyEquipped)
@ -167,6 +177,11 @@ namespace MWGui
} }
} }
void SpellWindow::onFilterChanged(MyGUI::EditBox *sender)
{
mSpellView->setModel(new SpellModel(MWMechanics::getPlayer(), sender->getCaption()));
}
void SpellWindow::onSpellSelected(const std::string& spellId) void SpellWindow::onSpellSelected(const std::string& spellId)
{ {
MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::Ptr player = MWMechanics::getPlayer();
@ -202,7 +217,7 @@ namespace MWGui
if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead() || stats.getHitRecovery()) if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead() || stats.getHitRecovery())
return; return;
mSpellView->setModel(new SpellModel(MWMechanics::getPlayer())); mSpellView->setModel(new SpellModel(MWMechanics::getPlayer(), ""));
SpellModel::ModelIndex selected = 0; SpellModel::ModelIndex selected = 0;
for (SpellModel::ModelIndex i = 0; i<int(mSpellView->getModel()->getItemCount()); ++i) for (SpellModel::ModelIndex i = 0; i<int(mSpellView->getModel()->getItemCount()); ++i)

@ -32,6 +32,7 @@ namespace MWGui
void onEnchantedItemSelected(MWWorld::Ptr item, bool alreadyEquipped); void onEnchantedItemSelected(MWWorld::Ptr item, bool alreadyEquipped);
void onSpellSelected(const std::string& spellId); void onSpellSelected(const std::string& spellId);
void onModelIndexSelected(SpellModel::ModelIndex index); void onModelIndexSelected(SpellModel::ModelIndex index);
void onFilterChanged(MyGUI::EditBox *sender);
void onDeleteSpellAccept(); void onDeleteSpellAccept();
void askDeleteSpell(const std::string& spellId); void askDeleteSpell(const std::string& spellId);
@ -41,6 +42,7 @@ namespace MWGui
SpellView* mSpellView; SpellView* mSpellView;
SpellIcons* mSpellIcons; SpellIcons* mSpellIcons;
MyGUI::EditBox* mFilterEdit;
private: private:
float mUpdateTimer; float mUpdateTimer;

@ -311,8 +311,7 @@ namespace MWGui
// check if the player is attempting to sell back an item stolen from this actor // check if the player is attempting to sell back an item stolen from this actor
for (std::vector<ItemStack>::iterator it = merchantBought.begin(); it != merchantBought.end(); ++it) for (std::vector<ItemStack>::iterator it = merchantBought.begin(); it != merchantBought.end(); ++it)
{ {
if (MWBase::Environment::get().getMechanicsManager()->isItemStolenFrom(it->mBase.getCellRef().getRefId(), if (MWBase::Environment::get().getMechanicsManager()->isItemStolenFrom(it->mBase.getCellRef().getRefId(), mPtr))
mPtr.getCellRef().getRefId()))
{ {
std::string msg = gmst.find("sNotifyMessage49")->getString(); std::string msg = gmst.find("sNotifyMessage49")->getString();
if (msg.find("%s") != std::string::npos) if (msg.find("%s") != std::string::npos)

@ -4,6 +4,8 @@
#include <MyGUI_ScrollView.h> #include <MyGUI_ScrollView.h>
#include <MyGUI_Gui.h> #include <MyGUI_Gui.h>
#include <components/settings/settings.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
@ -72,7 +74,10 @@ namespace MWGui
MWWorld::ActionTeleport::getFollowersToTeleport(player, followers); MWWorld::ActionTeleport::getFollowersToTeleport(player, followers);
// Apply followers cost, in vanilla one follower travels for free // Apply followers cost, in vanilla one follower travels for free
price *= std::max(1, static_cast<int>(followers.size())); if (Settings::Manager::getBool("charge for every follower travelling", "Game"))
price *= 1 + static_cast<int>(followers.size());
else
price *= std::max(1, static_cast<int>(followers.size()));
MyGUI::Button* toAdd = mDestinationsView->createWidget<MyGUI::Button>("SandTextButton", 0, mCurrentY, 200, sLineHeight, MyGUI::Align::Default); MyGUI::Button* toAdd = mDestinationsView->createWidget<MyGUI::Button>("SandTextButton", 0, mCurrentY, 200, sLineHeight, MyGUI::Align::Default);
toAdd->setEnabled(price <= playerGold); toAdd->setEnabled(price <= playerGold);

@ -310,8 +310,10 @@ namespace MWGui
void WaitDialog::wakeUp () void WaitDialog::wakeUp ()
{ {
mSleeping = false; mSleeping = false;
mTimeAdvancer.stop(); if (mInterruptAt != -1)
stopWaiting(); onWaitingInterrupted();
else
stopWaiting();
} }
} }

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

Loading…
Cancel
Save