Merge branch 'master' into screenshot360

0.6.3
Miloslav Číž 6 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)
Aesylwinn
aegis
AHSauge
Aleksandar Jovanov
Alex Haddad (rainChu)
Alex McKibben
@ -36,12 +37,15 @@ Programmers
Britt Mathis (galdor557)
Capostrophic
cc9cii
Cédric Mocquillon
Chris Boyce (slothlife)
Chris Robinson (KittyCat)
Cory F. Cohen (cfcohen)
Cris Mihalache (Mirceam)
crussell187
DanielVukelich
darkf
David Cernat (davidcernat)
devnexen
Dieho
Dmitry Shkurskiy (endorph)
@ -59,6 +63,7 @@ Programmers
Evgeniy Mineev (sandstranger)
Federico Guerra (FedeWar)
Fil Krynicki (filkry)
Florian Weber (Florianjw)
Gašper Sedej
gugus/gus
Hallfaer Tuilinn
@ -155,6 +160,8 @@ Programmers
terrorfisch
thegriglat
Thomas Luppi (Digmaster)
tri4ng1e
unelsson
Will Herrmann (Thunderforge)
Tom Mason (wheybags)
Torben Leif Carrington (TorbenC)
@ -224,7 +231,7 @@ Artwork
Necrod - OpenMW Logo
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
---------------------

