diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 000000000..9f5442ae4
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -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
diff --git a/AUTHORS.md b/AUTHORS.md
index 17f11730d..b13953824 100644
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -37,6 +37,7 @@ Programmers
Britt Mathis (galdor557)
Capostrophic
cc9cii
+ Cédric Mocquillon
Chris Boyce (slothlife)
Chris Robinson (KittyCat)
Cory F. Cohen (cfcohen)
@@ -62,6 +63,7 @@ Programmers
Evgeniy Mineev (sandstranger)
Federico Guerra (FedeWar)
Fil Krynicki (filkry)
+ Florian Weber (Florianjw)
Gašper Sedej
gugus/gus
Hallfaer Tuilinn
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ac2a5472e..fc150f344 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,155 @@
+0.45.0
+------
+
+ Bug #1990: Sunrise/sunset not set correct
+ Bug #2222: Fatigue's effect on selling price is backwards
+ Bug #2326: After a bound item expires the last equipped item of that type is not automatically re-equipped
+ Bug #2562: Forcing AI to activate a teleport door sometimes causes a crash
+ Bug #2772: Non-existing class or faction freezes the game
+ Bug #2835: Player able to slowly move when overencumbered
+ Bug #2852: No murder bounty when a player follower commits murder
+ Bug #2862: [macOS] Can't quit launcher using Command-Q or OpenMW->Quit
+ Bug #2971: Compiler did not reject lines with naked expressions beginning with x.y
+ Bug #3374: Touch spells not hitting kwama foragers
+ Bug #3486: [Mod] NPC Commands does not work
+ Bug #3591: Angled hit distance too low
+ Bug #3629: DB assassin attack never triggers creature spawning
+ Bug #3876: Landscape texture painting is misaligned
+ Bug #3897: Have Goodbye give all choices the effects of Goodbye
+ Bug #3911: [macOS] Typing in the "Content List name" dialog box produces double characters
+ Bug #3993: Terrain texture blending map is not upscaled
+ Bug #3997: Almalexia doesn't pace
+ Bug #4036: Weird behaviour of AI packages if package target has non-unique ID
+ Bug #4047: OpenMW not reporting its version number in MacOS; OpenMW-CS not doing it fully
+ Bug #4125: OpenMW logo cropped on bugtracker
+ Bug #4215: OpenMW shows book text after last
tag
+ Bug #4221: Characters get stuck in V-shaped terrain
+ Bug #4251: Stationary NPCs do not return to their position after combat
+ Bug #4286: Scripted animations can be interrupted
+ Bug #4291: Non-persistent actors that started the game as dead do not play death animations
+ Bug #4293: Faction members are not aware of faction ownerships in barter
+ Bug #4307: World cleanup should remove dead bodies only if death animation is finished
+ Bug #4327: Missing animations during spell/weapon stance switching
+ Bug #4368: Settings window ok button doesn't have key focus by default
+ Bug #4393: NPCs walk back to where they were after using ResetActors
+ Bug #4419: MRK NiStringExtraData is handled incorrectly
+ Bug #4426: RotateWorld behavior is incorrect
+ Bug #4429: [Windows] Error on build INSTALL.vcxproj project (debug) with cmake 3.7.2
+ Bug #4431: "Lock 0" console command is a no-op
+ Bug #4432: Guards behaviour is incorrect if they do not have AI packages
+ Bug #4433: Guard behaviour is incorrect with Alarm = 0
+ Bug #4451: Script fails to compile when using "Begin, [ScriptName]" syntax
+ Bug #4453: Quick keys behaviour is invalid for equipment
+ Bug #4454: AI opens doors too slow
+ Bug #4457: Item without CanCarry flag prevents shield autoequipping in dark areas
+ Bug #4458: AiWander console command handles idle chances incorrectly
+ Bug #4459: NotCell dialogue condition doesn't support partial matches
+ Feature #4256: Implement ToggleBorders (TB) console command
+ Feature #3276: Editor: Search- Show number of (remaining) search results and indicate a search without any results
+ Feature #4222: 360° screenshots
+ Feature #4324: Add CFBundleIdentifier in Info.plist to allow for macOS function key shortcuts
+ Feature #4345: Add equivalents for the command line commands to Launcher
+ Feature #4444: Per-group KF-animation files support
+
+0.44.0
+------
+
+ Bug #1428: Daedra summoning scripts aren't executed when the item is taken through the inventory
+ Bug #1987: Some glyphs are not supported
+ Bug #2254: Magic related visual effects are not rendered when loading a saved game
+ Bug #2485: Journal alphabetical index doesn't match "Morrowind content language" setting
+ Bug #2703: OnPCHitMe is not handled correctly
+ Bug #2829: Incorrect order for content list consisting of a game file and an esp without dependencies
+ Bug #2841: "Total eclipse" happens if weather settings are not defined.
+ Bug #2897: Editor: Rename "Original creature" field
+ Bug #3278: Editor: Unchecking "Auto Calc" flag changes certain values
+ Bug #3343: Editor: ID sorting is case-sensitive in certain tables
+ Bug #3557: Resource priority confusion when using the local data path as installation root
+ Bug #3587: Pathgrid and Flying Creatures wrong behaviour – abotWhereAreAllBirdsGoing
+ Bug #3603: SetPos should not skip weather transitions
+ Bug #3618: Myar Aranath total conversion can't be started due to capital-case extension of the master file
+ Bug #3638: Fast forwarding can move NPC inside objects
+ Bug #3664: Combat music does not start in dialogue
+ Bug #3696: Newlines are accompanied by empty rectangle glyph in dialogs
+ Bug #3708: Controllers broken on macOS
+ Bug #3726: Items with suppressed activation can be picked up via the inventory menu
+ Bug #3783: [Mod] Abot's Silt Striders 1.16 - silt strider "falls" to ground and glides on floor during travel
+ Bug #3863: Can be forced to not resist arrest if you cast Calm Humanoid on aggroed death warrant guards
+ Bug #3884: Incorrect enemy behavior when exhausted
+ Bug #3926: Installation Wizard places Morrowind.esm after Tribunal/Bloodmoon if it has a later file creation date
+ Bug #4061: Scripts error on special token included in name
+ Bug #4111: Crash when mouse over soulgem with a now-missing soul
+ Bug #4122: Swim animation should not be interrupted during underwater attack
+ Bug #4134: Battle music behaves different than vanilla
+ Bug #4135: Reflecting an absorb spell different from vanilla
+ Bug #4136: Enchanted weapons without "ignore normal weapons" flag don't bypass creature "ignore normal weapons" effect
+ Bug #4143: Antialiasing produces graphical artifacts when used with shader lighting
+ Bug #4159: NPCs' base skeleton files should not be optimized
+ Bug #4177: Jumping/landing animation interference/flickering
+ Bug #4179: NPCs do not face target
+ Bug #4180: Weapon switch sound playing even though no weapon is switched
+ Bug #4184: Guards can initiate dialogue even though you are far above them
+ Bug #4190: Enchanted clothes changes visibility with Chameleon on equip/unequip
+ Bug #4191: "screenshot saved" message also appears in the screenshot image
+ Bug #4192: Archers in OpenMW have shorter attack range than archers in Morrowind
+ Bug #4210: Some dialogue topics are not highlighted on first encounter
+ Bug #4211: FPS drops after minimizing the game during rainy weather
+ Bug #4216: Thrown weapon projectile doesn't rotate
+ Bug #4223: Displayed spell casting chance must be 0 if player doesn't have enough magicka to cast it
+ Bug #4225: Double "Activate" key presses with Mouse and Gamepad.
+ Bug #4226: The current player's class should be default value in the class select menu
+ Bug #4229: Tribunal/Bloodmoon summoned creatures fight other summons
+ Bug #4233: W and A keys override S and D Keys
+ Bug #4235: Wireframe mode affects local map
+ Bug #4239: Quick load from container screen causes crash
+ Bug #4242: Crime greetings display in Journal
+ Bug #4245: Merchant NPCs sell ingredients growing on potted plants they own
+ Bug #4246: Take armor condition into account when calcuting armor rating
+ Bug #4250: Jumping is not as fluid as it was pre-0.43.0
+ Bug #4252: "Error in frame: FFmpeg exception: Failed to allocate input stream" message spam if OpenMW encounter non-music file in the Music folder
+ Bug #4261: Magic effects from eaten ingredients always have 1 sec duration
+ Bug #4263: Arrow position is incorrect in 3rd person view during attack for beast races
+ Bug #4264: Player in god mode can be affected by some negative spell effects
+ Bug #4269: Crash when hovering the faction section and the 'sAnd' GMST is missing (as in MW 1.0)
+ Bug #4272: Root note transformations are discarded again
+ Bug #4279: Sometimes cells are not marked as explored on the map
+ Bug #4298: Problem with MessageBox and chargen menu interaction order
+ Bug #4301: Optimizer breaks LOD nodes
+ Bug #4308: PlaceAtMe doesn't inherit scale of calling object
+ Bug #4309: Only harmful effects with resistance effect set are resistable
+ Bug #4313: Non-humanoid creatures are capable of opening doors
+ Bug #4314: Rainy weather slows down the game when changing from indoors/outdoors
+ Bug #4319: Collisions for certain meshes are incorrectly ignored
+ Bug #4320: Using mouse 1 to move forward causes selection dialogues to jump selections forward.
+ Bug #4322: NPC disposition: negative faction reaction modifier doesn't take PC rank into account
+ Bug #4328: Ownership by dead actors is not cleared from picked items
+ Bug #4334: Torch and shield usage inconsistent with original game
+ Bug #4336: Wizard: Incorrect Morrowind assets path autodetection
+ Bug #4343: Error message for coc and starting cell shouldn't imply that it only works for interior cells
+ Bug #4346: Count formatting does not work well with very high numbers
+ Bug #4351: Using AddSoulgem fills all soul gems of the specified type
+ Bug #4391: No visual indication is provided when an unavailable spell fails to be chosen via a quick key
+ Bug #4392: Inventory filter breaks after loading a game
+ Bug #4405: No default terrain in empty cells when distant terrain is enabled
+ Bug #4410: [Mod] Arktwend: OpenMW does not use default marker definitions
+ Bug #4412: openmw-iniimporter ignores data paths from config
+ Bug #4413: Moving with 0 strength uses all of your fatigue
+ Bug #4420: Camera flickering when I open up and close menus while sneaking
+ Bug #4435: Item health is considered a signed integer
+ Bug #4441: Adding items to currently disabled weapon-wielding creatures crashes the game
+ Feature #1786: Round up encumbrance value in the encumbrance bar
+ Feature #2694: Editor: rename "model" column to make its purpose clear
+ Feature #3870: Editor: Terrain Texture Brush Button
+ Feature #3872: Editor: Edit functions in terrain texture editing mode
+ Feature #4054: Launcher: Create menu for settings.cfg options
+ Feature #4064: Option for fast travel services to charge for the first companion
+ Feature #4142: Implement fWereWolfHealth GMST
+ Feature #4174: Multiple quicksaves
+ Feature #4407: Support NiLookAtController
+ Feature #4423: Rebalance soul gem values
+ Task #4015: Use AppVeyor build artifact features to make continuous builds available
+ Editor: New (and more complete) icon set
+
0.43.0
------
diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh
index fffba980c..15a956d3f 100644
--- a/CI/before_script.msvc.sh
+++ b/CI/before_script.msvc.sh
@@ -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:-}
@@ -636,6 +653,17 @@ fi
-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
}
@@ -710,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
@@ -742,7 +770,7 @@ if [ -z $CI ]; then
cp "$DLL" "${BUILD_CONFIG}/platforms"
done
echo
-fi
+#fi
if [ -z $VERBOSE ]; then
printf -- "- Configuring... "
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 341318af8..1a1beafdc 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -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 "")
@@ -360,8 +360,6 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-but-set-parameter")
endif()
elseif (MSVC)
- set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS} /Zi /bigobj")
- set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /DEBUG /OPT:REF /OPT:ICF /INCREMENTAL:NO")
# Enable link-time code generation globally for all linking
if (OPENMW_LTO_BUILD)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GL")
@@ -707,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}")
diff --git a/README.md b/README.md
index 368609332..9af9ef976 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,13 @@
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: https://www.openmw.org
* IRC: #openmw on irc.freenode.net
diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt
index 99e7b4daa..ec2e963d1 100644
--- a/apps/launcher/CMakeLists.txt
+++ b/apps/launcher/CMakeLists.txt
@@ -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
diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp
index 9ae83419d..2b2d7b448 100644
--- a/apps/launcher/advancedpage.cpp
+++ b/apps/launcher/advancedpage.cpp
@@ -1,10 +1,18 @@
#include "advancedpage.hpp"
-#include
-
-Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent)
+#include
+#include
+#include
+#include
+#include
+#include
+
+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");
@@ -54,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");
@@ -95,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);
}
\ No newline at end of file
diff --git a/apps/launcher/advancedpage.hpp b/apps/launcher/advancedpage.hpp
index a8361c98e..59de3d319 100644
--- a/apps/launcher/advancedpage.hpp
+++ b/apps/launcher/advancedpage.hpp
@@ -8,6 +8,7 @@
#include
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);
};
diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp
index 0b0f8c75e..7b703a924 100644
--- a/apps/launcher/datafilespage.cpp
+++ b/apps/launcher/datafilespage.cpp
@@ -7,7 +7,10 @@
#include
#include
#include
+#include
+#include
+#include
#include
#include
@@ -16,6 +19,7 @@
#include
#include
+#include
#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 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);
+}
\ No newline at end of file
diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp
index d25d20fc9..2cbace38e 100644
--- a/apps/launcher/datafilespage.hpp
+++ b/apps/launcher/datafilespage.hpp
@@ -7,6 +7,7 @@
#include
#include
+#include
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 ¤t);
void slotProfileRenamed(const QString &previous, const QString ¤t);
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
{
diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp
index d3dbfa559..34442fe71 100644
--- a/apps/launcher/graphicspage.cpp
+++ b/apps/launcher/graphicspage.cpp
@@ -1,6 +1,7 @@
#include "graphicspage.hpp"
#include
+#include
#include
#include
#include
@@ -11,6 +12,7 @@
#define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__
#endif // MAC_OS_X_VERSION_MIN_REQUIRED
+#include
#include
#include
@@ -46,8 +48,28 @@ Launcher::GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, Settings:
}
+bool Launcher::GraphicsPage::connectToSdl() {
+ SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software");
+ SDL_SetMainReady();
+ // Required for determining screen resolution and such on the Graphics tab
+ if (SDL_Init(SDL_INIT_VIDEO) != 0)
+ {
+ return false;
+ }
+ signal(SIGINT, SIG_DFL); // We don't want to use the SDL event loop in the launcher,
+ // so reset SIGINT which SDL wants to redirect to an SDL_Quit event.
+
+ return true;
+}
+
bool Launcher::GraphicsPage::setupSDL()
{
+ bool sdlConnectSuccessful = connectToSdl();
+ if (!sdlConnectSuccessful)
+ {
+ return false;
+ }
+
int displays = SDL_GetNumVideoDisplays();
if (displays < 0)
@@ -67,6 +89,9 @@ bool Launcher::GraphicsPage::setupSDL()
screenComboBox->addItem(QString(tr("Screen ")) + QString::number(i + 1));
}
+ // Disconnect from SDL processes
+ SDL_Quit();
+
return true;
}
diff --git a/apps/launcher/graphicspage.hpp b/apps/launcher/graphicspage.hpp
index e6eb53a3b..0354e5202 100644
--- a/apps/launcher/graphicspage.hpp
+++ b/apps/launcher/graphicspage.hpp
@@ -37,6 +37,11 @@ namespace Launcher
QStringList getAvailableResolutions(int screen);
QRect getMaximumResolution();
+ /**
+ * Connect to the SDL so that we can use it to determine graphics
+ * @return whether or not connecting to SDL is successful
+ */
+ bool connectToSdl();
bool setupSDL();
};
}
diff --git a/apps/launcher/main.cpp b/apps/launcher/main.cpp
index 96cadc8a7..866ae2aa9 100644
--- a/apps/launcher/main.cpp
+++ b/apps/launcher/main.cpp
@@ -1,5 +1,4 @@
#include
-#include
#include
#include
@@ -12,24 +11,12 @@
#define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__
#endif // MAC_OS_X_VERSION_MIN_REQUIRED
-#include
-
#include "maindialog.hpp"
int main(int argc, char *argv[])
{
try
{
- SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software");
- SDL_SetMainReady();
- if (SDL_Init(SDL_INIT_VIDEO) != 0)
- {
- qDebug() << "SDL_Init failed: " << QString::fromUtf8(SDL_GetError());
- return 0;
- }
- signal(SIGINT, SIG_DFL); // We don't want to use the SDL event loop in the launcher,
- // so reset SIGINT which SDL wants to redirect to an SDL_Quit event.
-
QApplication app(argc, argv);
// Now we make sure the current dir is set to application path
@@ -46,13 +33,11 @@ int main(int argc, char *argv[])
if (result == Launcher::FirstRunDialogResultContinue)
mainWin.show();
- int returnValue = app.exec();
- SDL_Quit();
- return returnValue;
+ return app.exec();
}
catch (std::exception& e)
{
std::cerr << "ERROR: " << e.what() << std::endl;
return 0;
}
-}
+}
\ No newline at end of file
diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp
index 1a210ccc5..27fa2d903 100644
--- a/apps/launcher/maindialog.cpp
+++ b/apps/launcher/maindialog.cpp
@@ -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);
}
diff --git a/apps/launcher/utils/cellnameloader.cpp b/apps/launcher/utils/cellnameloader.cpp
new file mode 100644
index 000000000..6d1ed2f49
--- /dev/null
+++ b/apps/launcher/utils/cellnameloader.cpp
@@ -0,0 +1,48 @@
+#include "cellnameloader.hpp"
+
+#include
+#include
+
+QSet CellNameLoader::getCellNames(QStringList &contentPaths)
+{
+ QSet 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);
+}
\ No newline at end of file
diff --git a/apps/launcher/utils/cellnameloader.hpp b/apps/launcher/utils/cellnameloader.hpp
new file mode 100644
index 000000000..c58d09226
--- /dev/null
+++ b/apps/launcher/utils/cellnameloader.hpp
@@ -0,0 +1,41 @@
+#ifndef OPENMW_CELLNAMELOADER_H
+#define OPENMW_CELLNAMELOADER_H
+
+#include
+#include
+#include
+
+#include
+
+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 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
diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp
index 7a825ba39..e45d13aa9 100644
--- a/apps/opencs/model/doc/document.cpp
+++ b/apps/opencs/model/doc/document.cpp
@@ -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;
diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp
index d31fd5aca..4c442428e 100644
--- a/apps/opencs/model/doc/document.hpp
+++ b/apps/opencs/model/doc/document.hpp
@@ -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();
diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp
index d609a6253..df37afe60 100644
--- a/apps/opencs/model/world/columnbase.hpp
+++ b/apps/opencs/model/world/columnbase.hpp
@@ -88,6 +88,7 @@ namespace CSMWorld
Display_UnsignedInteger8,
Display_Integer,
Display_Float,
+ Display_Double,
Display_Var,
Display_GmstVarType,
Display_GlobalVarType,
diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp
index f1025acb9..4ad447b0a 100644
--- a/apps/opencs/model/world/columnimp.hpp
+++ b/apps/opencs/model/world/columnimp.hpp
@@ -1371,7 +1371,7 @@ namespace CSMWorld
RotColumn (ESM::Position ESXRecordT::* position, int index, bool door)
: Column (
(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& record) const
{
diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp
index 60a513cb6..6716c7240 100644
--- a/apps/opencs/model/world/refidcollection.cpp
+++ b/apps/opencs/model/world/refidcollection.cpp
@@ -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,
diff --git a/apps/opencs/view/tools/searchsubview.cpp b/apps/opencs/view/tools/searchsubview.cpp
index 493defa5a..9bada22af 100644
--- a/apps/opencs/view/tools/searchsubview.cpp
+++ b/apps/opencs/view/tools/searchsubview.cpp
@@ -3,11 +3,15 @@
#include
#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");
+ }
+}
diff --git a/apps/opencs/view/tools/searchsubview.hpp b/apps/opencs/view/tools/searchsubview.hpp
index ac0a5a762..c0f3eac84 100644
--- a/apps/opencs/view/tools/searchsubview.hpp
+++ b/apps/opencs/view/tools/searchsubview.hpp
@@ -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);
};
}
diff --git a/apps/opencs/view/world/tablebottombox.cpp b/apps/opencs/view/world/tablebottombox.cpp
index cfde5c694..f6b060a8f 100644
--- a/apps/opencs/view/world/tablebottombox.cpp
+++ b/apps/opencs/view/world/tablebottombox.cpp
@@ -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)
diff --git a/apps/opencs/view/world/tablebottombox.hpp b/apps/opencs/view/world/tablebottombox.hpp
index 5402c466e..baa68087b 100644
--- a/apps/opencs/view/world/tablebottombox.hpp
+++ b/apps/opencs/view/world/tablebottombox.hpp
@@ -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);
diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp
index efba1ea82..eab37e1bf 100644
--- a/apps/opencs/view/world/util.cpp
+++ b/apps/opencs/view/world/util.cpp
@@ -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);
diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp
index 78e368cfc..5052a13cc 100644
--- a/apps/openmw/engine.cpp
+++ b/apps/openmw/engine.cpp
@@ -499,7 +499,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
else
gameControllerdb = ""; //if it doesn't exist, pass in an empty string
- MWInput::InputManager* input = new MWInput::InputManager (mWindow, mViewer, mScreenCaptureHandler, keybinderUser, keybinderUserExists, gameControllerdb, mGrab);
+ MWInput::InputManager* input = new MWInput::InputManager (mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, gameControllerdb, mGrab);
mEnvironment.setInputManager (input);
std::string myguiResources = (mResDir / "mygui").string();
@@ -656,8 +656,11 @@ void OMW::Engine::go()
settingspath = loadSettings (settings);
- mScreenCaptureHandler = new osgViewer::ScreenCaptureHandler(new WriteScreenshotToFileOperation(mCfgMgr.getUserDataPath().string(),
- Settings::Manager::getString("screenshot format", "General")));
+ mScreenCaptureOperation = new WriteScreenshotToFileOperation(mCfgMgr.getUserDataPath().string(),
+ Settings::Manager::getString("screenshot format", "General"));
+
+ mScreenCaptureHandler = new osgViewer::ScreenCaptureHandler(mScreenCaptureOperation);
+
mViewer->addEventHandler(mScreenCaptureHandler);
mEnvironment.setFrameRateLimit(Settings::Manager::getFloat("framerate limit", "Video"));
diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp
index 2fff91c7e..e42a5c94f 100644
--- a/apps/openmw/engine.hpp
+++ b/apps/openmw/engine.hpp
@@ -7,7 +7,7 @@
#include
#include
-
+#include
#include "mwbase/environment.hpp"
@@ -82,6 +82,7 @@ namespace OMW
boost::filesystem::path mResDir;
osg::ref_ptr mViewer;
osg::ref_ptr mScreenCaptureHandler;
+ osgViewer::ScreenCaptureHandler::CaptureOperation *mScreenCaptureOperation;
std::string mCellName;
std::vector mContentFiles;
bool mSkipMenu;
diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp
index e40bf56bb..f15a86918 100644
--- a/apps/openmw/mwbase/mechanicsmanager.hpp
+++ b/apps/openmw/mwbase/mechanicsmanager.hpp
@@ -239,7 +239,7 @@ namespace MWBase
virtual std::vector > 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;
diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp
index 47502fd71..e07c1ffd9 100644
--- a/apps/openmw/mwbase/world.hpp
+++ b/apps/openmw/mwbase/world.hpp
@@ -120,6 +120,7 @@ namespace MWBase
virtual bool toggleWater() = 0;
virtual bool toggleWorld() = 0;
+ virtual bool toggleBorders() = 0;
virtual void adjustSky() = 0;
@@ -450,6 +451,7 @@ namespace MWBase
/// \todo this does not belong here
virtual void screenshot (osg::Image* image, int w, int h) = 0;
+ virtual bool screenshot360 (osg::Image* image, std::string settingStr) = 0;
/// Find default position inside exterior cell specified by name
/// \return false if exterior with given name not exists, true otherwise
@@ -564,6 +566,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;
diff --git a/apps/openmw/mwclass/actor.cpp b/apps/openmw/mwclass/actor.cpp
index 17af4725e..73a4d37d7 100644
--- a/apps/openmw/mwclass/actor.cpp
+++ b/apps/openmw/mwclass/actor.cpp
@@ -31,7 +31,7 @@ namespace MWClass
if (!model.empty())
{
physics.addActor(ptr, model);
- if (getCreatureStats(ptr).isDead())
+ if (getCreatureStats(ptr).isDead() && getCreatureStats(ptr).isDeathAnimationFinished())
MWBase::Environment::get().getWorld()->enableActorCollision(ptr, false);
}
}
diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp
index b6a46cff8..efe460b00 100644
--- a/apps/openmw/mwclass/container.cpp
+++ b/apps/openmw/mwclass/container.cpp
@@ -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);
}
}
@@ -242,7 +239,8 @@ namespace MWClass
info.caption = ref->mBase->mName;
std::string text;
- if (ptr.getCellRef().getLockLevel() > 0)
+ int lockLevel = ptr.getCellRef().getLockLevel();
+ if (lockLevel > 0 && lockLevel != ESM::UnbreakableLock)
text += "\n#{sLockLevel}: " + MWGui::ToolTips::toString(ptr.getCellRef().getLockLevel());
else if (ptr.getCellRef().getLockLevel() < 0)
text += "\n#{sUnlocked}";
@@ -274,15 +272,16 @@ namespace MWClass
void Container::lock (const MWWorld::Ptr& ptr, int lockLevel) const
{
- if(lockLevel!=0)
- ptr.getCellRef().setLockLevel(abs(lockLevel)); //Changes lock to locklevel, in positive
+ if(lockLevel != 0)
+ ptr.getCellRef().setLockLevel(abs(lockLevel)); //Changes lock to locklevel, if positive
else
- ptr.getCellRef().setLockLevel(abs(ptr.getCellRef().getLockLevel())); //No locklevel given, just flip the original one
+ ptr.getCellRef().setLockLevel(ESM::UnbreakableLock); // If zero, set to max lock level
}
void Container::unlock (const MWWorld::Ptr& ptr) const
{
- ptr.getCellRef().setLockLevel(-abs(ptr.getCellRef().getLockLevel())); //Makes lockLevel negative
+ int lockLevel = ptr.getCellRef().getLockLevel();
+ ptr.getCellRef().setLockLevel(-abs(lockLevel)); //Makes lockLevel negative
}
bool Container::canLock(const MWWorld::ConstPtr &ptr) const
diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp
index 2e16b13aa..a07a5c893 100644
--- a/apps/openmw/mwclass/creature.cpp
+++ b/apps/openmw/mwclass/creature.cpp
@@ -135,8 +135,9 @@ namespace MWClass
data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Flee, ref->mBase->mAiData.mFlee);
data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Alarm, ref->mBase->mAiData.mAlarm);
+ // Persistent actors with 0 health do not play death animation
if (data->mCreatureStats.isDead())
- data->mCreatureStats.setDeathAnimationFinished(true);
+ data->mCreatureStats.setDeathAnimationFinished(ptr.getClass().isPersistent(ptr));
// spells
for (std::vector::const_iterator iter (ref->mBase->mSpells.mList.begin());
@@ -814,6 +815,9 @@ namespace MWClass
if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead())
return;
+ if (!creatureStats.isDeathAnimationFinished())
+ return;
+
const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get();
static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->getFloat();
static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->getFloat();
diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp
index 903ec4958..d738974dd 100644
--- a/apps/openmw/mwclass/door.cpp
+++ b/apps/openmw/mwclass/door.cpp
@@ -114,42 +114,44 @@ 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();
bool hasKey = false;
std::string keyName;
+ // FIXME: If NPC activate teleporting door, it can lead to crash due to iterator invalidation in the Actors update.
+ // Make such activation a no-op for now, like how it is in the vanilla game.
+ if (actor != MWMechanics::getPlayer() && ptr.getCellRef().getTeleport())
+ {
+ std::shared_ptr action(new MWWorld::FailedAction(std::string(), ptr));
+ action->setSound(lockedSound);
+ return action;
+ }
+
// make door glow if player activates it with telekinesis
- if (actor == MWBase::Environment::get().getWorld()->getPlayerPtr() &&
- MWBase::Environment::get().getWorld()->getDistanceToFacedObject() >
+ if (actor == MWMechanics::getPlayer() &&
+ MWBase::Environment::get().getWorld()->getDistanceToFacedObject() >
MWBase::Environment::get().getWorld()->getMaxActivationDistance())
{
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr);
- const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
+ const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
int index = ESM::MagicEffect::effectStringToId("sEffectTelekinesis");
const ESM::MagicEffect *effect = store.get().find(index);
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);
}
}
@@ -191,7 +193,7 @@ namespace MWClass
std::shared_ptr action(new MWWorld::ActionTeleport (ptr.getCellRef().getDestCell(), ptr.getCellRef().getDoorDest(), true));
action->setSound(openSound);
return action;
- }
+ }
}
else
{
@@ -238,15 +240,16 @@ namespace MWClass
void Door::lock (const MWWorld::Ptr& ptr, int lockLevel) const
{
- if(lockLevel!=0)
- ptr.getCellRef().setLockLevel(abs(lockLevel)); //Changes lock to locklevel, in positive
+ if(lockLevel != 0)
+ ptr.getCellRef().setLockLevel(abs(lockLevel)); //Changes lock to locklevel, if positive
else
- ptr.getCellRef().setLockLevel(abs(ptr.getCellRef().getLockLevel())); //No locklevel given, just flip the original one
+ ptr.getCellRef().setLockLevel(ESM::UnbreakableLock); // If zero, set to max lock level
}
void Door::unlock (const MWWorld::Ptr& ptr) const
{
- ptr.getCellRef().setLockLevel(-abs(ptr.getCellRef().getLockLevel())); //Makes lockLevel negative
+ int lockLevel = ptr.getCellRef().getLockLevel();
+ ptr.getCellRef().setLockLevel(-abs(lockLevel)); //Makes lockLevel negative
}
bool Door::canLock(const MWWorld::ConstPtr &ptr) const
@@ -298,7 +301,8 @@ namespace MWClass
text += "\n" + getDestination(*ref);
}
- if (ptr.getCellRef().getLockLevel() > 0)
+ int lockLevel = ptr.getCellRef().getLockLevel();
+ if (lockLevel > 0 && lockLevel != ESM::UnbreakableLock)
text += "\n#{sLockLevel}: " + MWGui::ToolTips::toString(ptr.getCellRef().getLockLevel());
else if (ptr.getCellRef().getLockLevel() < 0)
text += "\n#{sUnlocked}";
diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp
index 8e8b5c3ad..92e25baee 100644
--- a/apps/openmw/mwclass/npc.cpp
+++ b/apps/openmw/mwclass/npc.cpp
@@ -352,8 +352,10 @@ namespace MWClass
data->mNpcStats.setNeedRecalcDynamicStats(true);
}
+
+ // Persistent actors with 0 health do not play death animation
if (data->mNpcStats.isDead())
- data->mNpcStats.setDeathAnimationFinished(true);
+ data->mNpcStats.setDeathAnimationFinished(ptr.getClass().isPersistent(ptr));
// race powers
const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get().find(ref->mBase->mRace);
@@ -1351,6 +1353,9 @@ namespace MWClass
if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead())
return;
+ if (!creatureStats.isDeathAnimationFinished())
+ return;
+
const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get();
static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->getFloat();
static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->getFloat();
diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp
index de9ca83ca..fb492ff3b 100644
--- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp
+++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp
@@ -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));
}
diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp
index e8757e4a3..15020898f 100644
--- a/apps/openmw/mwdialogue/filter.cpp
+++ b/apps/openmw/mwdialogue/filter.cpp
@@ -491,10 +491,11 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co
return !Misc::StringUtils::ciEqual(mActor.get()->mBase->mRace, select.getName());
case SelectWrapper::Function_NotCell:
-
- return !Misc::StringUtils::ciEqual(MWBase::Environment::get().getWorld()->getCellName(mActor.getCell())
- , select.getName());
-
+ {
+ const std::string& actorCell = MWBase::Environment::get().getWorld()->getCellName(mActor.getCell());
+ return !(actorCell.length() >= select.getName().length()
+ && Misc::StringUtils::ciEqual(actorCell.substr(0, select.getName().length()), select.getName()));
+ }
case SelectWrapper::Function_SameGender:
return (player.get()->mBase->mFlags & ESM::NPC::Female)==
diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp
index 14bbe81ef..450799f29 100644
--- a/apps/openmw/mwgui/dialogue.cpp
+++ b/apps/openmw/mwgui/dialogue.cpp
@@ -635,6 +635,11 @@ namespace MWGui
void DialogueWindow::onChoiceActivated(int id)
{
+ if (mGoodbye)
+ {
+ onGoodbyeActivated();
+ return;
+ }
MWBase::Environment::get().getDialogueManager()->questionAnswered(id, mCallback.get());
updateTopics();
}
diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp
index 3616b8b62..f7764e0f1 100644
--- a/apps/openmw/mwgui/enchantingdialog.cpp
+++ b/apps/openmw/mwgui/enchantingdialog.cpp
@@ -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().find("sNotifyMessage49")->getString();
if (msg.find("%s") != std::string::npos)
diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp
index a9319048e..16568e2f3 100644
--- a/apps/openmw/mwgui/formatting.cpp
+++ b/apps/openmw/mwgui/formatting.cpp
@@ -31,6 +31,14 @@ namespace MWGui
boost::algorithm::replace_all(mText, "\r", "");
+ // vanilla game does not show any text after last
tag.
+ const std::string lowerText = Misc::StringUtils::lowerCase(mText);
+ int index = lowerText.rfind("
");
+ if (index == -1)
+ mText = "";
+ else
+ mText = mText.substr(0, index+4);
+
registerTag("br", Event_BrTag);
registerTag("p", Event_PTag);
registerTag("img", Event_ImgTag);
diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp
index 2c12547fd..99fe777aa 100644
--- a/apps/openmw/mwgui/loadingscreen.cpp
+++ b/apps/openmw/mwgui/loadingscreen.cpp
@@ -153,7 +153,7 @@ namespace MWGui
virtual osg::BoundingSphere computeBound(const osg::Node&) const { return osg::BoundingSphere(); }
};
- void LoadingScreen::loadingOn()
+ void LoadingScreen::loadingOn(bool visible)
{
mLoadingOnTime = mTimer.time_m();
// Early-out if already on
@@ -169,7 +169,10 @@ namespace MWGui
// We are already using node masks to avoid the scene from being updated/rendered, but node masks don't work for computeBound()
mViewer->getSceneData()->setComputeBoundingSphereCallback(new DontComputeBoundCallback);
- mShowWallpaper = (MWBase::Environment::get().getStateManager()->getState()
+ mVisible = visible;
+ mLoadingBox->setVisible(mVisible);
+
+ mShowWallpaper = mVisible && (MWBase::Environment::get().getStateManager()->getState()
== MWBase::StateManager::State_NoGame);
setVisible(true);
@@ -180,10 +183,15 @@ namespace MWGui
}
MWBase::Environment::get().getWindowManager()->pushGuiMode(mShowWallpaper ? GM_LoadingWallpaper : GM_Loading);
+
+ if (!mVisible)
+ draw();
}
void LoadingScreen::loadingOff()
{
+ mLoadingBox->setVisible(true); // restore
+
if (mLastRenderTime < mLoadingOnTime)
{
// the loading was so fast that we didn't show loading screen at all
@@ -306,7 +314,7 @@ namespace MWGui
void LoadingScreen::draw()
{
- if (!needToDrawLoadingScreen())
+ if (mVisible && !needToDrawLoadingScreen())
return;
if (mShowWallpaper && mTimer.time_m() > mLastWallpaperChangeTime + 5000*1)
diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp
index 8536972a3..bdd210d00 100644
--- a/apps/openmw/mwgui/loadingscreen.hpp
+++ b/apps/openmw/mwgui/loadingscreen.hpp
@@ -35,7 +35,7 @@ namespace MWGui
/// Overridden from Loading::Listener, see the Loading::Listener documentation for usage details
virtual void setLabel (const std::string& label, bool important);
- virtual void loadingOn();
+ virtual void loadingOn(bool visible=true);
virtual void loadingOff();
virtual void setProgressRange (size_t range);
virtual void setProgress (size_t value);
@@ -63,6 +63,8 @@ namespace MWGui
bool mImportantLabel;
+ bool mVisible;
+
size_t mProgress;
bool mShowWallpaper;
diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp
index 2ce9d04e5..08192625f 100644
--- a/apps/openmw/mwgui/quickkeysmenu.cpp
+++ b/apps/openmw/mwgui/quickkeysmenu.cpp
@@ -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();
+ // 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();
- // 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)
{
@@ -498,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::const_iterator it = keys.mKeys.begin(); it != keys.mKeys.end(); ++it)
{
@@ -519,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);
diff --git a/apps/openmw/mwgui/quickkeysmenu.hpp b/apps/openmw/mwgui/quickkeysmenu.hpp
index 0070aa55b..b5bc60b19 100644
--- a/apps/openmw/mwgui/quickkeysmenu.hpp
+++ b/apps/openmw/mwgui/quickkeysmenu.hpp
@@ -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();
diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp
index 9bf6e4385..677ddefb3 100644
--- a/apps/openmw/mwgui/settingswindow.cpp
+++ b/apps/openmw/mwgui/settingswindow.cpp
@@ -562,8 +562,9 @@ namespace MWGui
void SettingsWindow::onOpen()
{
- updateControlsBox ();
+ updateControlsBox();
resetScrollbars();
+ MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mOkButton);
}
void SettingsWindow::onWindowResize(MyGUI::Window *_sender)
diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp
index 2eeeafe0d..0a9fe1b4a 100644
--- a/apps/openmw/mwgui/tradewindow.cpp
+++ b/apps/openmw/mwgui/tradewindow.cpp
@@ -311,8 +311,7 @@ namespace MWGui
// check if the player is attempting to sell back an item stolen from this actor
for (std::vector::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)
diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp
index 61febf315..52575e25c 100644
--- a/apps/openmw/mwgui/waitdialog.cpp
+++ b/apps/openmw/mwgui/waitdialog.cpp
@@ -310,8 +310,10 @@ namespace MWGui
void WaitDialog::wakeUp ()
{
mSleeping = false;
- mTimeAdvancer.stop();
- stopWaiting();
+ if (mInterruptAt != -1)
+ onWaitingInterrupted();
+ else
+ stopWaiting();
}
}
diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp
index 507e97a6a..242058c5f 100644
--- a/apps/openmw/mwinput/inputmanagerimp.cpp
+++ b/apps/openmw/mwinput/inputmanagerimp.cpp
@@ -36,12 +36,14 @@ namespace MWInput
SDL_Window* window,
osg::ref_ptr viewer,
osg::ref_ptr screenCaptureHandler,
+ osgViewer::ScreenCaptureHandler::CaptureOperation *screenCaptureOperation,
const std::string& userFile, bool userFileExists,
const std::string& controllerBindingsFile, bool grab)
: mWindow(window)
, mWindowVisible(true)
, mViewer(viewer)
, mScreenCaptureHandler(screenCaptureHandler)
+ , mScreenCaptureOperation(screenCaptureOperation)
, mJoystickLastUsed(false)
, mPlayer(NULL)
, mInputManager(NULL)
@@ -1033,8 +1035,28 @@ namespace MWInput
void InputManager::screenshot()
{
- mScreenCaptureHandler->setFramesToCapture(1);
- mScreenCaptureHandler->captureNextFrame(*mViewer);
+ bool regularScreenshot = true;
+
+ std::string settingStr;
+
+ settingStr = Settings::Manager::getString("screenshot type","Video");
+ regularScreenshot = settingStr.size() == 0 || settingStr.compare("regular") == 0;
+
+ if (regularScreenshot)
+ {
+ mScreenCaptureHandler->setFramesToCapture(1);
+ mScreenCaptureHandler->captureNextFrame(*mViewer);
+ }
+ else
+ {
+ osg::ref_ptr screenshot (new osg::Image);
+
+ if (MWBase::Environment::get().getWorld()->screenshot360(screenshot.get(),settingStr))
+ {
+ (*mScreenCaptureOperation) (*(screenshot.get()),0);
+ // FIXME: mScreenCaptureHandler->getCaptureOperation() causes crash for some reason
+ }
+ }
}
void InputManager::toggleInventory()
diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp
index cba7fc743..bc62ef7dc 100644
--- a/apps/openmw/mwinput/inputmanagerimp.hpp
+++ b/apps/openmw/mwinput/inputmanagerimp.hpp
@@ -4,6 +4,7 @@
#include "../mwgui/mode.hpp"
#include
+#include
#include
#include
@@ -14,7 +15,6 @@
#include "../mwbase/inputmanager.hpp"
-
namespace MWWorld
{
class Player;
@@ -74,6 +74,7 @@ namespace MWInput
SDL_Window* window,
osg::ref_ptr viewer,
osg::ref_ptr screenCaptureHandler,
+ osgViewer::ScreenCaptureHandler::CaptureOperation *screenCaptureOperation,
const std::string& userFile, bool userFileExists,
const std::string& controllerBindingsFile, bool grab);
@@ -158,6 +159,7 @@ namespace MWInput
bool mWindowVisible;
osg::ref_ptr mViewer;
osg::ref_ptr mScreenCaptureHandler;
+ osgViewer::ScreenCaptureHandler::CaptureOperation *mScreenCaptureOperation;
bool mJoystickLastUsed;
MWWorld::Player* mPlayer;
diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp
index 7990373a7..ec256a321 100644
--- a/apps/openmw/mwmechanics/actors.cpp
+++ b/apps/openmw/mwmechanics/actors.cpp
@@ -50,27 +50,36 @@ bool isConscious(const MWWorld::Ptr& ptr)
return !stats.isDead() && !stats.getKnockedDown();
}
-void adjustBoundItem (const std::string& item, bool bound, const MWWorld::Ptr& actor)
+int getBoundItemSlot (const std::string& itemId)
{
- if (bound)
+ static std::map boundItemsMap;
+ if (boundItemsMap.empty())
{
- if (actor.getClass().getContainerStore(actor).count(item) == 0)
- {
- MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor);
- MWWorld::Ptr newPtr = *store.MWWorld::ContainerStore::add(item, 1, actor);
- MWWorld::ActionEquip action(newPtr);
- action.execute(actor);
- MWWorld::ConstContainerStoreIterator rightHand = store.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
- // change draw state only if the item is in player's right hand
- if (actor == MWMechanics::getPlayer()
- && rightHand != store.end() && newPtr == *rightHand)
- {
- MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon);
- }
- }
+ std::string boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundBootsID")->getString();
+ boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Boots;
+
+ boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundCuirassID")->getString();
+ boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Cuirass;
+
+ boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundLeftGauntletID")->getString();
+ boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_LeftGauntlet;
+
+ boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundRightGauntletID")->getString();
+ boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_RightGauntlet;
+
+ boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundHelmID")->getString();
+ boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Helmet;
+
+ boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundShieldID")->getString();
+ boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_CarriedLeft;
}
- else
- actor.getClass().getInventoryStore(actor).remove(item, 1, actor, true);
+
+ int slot = MWWorld::InventoryStore::Slot_CarriedRight;
+ std::map::iterator it = boundItemsMap.find(itemId);
+ if (it != boundItemsMap.end())
+ slot = it->second;
+
+ return slot;
}
class CheckActorCommanded : public MWMechanics::EffectSourceVisitor
@@ -139,7 +148,6 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float
namespace MWMechanics
{
-
const float aiProcessingDistance = 7168;
const float sqrAiProcessingDistance = aiProcessingDistance*aiProcessingDistance;
@@ -227,6 +235,69 @@ namespace MWMechanics
}
};
+ void Actors::addBoundItem (const std::string& itemId, const MWWorld::Ptr& actor)
+ {
+ MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor);
+ int slot = getBoundItemSlot(itemId);
+
+ if (actor.getClass().getContainerStore(actor).count(itemId) != 0)
+ return;
+
+ MWWorld::ContainerStoreIterator prevItem = store.getSlot(slot);
+
+ MWWorld::Ptr boundPtr = *store.MWWorld::ContainerStore::add(itemId, 1, actor);
+ MWWorld::ActionEquip action(boundPtr);
+ action.execute(actor);
+
+ if (actor != MWMechanics::getPlayer())
+ return;
+
+ MWWorld::Ptr newItem = *store.getSlot(slot);
+
+ if (newItem.isEmpty() || boundPtr != newItem)
+ return;
+
+ MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer();
+
+ // change draw state only if the item is in player's right hand
+ if (slot == MWWorld::InventoryStore::Slot_CarriedRight)
+ player.setDrawState(MWMechanics::DrawState_Weapon);
+
+ if (prevItem != store.end())
+ player.setPreviousItem(itemId, prevItem->getCellRef().getRefId());
+ }
+
+ void Actors::removeBoundItem (const std::string& itemId, const MWWorld::Ptr& actor)
+ {
+ MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor);
+ int slot = getBoundItemSlot(itemId);
+
+ MWWorld::ContainerStoreIterator currentItem = store.getSlot(slot);
+
+ bool wasEquipped = currentItem != store.end() && Misc::StringUtils::ciEqual(currentItem->getCellRef().getRefId(), itemId);
+
+ store.remove(itemId, 1, actor, true);
+
+ if (actor != MWMechanics::getPlayer())
+ return;
+
+ MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer();
+ std::string prevItemId = player.getPreviousItem(itemId);
+ player.erasePreviousItem(itemId);
+
+ if (prevItemId.empty())
+ return;
+
+ // Find previous item (or its replacement) by id.
+ // we should equip previous item only if expired bound item was equipped.
+ MWWorld::Ptr item = store.findReplacement(prevItemId);
+ if (item.isEmpty() || !wasEquipped)
+ return;
+
+ MWWorld::ActionEquip action(item);
+ action.execute(actor);
+ }
+
void Actors::updateActor (const MWWorld::Ptr& ptr, float duration)
{
// magic effects
@@ -689,11 +760,14 @@ namespace MWMechanics
{
// The actor was killed by a magic effect. Figure out if the player was responsible for it.
const ActiveSpells& spells = creatureStats.getActiveSpells();
- bool killedByPlayer = false;
MWWorld::Ptr player = getPlayer();
+ std::set playerFollowers;
+ getActorsSidingWith(player, playerFollowers);
+
for (ActiveSpells::TIterator it = spells.begin(); it != spells.end(); ++it)
{
const ActiveSpells::ActiveSpellParams& spell = it->second;
+ MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spell.mCasterActorId);
for (std::vector::const_iterator effectIt = spell.mEffects.begin();
effectIt != spell.mEffects.end(); ++effectIt)
{
@@ -711,17 +785,19 @@ namespace MWMechanics
isDamageEffect = true;
}
- MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spell.mCasterActorId);
- if (isDamageEffect && caster == player)
- killedByPlayer = true;
+ if (isDamageEffect)
+ {
+ if (caster == player || playerFollowers.find(caster) != playerFollowers.end())
+ {
+ if (caster.getClass().getNpcStats(caster).isWerewolf())
+ caster.getClass().getNpcStats(caster).addWerewolfKill();
+
+ MWBase::Environment::get().getMechanicsManager()->actorKilled(ptr, player);
+ break;
+ }
+ }
}
}
- if (killedByPlayer)
- {
- MWBase::Environment::get().getMechanicsManager()->actorKilled(ptr, player);
- if (player.getClass().getNpcStats(player).isWerewolf())
- player.getClass().getNpcStats(player).addWerewolfKill();
- }
}
// TODO: dirty flag for magic effects to avoid some unnecessary work below?
@@ -756,25 +832,23 @@ namespace MWMechanics
float magnitude = effects.get(it->first).getMagnitude();
if (found != (magnitude > 0))
{
+ if (magnitude > 0)
+ creatureStats.mBoundItems.insert(it->first);
+ else
+ creatureStats.mBoundItems.erase(it->first);
+
std::string itemGmst = it->second;
std::string item = MWBase::Environment::get().getWorld()->getStore().get().find(
itemGmst)->getString();
+
+ magnitude > 0 ? addBoundItem(item, ptr) : removeBoundItem(item, ptr);
+
if (it->first == ESM::MagicEffect::BoundGloves)
{
- item = MWBase::Environment::get().getWorld()->getStore().get().find(
- "sMagicBoundLeftGauntletID")->getString();
- adjustBoundItem(item, magnitude > 0, ptr);
item = MWBase::Environment::get().getWorld()->getStore().get().find(
"sMagicBoundRightGauntletID")->getString();
- adjustBoundItem(item, magnitude > 0, ptr);
+ magnitude > 0 ? addBoundItem(item, ptr) : removeBoundItem(item, ptr);
}
- else
- adjustBoundItem(item, magnitude > 0, ptr);
-
- if (magnitude > 0)
- creatureStats.mBoundItems.insert(it->first);
- else
- creatureStats.mBoundItems.erase(it->first);
}
}
@@ -915,7 +989,8 @@ namespace MWMechanics
MWWorld::ContainerStoreIterator torch = inventoryStore.end();
for (MWWorld::ContainerStoreIterator it = inventoryStore.begin(); it != inventoryStore.end(); ++it)
{
- if (it->getTypeName() == typeid(ESM::Light).name())
+ if (it->getTypeName() == typeid(ESM::Light).name() &&
+ it->getClass().canBeEquipped(*it, ptr).first)
{
torch = it;
break;
@@ -936,8 +1011,7 @@ namespace MWMechanics
heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
// If we have a torch and can equip it, then equip it now.
- if (heldIter == inventoryStore.end()
- && torch->getClass().canBeEquipped(*torch, ptr).first == 1)
+ if (heldIter == inventoryStore.end())
{
inventoryStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, torch, ptr);
}
diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp
index 15f2d3dc8..0de1f4d6c 100644
--- a/apps/openmw/mwmechanics/actors.hpp
+++ b/apps/openmw/mwmechanics/actors.hpp
@@ -4,7 +4,6 @@
#include
#include
#include
-#include