@ -1,5 +1,22 @@
#!/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
APPVEYOR=${APPVEYOR:-}
@ -216,6 +233,8 @@ case $VS_VERSION in
15|15.0|2017 )
GENERATOR="Visual Studio 15 2017"
TOOLSET="vc140"
TOOLSET_REAL="vc141"
MSVC_REAL_VER="15"
MSVC_VER="14"
MSVC_YEAR="2015"
MSVC_DISPLAY_YEAR="2017"
@ -224,6 +243,8 @@ case $VS_VERSION in
14|14.0|2015 )
GENERATOR="Visual Studio 14 2015"
TOOLSET="vc140"
TOOLSET_REAL="vc140"
MSVC_REAL_VER="14"
MSVC_VER="14"
MSVC_YEAR="2015"
MSVC_DISPLAY_YEAR="2015"
@ -232,6 +253,8 @@ case $VS_VERSION in
12|12.0|2013 )
GENERATOR="Visual Studio 12 2013"
TOOLSET="vc120"
TOOLSET_REAL="vc120"
MSVC_REAL_VER="12"
MSVC_VER="12"
MSVC_YEAR="2013"
MSVC_DISPLAY_YEAR="2013"
@ -303,25 +326,25 @@ if [ -z $SKIP_DOWNLOAD ]; then
# Boost
if [ -z $APPVEYOR ]; then
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"
fi
# Bullet
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"
# FFmpeg
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" \
"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"
# MyGUI
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"
# OpenAL
@ -331,7 +354,7 @@ if [ -z $SKIP_DOWNLOAD ]; then
# OSG
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"
# Qt
@ -343,9 +366,9 @@ if [ -z $SKIP_DOWNLOAD ]; then
fi
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" \
"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"
fi
@ -385,7 +408,7 @@ else
if [ $MSVC_VER -eq 12 ]; then
printf "Boost 1.58.0 AppVeyor... "
else
printf "Boost 1.60.0 AppVeyor... "
printf "Boost 1.67.0 AppVeyor... "
fi
fi
{
@ -408,14 +431,20 @@ fi
echo Done.
else
# 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"
else
BOOST_SDK="c:/Libraries/boost_1_60_0"
BOOST_SDK="c:/Libraries/boost_1_67_0"
fi
if [ $MSVC_REAL_VER -eq 15 ]; then
LIB_SUFFIX="1"
else
LIB_SUFFIX="0"
fi
add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \
-DBOOST_LIBRARYDIR="${BOOST_SDK}/lib${BITS}-msvc-${MSVC_VER}.0"
add_cmake_opts -DBoost_COMPILER="-${TOOLSET}"
-DBOOST_LIBRARYDIR="${BOOST_SDK}/lib${BITS}-msvc-${MSVC_VER}.${LIB_SUFFIX}"
add_cmake_opts -DBoost_COMPILER="-${TOOLSET_REAL}"
echo Done.
fi
@ -568,7 +597,7 @@ echo
if [ -z $APPVEYOR ]; then
printf "Qt 5.7.0... "
else
printf "Qt 5.7 AppVeyor... "
printf "Qt 5.10 AppVeyor... "
fi
{
if [ $BITS -eq 64 ]; then
@ -613,18 +642,28 @@ fi
SUFFIX=""
fi
add_runtime_dlls "$(pwd)/bin/lib"{EGL,GLESv2}${SUFFIX}.dll \
"$(pwd)/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${SUFFIX}.dll
add_runtime_dlls "$(pwd)/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${SUFFIX}.dll
add_qt_platform_dlls "$(pwd)/plugins/platforms/qwindows${SUFFIX}.dll"
echo Done.
else
QT_SDK="C:/Qt/5.7/msvc${MSVC_YEAR}${SUFFIX}"
QT_SDK="C:/Qt/5.10/msvc${MSVC_DISPLAY_YEAR}${SUFFIX}"
add_cmake_opts -DDESIRED_QT_VERSION=5 \
-DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" \
-DCMAKE_PREFIX_PATH="$QT_SDK"
if [ $CONFIGURATION == "Debug" ]; then
SUFFIX="d"
else
SUFFIX=""
fi
DIR=$(echo "${QT_SDK}" | sed "s,\\\\,/,g" | sed "s,\(.\):,/\\1,")
add_runtime_dlls "${DIR}/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${SUFFIX}.dll
add_qt_platform_dlls "${DIR}/plugins/platforms/qwindows${SUFFIX}.dll"
echo Done.
fi
}
@ -699,7 +738,7 @@ if [ ! -z $CI ]; then
fi
# NOTE: Disable this when/if we want to run test cases
if [ -z $CI ]; then
#if [ -z $CI ]; then
echo "- Copying Runtime DLLs..."
mkdir -p $BUILD_CONFIG
for DLL in $RUNTIME_DLLS; do
@ -731,7 +770,7 @@ if [ -z $CI ]; then
cp "$DLL" "${BUILD_CONFIG}/platforms"
done
echo
fi
#fi
if [ -z $VERBOSE ]; then
printf -- "- Configuring... "

@ -6,4 +6,6 @@ DATE=`date +'%d%m%Y'`
SHORT_COMMIT=`git rev-parse --short ${TRAVIS_COMMIT}`
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...")
set(OPENMW_VERSION_MAJOR 0)
set(OPENMW_VERSION_MINOR 43)
set(OPENMW_VERSION_MINOR 44)
set(OPENMW_VERSION_RELEASE 0)
set(OPENMW_VERSION_COMMITHASH "")
@ -135,6 +135,8 @@ if (WIN32)
endif()
# Dependencies
find_package(OpenGL REQUIRED)
if (USE_QT)
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)
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
if (UNIX AND NOT APPLE)
find_package (Threads)
@ -505,8 +501,8 @@ if(WIN32)
SET(CPACK_PACKAGE_DESCRIPTION_FILE "${OpenMW_SOURCE_DIR}/README.md")
SET(CPACK_NSIS_EXECUTABLES_DIRECTORY ".")
SET(CPACK_NSIS_DISPLAY_NAME "OpenMW ${OPENMW_VERSION}")
SET(CPACK_NSIS_HELP_LINK "http:\\\\\\\\www.openmw.org")
SET(CPACK_NSIS_URL_INFO_ABOUT "http:\\\\\\\\www.openmw.org")
SET(CPACK_NSIS_HELP_LINK "https:\\\\\\\\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_MUI_FINISHPAGE_RUN "openmw-launcher.exe")
SET(CPACK_NSIS_MUI_ICON "${OpenMW_SOURCE_DIR}/files/windows/openmw.ico")
@ -549,11 +545,6 @@ endif()
# Components
add_subdirectory (components)
# Plugins
#if (BUILD_MYGUI_PLUGIN)
# add_subdirectory(plugins/mygui_resource_plugin)
#endif()
# Apps and tools
if (BUILD_OPENMW)
add_subdirectory( apps/openmw )
@ -714,8 +705,7 @@ if (WIN32)
endif()
if (BUILD_OPENMW)
# Very specific issue this, only needed on 32-bit VS2015 during unity builds.
if (MSVC_VERSION GREATER 1800 AND CMAKE_SIZEOF_VOID_P EQUAL 4 AND OPENMW_UNITY_BUILD)
if (OPENMW_UNITY_BUILD)
set_target_properties(openmw PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD} /bigobj")
else()
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:
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.
Note:
- 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.
- Larger Features should have a discussion before you start implementing.
- In many cases, it's best to have a discussion about possible solutions before you jump into coding.
* 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.
* Often, it's best to start a discussion about possible solutions before you jump into coding, especially for larger features.
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
=======================
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
* Reasoning / motivation behind the change
* What testing you have carried out to verify the change
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.
* 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.
* 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"
=================================
@ -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.
* 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:
@ -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:
- 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.
- 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.
- 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.
* 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.
* 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.
* 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.
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
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
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.
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.
@ -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,
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
<http://www.gnu.org/licenses/>.
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
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
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
======
[![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)
* Website: http://www.openmw.org
* Website: https://www.openmw.org
* IRC: #openmw on irc.freenode.net
Font Licenses:
@ -30,8 +30,8 @@ Getting Started
* [Build from source](https://wiki.openmw.org/index.php?title=Development_Environment_Setup)
* [Testing the game](https://wiki.openmw.org/index.php?title=Testing)
* [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!
* [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)
* [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](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
-------------

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

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

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

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

@ -1,10 +1,18 @@
#include "advancedpage.hpp"
#include <components/files/configurationmanager.hpp>
Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent)
#include <components/config/gamesettings.hpp>
#include <components/config/launchersettings.hpp>
#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)
, mCfgMgr(cfg)
, mGameSettings(gameSettings)
, mEngineSettings(engineSettings)
{
setObjectName ("AdvancedPage");
@ -13,8 +21,54 @@ Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg, Settings:
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()
{
// 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
loadSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game");
loadSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game");
@ -23,6 +77,7 @@ bool Launcher::AdvancedPage::loadSettings()
loadSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game");
loadSettingBool(showMeleeInfoCheckBox, "show melee info", "Game");
loadSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game");
loadSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game");
// Expected values are (0, 1, 2, 3)
int showOwnedIndex = mEngineSettings.getInt("show owned", "Game");
@ -35,9 +90,11 @@ bool Launcher::AdvancedPage::loadSettings()
loadSettingBool(grabCursorCheckBox, "grab cursor", "Input");
loadSettingBool(toggleSneakCheckBox, "toggle sneak", "Input");
// Other Settings
// Saves Settings
loadSettingBool(timePlayedCheckbox, "timeplayed", "Saves");
maximumQuicksavesComboBox->setValue(mEngineSettings.getInt("max quicksaves", "Saves"));
// Other Settings
QString screenshotFormatString = QString::fromStdString(mEngineSettings.getString("screenshot format", "General")).toUpper();
if (screenshotFormatComboBox->findText(screenshotFormatString) == -1)
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
// 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
saveSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game");
saveSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game");
@ -59,6 +129,7 @@ void Launcher::AdvancedPage::saveSettings()
saveSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game");
saveSettingBool(showMeleeInfoCheckBox, "show melee info", "Game");
saveSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game");
saveSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game");
int showOwnedCurrentIndex = showOwnedComboBox->currentIndex();
if (showOwnedCurrentIndex != mEngineSettings.getInt("show owned", "Game"))
@ -69,9 +140,14 @@ void Launcher::AdvancedPage::saveSettings()
saveSettingBool(grabCursorCheckBox, "grab cursor", "Input");
saveSettingBool(toggleSneakCheckBox, "toggle sneak", "Input");
// Other Settings
// Saves Settings
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();
if (screenshotFormatString != mEngineSettings.getString("screenshot format", "General"))
mEngineSettings.setString("screenshot format", "General", screenshotFormatString);
@ -86,4 +162,9 @@ void Launcher::AdvancedPage::saveSettingBool(QCheckBox *checkbox, const std::str
bool cValue = checkbox->checkState();
if (cValue != mEngineSettings.getBool(setting, group))
mEngineSettings.setBool(setting, group, cValue);
}
void Launcher::AdvancedPage::slotLoadedCellsChanged(QStringList cellNames)
{
loadCellsForAutocomplete(cellNames);
}

@ -8,6 +8,7 @@
#include <components/settings/settings.hpp>
namespace Files { struct ConfigurationManager; }
namespace Config { class GameSettings; }
namespace Launcher
{
@ -16,15 +17,29 @@ namespace Launcher
Q_OBJECT
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();
void saveSettings();
public slots:
void slotLoadedCellsChanged(QStringList cellNames);
private slots:
void on_skipMenuCheckBox_stateChanged(int state);
void on_runScriptAfterStartupBrowseButton_clicked();
private:
Files::ConfigurationManager &mCfgMgr;
Config::GameSettings &mGameSettings;
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 saveSettingBool(QCheckBox *checkbox, const std::string& setting, const std::string& group);
};

@ -7,7 +7,10 @@
#include <QCheckBox>
#include <QMenu>
#include <QSortFilterProxyModel>
#include <thread>
#include <mutex>
#include <apps/launcher/utils/cellnameloader.hpp>
#include <components/files/configurationmanager.hpp>
#include <components/contentselector/model/esmfile.hpp>
@ -16,6 +19,7 @@
#include <components/config/gamesettings.hpp>
#include <components/config/launchersettings.hpp>
#include <iostream>
#include "utils/textinputdialog.hpp"
#include "utils/profilescombobox.hpp"
@ -40,6 +44,13 @@ Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config:
buildView();
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()
@ -142,6 +153,17 @@ void Launcher::DataFilesPage::saveSettings(const QString &profile)
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)
{
mLauncherSettings.removeContentList(profile);
@ -308,3 +330,31 @@ bool Launcher::DataFilesPage::showDeleteMessageBox (const QString &text)
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 <QFile>
#include <QStringList>
class QSortFilterProxyModel;
class QAbstractItemModel;
@ -41,8 +42,15 @@ namespace Launcher
void saveSettings(const QString &profile = "");
bool loadSettings();
/**
* Returns the file paths of all selected content files
* @return the file paths of all selected content files
*/
QStringList selectedFilePaths();
signals:
void signalProfileChanged (int index);
void signalLoadedCellsChanged(QStringList selectedFiles);
public slots:
void slotProfileChanged (int index);
@ -52,6 +60,7 @@ namespace Launcher
void slotProfileChangedByUser(const QString &previous, const QString &current);
void slotProfileRenamed(const QString &previous, const QString &current);
void slotProfileDeleted(const QString &item);
void slotAddonDataChanged ();
void updateOkButton(const QString &text);
@ -72,7 +81,7 @@ namespace Launcher
Config::LauncherSettings &mLauncherSettings;
QString mPreviousProfile;
QStringList previousSelectedFiles;
QString mDataLocal;
void setPluginsCheckstates(Qt::CheckState state);
@ -87,6 +96,7 @@ namespace Launcher
void addProfile (const QString &profile, bool setAsCurrent);
void checkForDefaultProfile();
void populateFileViews(const QString& contentModelName);
void reloadCells(QStringList selectedFiles);
class PathIterator
{

@ -119,7 +119,7 @@ void Launcher::MainDialog::createPages()
mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this);
mGraphicsPage = new GraphicsPage(mCfgMgr, mEngineSettings, 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
mPlayPage->setProfilesModel(mDataFilesPage->profilesModel());
@ -139,6 +139,8 @@ void Launcher::MainDialog::createPages()
connect(mPlayPage, SIGNAL(signalProfileChanged(int)), mDataFilesPage, SLOT(slotProfileChanged(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 <sstream>
#include <components/misc/stringops.hpp>
#include <components/esm/esmreader.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
@ -653,12 +654,6 @@ void MwIniImporter::setVerbose(bool 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 {
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();
for(int i=0; it != ini.end(); i++) {
archive = baseArchive;
archive.append(this->numberToString(i));
archive.append(std::to_string(i));
it = ini.find(archive);
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 {
std::vector<std::pair<std::time_t, std::string> > contentFiles;
void MwIniImporter::dependencySortStep(std::string& element, MwIniImporter::dependencyList& source, std::vector<std::string>& result)
{
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 gameFile("");
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
const boost::filesystem::path gameFilesDir(iniFilename.parent_path() /= "Data Files");
if (cfg.count("data-local"))
addPaths(dataPaths, cfg["data-local"]);
dataPaths.push_back(iniFilename.parent_path() /= "Data Files");
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.append(this->numberToString(i));
gameFile.append(std::to_string(i));
it = ini.find(gameFile);
if(it == ini.end()) {
if(it == ini.end())
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));
Misc::StringUtils::lowerCaseInPlace(filetype);
if(filetype.compare("esm") == 0 || filetype.compare("esp") == 0) {
boost::filesystem::path filepath(gameFilesDir);
filepath /= *entry;
contentFiles.push_back(std::make_pair(lastWriteTime(filepath, defaultTime), *entry));
if(filetype.compare("esm") == 0 || filetype.compare("esp") == 0)
{
bool found = false;
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.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());
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) {
@ -901,9 +1003,5 @@ std::time_t MwIniImporter::lastWriteTime(const boost::filesystem::path& filename
std::cout << "content file: " << resolved << " timestamp = (" << writeTime <<
") " << timeStrBuffer << std::endl;
}
else
{
std::cout << "content file: " << filename << " not found" << std::endl;
}
return writeTime;
}

@ -14,6 +14,7 @@ class MwIniImporter {
public:
typedef std::map<std::string, std::string> strmap;
typedef std::map<std::string, std::vector<std::string> > multistrmap;
typedef std::vector< std::pair< std::string, std::vector<std::string> > > dependencyList;
MwIniImporter();
void setInputEncoding(const ToUTF8::FromType& encoding);
@ -22,14 +23,19 @@ class MwIniImporter {
static multistrmap loadCfgFile(const boost::filesystem::path& filename);
void merge(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;
void importArchives(multistrmap &cfg, const multistrmap &ini) const;
static void writeToFile(std::ostream &out, const multistrmap &cfg);
static std::vector<std::string> dependencySort(MwIniImporter::dependencyList source);
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 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
static std::time_t lastWriteTime(const boost::filesystem::path& filename, std::time_t defaultTime);
@ -40,5 +46,4 @@ class MwIniImporter {
ToUTF8::FromType mEncoding;
};
#endif

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

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

@ -320,12 +320,13 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration,
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 (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*)),
this, SIGNAL (mergeDone (CSMDoc::Document*)));
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 (
&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;
}
void CSMDoc::Document::operationDone (int type, bool failed)
void CSMDoc::Document::operationDone2 (int type, bool failed)
{
if (type==CSMDoc::State_Saving && !failed)
mDirty = false;

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

@ -49,7 +49,7 @@ namespace CSMPrefs
ShortcutMap::iterator shortcutListIt = mWidgetShortcuts.find(widget);
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;
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).
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");
@ -230,6 +238,12 @@ void CSMPrefs::State::declare()
addValues (insertOutsideCell);
declareEnum ("outside-visible-drop", "Handling drops outside of visible cells", showAndInsert).
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");

@ -28,12 +28,13 @@ void CSMTools::ClassCheckStage::perform (int stage, CSMDoc::Messages& messages)
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())
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())
messages.push_back (std::make_pair (id, class_.mId + " has an empty description"));
// A playable class should have a 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
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);
// 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);
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);

@ -239,9 +239,7 @@ void CSMTools::ReferenceableCheckStage::bookCheck(
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
if (baseRecord.isDeleted())
{
return;
}
const ESM::Book& book = (dynamic_cast<const CSMWorld::Record<ESM::Book>& >(baseRecord)).get();
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Book, book.mId);
@ -260,9 +258,7 @@ void CSMTools::ReferenceableCheckStage::activatorCheck(
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
if (baseRecord.isDeleted())
{
return;
}
const ESM::Activator& activator = (dynamic_cast<const CSMWorld::Record<ESM::Activator>& >(baseRecord)).get();
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Activator, activator.mId);
@ -283,9 +279,7 @@ void CSMTools::ReferenceableCheckStage::potionCheck(
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
if (baseRecord.isDeleted())
{
return;
}
const ESM::Potion& potion = (dynamic_cast<const CSMWorld::Record<ESM::Potion>& >(baseRecord)).get();
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Potion, potion.mId);
@ -306,9 +300,7 @@ void CSMTools::ReferenceableCheckStage::apparatusCheck(
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
if (baseRecord.isDeleted())
{
return;
}
const ESM::Apparatus& apparatus = (dynamic_cast<const CSMWorld::Record<ESM::Apparatus>& >(baseRecord)).get();
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Apparatus, apparatus.mId);
@ -329,9 +321,7 @@ void CSMTools::ReferenceableCheckStage::armorCheck(
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
if (baseRecord.isDeleted())
{
return;
}
const ESM::Armor& armor = (dynamic_cast<const CSMWorld::Record<ESM::Armor>& >(baseRecord)).get();
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Armor, armor.mId);
@ -358,9 +348,7 @@ void CSMTools::ReferenceableCheckStage::clothingCheck(
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
if (baseRecord.isDeleted())
{
return;
}
const ESM::Clothing& clothing = (dynamic_cast<const CSMWorld::Record<ESM::Clothing>& >(baseRecord)).get();
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Clothing, clothing.mId);
@ -378,9 +366,7 @@ void CSMTools::ReferenceableCheckStage::containerCheck(
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
if (baseRecord.isDeleted())
{
return;
}
const ESM::Container& container = (dynamic_cast<const CSMWorld::Record<ESM::Container>& >(baseRecord)).get();
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Container, container.mId);
@ -512,9 +498,7 @@ void CSMTools::ReferenceableCheckStage::ingredientCheck(
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
if (baseRecord.isDeleted())
{
return;
}
const ESM::Ingredient& ingredient = (dynamic_cast<const CSMWorld::Record<ESM::Ingredient>& >(baseRecord)).get();
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"));
if (light.mData.mFlags & ESM::Light::Carry)
{
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
scriptCheck<ESM::Light>(light, messages, id.toString());
}
@ -596,9 +575,7 @@ void CSMTools::ReferenceableCheckStage::lockpickCheck(
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
if (baseRecord.isDeleted())
{
return;
}
const ESM::Lockpick& lockpick = (dynamic_cast<const CSMWorld::Record<ESM::Lockpick>& >(baseRecord)).get();
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Lockpick, lockpick.mId);
@ -619,9 +596,7 @@ void CSMTools::ReferenceableCheckStage::miscCheck(
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
if (baseRecord.isDeleted())
{
return;
}
const ESM::Miscellaneous& miscellaneous = (dynamic_cast<const CSMWorld::Record<ESM::Miscellaneous>& >(baseRecord)).get();
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();
CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Npc, npc.mId);
short level(npc.mNpdt52.mLevel);
char disposition(npc.mNpdt52.mDisposition);
char reputation(npc.mNpdt52.mReputation);
char rank(npc.mNpdt52.mRank);
short level(npc.mNpdt.mLevel);
char disposition(npc.mNpdt.mDisposition);
char reputation(npc.mNpdt.mReputation);
char rank(npc.mNpdt.mRank);
//Don't know what unknown is for
int gold(npc.mNpdt52.mGold);
int gold(npc.mNpdt.mGold);
//Detect if player is present
if (Misc::StringUtils::ciEqual(npc.mId, "player")) //Happy now, scrawl?
@ -663,36 +638,36 @@ void CSMTools::ReferenceableCheckStage::npcCheck (
return;
}
level = npc.mNpdt12.mLevel;
disposition = npc.mNpdt12.mDisposition;
reputation = npc.mNpdt12.mReputation;
rank = npc.mNpdt12.mRank;
gold = npc.mNpdt12.mGold;
level = npc.mNpdt.mLevel;
disposition = npc.mNpdt.mDisposition;
reputation = npc.mNpdt.mReputation;
rank = npc.mNpdt.mRank;
gold = npc.mNpdt.mGold;
}
else
{
if (npc.mNpdt52.mAgility == 0)
if (npc.mNpdt.mAgility == 0)
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"));
if (npc.mNpdt52.mIntelligence == 0)
if (npc.mNpdt.mIntelligence == 0)
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"));
if (npc.mNpdt52.mPersonality == 0)
if (npc.mNpdt.mPersonality == 0)
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"));
if (npc.mNpdt52.mSpeed == 0)
if (npc.mNpdt.mSpeed == 0)
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"));
}
@ -706,22 +681,14 @@ void CSMTools::ReferenceableCheckStage::npcCheck (
messages.push_back (std::make_pair (id, npc.mId + " has any empty name"));
if (npc.mClass.empty())
{
messages.push_back (std::make_pair (id, npc.mId + " has any empty class"));
}
messages.push_back (std::make_pair (id, npc.mId + " has an empty class"));
else if (mClasses.searchId (npc.mClass) == -1)
{
messages.push_back (std::make_pair (id, npc.mId + " has invalid class"));
}
if (npc.mRace.empty())
{
messages.push_back (std::make_pair (id, npc.mId + " has any empty race"));
}
messages.push_back (std::make_pair (id, npc.mId + " has an empty race"));
else if (mRaces.searchId (npc.mRace) == -1)
{
messages.push_back (std::make_pair (id, npc.mId + " has invalid race"));
}
if (disposition < 0)
messages.push_back (std::make_pair (id, npc.mId + " has negative disposition"));
@ -823,7 +790,7 @@ void CSMTools::ReferenceableCheckStage::weaponCheck(
{
//checking of health
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)
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);
if (baseRecord.isDeleted())
{
return;
}
const ESM::Probe& probe = (dynamic_cast<const CSMWorld::Record<ESM::Probe>& >(baseRecord)).get();
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);
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
}

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

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

@ -1371,7 +1371,7 @@ namespace CSMWorld
RotColumn (ESM::Position ESXRecordT::* position, int index, bool door)
: Column<ESXRecordT> (
(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
{

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

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

@ -962,6 +962,29 @@ int CSMWorld::Data::startLoading (const boost::filesystem::path& path, bool base
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)
{
if (!mReader)
@ -983,6 +1006,9 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages)
mReader = 0;
mDialogue = 0;
loadFallbackEntries();
return true;
}

@ -144,6 +144,8 @@ namespace CSMWorld
static int count (RecordBase::State state, const CollectionBase& collection);
void loadFallbackEntries();
public:
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
{
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);
}
}

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

@ -184,11 +184,11 @@ CSMWorld::RefIdCollection::RefIdCollection()
mColumns.back().addColumn(
new RefIdColumn (Columns::ColumnId_PosZ, CSMWorld::ColumnBase::Display_Float));
mColumns.back().addColumn(
new RefIdColumn (Columns::ColumnId_RotX, CSMWorld::ColumnBase::Display_Float));
new RefIdColumn (Columns::ColumnId_RotX, CSMWorld::ColumnBase::Display_Double));
mColumns.back().addColumn(
new RefIdColumn (Columns::ColumnId_RotY, CSMWorld::ColumnBase::Display_Float));
new RefIdColumn (Columns::ColumnId_RotY, CSMWorld::ColumnBase::Display_Double));
mColumns.back().addColumn(
new RefIdColumn (Columns::ColumnId_RotZ, CSMWorld::ColumnBase::Display_Float));
new RefIdColumn (Columns::ColumnId_RotZ, CSMWorld::ColumnBase::Display_Double));
// Nested table
mColumns.push_back(RefIdColumn (Columns::ColumnId_AiPackageList,
@ -331,7 +331,7 @@ CSMWorld::RefIdCollection::RefIdCollection()
creatureColumns.mType = &mColumns.back();
mColumns.push_back (RefIdColumn (Columns::ColumnId_Scale, ColumnBase::Display_Float));
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();
static const struct

@ -64,21 +64,21 @@ namespace
static const TypeData sIdArg[] =
{
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Global, "Global Variable", ":./globvar.png" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Gmst, "Game Setting", ":./GMST.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_Skill, "Skill", ":./skill.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_Race, "Race", ":./race.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_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_Spell, "Spell", ":./spell.png" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Topic, "Topic", 0 },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Journal, "Journal", 0 },
{ CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_TopicInfo, "TopicInfo", 0 },
{ CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_JournalInfo, "JournalInfo", 0 },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Topic, "Topic", ":./dialogue-topics.png" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Journal, "Journal", ":./journal-topics.png" },
{ CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_TopicInfo, "TopicInfo", ":./dialogue-topic-infos.png" },
{ 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_Missing, "Cell", ":./cell.png" },
{ 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_Ingredient, "Ingredient", ":./ingredient.png" },
{ 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,
"Item Levelled List", ":./leveled-item.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_Collection, CSMWorld::UniversalId::Type_Scene, "Scene", 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_BodyPart, "Body Part", 0 },
{ CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Mesh, "Mesh", 0 },
{ CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Icon, "Icon", 0 },
{ CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Music, "Music", 0 },
{ CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_SoundRes, "Sound File", 0 },
{ CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Texture, "Texture", 0 },
{ CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Video, "Video", 0 },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Enchantment, "Enchantment", ":./enchantment.png" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_BodyPart, "Body Part", ":./body-part.png" },
{ CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Mesh, "Mesh", ":resources-mesh"},
{ CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Icon, "Icon", ":resources-icon"},
{ CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Music, "Music", ":resources-music" },
{ CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_SoundRes, "Sound File", ":resources-sound" },
{ CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Texture, "Texture", ":resources-texture"},
{ 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_SoundGen, "Sound Generator", 0 },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_MagicEffect, "Magic Effect", 0 },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Land, "Land", 0 },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_LandTexture, "LandTexture", 0 },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Pathgrid, "Pathgrid", 0 },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_SoundGen, "Sound Generator", ":./sound-generator.png" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_MagicEffect, "Magic Effect", ":./magic-effect.png" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Land, "Land", ":./land-heightmap.png" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_LandTexture, "LandTexture", ":./land-texture.png" },
{ 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_MetaData, "Meta Data", 0 },

@ -167,7 +167,7 @@ void CSVDoc::Loader::loadingStopped (CSMDoc::Document *document, bool completed,
delete iter->second;
mDocuments.erase (iter);
}
else if (!completed && !error.empty())
else
{
iter->second->abort (error);
// 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
/// 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;
font.setPointSize (12);

@ -92,7 +92,7 @@ osg::Vec3f CSVRender::InstanceMode::getScreenCoords(const osg::Vec3f& pos)
}
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),
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->addButton (new InstanceMoveMode (this), "move");
mSubMode->addButton (":placeholder", "rotate",
mSubMode->addButton (":scenetoolbar/transform-rotate", "rotate",
"Rotate selected instances"
"<ul><li>Use {scene-edit-primary} to rotate instances freely</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>"
"</ul>"
"<font color=Red>Grid rotate not implemented yet</font color>");
mSubMode->addButton (":placeholder", "scale",
mSubMode->addButton (":scenetoolbar/transform-scale", "scale",
"Scale selected instances"
"<ul><li>Use {scene-edit-primary} to scale instances freely</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)
{
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
if (mode=="Discard")
@ -585,7 +585,7 @@ void CSVRender::InstanceMode::dropEvent (QDropEvent* event)
{
// target cell exists, but is not shown
std::string mode =
CSMPrefs::get()["Scene Drops"]["outside-visible-drop"].toString();
CSMPrefs::get()["3D Scene Editing"]["outside-visible-drop"].toString();
if (mode=="Discard")
return;

@ -2,7 +2,7 @@
#include "instancemovemode.hpp"
CSVRender::InstanceMoveMode::InstanceMoveMode (QWidget *parent)
: ModeButton (QIcon (QPixmap (":placeholder")),
: ModeButton (QIcon (QPixmap (":scenetoolbar/transform-move")),
"Move selected instances"
"<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>"

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

@ -13,7 +13,7 @@ namespace CSVRender
, mWorldspaceWidget(worldspaceWidget)
, mInteractionMask(interactionMask)
{
addButton(":placeholder", "cube-centre",
addButton(":scenetoolbar/selection-mode-cube", "cube-centre",
"Centred cube"
"<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>"
@ -22,7 +22,7 @@ namespace CSVRender
"starting on an instance will have the same effect</li>"
"</ul>"
"<font color=Red>Not implemented yet</font color>");
addButton(":placeholder", "cube-corner",
addButton(":scenetoolbar/selection-mode-cube-corner", "cube-corner",
"Cube corner to corner"
"<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>"
@ -31,7 +31,7 @@ namespace CSVRender
"starting on an instance will have the same effect</li>"
"</ul>"
"<font color=Red>Not implemented yet</font color>");
addButton(":placeholder", "sphere",
addButton(":scenetoolbar/selection-mode-cube-sphere", "sphere",
"Centred sphere"
"<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>"

@ -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
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 };
return hit;
@ -648,6 +648,12 @@ void CSVRender::WorldspaceWidget::mouseMoveEvent (QMouseEvent *event)
mDragX = event->posF().x();
mDragY = height() - event->posF().y();
#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

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

@ -3,11 +3,15 @@
#include <QVBoxLayout>
#include "../../model/doc/document.hpp"
#include "../../model/doc/state.hpp"
#include "../../model/tools/search.hpp"
#include "../../model/tools/reportmodel.hpp"
#include "../../model/world/idtablebase.hpp"
#include "../../model/prefs/state.hpp"
#include "../world/tablebottombox.hpp"
#include "../world/creator.hpp"
#include "reporttable.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 (mBottom =
new CSVWorld::TableBottomBox (CSVWorld::NullCreatorFactory(), document, id, this), 0);
QWidget *widget = new QWidget;
widget->setLayout (layout);
@ -93,6 +100,15 @@ CSVTools::SearchSubView::SearchSubView (const CSMWorld::UniversalId& id, CSMDoc:
this, SLOT (startSearch (const CSMTools::Search&)));
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)
@ -101,6 +117,11 @@ void CSVTools::SearchSubView::setEditLock (bool locked)
mSearchBox.setEditLock (locked);
}
void CSVTools::SearchSubView::setStatusBar (bool show)
{
mBottom->setStatusBar(show);
}
void CSVTools::SearchSubView::stateChanged (int state, CSMDoc::Document *document)
{
mSearchBox.setSearchMode (!(state & CSMDoc::State_Searching));
@ -126,3 +147,17 @@ void CSVTools::SearchSubView::replaceAllRequest()
{
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;
}
namespace CSVWorld
{
class TableBottomBox;
}
namespace CSVTools
{
class ReportTable;
@ -28,6 +33,7 @@ namespace CSVTools
CSMDoc::Document& mDocument;
CSMTools::Search mSearch;
bool mLocked;
CSVWorld::TableBottomBox *mBottom;
private:
@ -43,6 +49,8 @@ namespace CSVTools
virtual void setEditLock (bool locked);
virtual void setStatusBar (bool show);
private slots:
void stateChanged (int state, CSMDoc::Document *document);
@ -52,6 +60,10 @@ namespace CSVTools
void replaceRequest();
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
mPrevButton = new QToolButton (this);
mPrevButton->setIcon(QIcon(":/go-previous.png"));
mPrevButton->setIcon(QIcon(":record-previous"));
mPrevButton->setToolTip ("Switch to previous record");
buttonsLayout->addWidget (mPrevButton, 0);
mNextButton = new QToolButton (this);
mNextButton->setIcon(QIcon(":/go-next.png"));
mNextButton->setIcon(QIcon(":/record-next"));
mNextButton->setToolTip ("Switch to next record");
buttonsLayout->addWidget (mNextButton, 1);
@ -72,7 +72,7 @@ CSVWorld::RecordButtonBar::RecordButtonBar (const CSMWorld::UniversalId& id,
if (mTable.getFeatures() & CSMWorld::IdTable::Feature_Preview)
{
QToolButton* previewButton = new QToolButton (this);
previewButton->setIcon(QIcon(":/edit-preview.png"));
previewButton->setIcon(QIcon(":edit-preview"));
previewButton->setToolTip ("Open a preview of this record");
buttonsLayout->addWidget(previewButton);
connect (previewButton, SIGNAL(clicked()), this, SIGNAL (showPreview()));
@ -89,22 +89,22 @@ CSVWorld::RecordButtonBar::RecordButtonBar (const CSMWorld::UniversalId& id,
// right section
mCloneButton = new QToolButton (this);
mCloneButton->setIcon(QIcon(":/edit-clone.png"));
mCloneButton->setIcon(QIcon(":edit-clone"));
mCloneButton->setToolTip ("Clone record");
buttonsLayout->addWidget(mCloneButton);
mAddButton = new QToolButton (this);
mAddButton->setIcon(QIcon(":/add.png"));
mAddButton->setIcon(QIcon(":edit-add"));
mAddButton->setToolTip ("Add new record");
buttonsLayout->addWidget(mAddButton);
mDeleteButton = new QToolButton (this);
mDeleteButton->setIcon(QIcon(":/edit-delete.png"));
mDeleteButton->setIcon(QIcon(":edit-delete"));
mDeleteButton->setToolTip ("Delete record");
buttonsLayout->addWidget(mDeleteButton);
mRevertButton = new QToolButton (this);
mRevertButton->setIcon(QIcon(":/edit-undo.png"));
mRevertButton->setIcon(QIcon(":edit-undo"));
mRevertButton->setToolTip ("Revert record");
buttonsLayout->addWidget(mRevertButton);

@ -27,7 +27,7 @@ CSVWorld::RecordStatusDelegateFactory::RecordStatusDelegateFactory()
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)

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

@ -764,10 +764,8 @@ std::vector< CSMWorld::UniversalId > CSVWorld::Table::getDraggedRecords() const
QModelIndexList selectedRows = selectionModel()->selectedRows();
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()));
}
return idToDrag;
}

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

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

@ -233,6 +233,15 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO
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:
{
QPlainTextEdit *edit = new QPlainTextEdit(parent);

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

@ -35,28 +35,11 @@ int Java_org_libsdl_app_SDLActivity_isMouseShown(JNIEnv *env, jclass cls, jobjec
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);
SDL_Android_Init(env, cls);
SDL_SetMainReady();
// 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");
/* Run the application code! */
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;
return 0;
}

@ -39,6 +39,7 @@ namespace MWBase
class ResponseCallback
{
public:
virtual ~ResponseCallback() = default;
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;
/// 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 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,
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 void breakInvisibility (const MWWorld::Ptr& actor) = 0;
// Are we in an exterior or pseudo-exterior cell and it's night?
virtual bool isDark() const = 0;
// Allow NPCs to use torches?
virtual bool useTorches() const = 0;
virtual bool findInteriorPositionInWorldSpace(const MWWorld::CellStore* cell, osg::Vec3f& result) = 0;
@ -563,6 +565,8 @@ namespace MWBase
virtual bool isPlayerInJail() const = 0;
virtual void rotateWorldObject (const MWWorld::Ptr& ptr, osg::Quat rotate) = 0;
/// Return terrain height at \a worldPos position.
virtual float getTerrainHeightAt(const osg::Vec3f& worldPos) const = 0;

@ -139,24 +139,21 @@ namespace MWClass
const std::string trapActivationSound = "Disarm Trap Fail";
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 isTrapped = !ptr.getCellRef().getTrap().empty();
bool hasKey = false;
std::string keyName;
// make key id lowercase
std::string keyId = ptr.getCellRef().getKey();
Misc::StringUtils::lowerCaseInPlace(keyId);
for (MWWorld::ConstContainerStoreIterator it = invStore.cbegin(); it != invStore.cend(); ++it)
const std::string keyId = ptr.getCellRef().getKey();
if (!keyId.empty())
{
std::string refId = it->getCellRef().getRefId();
Misc::StringUtils::lowerCaseInPlace(refId);
if (refId == keyId)
MWWorld::Ptr keyPtr = invStore.search(keyId);
if (!keyPtr.isEmpty())
{
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 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 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
}
// make key id lowercase
std::string keyId = ptr.getCellRef().getKey();
const std::string keyId = ptr.getCellRef().getKey();
if (!keyId.empty())
{
Misc::StringUtils::lowerCaseInPlace(keyId);
for (MWWorld::ConstContainerStoreIterator it = invStore.cbegin(); it != invStore.cend(); ++it)
MWWorld::Ptr keyPtr = invStore.search(keyId);
if (!keyPtr.isEmpty())
{
std::string refId = it->getCellRef().getRefId();
Misc::StringUtils::lowerCaseInPlace(refId);
if (refId == keyId)
{
hasKey = true;
keyName = it->getClass().getName(*it);
break;
}
hasKey = true;
keyName = keyPtr.getClass().getName(keyPtr);
}
}

@ -233,24 +233,6 @@ namespace MWClass
if (!(ref->mBase->mData.mFlags & ESM::Light::Carry))
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,"");
}

@ -1,6 +1,7 @@
#include "misc.hpp"
#include <components/esm/loadmisc.hpp>
#include <components/settings/settings.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
@ -83,8 +84,24 @@ namespace MWClass
if (ptr.getCellRef().getSoul() != "")
{
const ESM::Creature *creature = MWBase::Environment::get().getWorld()->getStore().get<ESM::Creature>().find(ref->mRef.getSoul());
value *= creature->mData.mSoul;
const ESM::Creature *creature = MWBase::Environment::get().getWorld()->getStore().get<ESM::Creature>().search(ref->mRef.getSoul());
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;
@ -148,8 +165,9 @@ namespace MWClass
if (ref->mRef.getSoul() != "")
{
const ESM::Creature *creature = store.get<ESM::Creature>().find(ref->mRef.getSoul());
info.caption += " (" + creature->mName + ")";
const ESM::Creature *creature = store.get<ESM::Creature>().search(ref->mRef.getSoul());
if (creature)
info.caption += " (" + creature->mName + ")";
}
std::string text;
@ -210,7 +228,7 @@ namespace MWClass
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());
else
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionSoulgem(ptr));

@ -312,40 +312,40 @@ namespace MWClass
int gold=0;
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)
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::Intelligence, ref->mBase->mNpdt52.mIntelligence);
data->mNpcStats.setAttribute(ESM::Attribute::Willpower, ref->mBase->mNpdt52.mWillpower);
data->mNpcStats.setAttribute(ESM::Attribute::Agility, ref->mBase->mNpdt52.mAgility);
data->mNpcStats.setAttribute(ESM::Attribute::Speed, ref->mBase->mNpdt52.mSpeed);
data->mNpcStats.setAttribute(ESM::Attribute::Endurance, ref->mBase->mNpdt52.mEndurance);
data->mNpcStats.setAttribute(ESM::Attribute::Personality, ref->mBase->mNpdt52.mPersonality);
data->mNpcStats.setAttribute(ESM::Attribute::Luck, ref->mBase->mNpdt52.mLuck);
data->mNpcStats.setAttribute(ESM::Attribute::Strength, ref->mBase->mNpdt.mStrength);
data->mNpcStats.setAttribute(ESM::Attribute::Intelligence, ref->mBase->mNpdt.mIntelligence);
data->mNpcStats.setAttribute(ESM::Attribute::Willpower, ref->mBase->mNpdt.mWillpower);
data->mNpcStats.setAttribute(ESM::Attribute::Agility, ref->mBase->mNpdt.mAgility);
data->mNpcStats.setAttribute(ESM::Attribute::Speed, ref->mBase->mNpdt.mSpeed);
data->mNpcStats.setAttribute(ESM::Attribute::Endurance, ref->mBase->mNpdt.mEndurance);
data->mNpcStats.setAttribute(ESM::Attribute::Personality, ref->mBase->mNpdt.mPersonality);
data->mNpcStats.setAttribute(ESM::Attribute::Luck, ref->mBase->mNpdt.mLuck);
data->mNpcStats.setHealth (ref->mBase->mNpdt52.mHealth);
data->mNpcStats.setMagicka (ref->mBase->mNpdt52.mMana);
data->mNpcStats.setFatigue (ref->mBase->mNpdt52.mFatigue);
data->mNpcStats.setHealth (ref->mBase->mNpdt.mHealth);
data->mNpcStats.setMagicka (ref->mBase->mNpdt.mMana);
data->mNpcStats.setFatigue (ref->mBase->mNpdt.mFatigue);
data->mNpcStats.setLevel(ref->mBase->mNpdt52.mLevel);
data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt52.mDisposition);
data->mNpcStats.setReputation(ref->mBase->mNpdt52.mReputation);
data->mNpcStats.setLevel(ref->mBase->mNpdt.mLevel);
data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt.mDisposition);
data->mNpcStats.setReputation(ref->mBase->mNpdt.mReputation);
data->mNpcStats.setNeedRecalcDynamicStats(false);
}
else
{
gold = ref->mBase->mNpdt12.mGold;
gold = ref->mBase->mNpdt.mGold;
for (int i=0; i<3; ++i)
data->mNpcStats.setDynamic (i, 10);
data->mNpcStats.setLevel(ref->mBase->mNpdt12.mLevel);
data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt12.mDisposition);
data->mNpcStats.setReputation(ref->mBase->mNpdt12.mReputation);
data->mNpcStats.setLevel(ref->mBase->mNpdt.mLevel);
data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt.mDisposition);
data->mNpcStats.setReputation(ref->mBase->mNpdt.mReputation);
autoCalculateAttributes(ref->mBase, data->mNpcStats);
autoCalculateSkills(ref->mBase, data->mNpcStats, ptr);
@ -406,7 +406,7 @@ namespace MWClass
// store
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 float encumbranceTerm = gmst.fJumpEncumbranceBase->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 b = 0.0f;
@ -1327,10 +1327,7 @@ namespace MWClass
int Npc::getBaseGold(const MWWorld::ConstPtr& ptr) const
{
const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
return ref->mBase->mNpdt52.mGold;
else
return ref->mBase->mNpdt12.mGold;
return ref->mBase->mNpdt.mGold;
}
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
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}");
}

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

@ -892,7 +892,7 @@ protected:
public:
typedef TypesetBookImpl::StyleImpl Style;
typedef std::map <TextFormat::Id, TextFormat*> ActiveTextFormats;
typedef std::map <TextFormat::Id, std::unique_ptr<TextFormat>> ActiveTextFormats;
int mViewTop;
int mViewBottom;
@ -1048,7 +1048,7 @@ public:
{
if (mNode != NULL)
i->second->destroyDrawItem (mNode);
delete i->second;
i->second.reset();
}
mActiveTextFormats.clear ();
@ -1115,11 +1115,11 @@ public:
if (j == this_->mActiveTextFormats.end ())
{
TextFormat * textFormat = new TextFormat (Font, this_);
std::unique_ptr<TextFormat> textFormat(new TextFormat (Font, this_));
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;

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

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

@ -31,6 +31,14 @@ namespace MWGui
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("p", Event_PTag);
registerTag("img", Event_ImgTag);

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

@ -16,8 +16,13 @@ namespace
{
if (count == 1)
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
return MyGUI::utility::toString(count);
}

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

@ -7,7 +7,6 @@
#include <components/translation/translation.hpp>
#include <components/misc/stringops.hpp>
#include <components/misc/utf8stream.hpp>
#include "../mwbase/world.hpp"
#include "../mwbase/journal.hpp"
@ -307,39 +306,22 @@ struct JournalViewModelImpl : JournalViewModel
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();
for (MWBase::Journal::TTopicIter i = journal->topicBegin (); i != journal->topicEnd (); ++i)
{
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;
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>
{
MWDialogue::Topic const & mTopic;

@ -6,6 +6,8 @@
#include <functional>
#include <stdint.h>
#include <components/misc/utf8stream.hpp>
namespace MWGui
{
/// View-Model for the journal GUI
@ -76,7 +78,7 @@ namespace MWGui
virtual void visitTopicName (TopicId topicId, std::function <void (Utf8Span)> visitor) const = 0;
/// 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
virtual void visitTopicEntries (TopicId topicId, std::function <void (TopicEntry const &)> visitor) const = 0;

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

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

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

@ -81,6 +81,47 @@ namespace MWGui
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)
{
key->clearUserStrings();
@ -122,12 +163,10 @@ namespace MWGui
assert(index != -1);
mSelectedIndex = index;
{
// open assign dialog
if (!mAssignDialog)
mAssignDialog = new QuickKeysMenuAssign(this);
mAssignDialog->setVisible (true);
}
// open assign dialog
if (!mAssignDialog)
mAssignDialog = new QuickKeysMenuAssign(this);
mAssignDialog->setVisible (true);
}
void QuickKeysMenu::onOkButtonClicked (MyGUI::Widget *sender)
@ -296,21 +335,16 @@ namespace MWGui
if (type == Type_Item || type == Type_MagicItem)
{
MWWorld::Ptr item = *button->getUserData<MWWorld::Ptr>();
// make sure the item is available
if (item.getRefData ().getCount() < 1)
// 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();
for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it)
{
if (Misc::StringUtils::ciEqual(it->getCellRef().getRefId(), id))
{
item = *it;
button->setUserData(MWWorld::Ptr(item));
break;
}
}
item = store.findReplacement(id);
button->setUserData(MWWorld::Ptr(item));
if (item.getRefData().getCount() < 1)
{
@ -330,7 +364,12 @@ namespace MWGui
MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player);
MWMechanics::Spells& spells = stats.getSpells();
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;
}
store.setSelectedEnchantItem(store.end());
MWBase::Environment::get().getWindowManager()->setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, player)));
MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Spell);
@ -493,6 +532,9 @@ namespace MWGui
ESM::QuickKeys keys;
keys.load(reader);
MWWorld::Ptr player = MWMechanics::getPlayer();
MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player);
int i=0;
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:
{
// Find the item by id
MWWorld::Ptr player = MWMechanics::getPlayer();
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;
}
}
}
MWWorld::Ptr item = store.findReplacement(id);
if (item.isEmpty())
unassign(button, i);

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

@ -15,11 +15,8 @@ namespace MWGui
// check if count of the reference has become 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()
{
updateControlsBox ();
updateControlsBox();
resetScrollbars();
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mOkButton);
}
void SettingsWindow::onWindowResize(MyGUI::Window *_sender)

@ -208,7 +208,7 @@ namespace MWGui
if ((mFilter & Filter_OnlyEnchanted) && !(item.mFlags & ItemStack::Flag_Enchanted))
return false;
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;
if ((mFilter & Filter_OnlyRepairTools) && (base.getTypeName() != typeid(ESM::Repair).name()))
return false;

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

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

@ -2,6 +2,7 @@
#include <boost/format.hpp>
#include <MyGUI_EditBox.h>
#include <MyGUI_InputManager.h>
#include <components/settings/settings.hpp>
@ -38,8 +39,12 @@ namespace MWGui
getWidget(mSpellView, "SpellView");
getWidget(mEffectBox, "EffectsBox");
getWidget(mFilterEdit, "FilterEdit");
mFilterEdit->setUserString("IgnoreTabKey", "y");
mSpellView->eventSpellClicked += MyGUI::newDelegate(this, &SpellWindow::onModelIndexSelected);
mFilterEdit->eventEditTextChange += MyGUI::newDelegate(this, &SpellWindow::onFilterChanged);
setCoord(498, 300, 302, 300);
}
@ -64,6 +69,11 @@ namespace MWGui
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();
}
@ -82,7 +92,7 @@ namespace MWGui
{
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)
@ -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)
{
MWWorld::Ptr player = MWMechanics::getPlayer();
@ -202,7 +217,7 @@ namespace MWGui
if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead() || stats.getHitRecovery())
return;
mSpellView->setModel(new SpellModel(MWMechanics::getPlayer()));
mSpellView->setModel(new SpellModel(MWMechanics::getPlayer(), ""));
SpellModel::ModelIndex selected = 0;
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 onSpellSelected(const std::string& spellId);
void onModelIndexSelected(SpellModel::ModelIndex index);
void onFilterChanged(MyGUI::EditBox *sender);
void onDeleteSpellAccept();
void askDeleteSpell(const std::string& spellId);
@ -41,6 +42,7 @@ namespace MWGui
SpellView* mSpellView;
SpellIcons* mSpellIcons;
MyGUI::EditBox* mFilterEdit;
private:
float mUpdateTimer;

@ -311,8 +311,7 @@ namespace MWGui
// 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)
{
if (MWBase::Environment::get().getMechanicsManager()->isItemStolenFrom(it->mBase.getCellRef().getRefId(),
mPtr.getCellRef().getRefId()))
if (MWBase::Environment::get().getMechanicsManager()->isItemStolenFrom(it->mBase.getCellRef().getRefId(), mPtr))
{
std::string msg = gmst.find("sNotifyMessage49")->getString();
if (msg.find("%s") != std::string::npos)

@ -4,6 +4,8 @@
#include <MyGUI_ScrollView.h>
#include <MyGUI_Gui.h>
#include <components/settings/settings.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/windowmanager.hpp"
@ -72,7 +74,10 @@ namespace MWGui
MWWorld::ActionTeleport::getFollowersToTeleport(player, followers);
// 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);
toAdd->setEnabled(price <= playerGold);

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

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

Loading…
Cancel
Save