Merge upstream/master (& fix merge conflicts)

pull/541/head
AnyOldName3 7 years ago
commit 553094669b

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

@ -37,6 +37,7 @@ Programmers
Britt Mathis (galdor557) Britt Mathis (galdor557)
Capostrophic Capostrophic
cc9cii cc9cii
Cédric Mocquillon
Chris Boyce (slothlife) Chris Boyce (slothlife)
Chris Robinson (KittyCat) Chris Robinson (KittyCat)
Cory F. Cohen (cfcohen) Cory F. Cohen (cfcohen)
@ -62,6 +63,7 @@ Programmers
Evgeniy Mineev (sandstranger) Evgeniy Mineev (sandstranger)
Federico Guerra (FedeWar) Federico Guerra (FedeWar)
Fil Krynicki (filkry) Fil Krynicki (filkry)
Florian Weber (Florianjw)
Gašper Sedej Gašper Sedej
gugus/gus gugus/gus
Hallfaer Tuilinn Hallfaer Tuilinn

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

@ -1,5 +1,22 @@
#!/bin/bash #!/bin/bash
MISSINGTOOLS=0
command -v 7z >/dev/null 2>&1 || { echo "Error: 7z (7zip) is not on the path."; MISSINGTOOLS=1; }
command -v cmake >/dev/null 2>&1 || { echo "Error: cmake (CMake) is not on the path."; MISSINGTOOLS=1; }
if [ $MISSINGTOOLS -ne 0 ]; then
exit 1
fi
WORKINGDIR="$(pwd)"
case "$WORKINGDIR" in
*[[:space:]]*)
echo "Error: Working directory contains spaces."
exit 1
;;
esac
set -euo pipefail set -euo pipefail
APPVEYOR=${APPVEYOR:-} APPVEYOR=${APPVEYOR:-}
@ -636,6 +653,17 @@ fi
-DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" \ -DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" \
-DCMAKE_PREFIX_PATH="$QT_SDK" -DCMAKE_PREFIX_PATH="$QT_SDK"
if [ $CONFIGURATION == "Debug" ]; then
SUFFIX="d"
else
SUFFIX=""
fi
DIR=$(echo "${QT_SDK}" | sed "s,\\\\,/,g" | sed "s,\(.\):,/\\1,")
add_runtime_dlls "${DIR}/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${SUFFIX}.dll
add_qt_platform_dlls "${DIR}/plugins/platforms/qwindows${SUFFIX}.dll"
echo Done. echo Done.
fi fi
} }
@ -710,7 +738,7 @@ if [ ! -z $CI ]; then
fi fi
# NOTE: Disable this when/if we want to run test cases # NOTE: Disable this when/if we want to run test cases
if [ -z $CI ]; then #if [ -z $CI ]; then
echo "- Copying Runtime DLLs..." echo "- Copying Runtime DLLs..."
mkdir -p $BUILD_CONFIG mkdir -p $BUILD_CONFIG
for DLL in $RUNTIME_DLLS; do for DLL in $RUNTIME_DLLS; do
@ -742,7 +770,7 @@ if [ -z $CI ]; then
cp "$DLL" "${BUILD_CONFIG}/platforms" cp "$DLL" "${BUILD_CONFIG}/platforms"
done done
echo echo
fi #fi
if [ -z $VERBOSE ]; then if [ -z $VERBOSE ]; then
printf -- "- Configuring... " printf -- "- Configuring... "

@ -54,7 +54,7 @@ endif()
message(STATUS "Configuring OpenMW...") message(STATUS "Configuring OpenMW...")
set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MAJOR 0)
set(OPENMW_VERSION_MINOR 43) set(OPENMW_VERSION_MINOR 44)
set(OPENMW_VERSION_RELEASE 0) set(OPENMW_VERSION_RELEASE 0)
set(OPENMW_VERSION_COMMITHASH "") set(OPENMW_VERSION_COMMITHASH "")
@ -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") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-but-set-parameter")
endif() endif()
elseif (MSVC) 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 # Enable link-time code generation globally for all linking
if (OPENMW_LTO_BUILD) if (OPENMW_LTO_BUILD)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GL") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GL")
@ -707,8 +705,7 @@ if (WIN32)
endif() endif()
if (BUILD_OPENMW) if (BUILD_OPENMW)
# Very specific issue this, only needed on 32-bit VS2015 during unity builds. if (OPENMW_UNITY_BUILD)
if (MSVC_VERSION GREATER 1800 AND CMAKE_SIZEOF_VOID_P EQUAL 4 AND OPENMW_UNITY_BUILD)
set_target_properties(openmw PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD} /bigobj") set_target_properties(openmw PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD} /bigobj")
else() else()
set_target_properties(openmw PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") set_target_properties(openmw PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")

@ -1,13 +1,13 @@
OpenMW OpenMW
====== ======
[![Build Status](https://api.travis-ci.org/OpenMW/openmw.svg)](https://travis-ci.org/OpenMW/openmw) [![Build status](https://ci.appveyor.com/api/projects/status/e6bqw8oouy8ufd46?svg=true)](https://ci.appveyor.com/project/scrawl/openmw) [![Coverity Scan Build Status](https://scan.coverity.com/projects/3740/badge.svg)](https://scan.coverity.com/projects/3740) [![Build Status](https://api.travis-ci.org/OpenMW/openmw.svg)](https://travis-ci.org/OpenMW/openmw) [![Build status](https://ci.appveyor.com/api/projects/status/github/openmw/openmw?svg=true)](https://ci.appveyor.com/project/psi29a/openmw) [![Coverity Scan Build Status](https://scan.coverity.com/projects/3740/badge.svg)](https://scan.coverity.com/projects/3740) [![pipeline status](https://gitlab.com/OpenMW/openmw/badges/master/pipeline.svg)](https://gitlab.com/OpenMW/openmw/commits/master)
OpenMW is a recreation of the engine for the popular role-playing game Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work. OpenMW is an open-source game engine that supports playing Morrowind by Bethesda Softworks. You need to own the game for OpenMW to play Morrowind.
OpenMW also comes with OpenMW-CS, a replacement for Morrowind's TES Construction Set. OpenMW also comes with OpenMW-CS, a replacement for Bethesda's Construction Set.
* Version: 0.43.0 * Version: 0.44.0
* License: GPLv3 (see [LICENSE](https://github.com/OpenMW/openmw/blob/master/LICENSE) for more information) * License: GPLv3 (see [LICENSE](https://github.com/OpenMW/openmw/blob/master/LICENSE) for more information)
* Website: https://www.openmw.org * Website: https://www.openmw.org
* IRC: #openmw on irc.freenode.net * IRC: #openmw on irc.freenode.net

@ -8,6 +8,7 @@ set(LAUNCHER
settingspage.cpp settingspage.cpp
advancedpage.cpp advancedpage.cpp
utils/cellnameloader.cpp
utils/profilescombobox.cpp utils/profilescombobox.cpp
utils/textinputdialog.cpp utils/textinputdialog.cpp
utils/lineedit.cpp utils/lineedit.cpp
@ -24,6 +25,7 @@ set(LAUNCHER_HEADER
settingspage.hpp settingspage.hpp
advancedpage.hpp advancedpage.hpp
utils/cellnameloader.hpp
utils/profilescombobox.hpp utils/profilescombobox.hpp
utils/textinputdialog.hpp utils/textinputdialog.hpp
utils/lineedit.hpp utils/lineedit.hpp
@ -39,6 +41,7 @@ set(LAUNCHER_HEADER_MOC
settingspage.hpp settingspage.hpp
advancedpage.hpp advancedpage.hpp
utils/cellnameloader.hpp
utils/textinputdialog.hpp utils/textinputdialog.hpp
utils/profilescombobox.hpp utils/profilescombobox.hpp
utils/lineedit.hpp utils/lineedit.hpp

@ -1,10 +1,18 @@
#include "advancedpage.hpp" #include "advancedpage.hpp"
#include <components/files/configurationmanager.hpp> #include <components/config/gamesettings.hpp>
#include <components/config/launchersettings.hpp>
Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent) #include <QFileDialog>
#include <QCompleter>
#include <components/contentselector/view/contentselector.hpp>
#include <components/contentselector/model/esmfile.hpp>
Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg,
Config::GameSettings &gameSettings,
Settings::Manager &engineSettings, QWidget *parent)
: QWidget(parent) : QWidget(parent)
, mCfgMgr(cfg) , mCfgMgr(cfg)
, mGameSettings(gameSettings)
, mEngineSettings(engineSettings) , mEngineSettings(engineSettings)
{ {
setObjectName ("AdvancedPage"); setObjectName ("AdvancedPage");
@ -13,8 +21,54 @@ Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg, Settings:
loadSettings(); loadSettings();
} }
void Launcher::AdvancedPage::loadCellsForAutocomplete(QStringList cellNames) {
// Set up an auto-completer for the "Start default character at" field
auto *completer = new QCompleter(cellNames);
completer->setCompletionMode(QCompleter::PopupCompletion);
completer->setCaseSensitivity(Qt::CaseSensitivity::CaseInsensitive);
startDefaultCharacterAtField->setCompleter(completer);
}
void Launcher::AdvancedPage::on_skipMenuCheckBox_stateChanged(int state) {
startDefaultCharacterAtLabel->setEnabled(state == Qt::Checked);
startDefaultCharacterAtField->setEnabled(state == Qt::Checked);
}
void Launcher::AdvancedPage::on_runScriptAfterStartupBrowseButton_clicked()
{
QString scriptFile = QFileDialog::getOpenFileName(
this,
QObject::tr("Select script file"),
QDir::currentPath(),
QString(tr("Text file (*.txt)")));
if (scriptFile.isEmpty())
return;
QFileInfo info(scriptFile);
if (!info.exists() || !info.isReadable())
return;
const QString path(QDir::toNativeSeparators(info.absoluteFilePath()));
}
bool Launcher::AdvancedPage::loadSettings() bool Launcher::AdvancedPage::loadSettings()
{ {
// Testing
bool skipMenu = mGameSettings.value("skip-menu").toInt() == 1;
if (skipMenu) {
skipMenuCheckBox->setCheckState(Qt::Checked);
}
startDefaultCharacterAtLabel->setEnabled(skipMenu);
startDefaultCharacterAtField->setEnabled(skipMenu);
startDefaultCharacterAtField->setText(mGameSettings.value("start"));
runScriptAfterStartupField->setText(mGameSettings.value("script-run"));
// Game Settings // Game Settings
loadSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game"); loadSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game");
loadSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game"); loadSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game");
@ -54,6 +108,19 @@ void Launcher::AdvancedPage::saveSettings()
// Ensure we only set the new settings if they changed. This is to avoid cluttering the // Ensure we only set the new settings if they changed. This is to avoid cluttering the
// user settings file (which by definition should only contain settings the user has touched) // user settings file (which by definition should only contain settings the user has touched)
// Testing
int skipMenu = skipMenuCheckBox->checkState() == Qt::Checked;
if (skipMenu != mGameSettings.value("skip-menu").toInt())
mGameSettings.setValue("skip-menu", QString::number(skipMenu));
QString startCell = startDefaultCharacterAtField->text();
if (startCell != mGameSettings.value("start")) {
mGameSettings.setValue("start", startCell);
}
QString scriptRun = runScriptAfterStartupField->text();
if (scriptRun != mGameSettings.value("script-run"))
mGameSettings.setValue("script-run", scriptRun);
// Game Settings // Game Settings
saveSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game"); saveSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game");
saveSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game"); saveSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game");
@ -95,4 +162,9 @@ void Launcher::AdvancedPage::saveSettingBool(QCheckBox *checkbox, const std::str
bool cValue = checkbox->checkState(); bool cValue = checkbox->checkState();
if (cValue != mEngineSettings.getBool(setting, group)) if (cValue != mEngineSettings.getBool(setting, group))
mEngineSettings.setBool(setting, group, cValue); mEngineSettings.setBool(setting, group, cValue);
}
void Launcher::AdvancedPage::slotLoadedCellsChanged(QStringList cellNames)
{
loadCellsForAutocomplete(cellNames);
} }

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

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

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

@ -1,6 +1,7 @@
#include "graphicspage.hpp" #include "graphicspage.hpp"
#include <boost/math/common_factor.hpp> #include <boost/math/common_factor.hpp>
#include <csignal>
#include <QDesktopWidget> #include <QDesktopWidget>
#include <QMessageBox> #include <QMessageBox>
#include <QDir> #include <QDir>
@ -11,6 +12,7 @@
#define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ #define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__
#endif // MAC_OS_X_VERSION_MIN_REQUIRED #endif // MAC_OS_X_VERSION_MIN_REQUIRED
#include <SDL.h>
#include <SDL_video.h> #include <SDL_video.h>
#include <components/files/configurationmanager.hpp> #include <components/files/configurationmanager.hpp>
@ -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 Launcher::GraphicsPage::setupSDL()
{ {
bool sdlConnectSuccessful = connectToSdl();
if (!sdlConnectSuccessful)
{
return false;
}
int displays = SDL_GetNumVideoDisplays(); int displays = SDL_GetNumVideoDisplays();
if (displays < 0) if (displays < 0)
@ -67,6 +89,9 @@ bool Launcher::GraphicsPage::setupSDL()
screenComboBox->addItem(QString(tr("Screen ")) + QString::number(i + 1)); screenComboBox->addItem(QString(tr("Screen ")) + QString::number(i + 1));
} }
// Disconnect from SDL processes
SDL_Quit();
return true; return true;
} }

@ -37,6 +37,11 @@ namespace Launcher
QStringList getAvailableResolutions(int screen); QStringList getAvailableResolutions(int screen);
QRect getMaximumResolution(); 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(); bool setupSDL();
}; };
} }

@ -1,5 +1,4 @@
#include <iostream> #include <iostream>
#include <csignal>
#include <QApplication> #include <QApplication>
#include <QTextCodec> #include <QTextCodec>
@ -12,24 +11,12 @@
#define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ #define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__
#endif // MAC_OS_X_VERSION_MIN_REQUIRED #endif // MAC_OS_X_VERSION_MIN_REQUIRED
#include <SDL.h>
#include "maindialog.hpp" #include "maindialog.hpp"
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
try 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); QApplication app(argc, argv);
// Now we make sure the current dir is set to application path // 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) if (result == Launcher::FirstRunDialogResultContinue)
mainWin.show(); mainWin.show();
int returnValue = app.exec(); return app.exec();
SDL_Quit();
return returnValue;
} }
catch (std::exception& e) catch (std::exception& e)
{ {
std::cerr << "ERROR: " << e.what() << std::endl; std::cerr << "ERROR: " << e.what() << std::endl;
return 0; return 0;
} }
} }

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

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

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

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

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

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

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

@ -184,11 +184,11 @@ CSMWorld::RefIdCollection::RefIdCollection()
mColumns.back().addColumn( mColumns.back().addColumn(
new RefIdColumn (Columns::ColumnId_PosZ, CSMWorld::ColumnBase::Display_Float)); new RefIdColumn (Columns::ColumnId_PosZ, CSMWorld::ColumnBase::Display_Float));
mColumns.back().addColumn( mColumns.back().addColumn(
new RefIdColumn (Columns::ColumnId_RotX, CSMWorld::ColumnBase::Display_Float)); new RefIdColumn (Columns::ColumnId_RotX, CSMWorld::ColumnBase::Display_Double));
mColumns.back().addColumn( mColumns.back().addColumn(
new RefIdColumn (Columns::ColumnId_RotY, CSMWorld::ColumnBase::Display_Float)); new RefIdColumn (Columns::ColumnId_RotY, CSMWorld::ColumnBase::Display_Double));
mColumns.back().addColumn( mColumns.back().addColumn(
new RefIdColumn (Columns::ColumnId_RotZ, CSMWorld::ColumnBase::Display_Float)); new RefIdColumn (Columns::ColumnId_RotZ, CSMWorld::ColumnBase::Display_Double));
// Nested table // Nested table
mColumns.push_back(RefIdColumn (Columns::ColumnId_AiPackageList, mColumns.push_back(RefIdColumn (Columns::ColumnId_AiPackageList,

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

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

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

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

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

@ -499,7 +499,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
else else
gameControllerdb = ""; //if it doesn't exist, pass in an empty string 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); mEnvironment.setInputManager (input);
std::string myguiResources = (mResDir / "mygui").string(); std::string myguiResources = (mResDir / "mygui").string();
@ -656,8 +656,11 @@ void OMW::Engine::go()
settingspath = loadSettings (settings); settingspath = loadSettings (settings);
mScreenCaptureHandler = new osgViewer::ScreenCaptureHandler(new WriteScreenshotToFileOperation(mCfgMgr.getUserDataPath().string(), mScreenCaptureOperation = new WriteScreenshotToFileOperation(mCfgMgr.getUserDataPath().string(),
Settings::Manager::getString("screenshot format", "General"))); Settings::Manager::getString("screenshot format", "General"));
mScreenCaptureHandler = new osgViewer::ScreenCaptureHandler(mScreenCaptureOperation);
mViewer->addEventHandler(mScreenCaptureHandler); mViewer->addEventHandler(mScreenCaptureHandler);
mEnvironment.setFrameRateLimit(Settings::Manager::getFloat("framerate limit", "Video")); mEnvironment.setFrameRateLimit(Settings::Manager::getFloat("framerate limit", "Video"));

@ -7,7 +7,7 @@
#include <components/settings/settings.hpp> #include <components/settings/settings.hpp>
#include <osgViewer/Viewer> #include <osgViewer/Viewer>
#include <osgViewer/ViewerEventHandlers>
#include "mwbase/environment.hpp" #include "mwbase/environment.hpp"
@ -82,6 +82,7 @@ namespace OMW
boost::filesystem::path mResDir; boost::filesystem::path mResDir;
osg::ref_ptr<osgViewer::Viewer> mViewer; osg::ref_ptr<osgViewer::Viewer> mViewer;
osg::ref_ptr<osgViewer::ScreenCaptureHandler> mScreenCaptureHandler; osg::ref_ptr<osgViewer::ScreenCaptureHandler> mScreenCaptureHandler;
osgViewer::ScreenCaptureHandler::CaptureOperation *mScreenCaptureOperation;
std::string mCellName; std::string mCellName;
std::vector<std::string> mContentFiles; std::vector<std::string> mContentFiles;
bool mSkipMenu; bool mSkipMenu;

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

@ -120,6 +120,7 @@ namespace MWBase
virtual bool toggleWater() = 0; virtual bool toggleWater() = 0;
virtual bool toggleWorld() = 0; virtual bool toggleWorld() = 0;
virtual bool toggleBorders() = 0;
virtual void adjustSky() = 0; virtual void adjustSky() = 0;
@ -450,6 +451,7 @@ namespace MWBase
/// \todo this does not belong here /// \todo this does not belong here
virtual void screenshot (osg::Image* image, int w, int h) = 0; 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 /// Find default position inside exterior cell specified by name
/// \return false if exterior with given name not exists, true otherwise /// \return false if exterior with given name not exists, true otherwise
@ -564,6 +566,8 @@ namespace MWBase
virtual bool isPlayerInJail() const = 0; virtual bool isPlayerInJail() const = 0;
virtual void rotateWorldObject (const MWWorld::Ptr& ptr, osg::Quat rotate) = 0;
/// Return terrain height at \a worldPos position. /// Return terrain height at \a worldPos position.
virtual float getTerrainHeightAt(const osg::Vec3f& worldPos) const = 0; virtual float getTerrainHeightAt(const osg::Vec3f& worldPos) const = 0;

@ -31,7 +31,7 @@ namespace MWClass
if (!model.empty()) if (!model.empty())
{ {
physics.addActor(ptr, model); physics.addActor(ptr, model);
if (getCreatureStats(ptr).isDead()) if (getCreatureStats(ptr).isDead() && getCreatureStats(ptr).isDeathAnimationFinished())
MWBase::Environment::get().getWorld()->enableActorCollision(ptr, false); MWBase::Environment::get().getWorld()->enableActorCollision(ptr, false);
} }
} }

@ -139,24 +139,21 @@ namespace MWClass
const std::string trapActivationSound = "Disarm Trap Fail"; const std::string trapActivationSound = "Disarm Trap Fail";
MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr();
const MWWorld::InventoryStore& invStore = player.getClass().getInventoryStore(player); MWWorld::InventoryStore& invStore = player.getClass().getInventoryStore(player);
bool isLocked = ptr.getCellRef().getLockLevel() > 0; bool isLocked = ptr.getCellRef().getLockLevel() > 0;
bool isTrapped = !ptr.getCellRef().getTrap().empty(); bool isTrapped = !ptr.getCellRef().getTrap().empty();
bool hasKey = false; bool hasKey = false;
std::string keyName; std::string keyName;
// make key id lowercase const std::string keyId = ptr.getCellRef().getKey();
std::string keyId = ptr.getCellRef().getKey(); if (!keyId.empty())
Misc::StringUtils::lowerCaseInPlace(keyId);
for (MWWorld::ConstContainerStoreIterator it = invStore.cbegin(); it != invStore.cend(); ++it)
{ {
std::string refId = it->getCellRef().getRefId(); MWWorld::Ptr keyPtr = invStore.search(keyId);
Misc::StringUtils::lowerCaseInPlace(refId); if (!keyPtr.isEmpty())
if (refId == keyId)
{ {
hasKey = true; hasKey = true;
keyName = it->getClass().getName(*it); keyName = keyPtr.getClass().getName(keyPtr);
} }
} }
@ -242,7 +239,8 @@ namespace MWClass
info.caption = ref->mBase->mName; info.caption = ref->mBase->mName;
std::string text; 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()); text += "\n#{sLockLevel}: " + MWGui::ToolTips::toString(ptr.getCellRef().getLockLevel());
else if (ptr.getCellRef().getLockLevel() < 0) else if (ptr.getCellRef().getLockLevel() < 0)
text += "\n#{sUnlocked}"; text += "\n#{sUnlocked}";
@ -274,15 +272,16 @@ namespace MWClass
void Container::lock (const MWWorld::Ptr& ptr, int lockLevel) const void Container::lock (const MWWorld::Ptr& ptr, int lockLevel) const
{ {
if(lockLevel!=0) if(lockLevel != 0)
ptr.getCellRef().setLockLevel(abs(lockLevel)); //Changes lock to locklevel, in positive ptr.getCellRef().setLockLevel(abs(lockLevel)); //Changes lock to locklevel, if positive
else 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 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 bool Container::canLock(const MWWorld::ConstPtr &ptr) const

@ -135,8 +135,9 @@ namespace MWClass
data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Flee, ref->mBase->mAiData.mFlee); data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Flee, ref->mBase->mAiData.mFlee);
data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Alarm, ref->mBase->mAiData.mAlarm); 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()) if (data->mCreatureStats.isDead())
data->mCreatureStats.setDeathAnimationFinished(true); data->mCreatureStats.setDeathAnimationFinished(ptr.getClass().isPersistent(ptr));
// spells // spells
for (std::vector<std::string>::const_iterator iter (ref->mBase->mSpells.mList.begin()); for (std::vector<std::string>::const_iterator iter (ref->mBase->mSpells.mList.begin());
@ -814,6 +815,9 @@ namespace MWClass
if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead()) if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead())
return; return;
if (!creatureStats.isDeathAnimationFinished())
return;
const MWWorld::Store<ESM::GameSetting>& gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>(); const MWWorld::Store<ESM::GameSetting>& gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->getFloat(); static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->getFloat();
static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->getFloat(); static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->getFloat();

@ -114,42 +114,44 @@ namespace MWClass
const std::string lockedSound = "LockedDoor"; const std::string lockedSound = "LockedDoor";
const std::string trapActivationSound = "Disarm Trap Fail"; const std::string trapActivationSound = "Disarm Trap Fail";
const MWWorld::ContainerStore &invStore = actor.getClass().getContainerStore(actor); MWWorld::ContainerStore &invStore = actor.getClass().getContainerStore(actor);
bool isLocked = ptr.getCellRef().getLockLevel() > 0; bool isLocked = ptr.getCellRef().getLockLevel() > 0;
bool isTrapped = !ptr.getCellRef().getTrap().empty(); bool isTrapped = !ptr.getCellRef().getTrap().empty();
bool hasKey = false; bool hasKey = false;
std::string keyName; 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<MWWorld::Action> action(new MWWorld::FailedAction(std::string(), ptr));
action->setSound(lockedSound);
return action;
}
// make door glow if player activates it with telekinesis // make door glow if player activates it with telekinesis
if (actor == MWBase::Environment::get().getWorld()->getPlayerPtr() && if (actor == MWMechanics::getPlayer() &&
MWBase::Environment::get().getWorld()->getDistanceToFacedObject() > MWBase::Environment::get().getWorld()->getDistanceToFacedObject() >
MWBase::Environment::get().getWorld()->getMaxActivationDistance()) MWBase::Environment::get().getWorld()->getMaxActivationDistance())
{ {
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr); 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"); int index = ESM::MagicEffect::effectStringToId("sEffectTelekinesis");
const ESM::MagicEffect *effect = store.get<ESM::MagicEffect>().find(index); const ESM::MagicEffect *effect = store.get<ESM::MagicEffect>().find(index);
animation->addSpellCastGlow(effect, 1); // 1 second glow to match the time taken for a door opening or closing animation->addSpellCastGlow(effect, 1); // 1 second glow to match the time taken for a door opening or closing
} }
// make key id lowercase const std::string keyId = ptr.getCellRef().getKey();
std::string keyId = ptr.getCellRef().getKey();
if (!keyId.empty()) if (!keyId.empty())
{ {
Misc::StringUtils::lowerCaseInPlace(keyId); MWWorld::Ptr keyPtr = invStore.search(keyId);
for (MWWorld::ConstContainerStoreIterator it = invStore.cbegin(); it != invStore.cend(); ++it) if (!keyPtr.isEmpty())
{ {
std::string refId = it->getCellRef().getRefId(); hasKey = true;
Misc::StringUtils::lowerCaseInPlace(refId); keyName = keyPtr.getClass().getName(keyPtr);
if (refId == keyId)
{
hasKey = true;
keyName = it->getClass().getName(*it);
break;
}
} }
} }
@ -191,7 +193,7 @@ namespace MWClass
std::shared_ptr<MWWorld::Action> action(new MWWorld::ActionTeleport (ptr.getCellRef().getDestCell(), ptr.getCellRef().getDoorDest(), true)); std::shared_ptr<MWWorld::Action> action(new MWWorld::ActionTeleport (ptr.getCellRef().getDestCell(), ptr.getCellRef().getDoorDest(), true));
action->setSound(openSound); action->setSound(openSound);
return action; return action;
} }
} }
else else
{ {
@ -238,15 +240,16 @@ namespace MWClass
void Door::lock (const MWWorld::Ptr& ptr, int lockLevel) const void Door::lock (const MWWorld::Ptr& ptr, int lockLevel) const
{ {
if(lockLevel!=0) if(lockLevel != 0)
ptr.getCellRef().setLockLevel(abs(lockLevel)); //Changes lock to locklevel, in positive ptr.getCellRef().setLockLevel(abs(lockLevel)); //Changes lock to locklevel, if positive
else 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 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 bool Door::canLock(const MWWorld::ConstPtr &ptr) const
@ -298,7 +301,8 @@ namespace MWClass
text += "\n" + getDestination(*ref); 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()); text += "\n#{sLockLevel}: " + MWGui::ToolTips::toString(ptr.getCellRef().getLockLevel());
else if (ptr.getCellRef().getLockLevel() < 0) else if (ptr.getCellRef().getLockLevel() < 0)
text += "\n#{sUnlocked}"; text += "\n#{sUnlocked}";

@ -352,8 +352,10 @@ namespace MWClass
data->mNpcStats.setNeedRecalcDynamicStats(true); data->mNpcStats.setNeedRecalcDynamicStats(true);
} }
// Persistent actors with 0 health do not play death animation
if (data->mNpcStats.isDead()) if (data->mNpcStats.isDead())
data->mNpcStats.setDeathAnimationFinished(true); data->mNpcStats.setDeathAnimationFinished(ptr.getClass().isPersistent(ptr));
// race powers // race powers
const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace); const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace);
@ -1351,6 +1353,9 @@ namespace MWClass
if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead()) if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead())
return; return;
if (!creatureStats.isDeathAnimationFinished())
return;
const MWWorld::Store<ESM::GameSetting>& gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>(); const MWWorld::Store<ESM::GameSetting>& gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->getFloat(); static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->getFloat();
static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->getFloat(); static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->getFloat();

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

@ -491,10 +491,11 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co
return !Misc::StringUtils::ciEqual(mActor.get<ESM::NPC>()->mBase->mRace, select.getName()); return !Misc::StringUtils::ciEqual(mActor.get<ESM::NPC>()->mBase->mRace, select.getName());
case SelectWrapper::Function_NotCell: case SelectWrapper::Function_NotCell:
{
return !Misc::StringUtils::ciEqual(MWBase::Environment::get().getWorld()->getCellName(mActor.getCell()) const std::string& actorCell = MWBase::Environment::get().getWorld()->getCellName(mActor.getCell());
, select.getName()); return !(actorCell.length() >= select.getName().length()
&& Misc::StringUtils::ciEqual(actorCell.substr(0, select.getName().length()), select.getName()));
}
case SelectWrapper::Function_SameGender: case SelectWrapper::Function_SameGender:
return (player.get<ESM::NPC>()->mBase->mFlags & ESM::NPC::Female)== return (player.get<ESM::NPC>()->mBase->mFlags & ESM::NPC::Female)==

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

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

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

@ -153,7 +153,7 @@ namespace MWGui
virtual osg::BoundingSphere computeBound(const osg::Node&) const { return osg::BoundingSphere(); } virtual osg::BoundingSphere computeBound(const osg::Node&) const { return osg::BoundingSphere(); }
}; };
void LoadingScreen::loadingOn() void LoadingScreen::loadingOn(bool visible)
{ {
mLoadingOnTime = mTimer.time_m(); mLoadingOnTime = mTimer.time_m();
// Early-out if already on // 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() // 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); 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); == MWBase::StateManager::State_NoGame);
setVisible(true); setVisible(true);
@ -180,10 +183,15 @@ namespace MWGui
} }
MWBase::Environment::get().getWindowManager()->pushGuiMode(mShowWallpaper ? GM_LoadingWallpaper : GM_Loading); MWBase::Environment::get().getWindowManager()->pushGuiMode(mShowWallpaper ? GM_LoadingWallpaper : GM_Loading);
if (!mVisible)
draw();
} }
void LoadingScreen::loadingOff() void LoadingScreen::loadingOff()
{ {
mLoadingBox->setVisible(true); // restore
if (mLastRenderTime < mLoadingOnTime) if (mLastRenderTime < mLoadingOnTime)
{ {
// the loading was so fast that we didn't show loading screen at all // the loading was so fast that we didn't show loading screen at all
@ -306,7 +314,7 @@ namespace MWGui
void LoadingScreen::draw() void LoadingScreen::draw()
{ {
if (!needToDrawLoadingScreen()) if (mVisible && !needToDrawLoadingScreen())
return; return;
if (mShowWallpaper && mTimer.time_m() > mLastWallpaperChangeTime + 5000*1) if (mShowWallpaper && mTimer.time_m() > mLastWallpaperChangeTime + 5000*1)

@ -35,7 +35,7 @@ namespace MWGui
/// Overridden from Loading::Listener, see the Loading::Listener documentation for usage details /// Overridden from Loading::Listener, see the Loading::Listener documentation for usage details
virtual void setLabel (const std::string& label, bool important); virtual void setLabel (const std::string& label, bool important);
virtual void loadingOn(); virtual void loadingOn(bool visible=true);
virtual void loadingOff(); virtual void loadingOff();
virtual void setProgressRange (size_t range); virtual void setProgressRange (size_t range);
virtual void setProgress (size_t value); virtual void setProgress (size_t value);
@ -63,6 +63,8 @@ namespace MWGui
bool mImportantLabel; bool mImportantLabel;
bool mVisible;
size_t mProgress; size_t mProgress;
bool mShowWallpaper; bool mShowWallpaper;

@ -81,6 +81,47 @@ namespace MWGui
delete mMagicSelectionDialog; delete mMagicSelectionDialog;
} }
void QuickKeysMenu::onOpen()
{
WindowBase::onOpen();
MWWorld::Ptr player = MWMechanics::getPlayer();
MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player);
// Check if quick keys are still valid
for (int i=0; i<10; ++i)
{
ItemWidget* button = mQuickKeyButtons[i];
int type = mAssigned[i];
switch (type)
{
case Type_Unassigned:
case Type_HandToHand:
case Type_Magic:
break;
case Type_Item:
case Type_MagicItem:
{
MWWorld::Ptr item = *button->getUserData<MWWorld::Ptr>();
// Make sure the item is available and is not broken
if (item.getRefData().getCount() < 1 ||
(item.getClass().hasItemHealth(item) &&
item.getClass().getItemHealth(item) <= 0))
{
// Try searching for a compatible replacement
std::string id = item.getCellRef().getRefId();
item = store.findReplacement(id);
button->setUserData(MWWorld::Ptr(item));
break;
}
}
}
}
}
void QuickKeysMenu::unassign(ItemWidget* key, int index) void QuickKeysMenu::unassign(ItemWidget* key, int index)
{ {
key->clearUserStrings(); key->clearUserStrings();
@ -122,12 +163,10 @@ namespace MWGui
assert(index != -1); assert(index != -1);
mSelectedIndex = index; mSelectedIndex = index;
{ // open assign dialog
// open assign dialog if (!mAssignDialog)
if (!mAssignDialog) mAssignDialog = new QuickKeysMenuAssign(this);
mAssignDialog = new QuickKeysMenuAssign(this); mAssignDialog->setVisible (true);
mAssignDialog->setVisible (true);
}
} }
void QuickKeysMenu::onOkButtonClicked (MyGUI::Widget *sender) void QuickKeysMenu::onOkButtonClicked (MyGUI::Widget *sender)
@ -296,21 +335,16 @@ namespace MWGui
if (type == Type_Item || type == Type_MagicItem) if (type == Type_Item || type == Type_MagicItem)
{ {
MWWorld::Ptr item = *button->getUserData<MWWorld::Ptr>(); MWWorld::Ptr item = *button->getUserData<MWWorld::Ptr>();
// make sure the item is available // Make sure the item is available and is not broken
if (item.getRefData ().getCount() < 1) if (item.getRefData().getCount() < 1 ||
(item.getClass().hasItemHealth(item) &&
item.getClass().getItemHealth(item) <= 0))
{ {
// Try searching for a compatible replacement // Try searching for a compatible replacement
std::string id = item.getCellRef().getRefId(); std::string id = item.getCellRef().getRefId();
for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) item = store.findReplacement(id);
{ button->setUserData(MWWorld::Ptr(item));
if (Misc::StringUtils::ciEqual(it->getCellRef().getRefId(), id))
{
item = *it;
button->setUserData(MWWorld::Ptr(item));
break;
}
}
if (item.getRefData().getCount() < 1) if (item.getRefData().getCount() < 1)
{ {
@ -498,6 +532,9 @@ namespace MWGui
ESM::QuickKeys keys; ESM::QuickKeys keys;
keys.load(reader); keys.load(reader);
MWWorld::Ptr player = MWMechanics::getPlayer();
MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player);
int i=0; int i=0;
for (std::vector<ESM::QuickKeys::QuickKey>::const_iterator it = keys.mKeys.begin(); it != keys.mKeys.end(); ++it) for (std::vector<ESM::QuickKeys::QuickKey>::const_iterator it = keys.mKeys.begin(); it != keys.mKeys.end(); ++it)
{ {
@ -519,22 +556,7 @@ namespace MWGui
case Type_MagicItem: case Type_MagicItem:
{ {
// Find the item by id // Find the item by id
MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::Ptr item = store.findReplacement(id);
MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player);
MWWorld::Ptr item;
for (MWWorld::ContainerStoreIterator iter = store.begin(); iter != store.end(); ++iter)
{
if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), id))
{
if (item.isEmpty() ||
// Prefer the stack with the lowest remaining uses
!item.getClass().hasItemHealth(*iter) ||
iter->getClass().getItemHealth(*iter) < item.getClass().getItemHealth(item))
{
item = *iter;
}
}
}
if (item.isEmpty()) if (item.isEmpty())
unassign(button, i); unassign(button, i);

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

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

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

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

@ -36,12 +36,14 @@ namespace MWInput
SDL_Window* window, SDL_Window* window,
osg::ref_ptr<osgViewer::Viewer> viewer, osg::ref_ptr<osgViewer::Viewer> viewer,
osg::ref_ptr<osgViewer::ScreenCaptureHandler> screenCaptureHandler, osg::ref_ptr<osgViewer::ScreenCaptureHandler> screenCaptureHandler,
osgViewer::ScreenCaptureHandler::CaptureOperation *screenCaptureOperation,
const std::string& userFile, bool userFileExists, const std::string& userFile, bool userFileExists,
const std::string& controllerBindingsFile, bool grab) const std::string& controllerBindingsFile, bool grab)
: mWindow(window) : mWindow(window)
, mWindowVisible(true) , mWindowVisible(true)
, mViewer(viewer) , mViewer(viewer)
, mScreenCaptureHandler(screenCaptureHandler) , mScreenCaptureHandler(screenCaptureHandler)
, mScreenCaptureOperation(screenCaptureOperation)
, mJoystickLastUsed(false) , mJoystickLastUsed(false)
, mPlayer(NULL) , mPlayer(NULL)
, mInputManager(NULL) , mInputManager(NULL)
@ -1033,8 +1035,28 @@ namespace MWInput
void InputManager::screenshot() void InputManager::screenshot()
{ {
mScreenCaptureHandler->setFramesToCapture(1); bool regularScreenshot = true;
mScreenCaptureHandler->captureNextFrame(*mViewer);
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<osg::Image> 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() void InputManager::toggleInventory()

@ -4,6 +4,7 @@
#include "../mwgui/mode.hpp" #include "../mwgui/mode.hpp"
#include <osg/ref_ptr> #include <osg/ref_ptr>
#include <osgViewer/ViewerEventHandlers>
#include <extern/oics/ICSChannelListener.h> #include <extern/oics/ICSChannelListener.h>
#include <extern/oics/ICSInputControlSystem.h> #include <extern/oics/ICSInputControlSystem.h>
@ -14,7 +15,6 @@
#include "../mwbase/inputmanager.hpp" #include "../mwbase/inputmanager.hpp"
namespace MWWorld namespace MWWorld
{ {
class Player; class Player;
@ -74,6 +74,7 @@ namespace MWInput
SDL_Window* window, SDL_Window* window,
osg::ref_ptr<osgViewer::Viewer> viewer, osg::ref_ptr<osgViewer::Viewer> viewer,
osg::ref_ptr<osgViewer::ScreenCaptureHandler> screenCaptureHandler, osg::ref_ptr<osgViewer::ScreenCaptureHandler> screenCaptureHandler,
osgViewer::ScreenCaptureHandler::CaptureOperation *screenCaptureOperation,
const std::string& userFile, bool userFileExists, const std::string& userFile, bool userFileExists,
const std::string& controllerBindingsFile, bool grab); const std::string& controllerBindingsFile, bool grab);
@ -158,6 +159,7 @@ namespace MWInput
bool mWindowVisible; bool mWindowVisible;
osg::ref_ptr<osgViewer::Viewer> mViewer; osg::ref_ptr<osgViewer::Viewer> mViewer;
osg::ref_ptr<osgViewer::ScreenCaptureHandler> mScreenCaptureHandler; osg::ref_ptr<osgViewer::ScreenCaptureHandler> mScreenCaptureHandler;
osgViewer::ScreenCaptureHandler::CaptureOperation *mScreenCaptureOperation;
bool mJoystickLastUsed; bool mJoystickLastUsed;
MWWorld::Player* mPlayer; MWWorld::Player* mPlayer;

@ -50,27 +50,36 @@ bool isConscious(const MWWorld::Ptr& ptr)
return !stats.isDead() && !stats.getKnockedDown(); 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<std::string, int> boundItemsMap;
if (boundItemsMap.empty())
{ {
if (actor.getClass().getContainerStore(actor).count(item) == 0) std::string boundId = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sMagicBoundBootsID")->getString();
{ boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Boots;
MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor);
MWWorld::Ptr newPtr = *store.MWWorld::ContainerStore::add(item, 1, actor); boundId = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sMagicBoundCuirassID")->getString();
MWWorld::ActionEquip action(newPtr); boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Cuirass;
action.execute(actor);
MWWorld::ConstContainerStoreIterator rightHand = store.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); boundId = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sMagicBoundLeftGauntletID")->getString();
// change draw state only if the item is in player's right hand boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_LeftGauntlet;
if (actor == MWMechanics::getPlayer()
&& rightHand != store.end() && newPtr == *rightHand) boundId = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sMagicBoundRightGauntletID")->getString();
{ boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_RightGauntlet;
MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon);
} boundId = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sMagicBoundHelmID")->getString();
} boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Helmet;
boundId = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().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<std::string, int>::iterator it = boundItemsMap.find(itemId);
if (it != boundItemsMap.end())
slot = it->second;
return slot;
} }
class CheckActorCommanded : public MWMechanics::EffectSourceVisitor class CheckActorCommanded : public MWMechanics::EffectSourceVisitor
@ -139,7 +148,6 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float
namespace MWMechanics namespace MWMechanics
{ {
const float aiProcessingDistance = 7168; const float aiProcessingDistance = 7168;
const float sqrAiProcessingDistance = aiProcessingDistance*aiProcessingDistance; 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) void Actors::updateActor (const MWWorld::Ptr& ptr, float duration)
{ {
// magic effects // 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. // The actor was killed by a magic effect. Figure out if the player was responsible for it.
const ActiveSpells& spells = creatureStats.getActiveSpells(); const ActiveSpells& spells = creatureStats.getActiveSpells();
bool killedByPlayer = false;
MWWorld::Ptr player = getPlayer(); MWWorld::Ptr player = getPlayer();
std::set<MWWorld::Ptr> playerFollowers;
getActorsSidingWith(player, playerFollowers);
for (ActiveSpells::TIterator it = spells.begin(); it != spells.end(); ++it) for (ActiveSpells::TIterator it = spells.begin(); it != spells.end(); ++it)
{ {
const ActiveSpells::ActiveSpellParams& spell = it->second; const ActiveSpells::ActiveSpellParams& spell = it->second;
MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spell.mCasterActorId);
for (std::vector<ActiveSpells::ActiveEffect>::const_iterator effectIt = spell.mEffects.begin(); for (std::vector<ActiveSpells::ActiveEffect>::const_iterator effectIt = spell.mEffects.begin();
effectIt != spell.mEffects.end(); ++effectIt) effectIt != spell.mEffects.end(); ++effectIt)
{ {
@ -711,17 +785,19 @@ namespace MWMechanics
isDamageEffect = true; isDamageEffect = true;
} }
MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spell.mCasterActorId); if (isDamageEffect)
if (isDamageEffect && caster == player) {
killedByPlayer = true; 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? // 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(); float magnitude = effects.get(it->first).getMagnitude();
if (found != (magnitude > 0)) 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 itemGmst = it->second;
std::string item = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find( std::string item = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
itemGmst)->getString(); itemGmst)->getString();
magnitude > 0 ? addBoundItem(item, ptr) : removeBoundItem(item, ptr);
if (it->first == ESM::MagicEffect::BoundGloves) if (it->first == ESM::MagicEffect::BoundGloves)
{ {
item = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
"sMagicBoundLeftGauntletID")->getString();
adjustBoundItem(item, magnitude > 0, ptr);
item = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find( item = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
"sMagicBoundRightGauntletID")->getString(); "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(); MWWorld::ContainerStoreIterator torch = inventoryStore.end();
for (MWWorld::ContainerStoreIterator it = inventoryStore.begin(); it != inventoryStore.end(); ++it) 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; torch = it;
break; break;
@ -936,8 +1011,7 @@ namespace MWMechanics
heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
// If we have a torch and can equip it, then equip it now. // If we have a torch and can equip it, then equip it now.
if (heldIter == inventoryStore.end() if (heldIter == inventoryStore.end())
&& torch->getClass().canBeEquipped(*torch, ptr).first == 1)
{ {
inventoryStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, torch, ptr); inventoryStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, torch, ptr);
} }

@ -4,7 +4,6 @@
#include <set> #include <set>
#include <vector> #include <vector>
#include <string> #include <string>
#include <map>
#include <list> #include <list>
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
@ -26,6 +25,9 @@ namespace MWMechanics
{ {
std::map<std::string, int> mDeathCount; std::map<std::string, int> mDeathCount;
void addBoundItem (const std::string& itemId, const MWWorld::Ptr& actor);
void removeBoundItem (const std::string& itemId, const MWWorld::Ptr& actor);
void updateNpc(const MWWorld::Ptr &ptr, float duration); void updateNpc(const MWWorld::Ptr &ptr, float duration);
void adjustMagicEffects (const MWWorld::Ptr& creature); void adjustMagicEffects (const MWWorld::Ptr& creature);

@ -103,9 +103,10 @@ namespace MWMechanics
bool isFleeing(); bool isFleeing();
}; };
AiCombat::AiCombat(const MWWorld::Ptr& actor) : AiCombat::AiCombat(const MWWorld::Ptr& actor)
mTargetActorId(actor.getClass().getCreatureStats(actor).getActorId()) {
{} mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId();
}
AiCombat::AiCombat(const ESM::AiSequence::AiCombat *combat) AiCombat::AiCombat(const ESM::AiSequence::AiCombat *combat)
{ {

@ -54,9 +54,6 @@ namespace MWMechanics
virtual bool shouldCancelPreviousAi() const { return false; } virtual bool shouldCancelPreviousAi() const { return false; }
private: private:
int mTargetActorId;
/// Returns true if combat should end /// Returns true if combat should end
bool attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController); bool attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController);

@ -22,28 +22,32 @@
namespace MWMechanics namespace MWMechanics
{ {
AiEscort::AiEscort(const std::string &actorId, int duration, float x, float y, float z) AiEscort::AiEscort(const std::string &actorId, int duration, float x, float y, float z)
: mActorId(actorId), mX(x), mY(y), mZ(z), mDuration(duration), mRemainingDuration(static_cast<float>(duration)) : mX(x), mY(y), mZ(z), mDuration(duration), mRemainingDuration(static_cast<float>(duration))
, mCellX(std::numeric_limits<int>::max()) , mCellX(std::numeric_limits<int>::max())
, mCellY(std::numeric_limits<int>::max()) , mCellY(std::numeric_limits<int>::max())
{ {
mTargetActorRefId = actorId;
mMaxDist = 450; mMaxDist = 450;
} }
AiEscort::AiEscort(const std::string &actorId, const std::string &cellId,int duration, float x, float y, float z) AiEscort::AiEscort(const std::string &actorId, const std::string &cellId, int duration, float x, float y, float z)
: mActorId(actorId), mCellId(cellId), mX(x), mY(y), mZ(z), mDuration(duration), mRemainingDuration(static_cast<float>(duration)) : mCellId(cellId), mX(x), mY(y), mZ(z), mDuration(duration), mRemainingDuration(static_cast<float>(duration))
, mCellX(std::numeric_limits<int>::max()) , mCellX(std::numeric_limits<int>::max())
, mCellY(std::numeric_limits<int>::max()) , mCellY(std::numeric_limits<int>::max())
{ {
mTargetActorRefId = actorId;
mMaxDist = 450; mMaxDist = 450;
} }
AiEscort::AiEscort(const ESM::AiSequence::AiEscort *escort) AiEscort::AiEscort(const ESM::AiSequence::AiEscort *escort)
: mActorId(escort->mTargetId), mCellId(escort->mCellId), mX(escort->mData.mX), mY(escort->mData.mY), mZ(escort->mData.mZ) : mCellId(escort->mCellId), mX(escort->mData.mX), mY(escort->mData.mY), mZ(escort->mData.mZ)
, mMaxDist(450) , mMaxDist(450)
, mRemainingDuration(escort->mRemainingDuration) , mRemainingDuration(escort->mRemainingDuration)
, mCellX(std::numeric_limits<int>::max()) , mCellX(std::numeric_limits<int>::max())
, mCellY(std::numeric_limits<int>::max()) , mCellY(std::numeric_limits<int>::max())
{ {
mTargetActorRefId = escort->mTargetId;
mTargetActorId = escort->mTargetActorId;
// mDuration isn't saved in the save file, so just giving it "1" for now if the package has a duration. // mDuration isn't saved in the save file, so just giving it "1" for now if the package has a duration.
// The exact value of mDuration only matters for repeating packages. // The exact value of mDuration only matters for repeating packages.
if (mRemainingDuration > 0) // Previously mRemainingDuration could be negative even when mDuration was 0. Checking for > 0 should fix old saves. if (mRemainingDuration > 0) // Previously mRemainingDuration could be negative even when mDuration was 0. Checking for > 0 should fix old saves.
@ -78,7 +82,7 @@ namespace MWMechanics
actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing);
actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false); actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false);
const MWWorld::Ptr follower = MWBase::Environment::get().getWorld()->getPtr(mActorId, false); const MWWorld::Ptr follower = MWBase::Environment::get().getWorld()->getPtr(mTargetActorRefId, false);
const float* const leaderPos = actor.getRefData().getPosition().pos; const float* const leaderPos = actor.getRefData().getPosition().pos;
const float* const followerPos = follower.getRefData().getPosition().pos; const float* const followerPos = follower.getRefData().getPosition().pos;
double differenceBetween[3]; double differenceBetween[3];
@ -119,18 +123,14 @@ namespace MWMechanics
return TypeIdEscort; return TypeIdEscort;
} }
MWWorld::Ptr AiEscort::getTarget() const
{
return MWBase::Environment::get().getWorld()->getPtr(mActorId, false);
}
void AiEscort::writeState(ESM::AiSequence::AiSequence &sequence) const void AiEscort::writeState(ESM::AiSequence::AiSequence &sequence) const
{ {
std::unique_ptr<ESM::AiSequence::AiEscort> escort(new ESM::AiSequence::AiEscort()); std::unique_ptr<ESM::AiSequence::AiEscort> escort(new ESM::AiSequence::AiEscort());
escort->mData.mX = mX; escort->mData.mX = mX;
escort->mData.mY = mY; escort->mData.mY = mY;
escort->mData.mZ = mZ; escort->mData.mZ = mZ;
escort->mTargetId = mActorId; escort->mTargetId = mTargetActorRefId;
escort->mTargetActorId = mTargetActorId;
escort->mRemainingDuration = mRemainingDuration; escort->mRemainingDuration = mRemainingDuration;
escort->mCellId = mCellId; escort->mCellId = mCellId;

@ -24,11 +24,11 @@ namespace MWMechanics
/// Implementation of AiEscort /// Implementation of AiEscort
/** The Actor will escort the specified actor to the world position x, y, z until they reach their position, or they run out of time /** The Actor will escort the specified actor to the world position x, y, z until they reach their position, or they run out of time
\implement AiEscort **/ \implement AiEscort **/
AiEscort(const std::string &actorId,int duration, float x, float y, float z); AiEscort(const std::string &actorId, int duration, float x, float y, float z);
/// Implementation of AiEscortCell /// Implementation of AiEscortCell
/** The Actor will escort the specified actor to the cell position x, y, z until they reach their position, or they run out of time /** The Actor will escort the specified actor to the cell position x, y, z until they reach their position, or they run out of time
\implement AiEscortCell **/ \implement AiEscortCell **/
AiEscort(const std::string &actorId,const std::string &cellId,int duration, float x, float y, float z); AiEscort(const std::string &actorId, const std::string &cellId, int duration, float x, float y, float z);
AiEscort(const ESM::AiSequence::AiEscort* escort); AiEscort(const ESM::AiSequence::AiEscort* escort);
@ -38,7 +38,6 @@ namespace MWMechanics
virtual int getTypeId() const; virtual int getTypeId() const;
MWWorld::Ptr getTarget() const;
virtual bool sideWithTarget() const { return true; } virtual bool sideWithTarget() const { return true; }
void writeState(ESM::AiSequence::AiSequence &sequence) const; void writeState(ESM::AiSequence::AiSequence &sequence) const;
@ -46,7 +45,6 @@ namespace MWMechanics
void fastForward(const MWWorld::Ptr& actor, AiState& state); void fastForward(const MWWorld::Ptr& actor, AiState& state);
private: private:
std::string mActorId;
std::string mCellId; std::string mCellId;
float mX; float mX;
float mY; float mY;

@ -35,32 +35,53 @@ struct AiFollowStorage : AiTemporaryBase
int AiFollow::mFollowIndexCounter = 0; int AiFollow::mFollowIndexCounter = 0;
AiFollow::AiFollow(const std::string &actorId,float duration, float x, float y, float z) AiFollow::AiFollow(const std::string &actorId, float duration, float x, float y, float z)
: mAlwaysFollow(false), mCommanded(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) : mAlwaysFollow(false), mCommanded(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z)
, mActorRefId(actorId), mActorId(-1), mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++) , mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++)
{ {
mTargetActorRefId = actorId;
} }
AiFollow::AiFollow(const std::string &actorId,const std::string &cellId,float duration, float x, float y, float z) AiFollow::AiFollow(const std::string &actorId, const std::string &cellId, float duration, float x, float y, float z)
: mAlwaysFollow(false), mCommanded(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) : mAlwaysFollow(false), mCommanded(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z)
, mActorRefId(actorId), mActorId(-1), mCellId(cellId), mActive(false), mFollowIndex(mFollowIndexCounter++) , mCellId(cellId), mActive(false), mFollowIndex(mFollowIndexCounter++)
{ {
mTargetActorRefId = actorId;
} }
AiFollow::AiFollow(const std::string &actorId, bool commanded) AiFollow::AiFollow(const MWWorld::Ptr& actor, float duration, float x, float y, float z)
: mAlwaysFollow(false), mCommanded(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z)
, mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++)
{
mTargetActorRefId = actor.getCellRef().getRefId();
mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId();
}
AiFollow::AiFollow(const MWWorld::Ptr& actor, const std::string &cellId, float duration, float x, float y, float z)
: mAlwaysFollow(false), mCommanded(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z)
, mCellId(cellId), mActive(false), mFollowIndex(mFollowIndexCounter++)
{
mTargetActorRefId = actor.getCellRef().getRefId();
mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId();
}
AiFollow::AiFollow(const MWWorld::Ptr& actor, bool commanded)
: mAlwaysFollow(true), mCommanded(commanded), mDuration(0), mRemainingDuration(0), mX(0), mY(0), mZ(0) : mAlwaysFollow(true), mCommanded(commanded), mDuration(0), mRemainingDuration(0), mX(0), mY(0), mZ(0)
, mActorRefId(actorId), mActorId(-1), mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++) , mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++)
{ {
mTargetActorRefId = actor.getCellRef().getRefId();
mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId();
} }
AiFollow::AiFollow(const ESM::AiSequence::AiFollow *follow) AiFollow::AiFollow(const ESM::AiSequence::AiFollow *follow)
: mAlwaysFollow(follow->mAlwaysFollow), mCommanded(follow->mCommanded), mRemainingDuration(follow->mRemainingDuration) : mAlwaysFollow(follow->mAlwaysFollow), mCommanded(follow->mCommanded), mRemainingDuration(follow->mRemainingDuration)
, mX(follow->mData.mX), mY(follow->mData.mY), mZ(follow->mData.mZ) , mX(follow->mData.mX), mY(follow->mData.mY), mZ(follow->mData.mZ)
, mActorRefId(follow->mTargetId), mActorId(-1)
, mCellId(follow->mCellId), mActive(follow->mActive), mFollowIndex(mFollowIndexCounter++) , mCellId(follow->mCellId), mActive(follow->mActive), mFollowIndex(mFollowIndexCounter++)
{ {
// mDuration isn't saved in the save file, so just giving it "1" for now if the package had a duration. mTargetActorRefId = follow->mTargetId;
// The exact value of mDuration only matters for repeating packages. mTargetActorId = follow->mTargetActorId;
// mDuration isn't saved in the save file, so just giving it "1" for now if the package had a duration.
// The exact value of mDuration only matters for repeating packages.
if (mRemainingDuration > 0) // Previously mRemainingDuration could be negative even when mDuration was 0. Checking for > 0 should fix old saves. if (mRemainingDuration > 0) // Previously mRemainingDuration could be negative even when mDuration was 0. Checking for > 0 should fix old saves.
mDuration = 1; mDuration = 1;
else else
@ -204,7 +225,7 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte
std::string AiFollow::getFollowedActor() std::string AiFollow::getFollowedActor()
{ {
return mActorRefId; return mTargetActorRefId;
} }
AiFollow *MWMechanics::AiFollow::clone() const AiFollow *MWMechanics::AiFollow::clone() const
@ -228,7 +249,8 @@ void AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) const
follow->mData.mX = mX; follow->mData.mX = mX;
follow->mData.mY = mY; follow->mData.mY = mY;
follow->mData.mZ = mZ; follow->mData.mZ = mZ;
follow->mTargetId = mActorRefId; follow->mTargetId = mTargetActorRefId;
follow->mTargetActorId = mTargetActorId;
follow->mRemainingDuration = mRemainingDuration; follow->mRemainingDuration = mRemainingDuration;
follow->mCellId = mCellId; follow->mCellId = mCellId;
follow->mAlwaysFollow = mAlwaysFollow; follow->mAlwaysFollow = mAlwaysFollow;
@ -241,29 +263,6 @@ void AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) const
sequence.mPackages.push_back(package); sequence.mPackages.push_back(package);
} }
MWWorld::Ptr AiFollow::getTarget() const
{
if (mActorId == -2)
return MWWorld::Ptr();
if (mActorId == -1)
{
MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mActorRefId, false);
if (target.isEmpty())
{
mActorId = -2;
return target;
}
else
mActorId = target.getClass().getCreatureStats(target).getActorId();
}
if (mActorId != -1)
return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mActorId);
else
return MWWorld::Ptr();
}
int AiFollow::getFollowIndex() const int AiFollow::getFollowIndex() const
{ {
return mFollowIndex; return mFollowIndex;

@ -25,16 +25,17 @@ namespace MWMechanics
class AiFollow : public AiPackage class AiFollow : public AiPackage
{ {
public: public:
AiFollow(const std::string &actorId, float duration, float x, float y, float z);
AiFollow(const std::string &actorId, const std::string &CellId, float duration, float x, float y, float z);
/// Follow Actor for duration or until you arrive at a world position /// Follow Actor for duration or until you arrive at a world position
AiFollow(const std::string &ActorId,float duration, float X, float Y, float Z); AiFollow(const MWWorld::Ptr& actor, float duration, float X, float Y, float Z);
/// Follow Actor for duration or until you arrive at a position in a cell /// Follow Actor for duration or until you arrive at a position in a cell
AiFollow(const std::string &ActorId,const std::string &CellId,float duration, float X, float Y, float Z); AiFollow(const MWWorld::Ptr& actor, const std::string &CellId, float duration, float X, float Y, float Z);
/// Follow Actor indefinitively /// Follow Actor indefinitively
AiFollow(const std::string &ActorId, bool commanded=false); AiFollow(const MWWorld::Ptr& actor, bool commanded=false);
AiFollow(const ESM::AiSequence::AiFollow* follow); AiFollow(const ESM::AiSequence::AiFollow* follow);
MWWorld::Ptr getTarget() const;
virtual bool sideWithTarget() const { return true; } virtual bool sideWithTarget() const { return true; }
virtual bool followTargetThroughDoors() const { return true; } virtual bool followTargetThroughDoors() const { return true; }
virtual bool shouldCancelPreviousAi() const { return !mCommanded; } virtual bool shouldCancelPreviousAi() const { return !mCommanded; }
@ -66,8 +67,6 @@ namespace MWMechanics
float mX; float mX;
float mY; float mY;
float mZ; float mZ;
std::string mActorRefId;
mutable int mActorId;
std::string mCellId; std::string mCellId;
bool mActive; // have we spotted the target? bool mActive; // have we spotted the target?
int mFollowIndex; int mFollowIndex;

@ -27,15 +27,36 @@ MWMechanics::AiPackage::~AiPackage() {}
MWMechanics::AiPackage::AiPackage() : MWMechanics::AiPackage::AiPackage() :
mTimer(AI_REACTION_TIME + 1.0f), // to force initial pathbuild mTimer(AI_REACTION_TIME + 1.0f), // to force initial pathbuild
mTargetActorRefId(""),
mTargetActorId(-1),
mRotateOnTheRunChecks(0), mRotateOnTheRunChecks(0),
mIsShortcutting(false), mIsShortcutting(false),
mShortcutProhibited(false), mShortcutFailPos() mShortcutProhibited(false),
mShortcutFailPos()
{ {
} }
MWWorld::Ptr MWMechanics::AiPackage::getTarget() const MWWorld::Ptr MWMechanics::AiPackage::getTarget() const
{ {
return MWWorld::Ptr(); if (mTargetActorId == -2)
return MWWorld::Ptr();
if (mTargetActorId == -1)
{
MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mTargetActorRefId, false);
if (target.isEmpty())
{
mTargetActorId = -2;
return target;
}
else
mTargetActorId = target.getClass().getCreatureStats(target).getActorId();
}
if (mTargetActorId != -1)
return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId);
else
return MWWorld::Ptr();
} }
bool MWMechanics::AiPackage::sideWithTarget() const bool MWMechanics::AiPackage::sideWithTarget() const
@ -99,6 +120,9 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr
if (!isDestReached && mTimer > AI_REACTION_TIME) if (!isDestReached && mTimer > AI_REACTION_TIME)
{ {
if (actor.getClass().isBipedal(actor))
openDoors(actor);
bool wasShortcutting = mIsShortcutting; bool wasShortcutting = mIsShortcutting;
bool destInLOS = false; bool destInLOS = false;
@ -188,41 +212,10 @@ void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor, float dur
// first check if obstacle is a door // first check if obstacle is a door
static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance(); static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance();
MWWorld::Ptr door = getNearbyDoor(actor, distance); const MWWorld::Ptr door = getNearbyDoor(actor, distance);
if (door != MWWorld::Ptr() && actor.getClass().isBipedal(actor)) if (!door.isEmpty() && actor.getClass().isBipedal(actor))
{ {
// note: AiWander currently does not open doors openDoors(actor);
if (getTypeId() != TypeIdWander && !door.getCellRef().getTeleport() && door.getClass().getDoorState(door) == 0)
{
if ((door.getCellRef().getTrap().empty() && door.getCellRef().getLockLevel() <= 0 ))
{
MWBase::Environment::get().getWorld()->activate(door, actor);
return;
}
std::string keyId = door.getCellRef().getKey();
if (keyId.empty())
return;
bool hasKey = false;
const MWWorld::ContainerStore &invStore = actor.getClass().getContainerStore(actor);
// make key id lowercase
Misc::StringUtils::lowerCaseInPlace(keyId);
for (MWWorld::ConstContainerStoreIterator it = invStore.cbegin(); it != invStore.cend(); ++it)
{
std::string refId = it->getCellRef().getRefId();
Misc::StringUtils::lowerCaseInPlace(refId);
if (refId == keyId)
{
hasKey = true;
break;
}
}
if (hasKey)
MWBase::Environment::get().getWorld()->activate(door, actor);
}
} }
else else
{ {
@ -230,6 +223,35 @@ void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor, float dur
} }
} }
void MWMechanics::AiPackage::openDoors(const MWWorld::Ptr& actor)
{
static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance();
const MWWorld::Ptr door = getNearbyDoor(actor, distance);
if (door == MWWorld::Ptr())
return;
// note: AiWander currently does not open doors
if (getTypeId() != TypeIdWander && !door.getCellRef().getTeleport() && door.getClass().getDoorState(door) == 0)
{
if ((door.getCellRef().getTrap().empty() && door.getCellRef().getLockLevel() <= 0 ))
{
MWBase::Environment::get().getWorld()->activate(door, actor);
return;
}
const std::string keyId = door.getCellRef().getKey();
if (keyId.empty())
return;
MWWorld::ContainerStore &invStore = actor.getClass().getContainerStore(actor);
MWWorld::Ptr keyPtr = invStore.search(keyId);
if (!keyPtr.isEmpty())
MWBase::Environment::get().getWorld()->activate(door, actor);
}
}
const MWMechanics::PathgridGraph& MWMechanics::AiPackage::getPathGridGraph(const MWWorld::CellStore *cell) const MWMechanics::PathgridGraph& MWMechanics::AiPackage::getPathGridGraph(const MWWorld::CellStore *cell)
{ {
const ESM::CellId& id = cell->getCell()->getCellId(); const ESM::CellId& id = cell->getCell()->getCellId();

@ -48,7 +48,8 @@ namespace MWMechanics
TypeIdPursue = 6, TypeIdPursue = 6,
TypeIdAvoidDoor = 7, TypeIdAvoidDoor = 7,
TypeIdFace = 8, TypeIdFace = 8,
TypeIdBreathe = 9 TypeIdBreathe = 9,
TypeIdInternalTravel = 10
}; };
///Default constructor ///Default constructor
@ -79,6 +80,9 @@ namespace MWMechanics
/// Get the target actor the AI is targeted at (not applicable to all AI packages, default return empty Ptr) /// Get the target actor the AI is targeted at (not applicable to all AI packages, default return empty Ptr)
virtual MWWorld::Ptr getTarget() const; virtual MWWorld::Ptr getTarget() const;
/// Get the destination point of the AI package (not applicable to all AI packages, default return (0, 0, 0))
virtual osg::Vec3f getDestination(const MWWorld::Ptr& actor) const { return osg::Vec3f(0, 0, 0); };
/// Return true if having this AiPackage makes the actor side with the target in fights (default false) /// Return true if having this AiPackage makes the actor side with the target in fights (default false)
virtual bool sideWithTarget() const; virtual bool sideWithTarget() const;
@ -119,6 +123,7 @@ namespace MWMechanics
virtual bool doesPathNeedRecalc(const ESM::Pathgrid::Point& newDest, const MWWorld::CellStore* currentCell); virtual bool doesPathNeedRecalc(const ESM::Pathgrid::Point& newDest, const MWWorld::CellStore* currentCell);
void evadeObstacles(const MWWorld::Ptr& actor, float duration, const ESM::Position& pos); void evadeObstacles(const MWWorld::Ptr& actor, float duration, const ESM::Position& pos);
void openDoors(const MWWorld::Ptr& actor);
const PathgridGraph& getPathGridGraph(const MWWorld::CellStore* cell); const PathgridGraph& getPathGridGraph(const MWWorld::CellStore* cell);
@ -128,6 +133,9 @@ namespace MWMechanics
float mTimer; float mTimer;
std::string mTargetActorRefId;
mutable int mTargetActorId;
osg::Vec3f mLastActorPos; osg::Vec3f mLastActorPos;
short mRotateOnTheRunChecks; // attempts to check rotation to the pathpoint on the run possibility short mRotateOnTheRunChecks; // attempts to check rotation to the pathpoint on the run possibility

@ -15,13 +15,13 @@ namespace MWMechanics
{ {
AiPursue::AiPursue(const MWWorld::Ptr& actor) AiPursue::AiPursue(const MWWorld::Ptr& actor)
: mTargetActorId(actor.getClass().getCreatureStats(actor).getActorId())
{ {
mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId();
} }
AiPursue::AiPursue(const ESM::AiSequence::AiPursue *pursue) AiPursue::AiPursue(const ESM::AiSequence::AiPursue *pursue)
: mTargetActorId(pursue->mTargetActorId)
{ {
mTargetActorId = pursue->mTargetActorId;
} }
AiPursue *MWMechanics::AiPursue::clone() const AiPursue *MWMechanics::AiPursue::clone() const

@ -40,10 +40,6 @@ namespace MWMechanics
virtual bool canCancel() const { return false; } virtual bool canCancel() const { return false; }
virtual bool shouldCancelPreviousAi() const { return false; } virtual bool shouldCancelPreviousAi() const { return false; }
private:
int mTargetActorId; // The actor to pursue
}; };
} }
#endif #endif

@ -184,7 +184,8 @@ bool isActualAiPackage(int packageTypeId)
&& packageTypeId != AiPackage::TypeIdPursue && packageTypeId != AiPackage::TypeIdPursue
&& packageTypeId != AiPackage::TypeIdAvoidDoor && packageTypeId != AiPackage::TypeIdAvoidDoor
&& packageTypeId != AiPackage::TypeIdFace && packageTypeId != AiPackage::TypeIdFace
&& packageTypeId != AiPackage::TypeIdBreathe); && packageTypeId != AiPackage::TypeIdBreathe
&& packageTypeId != AiPackage::TypeIdInternalTravel);
} }
void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration)
@ -298,7 +299,7 @@ void AiSequence::clear()
mPackages.clear(); mPackages.clear();
} }
void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor) void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, bool cancelOther)
{ {
if (actor == getPlayer()) if (actor == getPlayer())
throw std::runtime_error("Can't add AI packages to player"); throw std::runtime_error("Can't add AI packages to player");
@ -307,8 +308,33 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor)
if (isActualAiPackage(package.getTypeId())) if (isActualAiPackage(package.getTypeId()))
stopCombat(); stopCombat();
// We should return a wandering actor back after combat or pursuit.
// The same thing for actors without AI packages.
// Also there is no point to stack return packages.
int currentTypeId = getTypeId();
int newTypeId = package.getTypeId();
if (currentTypeId <= MWMechanics::AiPackage::TypeIdWander
&& !hasPackage(MWMechanics::AiPackage::TypeIdInternalTravel)
&& (newTypeId <= MWMechanics::AiPackage::TypeIdCombat
|| newTypeId == MWMechanics::AiPackage::TypeIdPursue))
{
osg::Vec3f dest;
if (currentTypeId == MWMechanics::AiPackage::TypeIdWander)
{
AiPackage* activePackage = getActivePackage();
dest = activePackage->getDestination(actor);
}
else
{
dest = actor.getRefData().getPosition().asVec3();
}
MWMechanics::AiTravel travelPackage(dest.x(), dest.y(), dest.z(), true);
stack(travelPackage, actor, false);
}
// remove previous packages if required // remove previous packages if required
if (package.shouldCancelPreviousAi()) if (cancelOther && package.shouldCancelPreviousAi())
{ {
for(std::list<AiPackage *>::iterator it = mPackages.begin(); it != mPackages.end();) for(std::list<AiPackage *>::iterator it = mPackages.begin(); it != mPackages.end();)
{ {
@ -392,6 +418,8 @@ void AiSequence::writeState(ESM::AiSequence::AiSequence &sequence) const
{ {
(*iter)->writeState(sequence); (*iter)->writeState(sequence);
} }
sequence.mLastAiPackage = mLastAiPackage;
} }
void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence) void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence)
@ -403,7 +431,7 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence)
int count = 0; int count = 0;
for (std::vector<ESM::AiSequence::AiPackageContainer>::const_iterator it = sequence.mPackages.begin(); for (std::vector<ESM::AiSequence::AiPackageContainer>::const_iterator it = sequence.mPackages.begin();
it != sequence.mPackages.end(); ++it) it != sequence.mPackages.end(); ++it)
{ {
if (isActualAiPackage(it->mType)) if (isActualAiPackage(it->mType))
count++; count++;
} }
@ -462,6 +490,8 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence)
mPackages.push_back(package.release()); mPackages.push_back(package.release());
} }
mLastAiPackage = sequence.mLastAiPackage;
} }
void AiSequence::fastForward(const MWWorld::Ptr& actor, AiState& state) void AiSequence::fastForward(const MWWorld::Ptr& actor, AiState& state)

@ -115,7 +115,7 @@ namespace MWMechanics
///< Add \a package to the front of the sequence ///< Add \a package to the front of the sequence
/** Suspends current package /** Suspends current package
@param actor The actor that owns this AiSequence **/ @param actor The actor that owns this AiSequence **/
void stack (const AiPackage& package, const MWWorld::Ptr& actor); void stack (const AiPackage& package, const MWWorld::Ptr& actor, bool cancelOther=true);
/// Return the current active package. /// Return the current active package.
/** If there is no active package, it will throw an exception **/ /** If there is no active package, it will throw an exception **/

@ -28,15 +28,14 @@ bool isWithinMaxRange(const osg::Vec3f& pos1, const osg::Vec3f& pos2)
namespace MWMechanics namespace MWMechanics
{ {
AiTravel::AiTravel(float x, float y, float z) AiTravel::AiTravel(float x, float y, float z, bool hidden)
: mX(x),mY(y),mZ(z) : mX(x),mY(y),mZ(z),mHidden(hidden)
{ {
} }
AiTravel::AiTravel(const ESM::AiSequence::AiTravel *travel) AiTravel::AiTravel(const ESM::AiSequence::AiTravel *travel)
: mX(travel->mData.mX), mY(travel->mData.mY), mZ(travel->mData.mZ) : mX(travel->mData.mX), mY(travel->mData.mY), mZ(travel->mData.mZ), mHidden(travel->mHidden)
{ {
} }
AiTravel *MWMechanics::AiTravel::clone() const AiTravel *MWMechanics::AiTravel::clone() const
@ -64,7 +63,7 @@ namespace MWMechanics
int AiTravel::getTypeId() const int AiTravel::getTypeId() const
{ {
return TypeIdTravel; return mHidden ? TypeIdInternalTravel : TypeIdTravel;
} }
void AiTravel::fastForward(const MWWorld::Ptr& actor, AiState& state) void AiTravel::fastForward(const MWWorld::Ptr& actor, AiState& state)
@ -83,6 +82,7 @@ namespace MWMechanics
travel->mData.mX = mX; travel->mData.mX = mX;
travel->mData.mY = mY; travel->mData.mY = mY;
travel->mData.mZ = mZ; travel->mData.mZ = mZ;
travel->mHidden = mHidden;
ESM::AiSequence::AiPackageContainer package; ESM::AiSequence::AiPackageContainer package;
package.mType = ESM::AiSequence::Ai_Travel; package.mType = ESM::AiSequence::Ai_Travel;

@ -20,7 +20,7 @@ namespace MWMechanics
{ {
public: public:
/// Default constructor /// Default constructor
AiTravel(float x, float y, float z); AiTravel(float x, float y, float z, bool hidden = false);
AiTravel(const ESM::AiSequence::AiTravel* travel); AiTravel(const ESM::AiSequence::AiTravel* travel);
/// Simulates the passing of time /// Simulates the passing of time
@ -38,6 +38,8 @@ namespace MWMechanics
float mX; float mX;
float mY; float mY;
float mZ; float mZ;
bool mHidden;
}; };
} }

@ -59,7 +59,7 @@ namespace MWMechanics
float mTargetAngleRadians; float mTargetAngleRadians;
bool mTurnActorGivingGreetingToFacePlayer; bool mTurnActorGivingGreetingToFacePlayer;
float mReaction; // update some actions infrequently float mReaction; // update some actions infrequently
AiWander::GreetingState mSaidGreeting; AiWander::GreetingState mSaidGreeting;
int mGreetingTimer; int mGreetingTimer;
@ -70,7 +70,7 @@ namespace MWMechanics
bool mIsWanderingManually; bool mIsWanderingManually;
bool mCanWanderAlongPathGrid; bool mCanWanderAlongPathGrid;
unsigned short mIdleAnimation; unsigned short mIdleAnimation;
std::vector<unsigned short> mBadIdles; // Idle animations that when called cause errors std::vector<unsigned short> mBadIdles; // Idle animations that when called cause errors
@ -86,7 +86,7 @@ namespace MWMechanics
float mDoorCheckDuration; float mDoorCheckDuration;
int mStuckCount; int mStuckCount;
AiWanderStorage(): AiWanderStorage():
mTargetAngleRadians(0), mTargetAngleRadians(0),
mTurnActorGivingGreetingToFacePlayer(false), mTurnActorGivingGreetingToFacePlayer(false),
@ -111,7 +111,7 @@ namespace MWMechanics
mIsWanderingManually = isManualWander; mIsWanderingManually = isManualWander;
} }
}; };
AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector<unsigned char>& idle, bool repeat): AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector<unsigned char>& idle, bool repeat):
mDistance(distance), mDuration(duration), mRemainingDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), mDistance(distance), mDuration(duration), mRemainingDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle),
mRepeat(repeat), mStoredInitialActorPosition(false), mInitialActorPosition(osg::Vec3f(0, 0, 0)), mHasDestination(false), mDestination(osg::Vec3f(0, 0, 0)) mRepeat(repeat), mStoredInitialActorPosition(false), mInitialActorPosition(osg::Vec3f(0, 0, 0)), mHasDestination(false), mDestination(osg::Vec3f(0, 0, 0))
@ -223,7 +223,7 @@ namespace MWMechanics
if (mPathFinder.isPathConstructed()) if (mPathFinder.isPathConstructed())
storage.setState(Wander_Walking); storage.setState(Wander_Walking);
} }
doPerFrameActionsForState(actor, duration, storage, pos); doPerFrameActionsForState(actor, duration, storage, pos);
playIdleDialogueRandomly(actor); playIdleDialogueRandomly(actor);
@ -298,13 +298,6 @@ namespace MWMechanics
if(mDistance && cellChange) if(mDistance && cellChange)
mDistance = 0; mDistance = 0;
// For stationary NPCs, move back to the starting location if another AiPackage moved us elsewhere
if (mDistance == 0 && !cellChange
&& (pos.asVec3() - mInitialActorPosition).length2() > (DESTINATION_TOLERANCE * DESTINATION_TOLERANCE))
{
returnToStartLocation(actor, storage, pos);
}
// Allow interrupting a walking actor to trigger a greeting // Allow interrupting a walking actor to trigger a greeting
WanderState& wanderState = storage.mState; WanderState& wanderState = storage.mState;
if ((wanderState == Wander_IdleNow) || (wanderState == Wander_Walking)) if ((wanderState == Wander_IdleNow) || (wanderState == Wander_Walking))
@ -321,7 +314,7 @@ namespace MWMechanics
{ {
setPathToAnAllowedNode(actor, storage, pos); setPathToAnAllowedNode(actor, storage, pos);
} }
} }
} else if (storage.mIsWanderingManually && mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DESTINATION_TOLERANCE)) { } else if (storage.mIsWanderingManually && mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DESTINATION_TOLERANCE)) {
completeManualWalking(actor, storage); completeManualWalking(actor, storage);
} }
@ -330,10 +323,19 @@ namespace MWMechanics
} }
bool AiWander::getRepeat() const bool AiWander::getRepeat() const
{ {
return mRepeat; return mRepeat;
} }
osg::Vec3f AiWander::getDestination(const MWWorld::Ptr& actor) const
{
if (mHasDestination)
return mDestination;
const ESM::Pathgrid::Point currentPosition = actor.getRefData().getPosition().pos;
const osg::Vec3f currentPositionVec3f = osg::Vec3f(currentPosition.mX, currentPosition.mY, currentPosition.mZ);
return currentPositionVec3f;
}
bool AiWander::isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage) bool AiWander::isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage)
{ {
@ -342,35 +344,14 @@ namespace MWMechanics
// End package if duration is complete // End package if duration is complete
if (mRemainingDuration <= 0) if (mRemainingDuration <= 0)
{ {
stopWalking(actor, storage); stopWalking(actor, storage);
return true; return true;
} }
} }
// if get here, not yet completed // if get here, not yet completed
return false; return false;
} }
void AiWander::returnToStartLocation(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos)
{
if (!mPathFinder.isPathConstructed())
{
mDestination = mInitialActorPosition;
ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(mDestination));
// actor position is already in world coordinates
ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos));
// don't take shortcuts for wandering
mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell()));
if (mPathFinder.isPathConstructed())
{
storage.setState(Wander_Walking);
mHasDestination = true;
}
}
}
/* /*
* Commands actor to walk to a random location near original spawn location. * Commands actor to walk to a random location near original spawn location.
*/ */
@ -497,7 +478,7 @@ namespace MWMechanics
} }
} }
void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor,
float duration, AiWanderStorage& storage, ESM::Position& pos) float duration, AiWanderStorage& storage, ESM::Position& pos)
{ {
// Is there no destination or are we there yet? // Is there no destination or are we there yet?
@ -873,7 +854,7 @@ namespace MWMechanics
state.moveIn(new AiWanderStorage()); state.moveIn(new AiWanderStorage());
MWBase::Environment::get().getWorld()->moveObject(actor, static_cast<float>(dest.mX), MWBase::Environment::get().getWorld()->moveObject(actor, static_cast<float>(dest.mX),
static_cast<float>(dest.mY), static_cast<float>(dest.mZ)); static_cast<float>(dest.mY), static_cast<float>(dest.mZ));
actor.getClass().adjustPosition(actor, false); actor.getClass().adjustPosition(actor, false);
} }
@ -914,7 +895,7 @@ namespace MWMechanics
// get NPC's position in local (i.e. cell) coordinates // get NPC's position in local (i.e. cell) coordinates
osg::Vec3f npcPos(mInitialActorPosition); osg::Vec3f npcPos(mInitialActorPosition);
CoordinateConverter(cell).toLocal(npcPos); CoordinateConverter(cell).toLocal(npcPos);
// Find closest pathgrid point // Find closest pathgrid point
int closestPointIndex = PathFinder::GetClosestPoint(pathgrid, npcPos); int closestPointIndex = PathFinder::GetClosestPoint(pathgrid, npcPos);
@ -945,7 +926,7 @@ namespace MWMechanics
storage.mPopulateAvailableNodes = false; storage.mPopulateAvailableNodes = false;
} }
// When only one path grid point in wander distance, // When only one path grid point in wander distance,
// additional points for NPC to wander to are: // additional points for NPC to wander to are:
// 1. NPC's initial location // 1. NPC's initial location
// 2. Partway along the path between the point and its connected points. // 2. Partway along the path between the point and its connected points.
@ -969,7 +950,7 @@ namespace MWMechanics
delta.normalize(); delta.normalize();
int distance = std::max(mDistance / 2, MINIMUM_WANDER_DISTANCE); int distance = std::max(mDistance / 2, MINIMUM_WANDER_DISTANCE);
// must not travel longer than distance between waypoints or NPC goes past waypoint // must not travel longer than distance between waypoints or NPC goes past waypoint
distance = std::min(distance, static_cast<int>(length)); distance = std::min(distance, static_cast<int>(length));
delta *= distance; delta *= distance;
@ -1041,4 +1022,3 @@ namespace MWMechanics
init(); init();
} }
} }

@ -47,9 +47,11 @@ namespace MWMechanics
virtual void writeState(ESM::AiSequence::AiSequence &sequence) const; virtual void writeState(ESM::AiSequence::AiSequence &sequence) const;
virtual void fastForward(const MWWorld::Ptr& actor, AiState& state); virtual void fastForward(const MWWorld::Ptr& actor, AiState& state);
bool getRepeat() const; bool getRepeat() const;
osg::Vec3f getDestination(const MWWorld::Ptr& actor) const;
enum GreetingState { enum GreetingState {
Greet_None, Greet_None,
Greet_InProgress, Greet_InProgress,
@ -85,7 +87,6 @@ namespace MWMechanics
bool reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, bool reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage,
const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos, float duration); const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos, float duration);
bool isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage); bool isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage);
void returnToStartLocation(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos);
void wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance); void wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance);
bool destinationIsAtWater(const MWWorld::Ptr &actor, const osg::Vec3f& destination); bool destinationIsAtWater(const MWWorld::Ptr &actor, const osg::Vec3f& destination);
bool destinationThroughGround(const osg::Vec3f& startPoint, const osg::Vec3f& destination); bool destinationThroughGround(const osg::Vec3f& startPoint, const osg::Vec3f& destination);

@ -561,6 +561,10 @@ void CharacterController::refreshIdleAnims(const WeaponInfo* weap, CharacterStat
void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force) void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force)
{ {
// If the current animation is persistent, do not touch it
if (isPersistentAnimPlaying())
return;
if (mPtr.getClass().isActor()) if (mPtr.getClass().isActor())
refreshHitRecoilAnims(); refreshHitRecoilAnims();
@ -744,11 +748,17 @@ void CharacterController::playRandomDeath(float startpoint)
{ {
mDeathState = chooseRandomDeathState(); mDeathState = chooseRandomDeathState();
} }
// Do not interrupt scripted animation by death
if (isPersistentAnimPlaying())
return;
playDeath(startpoint, mDeathState); playDeath(startpoint, mDeathState);
} }
CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim) CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim)
: mPtr(ptr) : mPtr(ptr)
, mWeapon(MWWorld::Ptr())
, mAnimation(anim) , mAnimation(anim)
, mIdleState(CharState_None) , mIdleState(CharState_None)
, mMovementState(CharState_None) , mMovementState(CharState_None)
@ -828,8 +838,8 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim
mIdleState = CharState_Idle; mIdleState = CharState_Idle;
} }
// Do not update animation status for dead actors
if(mDeathState == CharState_None) if(mDeathState == CharState_None && (!cls.isActor() || !cls.getCreatureStats(mPtr).isDead()))
refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true); refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true);
mAnimation->runAnimation(0.f); mAnimation->runAnimation(0.f);
@ -1156,17 +1166,26 @@ bool CharacterController::updateWeaponState()
const bool isWerewolf = cls.isNpc() && cls.getNpcStats(mPtr).isWerewolf(); const bool isWerewolf = cls.isNpc() && cls.getNpcStats(mPtr).isWerewolf();
std::string soundid; std::string upSoundId;
std::string downSoundId;
if (mPtr.getClass().hasInventoryStore(mPtr)) if (mPtr.getClass().hasInventoryStore(mPtr))
{ {
MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr);
MWWorld::ConstContainerStoreIterator weapon = getActiveWeapon(stats, inv, &weaptype); MWWorld::ContainerStoreIterator weapon = getActiveWeapon(stats, inv, &weaptype);
if(weapon != inv.end() && !(weaptype == WeapType_None && mWeaponType == WeapType_Spell)) if(stats.getDrawState() == DrawState_Spell)
{ weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
soundid = (weaptype == WeapType_None) ?
weapon->getClass().getDownSoundId(*weapon) : if(weapon != inv.end() && mWeaponType != WeapType_HandToHand && weaptype > WeapType_HandToHand && weaptype < WeapType_Spell)
weapon->getClass().getUpSoundId(*weapon); upSoundId = weapon->getClass().getUpSoundId(*weapon);
}
if(weapon != inv.end() && mWeaponType > WeapType_HandToHand && mWeaponType < WeapType_Spell)
downSoundId = weapon->getClass().getDownSoundId(*weapon);
// weapon->HtH switch: weapon is empty already, so we need to take sound from previous weapon
if(weapon == inv.end() && !mWeapon.isEmpty() && weaptype == WeapType_HandToHand && mWeaponType != WeapType_Spell)
downSoundId = mWeapon.getClass().getDownSoundId(mWeapon);
mWeapon = weapon != inv.end() ? *weapon : MWWorld::Ptr();
} }
MWRender::Animation::AnimPriority priorityWeapon(Priority_Weapon); MWRender::Animation::AnimPriority priorityWeapon(Priority_Weapon);
@ -1181,34 +1200,50 @@ bool CharacterController::updateWeaponState()
if(weaptype != mWeaponType && !isKnockedOut() && if(weaptype != mWeaponType && !isKnockedOut() &&
!isKnockedDown() && !isRecovery()) !isKnockedDown() && !isRecovery())
{ {
forcestateupdate = true;
mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype));
std::string weapgroup; std::string weapgroup;
if(weaptype == WeapType_None) if ((!isWerewolf || mWeaponType != WeapType_Spell)
&& mUpperBodyState != UpperCharState_UnEquipingWeap
&& !isStillWeapon)
{ {
if (!isWerewolf || mWeaponType != WeapType_Spell) // Note: we do not disable unequipping animation automatically to avoid body desync
getWeaponGroup(mWeaponType, weapgroup);
mAnimation->play(weapgroup, priorityWeapon,
MWRender::Animation::BlendMask_All, false,
1.0f, "unequip start", "unequip stop", 0.0f, 0);
mUpperBodyState = UpperCharState_UnEquipingWeap;
if(!downSoundId.empty())
{ {
getWeaponGroup(mWeaponType, weapgroup); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
mAnimation->play(weapgroup, priorityWeapon, sndMgr->playSound3D(mPtr, downSoundId, 1.0f, 1.0f);
MWRender::Animation::BlendMask_All, true,
1.0f, "unequip start", "unequip stop", 0.0f, 0);
mUpperBodyState = UpperCharState_UnEquipingWeap;
} }
} }
else
float complete;
bool animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete);
if (!animPlaying || complete >= 1.0f)
{ {
forcestateupdate = true;
mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype));
getWeaponGroup(weaptype, weapgroup); getWeaponGroup(weaptype, weapgroup);
mAnimation->setWeaponGroup(weapgroup); mAnimation->setWeaponGroup(weapgroup);
if (!isStillWeapon) if (!isStillWeapon)
{ {
mAnimation->showWeapons(false); if (weaptype == WeapType_None)
mAnimation->play(weapgroup, priorityWeapon, {
MWRender::Animation::BlendMask_All, true, // Disable current weapon animation manually
1.0f, "equip start", "equip stop", 0.0f, 0); mAnimation->disable(mCurrentWeapon);
mUpperBodyState = UpperCharState_EquipingWeap; }
else
{
mAnimation->showWeapons(false);
mAnimation->play(weapgroup, priorityWeapon,
MWRender::Animation::BlendMask_All, true,
1.0f, "equip start", "equip stop", 0.0f, 0);
mUpperBodyState = UpperCharState_EquipingWeap;
}
} }
if(isWerewolf) if(isWerewolf)
@ -1221,16 +1256,16 @@ bool CharacterController::updateWeaponState()
sndMgr->playSound3D(mPtr, sound->mId, 1.0f, 1.0f); sndMgr->playSound3D(mPtr, sound->mId, 1.0f, 1.0f);
} }
} }
}
if(!soundid.empty() && !isStillWeapon) mWeaponType = weaptype;
{ getWeaponGroup(mWeaponType, mCurrentWeapon);
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
sndMgr->playSound3D(mPtr, soundid, 1.0f, 1.0f);
}
mWeaponType = weaptype; if(!upSoundId.empty() && !isStillWeapon)
getWeaponGroup(mWeaponType, mCurrentWeapon); {
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
sndMgr->playSound3D(mPtr, upSoundId, 1.0f, 1.0f);
}
}
} }
if(isWerewolf) if(isWerewolf)
@ -1273,6 +1308,10 @@ bool CharacterController::updateWeaponState()
} }
} }
// Combat for actors with persistent animations obviously will be buggy
if (isPersistentAnimPlaying())
return forcestateupdate;
float complete; float complete;
bool animPlaying; bool animPlaying;
if(mAttackingOrSpell) if(mAttackingOrSpell)
@ -1987,15 +2026,17 @@ void CharacterController::update(float duration)
{ {
// initial start of death animation for actors that started the game as dead // initial start of death animation for actors that started the game as dead
// not done in constructor since we need to give scripts a chance to set the mSkipAnim flag // not done in constructor since we need to give scripts a chance to set the mSkipAnim flag
if (!mSkipAnim && mDeathState != CharState_None && mCurrentDeath.empty()) if (!mSkipAnim && mDeathState != CharState_None && mCurrentDeath.empty() && cls.isPersistent(mPtr))
{ {
// Fast-forward death animation to end for persisting corpses
playDeath(1.f, mDeathState); playDeath(1.f, mDeathState);
} }
// We must always queue movement, even if there is none, to apply gravity. // We must always queue movement, even if there is none, to apply gravity.
world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f));
} }
osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim ? 0.f : duration); bool isPersist = isPersistentAnimPlaying();
osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim && !isPersist ? 0.f : duration);
if(duration > 0.0f) if(duration > 0.0f)
moved /= duration; moved /= duration;
else else
@ -2109,6 +2150,10 @@ bool CharacterController::playGroup(const std::string &groupname, int mode, int
if(!mAnimation || !mAnimation->hasAnimation(groupname)) if(!mAnimation || !mAnimation->hasAnimation(groupname))
return false; return false;
// We should not interrupt persistent animations by non-persistent ones
if (isPersistentAnimPlaying() && !persist)
return false;
// If this animation is a looped animation (has a "loop start" key) that is already playing // If this animation is a looped animation (has a "loop start" key) that is already playing
// and has not yet reached the end of the loop, allow it to continue animating with its existing loop count // and has not yet reached the end of the loop, allow it to continue animating with its existing loop count
// and remove any other animations that were queued. // and remove any other animations that were queued.
@ -2138,23 +2183,28 @@ bool CharacterController::playGroup(const std::string &groupname, int mode, int
if(mode != 0 || mAnimQueue.empty() || !isAnimPlaying(mAnimQueue.front().mGroup)) if(mode != 0 || mAnimQueue.empty() || !isAnimPlaying(mAnimQueue.front().mGroup))
{ {
clearAnimQueue(); clearAnimQueue(persist);
mAnimQueue.push_back(entry);
mAnimation->disable(mCurrentIdle); mAnimation->disable(mCurrentIdle);
mCurrentIdle.clear(); mCurrentIdle.clear();
mIdleState = CharState_SpecialIdle; mIdleState = CharState_SpecialIdle;
bool loopfallback = (entry.mGroup.compare(0,4,"idle") == 0); bool loopfallback = (entry.mGroup.compare(0,4,"idle") == 0);
mAnimation->play(groupname, Priority_Default, mAnimation->play(groupname, persist && groupname != "idle" ? Priority_Persistent : Priority_Default,
MWRender::Animation::BlendMask_All, false, 1.0f, MWRender::Animation::BlendMask_All, false, 1.0f,
((mode==2) ? "loop start" : "start"), "stop", 0.0f, count-1, loopfallback); ((mode==2) ? "loop start" : "start"), "stop", 0.0f, count-1, loopfallback);
} }
else else
{ {
mAnimQueue.resize(1); mAnimQueue.resize(1);
mAnimQueue.push_back(entry);
} }
// "PlayGroup idle" is a special case, used to remove to stop scripted animations playing
if (groupname == "idle")
entry.mPersist = false;
mAnimQueue.push_back(entry);
return true; return true;
} }
@ -2163,6 +2213,17 @@ void CharacterController::skipAnim()
mSkipAnim = true; mSkipAnim = true;
} }
bool CharacterController::isPersistentAnimPlaying()
{
if (!mAnimQueue.empty())
{
AnimationQueueEntry& first = mAnimQueue.front();
return first.mPersist && isAnimPlaying(first.mGroup);
}
return false;
}
bool CharacterController::isAnimPlaying(const std::string &groupName) bool CharacterController::isAnimPlaying(const std::string &groupName)
{ {
if(mAnimation == NULL) if(mAnimation == NULL)
@ -2170,12 +2231,19 @@ bool CharacterController::isAnimPlaying(const std::string &groupName)
return mAnimation->isPlaying(groupName); return mAnimation->isPlaying(groupName);
} }
void CharacterController::clearAnimQueue(bool clearPersistAnims)
void CharacterController::clearAnimQueue()
{ {
if(!mAnimQueue.empty()) // Do not interrupt scripted animations, if we want to keep them
if ((!isPersistentAnimPlaying() || clearPersistAnims) && !mAnimQueue.empty())
mAnimation->disable(mAnimQueue.front().mGroup); mAnimation->disable(mAnimQueue.front().mGroup);
mAnimQueue.clear();
for (AnimationQueue::iterator it = mAnimQueue.begin(); it != mAnimQueue.end();)
{
if (clearPersistAnims || !it->mPersist)
it = mAnimQueue.erase(it);
else
++it;
}
} }
void CharacterController::forceStateUpdate() void CharacterController::forceStateUpdate()
@ -2185,6 +2253,7 @@ void CharacterController::forceStateUpdate()
clearAnimQueue(); clearAnimQueue();
refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true); refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true);
if(mDeathState != CharState_None) if(mDeathState != CharState_None)
{ {
playRandomDeath(); playRandomDeath();

@ -39,8 +39,8 @@ enum Priority {
Priority_Knockdown, Priority_Knockdown,
Priority_Torch, Priority_Torch,
Priority_Storm, Priority_Storm,
Priority_Death, Priority_Death,
Priority_Persistent,
Num_Priorities Num_Priorities
}; };
@ -152,6 +152,7 @@ struct WeaponInfo;
class CharacterController : public MWRender::Animation::TextKeyListener class CharacterController : public MWRender::Animation::TextKeyListener
{ {
MWWorld::Ptr mPtr; MWWorld::Ptr mPtr;
MWWorld::Ptr mWeapon;
MWRender::Animation *mAnimation; MWRender::Animation *mAnimation;
struct AnimationQueueEntry struct AnimationQueueEntry
@ -214,12 +215,14 @@ class CharacterController : public MWRender::Animation::TextKeyListener
void refreshMovementAnims(const WeaponInfo* weap, CharacterState movement, bool force=false); void refreshMovementAnims(const WeaponInfo* weap, CharacterState movement, bool force=false);
void refreshIdleAnims(const WeaponInfo* weap, CharacterState idle, bool force=false); void refreshIdleAnims(const WeaponInfo* weap, CharacterState idle, bool force=false);
void clearAnimQueue(); void clearAnimQueue(bool clearPersistAnims = false);
bool updateWeaponState(); bool updateWeaponState();
bool updateCreatureState(); bool updateCreatureState();
void updateIdleStormState(bool inwater); void updateIdleStormState(bool inwater);
bool isPersistentAnimPlaying();
void updateAnimQueue(); void updateAnimQueue();
void updateHeadTracking(float duration); void updateHeadTracking(float duration);

@ -22,6 +22,7 @@
#include "aicombat.hpp" #include "aicombat.hpp"
#include "aipursue.hpp" #include "aipursue.hpp"
#include "aitravel.hpp"
#include "spellcasting.hpp" #include "spellcasting.hpp"
#include "autocalcspell.hpp" #include "autocalcspell.hpp"
#include "npcstats.hpp" #include "npcstats.hpp"
@ -629,22 +630,12 @@ namespace MWMechanics
float d = static_cast<float>(std::min(sellerStats.getSkill(ESM::Skill::Mercantile).getModified(), 100)); float d = static_cast<float>(std::min(sellerStats.getSkill(ESM::Skill::Mercantile).getModified(), 100));
float e = std::min(0.1f * sellerStats.getAttribute(ESM::Attribute::Luck).getModified(), 10.f); float e = std::min(0.1f * sellerStats.getAttribute(ESM::Attribute::Luck).getModified(), 10.f);
float f = std::min(0.2f * sellerStats.getAttribute(ESM::Attribute::Personality).getModified(), 10.f); float f = std::min(0.2f * sellerStats.getAttribute(ESM::Attribute::Personality).getModified(), 10.f);
float pcTerm = (clampedDisposition - 50 + a + b + c) * playerStats.getFatigueTerm(); float pcTerm = (clampedDisposition - 50 + a + b + c) * playerStats.getFatigueTerm();
float npcTerm = (d + e + f) * sellerStats.getFatigueTerm(); float npcTerm = (d + e + f) * sellerStats.getFatigueTerm();
float buyTerm = 0.01f * (100 - 0.5f * (pcTerm - npcTerm)); float buyTerm = 0.01f * std::max(75.f, (100 - 0.5f * (pcTerm - npcTerm)));
float sellTerm = 0.01f * (50 - 0.5f * (npcTerm - pcTerm)); float sellTerm = 0.01f * std::min(75.f, (50 - 0.5f * (npcTerm - pcTerm)));
int offerPrice = int(basePrice * (buying ? buyTerm : sellTerm));
float x; return std::max(1, offerPrice);
if(buying) x = buyTerm;
else x = std::min(buyTerm, sellTerm);
int offerPrice;
if (x < 1)
offerPrice = int(x * basePrice);
else
offerPrice = basePrice + int((x - 1) * basePrice);
offerPrice = std::max(1, offerPrice);
return offerPrice;
} }
int MechanicsManager::countDeaths (const std::string& id) const int MechanicsManager::countDeaths (const std::string& id) const
@ -893,8 +884,13 @@ namespace MWMechanics
const MWWorld::CellRef& cellref = target.getCellRef(); const MWWorld::CellRef& cellref = target.getCellRef();
// there is no harm to use unlocked doors // there is no harm to use unlocked doors
if (target.getClass().isDoor() && cellref.getLockLevel() <= 0 && ptr.getCellRef().getTrap().empty()) int lockLevel = cellref.getLockLevel();
if (target.getClass().isDoor() &&
(lockLevel <= 0 || lockLevel == ESM::UnbreakableLock) &&
ptr.getCellRef().getTrap().empty())
{
return true; return true;
}
// TODO: implement a better check to check if target is owned bed // TODO: implement a better check to check if target is owned bed
if (target.getClass().isActivator() && target.getClass().getScript(target).compare(0, 3, "Bed") != 0) if (target.getClass().isActivator() && target.getClass().getScript(target).compare(0, 3, "Bed") != 0)
@ -992,14 +988,26 @@ namespace MWMechanics
} }
} }
bool MechanicsManager::isItemStolenFrom(const std::string &itemid, const std::string &ownerid) bool MechanicsManager::isItemStolenFrom(const std::string &itemid, const MWWorld::Ptr& ptr)
{ {
StolenItemsMap::const_iterator it = mStolenItems.find(Misc::StringUtils::lowerCase(itemid)); StolenItemsMap::const_iterator it = mStolenItems.find(Misc::StringUtils::lowerCase(itemid));
if (it == mStolenItems.end()) if (it == mStolenItems.end())
return false; return false;
const OwnerMap& owners = it->second; const OwnerMap& owners = it->second;
const std::string ownerid = ptr.getCellRef().getRefId();
OwnerMap::const_iterator ownerFound = owners.find(std::make_pair(Misc::StringUtils::lowerCase(ownerid), false)); OwnerMap::const_iterator ownerFound = owners.find(std::make_pair(Misc::StringUtils::lowerCase(ownerid), false));
return ownerFound != owners.end(); if (ownerFound != owners.end())
return true;
const std::string factionid = ptr.getClass().getPrimaryFaction(ptr);
if (!factionid.empty())
{
OwnerMap::const_iterator factionOwnerFound = owners.find(std::make_pair(Misc::StringUtils::lowerCase(factionid), true));
return factionOwnerFound != owners.end();
}
return false;
} }
void MechanicsManager::confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count) void MechanicsManager::confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count)
@ -1017,6 +1025,13 @@ namespace MWMechanics
owner.first = victim.getCellRef().getRefId(); owner.first = victim.getCellRef().getRefId();
owner.second = false; owner.second = false;
const std::string victimFaction = victim.getClass().getPrimaryFaction(victim);
if (!victimFaction.empty() && Misc::StringUtils::ciEqual(item.getCellRef().getFaction(), victimFaction)) // Is the item faction-owned?
{
owner.first = victimFaction;
owner.second = true;
}
Misc::StringUtils::lowerCaseInPlace(owner.first); Misc::StringUtils::lowerCaseInPlace(owner.first);
// decrease count of stolen items // decrease count of stolen items
@ -1194,16 +1209,41 @@ namespace MWMechanics
reportCrime(player, victim, type, arg); reportCrime(player, victim, type, arg);
else if (type == OT_Assault && !victim.isEmpty()) else if (type == OT_Assault && !victim.isEmpty())
{ {
bool reported = false;
if (victim.getClass().isClass(victim, "guard") if (victim.getClass().isClass(victim, "guard")
&& !victim.getClass().getCreatureStats(victim).getAiSequence().hasPackage(AiPackage::TypeIdPursue)) && !victim.getClass().getCreatureStats(victim).getAiSequence().hasPackage(AiPackage::TypeIdPursue))
reportCrime(player, victim, type, arg); reported = reportCrime(player, victim, type, arg);
else
if (!reported)
startCombat(victim, player); // TODO: combat should be started with an "unaware" flag, which makes the victim flee? startCombat(victim, player); // TODO: combat should be started with an "unaware" flag, which makes the victim flee?
} }
return crimeSeen; return crimeSeen;
} }
void MechanicsManager::reportCrime(const MWWorld::Ptr &player, const MWWorld::Ptr &victim, OffenseType type, int arg) bool MechanicsManager::canReportCrime(const MWWorld::Ptr &actor, const MWWorld::Ptr &victim, std::set<MWWorld::Ptr> &playerFollowers)
{
if (actor == getPlayer()
|| !actor.getClass().isNpc() || actor.getClass().getCreatureStats(actor).isDead())
return false;
if (actor.getClass().getCreatureStats(actor).getAiSequence().isInCombat(victim))
return false;
// Unconsious actor can not report about crime and should not become hostile
if (actor.getClass().getCreatureStats(actor).getKnockedDown())
return false;
// Player's followers should not attack player, or try to arrest him
if (actor.getClass().getCreatureStats(actor).getAiSequence().hasPackage(AiPackage::TypeIdFollow))
{
if (playerFollowers.find(actor) != playerFollowers.end())
return false;
}
return true;
}
bool MechanicsManager::reportCrime(const MWWorld::Ptr &player, const MWWorld::Ptr &victim, OffenseType type, int arg)
{ {
const MWWorld::Store<ESM::GameSetting>& store = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>(); const MWWorld::Store<ESM::GameSetting>& store = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
@ -1278,29 +1318,15 @@ namespace MWMechanics
bool reported = false; bool reported = false;
std::set<MWWorld::Ptr> playerFollowers;
getActorsSidingWith(player, playerFollowers);
// Tell everyone (including the original reporter) in alarm range // Tell everyone (including the original reporter) in alarm range
for (std::vector<MWWorld::Ptr>::iterator it = neighbors.begin(); it != neighbors.end(); ++it) for (std::vector<MWWorld::Ptr>::iterator it = neighbors.begin(); it != neighbors.end(); ++it)
{ {
if (*it == player if (!canReportCrime(*it, victim, playerFollowers))
|| !it->getClass().isNpc() || it->getClass().getCreatureStats(*it).isDead()) continue;
if (it->getClass().getCreatureStats(*it).getAiSequence().isInCombat(victim))
continue;
// Unconsious actor can not report about crime and should not become hostile
if (it->getClass().getCreatureStats(*it).getKnockedDown())
continue; continue;
// Player's followers should not attack player, or try to arrest him
if (it->getClass().getCreatureStats(*it).getAiSequence().hasPackage(AiPackage::TypeIdFollow))
{
std::set<MWWorld::Ptr> playerFollowers;
getActorsSidingWith(player, playerFollowers);
if (playerFollowers.find(*it) != playerFollowers.end())
continue;
}
// Will the witness report the crime? // Will the witness report the crime?
if (it->getClass().getCreatureStats(*it).getAiSetting(CreatureStats::AI_Alarm).getBase() >= 100) if (it->getClass().getCreatureStats(*it).getAiSetting(CreatureStats::AI_Alarm).getBase() >= 100)
{ {
@ -1309,8 +1335,14 @@ namespace MWMechanics
if (type == OT_Trespassing) if (type == OT_Trespassing)
MWBase::Environment::get().getDialogueManager()->say(*it, "intruder"); MWBase::Environment::get().getDialogueManager()->say(*it, "intruder");
} }
}
if (it->getClass().isClass(*it, "guard")) for (std::vector<MWWorld::Ptr>::iterator it = neighbors.begin(); it != neighbors.end(); ++it)
{
if (!canReportCrime(*it, victim, playerFollowers))
continue;
if (it->getClass().isClass(*it, "guard") && reported)
{ {
// Mark as Alarmed for dialogue // Mark as Alarmed for dialogue
it->getClass().getCreatureStats(*it).setAlarmed(true); it->getClass().getCreatureStats(*it).setAlarmed(true);
@ -1398,6 +1430,8 @@ namespace MWMechanics
victim.getClass().getNpcStats(victim).setCrimeId(id); victim.getClass().getNpcStats(victim).setCrimeId(id);
} }
} }
return reported;
} }
bool MechanicsManager::actorAttacked(const MWWorld::Ptr &target, const MWWorld::Ptr &attacker) bool MechanicsManager::actorAttacked(const MWWorld::Ptr &target, const MWWorld::Ptr &attacker)
@ -1466,7 +1500,7 @@ namespace MWMechanics
void MechanicsManager::actorKilled(const MWWorld::Ptr &victim, const MWWorld::Ptr &attacker) void MechanicsManager::actorKilled(const MWWorld::Ptr &victim, const MWWorld::Ptr &attacker)
{ {
if (attacker.isEmpty() || attacker != getPlayer()) if (attacker.isEmpty() || victim.isEmpty())
return; return;
if (victim == attacker) if (victim == attacker)
@ -1476,13 +1510,23 @@ namespace MWMechanics
return; // TODO: implement animal rights return; // TODO: implement animal rights
const MWMechanics::NpcStats& victimStats = victim.getClass().getNpcStats(victim); const MWMechanics::NpcStats& victimStats = victim.getClass().getNpcStats(victim);
if (victimStats.getCrimeId() == -1)
return;
// For now we report only about crimes of player and player's followers
const MWWorld::Ptr &player = getPlayer();
if (attacker != player)
{
std::set<MWWorld::Ptr> playerFollowers;
getActorsSidingWith(player, playerFollowers);
if (playerFollowers.find(attacker) == playerFollowers.end())
return;
}
// Simple check for who attacked first: if the player attacked first, a crimeId should be set // Simple check for who attacked first: if the player attacked first, a crimeId should be set
// Doesn't handle possible edge case where no one reported the assault, but in such a case, // Doesn't handle possible edge case where no one reported the assault, but in such a case,
// for bystanders it is not possible to tell who attacked first, anyway. // for bystanders it is not possible to tell who attacked first, anyway.
if (victimStats.getCrimeId() != -1) commitCrime(player, victim, MWBase::MechanicsManager::OT_Murder);
commitCrime(attacker, victim, MWBase::MechanicsManager::OT_Murder);
} }
bool MechanicsManager::awarenessCheck(const MWWorld::Ptr &ptr, const MWWorld::Ptr &observer) bool MechanicsManager::awarenessCheck(const MWWorld::Ptr &ptr, const MWWorld::Ptr &observer)
@ -1560,9 +1604,12 @@ namespace MWMechanics
void MechanicsManager::startCombat(const MWWorld::Ptr &ptr, const MWWorld::Ptr &target) void MechanicsManager::startCombat(const MWWorld::Ptr &ptr, const MWWorld::Ptr &target)
{ {
if (ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat(target)) MWMechanics::AiSequence& aiSequence = ptr.getClass().getCreatureStats(ptr).getAiSequence();
if (aiSequence.isInCombat(target))
return; return;
ptr.getClass().getCreatureStats(ptr).getAiSequence().stack(MWMechanics::AiCombat(target), ptr);
aiSequence.stack(MWMechanics::AiCombat(target), ptr);
if (target == getPlayer()) if (target == getPlayer())
{ {
// if guard starts combat with player, guards pursuing player should do the same // if guard starts combat with player, guards pursuing player should do the same

@ -205,7 +205,7 @@ namespace MWMechanics
virtual std::vector<std::pair<std::string, int> > getStolenItemOwners(const std::string& itemid); virtual std::vector<std::pair<std::string, int> > getStolenItemOwners(const std::string& itemid);
/// Has the player stolen this item from the given owner? /// Has the player stolen this item from the given owner?
virtual bool isItemStolenFrom(const std::string& itemid, const std::string& ownerid); virtual bool isItemStolenFrom(const std::string& itemid, const MWWorld::Ptr& ptr);
virtual bool isBoundItem(const MWWorld::Ptr& item); virtual bool isBoundItem(const MWWorld::Ptr& item);
@ -224,7 +224,9 @@ namespace MWMechanics
virtual bool isSneaking(const MWWorld::Ptr& ptr); virtual bool isSneaking(const MWWorld::Ptr& ptr);
private: private:
void reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, bool canReportCrime(const MWWorld::Ptr &actor, const MWWorld::Ptr &victim, std::set<MWWorld::Ptr> &playerFollowers);
bool reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim,
OffenseType type, int arg=0); OffenseType type, int arg=0);

@ -26,13 +26,13 @@ namespace MWMechanics
bool proximityToDoor(const MWWorld::Ptr& actor, float minDist) bool proximityToDoor(const MWWorld::Ptr& actor, float minDist)
{ {
if(getNearbyDoor(actor, minDist)!=MWWorld::Ptr()) if(getNearbyDoor(actor, minDist).isEmpty())
return true;
else
return false; return false;
else
return true;
} }
MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minDist) const MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minDist)
{ {
MWWorld::CellStore *cell = actor.getCell(); MWWorld::CellStore *cell = actor.getCell();
@ -50,6 +50,16 @@ namespace MWMechanics
const MWWorld::LiveCellRef<ESM::Door>& ref = *it; const MWWorld::LiveCellRef<ESM::Door>& ref = *it;
osg::Vec3f doorPos(ref.mData.getPosition().asVec3()); osg::Vec3f doorPos(ref.mData.getPosition().asVec3());
// FIXME: cast
const MWWorld::Ptr doorPtr = MWWorld::Ptr(&const_cast<MWWorld::LiveCellRef<ESM::Door> &>(ref), actor.getCell());
int doorState = doorPtr.getClass().getDoorState(doorPtr);
float doorRot = ref.mData.getPosition().rot[2] - doorPtr.getCellRef().getPosition().rot[2];
if (doorState != 0 || doorRot != 0)
continue; // the door is already opened/opening
doorPos.z() = 0; doorPos.z() = 0;
float angle = std::acos(actorDir * (doorPos - pos) / (actorDir.length() * (doorPos - pos).length())); float angle = std::acos(actorDir * (doorPos - pos) / (actorDir.length() * (doorPos - pos).length()));
@ -62,8 +72,7 @@ namespace MWMechanics
if ((pos - doorPos).length2() > minDist*minDist) if ((pos - doorPos).length2() > minDist*minDist)
continue; continue;
// FIXME cast return doorPtr; // found, stop searching
return MWWorld::Ptr(&const_cast<MWWorld::LiveCellRef<ESM::Door> &>(ref), actor.getCell()); // found, stop searching
} }
return MWWorld::Ptr(); // none found return MWWorld::Ptr(); // none found

@ -17,7 +17,7 @@ namespace MWMechanics
/// Returns door pointer within range. No guarantee is given as to which one /// Returns door pointer within range. No guarantee is given as to which one
/** \return Pointer to the door, or NULL if none exists **/ /** \return Pointer to the door, or NULL if none exists **/
MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minDist); const MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minDist);
class ObstacleCheck class ObstacleCheck
{ {

@ -31,7 +31,9 @@ namespace MWMechanics
void Security::pickLock(const MWWorld::Ptr &lock, const MWWorld::Ptr &lockpick, void Security::pickLock(const MWWorld::Ptr &lock, const MWWorld::Ptr &lockpick,
std::string& resultMessage, std::string& resultSound) std::string& resultMessage, std::string& resultSound)
{ {
if (!(lock.getCellRef().getLockLevel() > 0) || !lock.getClass().canLock(lock)) //If it's unlocked back out immediately if (lock.getCellRef().getLockLevel() <= 0 ||
lock.getCellRef().getLockLevel() == ESM::UnbreakableLock ||
!lock.getClass().canLock(lock)) //If it's unlocked or can not be unlocked back out immediately
return; return;
int lockStrength = lock.getCellRef().getLockLevel(); int lockStrength = lock.getCellRef().getLockLevel();

@ -221,7 +221,6 @@ namespace MWMechanics
if (effects) if (effects)
magicEffects = effects; magicEffects = effects;
float resisted = 0;
// Effects with no resistance attribute belonging to them can not be resisted // Effects with no resistance attribute belonging to them can not be resisted
if (ESM::MagicEffect::getResistanceEffect(effectId) == -1) if (ESM::MagicEffect::getResistanceEffect(effectId) == -1)
return 0.f; return 0.f;
@ -256,10 +255,7 @@ namespace MWMechanics
} }
x = std::min(x + resistance, 100.f); x = std::min(x + resistance, 100.f);
return x;
resisted = x;
return resisted;
} }
float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster,
@ -456,10 +452,11 @@ namespace MWMechanics
float magnitudeMult = 1; float magnitudeMult = 1;
if (!absorbed) if (!absorbed && target.getClass().isActor())
{ {
bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful;
// Reflect harmful effects // Reflect harmful effects
if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful && !reflected && !caster.isEmpty() && caster != target && !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable)) if (isHarmful && !reflected && !caster.isEmpty() && caster != target && !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable))
{ {
float reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).getMagnitude(); float reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).getMagnitude();
bool isReflected = (Misc::Rng::roll0to99() < reflect); bool isReflected = (Misc::Rng::roll0to99() < reflect);
@ -483,17 +480,17 @@ namespace MWMechanics
else if (castByPlayer) else if (castByPlayer)
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}"); MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}");
} }
else if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful && castByPlayer && target != caster) else if (isHarmful && castByPlayer && target != caster)
{ {
// If player is attempting to cast a harmful spell and it wasn't fully resisted, show the target's HP bar // If player is attempting to cast a harmful spell and it wasn't fully resisted, show the target's HP bar
MWBase::Environment::get().getWindowManager()->setEnemy(target); MWBase::Environment::get().getWindowManager()->setEnemy(target);
} }
if (target == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState()) if (target == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState() && isHarmful)
magnitudeMult = 0; magnitudeMult = 0;
// Notify the target actor they've been hit // Notify the target actor they've been hit
if (target != caster && !caster.isEmpty() && magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) if (target != caster && !caster.isEmpty() && isHarmful)
target.getClass().onHit(target, 0.0f, true, MWWorld::Ptr(), caster, osg::Vec3f(), true); target.getClass().onHit(target, 0.0f, true, MWWorld::Ptr(), caster, osg::Vec3f(), true);
} }
@ -550,7 +547,7 @@ namespace MWMechanics
|| (effectIt->mEffectID == ESM::MagicEffect::CommandCreature && target.getTypeName() == typeid(ESM::Creature).name())) || (effectIt->mEffectID == ESM::MagicEffect::CommandCreature && target.getTypeName() == typeid(ESM::Creature).name()))
&& !caster.isEmpty() && caster.getClass().isActor() && target != getPlayer() && magnitude >= target.getClass().getCreatureStats(target).getLevel()) && !caster.isEmpty() && caster.getClass().isActor() && target != getPlayer() && magnitude >= target.getClass().getCreatureStats(target).getLevel())
{ {
MWMechanics::AiFollow package(caster.getCellRef().getRefId(), true); MWMechanics::AiFollow package(caster, true);
target.getClass().getCreatureStats(target).getAiSequence().stack(package, target); target.getClass().getCreatureStats(target).getAiSequence().stack(package, target);
} }

@ -59,7 +59,7 @@ namespace MWMechanics
MWMechanics::CreatureStats& summonedCreatureStats = ref.getPtr().getClass().getCreatureStats(ref.getPtr()); MWMechanics::CreatureStats& summonedCreatureStats = ref.getPtr().getClass().getCreatureStats(ref.getPtr());
// Make the summoned creature follow its master and help in fights // Make the summoned creature follow its master and help in fights
AiFollow package(mActor.getCellRef().getRefId()); AiFollow package(mActor);
summonedCreatureStats.getAiSequence().stack(package, ref.getPtr()); summonedCreatureStats.getAiSequence().stack(package, ref.getPtr());
creatureActorId = summonedCreatureStats.getActorId(); creatureActorId = summonedCreatureStats.getActorId();

@ -32,7 +32,6 @@ namespace MWMechanics
// Is the player buying? // Is the player buying?
bool buying = (merchantOffer < 0); bool buying = (merchantOffer < 0);
int a = std::abs(merchantOffer); int a = std::abs(merchantOffer);
int b = std::abs(playerOffer); int b = std::abs(playerOffer);
int d = (buying) int d = (buying)
@ -56,7 +55,7 @@ namespace MWMechanics
float npcTerm = (d1 + e1 + f1) * merchantStats.getFatigueTerm(); float npcTerm = (d1 + e1 + f1) * merchantStats.getFatigueTerm();
float x = gmst.find("fBargainOfferMulti")->getFloat() * d float x = gmst.find("fBargainOfferMulti")->getFloat() * d
+ gmst.find("fBargainOfferBase")->getFloat() + gmst.find("fBargainOfferBase")->getFloat()
+ std::abs(int(pcTerm - npcTerm)); + int(pcTerm - npcTerm);
int roll = Misc::Rng::rollDice(100) + 1; int roll = Misc::Rng::rollDice(100) + 1;

@ -308,6 +308,7 @@ namespace MWPhysics
float swimlevel = waterlevel + halfExtents.z() - (physicActor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale); float swimlevel = waterlevel + halfExtents.z() - (physicActor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale);
ActorTracer tracer; ActorTracer tracer;
osg::Vec3f inertia = physicActor->getInertialForce(); osg::Vec3f inertia = physicActor->getInertialForce();
osg::Vec3f velocity; osg::Vec3f velocity;
@ -320,10 +321,11 @@ namespace MWPhysics
{ {
velocity = (osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * movement; velocity = (osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * movement;
if (velocity.z() > 0.f && physicActor->getOnGround() && !physicActor->getOnSlope()) if ((velocity.z() > 0.f && physicActor->getOnGround() && !physicActor->getOnSlope())
|| (velocity.z() > 0.f && velocity.z() + inertia.z() <= -velocity.z() && physicActor->getOnSlope()))
inertia = velocity; inertia = velocity;
else if(!physicActor->getOnGround() || physicActor->getOnSlope()) else if (!physicActor->getOnGround() || physicActor->getOnSlope())
velocity = velocity + physicActor->getInertialForce(); velocity = velocity + inertia;
} }
// dead actors underwater will float to the surface, if the CharacterController tells us to do so // dead actors underwater will float to the surface, if the CharacterController tells us to do so
@ -846,6 +848,16 @@ namespace MWPhysics
const osg::Quat &orient, const osg::Quat &orient,
float queryDistance, std::vector<MWWorld::Ptr> targets) float queryDistance, std::vector<MWWorld::Ptr> targets)
{ {
// First of all, try to hit where you aim to
int hitmask = CollisionType_World | CollisionType_Door | CollisionType_HeightMap | CollisionType_Actor;
RayResult result = castRay(origin, origin + (orient * osg::Vec3f(0.0f, queryDistance, 0.0f)), actor, targets, CollisionType_Actor, hitmask);
if (result.mHit)
{
return std::make_pair(result.mHitObject, result.mHitPos);
}
// Use cone shape as fallback
const MWWorld::Store<ESM::GameSetting> &store = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>(); const MWWorld::Store<ESM::GameSetting> &store = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
btConeShape shape (osg::DegreesToRadians(store.find("fCombatAngleXY")->getFloat()/2.0f), queryDistance); btConeShape shape (osg::DegreesToRadians(store.find("fCombatAngleXY")->getFloat()/2.0f), queryDistance);

@ -31,6 +31,8 @@
#include <components/sceneutil/skeleton.hpp> #include <components/sceneutil/skeleton.hpp>
#include <components/sceneutil/positionattitudetransform.hpp> #include <components/sceneutil/positionattitudetransform.hpp>
#include <components/settings/settings.hpp>
#include <components/fallback/fallback.hpp> #include <components/fallback/fallback.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
@ -466,6 +468,8 @@ namespace MWRender
mAnimationTimePtr[i].reset(new AnimationTime); mAnimationTimePtr[i].reset(new AnimationTime);
mLightListCallback = new SceneUtil::LightListCallback; mLightListCallback = new SceneUtil::LightListCallback;
mUseAdditionalSources = Settings::Manager::getBool ("use additional anim sources", "Game");
} }
Animation::~Animation() Animation::~Animation()
@ -536,6 +540,35 @@ namespace MWRender
return mKeyframes->mTextKeys; return mKeyframes->mTextKeys;
} }
void Animation::loadAllAnimationsInFolder(const std::string &model, const std::string &baseModel)
{
const std::map<std::string, VFS::File*>& index = mResourceSystem->getVFS()->getIndex();
std::string animationPath = model;
if (animationPath.find("meshes") == 0)
{
animationPath.replace(0, 6, "animations");
}
animationPath.replace(animationPath.size()-3, 3, "/");
mResourceSystem->getVFS()->normalizeFilename(animationPath);
std::map<std::string, VFS::File*>::const_iterator found = index.lower_bound(animationPath);
while (found != index.end())
{
const std::string& name = found->first;
if (name.size() >= animationPath.size() && name.substr(0, animationPath.size()) == animationPath)
{
size_t pos = name.find_last_of('.');
if (pos != std::string::npos && name.compare(pos, name.size()-pos, ".kf") == 0)
addSingleAnimSource(name, baseModel);
}
else
break;
++found;
}
}
void Animation::addAnimSource(const std::string &model, const std::string& baseModel) void Animation::addAnimSource(const std::string &model, const std::string& baseModel)
{ {
std::string kfname = model; std::string kfname = model;
@ -546,6 +579,14 @@ namespace MWRender
else else
return; return;
addSingleAnimSource(kfname, baseModel);
if (mUseAdditionalSources)
loadAllAnimationsInFolder(kfname, baseModel);
}
void Animation::addSingleAnimSource(const std::string &kfname, const std::string& baseModel)
{
if(!mResourceSystem->getVFS()->exists(kfname)) if(!mResourceSystem->getVFS()->exists(kfname))
return; return;
@ -1048,11 +1089,28 @@ namespace MWRender
osg::Vec3f Animation::runAnimation(float duration) osg::Vec3f Animation::runAnimation(float duration)
{ {
// If we have scripted animations, play only them
bool hasScriptedAnims = false;
for (AnimStateMap::iterator stateiter = mStates.begin(); stateiter != mStates.end(); stateiter++)
{
if (stateiter->second.mPriority.contains(int(MWMechanics::Priority_Persistent)) && stateiter->second.mPlaying)
{
hasScriptedAnims = true;
break;
}
}
osg::Vec3f movement(0.f, 0.f, 0.f); osg::Vec3f movement(0.f, 0.f, 0.f);
AnimStateMap::iterator stateiter = mStates.begin(); AnimStateMap::iterator stateiter = mStates.begin();
while(stateiter != mStates.end()) while(stateiter != mStates.end())
{ {
AnimState &state = stateiter->second; AnimState &state = stateiter->second;
if (hasScriptedAnims && !state.mPriority.contains(int(MWMechanics::Priority_Persistent)))
{
++stateiter;
continue;
}
const NifOsg::TextKeyMap &textkeys = state.mSource->getTextKeys(); const NifOsg::TextKeyMap &textkeys = state.mSource->getTextKeys();
NifOsg::TextKeyMap::const_iterator textkey(textkeys.upper_bound(state.getTime())); NifOsg::TextKeyMap::const_iterator textkey(textkeys.upper_bound(state.getTime()));

@ -275,6 +275,8 @@ protected:
osg::ref_ptr<SceneUtil::LightListCallback> mLightListCallback; osg::ref_ptr<SceneUtil::LightListCallback> mLightListCallback;
bool mUseAdditionalSources;
const NodeMap& getNodeMap() const; const NodeMap& getNodeMap() const;
/* Sets the appropriate animations on the bone groups based on priority. /* Sets the appropriate animations on the bone groups based on priority.
@ -309,12 +311,15 @@ protected:
*/ */
void setObjectRoot(const std::string &model, bool forceskeleton, bool baseonly, bool isCreature); void setObjectRoot(const std::string &model, bool forceskeleton, bool baseonly, bool isCreature);
void loadAllAnimationsInFolder(const std::string &model, const std::string &baseModel);
/** Adds the keyframe controllers in the specified model as a new animation source. /** Adds the keyframe controllers in the specified model as a new animation source.
* @note Later added animation sources have the highest priority when it comes to finding a particular animation. * @note Later added animation sources have the highest priority when it comes to finding a particular animation.
* @param model The file to add the keyframes for. Note that the .nif file extension will be replaced with .kf. * @param model The file to add the keyframes for. Note that the .nif file extension will be replaced with .kf.
* @param baseModel The filename of the mObjectRoot, only used for error messages. * @param baseModel The filename of the mObjectRoot, only used for error messages.
*/ */
void addAnimSource(const std::string &model, const std::string& baseModel); void addAnimSource(const std::string &model, const std::string& baseModel);
void addSingleAnimSource(const std::string &model, const std::string& baseModel);
/** Adds an additional light to the given node using the specified ESM record. */ /** Adds an additional light to the given node using the specified ESM record. */
void addExtraLight(osg::ref_ptr<osg::Group> parent, const ESM::Light *light); void addExtraLight(osg::ref_ptr<osg::Group> parent, const ESM::Light *light);

@ -124,11 +124,12 @@ bool Objects::removeObject (const MWWorld::Ptr& ptr)
mObjects.erase(iter); mObjects.erase(iter);
if (ptr.getClass().isNpc()) if (ptr.getClass().isActor())
{ {
MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); if (ptr.getClass().hasInventoryStore(ptr))
store.setInvListener(NULL, ptr); ptr.getClass().getInventoryStore(ptr).setInvListener(NULL, ptr);
store.setContListener(NULL);
ptr.getClass().getContainerStore(ptr).setContListener(NULL);
} }
ptr.getRefData().getBaseNode()->getParent(0)->removeChild(ptr.getRefData().getBaseNode()); ptr.getRefData().getBaseNode()->getParent(0)->removeChild(ptr.getRefData().getBaseNode());

@ -12,16 +12,21 @@
#include <osg/Group> #include <osg/Group>
#include <osg/UserDataContainer> #include <osg/UserDataContainer>
#include <osg/ComputeBoundsVisitor> #include <osg/ComputeBoundsVisitor>
#include <osg/ShapeDrawable>
#include <osg/TextureCubeMap>
#include <osgUtil/LineSegmentIntersector> #include <osgUtil/LineSegmentIntersector>
#include <osgUtil/IncrementalCompileOperation> #include <osgUtil/IncrementalCompileOperation>
#include <osg/ImageUtils>
#include <osgViewer/Viewer> #include <osgViewer/Viewer>
#include <components/resource/resourcesystem.hpp> #include <components/resource/resourcesystem.hpp>
#include <components/resource/imagemanager.hpp> #include <components/resource/imagemanager.hpp>
#include <components/resource/scenemanager.hpp> #include <components/resource/scenemanager.hpp>
#include <components/resource/keyframemanager.hpp> #include <components/resource/keyframemanager.hpp>
#include <components/shader/shadermanager.hpp>
#include <components/settings/settings.hpp> #include <components/settings/settings.hpp>
@ -40,7 +45,12 @@
#include <components/esm/loadcell.hpp> #include <components/esm/loadcell.hpp>
#include <components/fallback/fallback.hpp> #include <components/fallback/fallback.hpp>
#include <boost/algorithm/string.hpp>
#include "../mwworld/cellstore.hpp" #include "../mwworld/cellstore.hpp"
#include "../mwgui/loadingscreen.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp"
#include "sky.hpp" #include "sky.hpp"
#include "effectmanager.hpp" #include "effectmanager.hpp"
@ -194,11 +204,10 @@ namespace MWRender
, mNightEyeFactor(0.f) , mNightEyeFactor(0.f)
, mDistantFog(false) , mDistantFog(false)
, mDistantTerrain(false) , mDistantTerrain(false)
, mFieldOfViewOverridden(false)
, mFieldOfViewOverride(0.f) , mFieldOfViewOverride(0.f)
, mBorders(false)
{ {
resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem); resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem);
resourceSystem->getSceneManager()->setShaderPath(resourcePath + "/shaders"); resourceSystem->getSceneManager()->setShaderPath(resourcePath + "/shaders");
resourceSystem->getSceneManager()->setForceShaders(Settings::Manager::getBool("force shaders", "Shaders") || Settings::Manager::getBool("enable shadows", "Shadows")); // Shadows have problems with fixed-function mode resourceSystem->getSceneManager()->setForceShaders(Settings::Manager::getBool("force shaders", "Shaders") || Settings::Manager::getBool("enable shadows", "Shadows")); // Shadows have problems with fixed-function mode
resourceSystem->getSceneManager()->setClampLighting(Settings::Manager::getBool("clamp lighting", "Shaders")); resourceSystem->getSceneManager()->setClampLighting(Settings::Manager::getBool("clamp lighting", "Shaders"));
@ -267,9 +276,10 @@ namespace MWRender
Settings::Manager::getBool("auto use terrain specular maps", "Shaders")); Settings::Manager::getBool("auto use terrain specular maps", "Shaders"));
if (mDistantTerrain) if (mDistantTerrain)
mTerrain.reset(new Terrain::QuadTreeWorld(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile)); mTerrain.reset(new Terrain::QuadTreeWorld(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug));
else else
mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile)); mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug));
mTerrain->setDefaultViewer(mViewer->getCamera()); mTerrain->setDefaultViewer(mViewer->getCamera());
mTerrain->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells")); mTerrain->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells"));
@ -494,6 +504,13 @@ namespace MWRender
else else
mShadowManager->enableIndoorMode(); mShadowManager->enableIndoorMode();
} }
bool RenderingManager::toggleBorders()
{
mBorders = !mBorders;
mTerrain->setBordersVisible(mBorders);
return mBorders;
}
bool RenderingManager::toggleRenderMode(RenderMode mode) bool RenderingManager::toggleRenderMode(RenderMode mode)
{ {
@ -695,7 +712,203 @@ namespace MWRender
mutable bool mDone; mutable bool mDone;
}; };
void RenderingManager::screenshot(osg::Image *image, int w, int h) bool RenderingManager::screenshot360(osg::Image* image, std::string settingStr)
{
int screenshotW = mViewer->getCamera()->getViewport()->width();
int screenshotH = mViewer->getCamera()->getViewport()->height();
int screenshotMapping = 0;
int cubeSize = screenshotMapping == 2 ?
screenshotW: // planet mapping needs higher resolution
screenshotW / 2;
std::vector<std::string> settingArgs;
boost::algorithm::split(settingArgs,settingStr,boost::is_any_of(" "));
if (settingArgs.size() > 0)
{
std::string typeStrings[4] = {"spherical","cylindrical","planet","cubemap"};
bool found = false;
for (int i = 0; i < 4; ++i)
if (settingArgs[0].compare(typeStrings[i]) == 0)
{
screenshotMapping = i;
found = true;
break;
}
if (!found)
{
std::cerr << "Wrong screenshot type: " << settingArgs[0] << "." << std::endl;
return false;
}
}
if (settingArgs.size() > 1)
screenshotW = std::min(10000,std::atoi(settingArgs[1].c_str()));
if (settingArgs.size() > 2)
screenshotH = std::min(10000,std::atoi(settingArgs[2].c_str()));
if (settingArgs.size() > 3)
cubeSize = std::min(5000,std::atoi(settingArgs[3].c_str()));
if (mCamera->isVanityOrPreviewModeEnabled())
{
std::cerr << "Spherical screenshots are not allowed in preview mode." << std::endl;
return false;
}
bool rawCubemap = screenshotMapping == 3;
if (rawCubemap)
screenshotW = cubeSize * 6; // the image will consist of 6 cube sides in a row
else if (screenshotMapping == 2)
screenshotH = screenshotW; // use square resolution for planet mapping
std::vector<osg::ref_ptr<osg::Image>> images;
for (int i = 0; i < 6; ++i)
images.push_back(new osg::Image);
osg::Vec3 directions[6] = {
rawCubemap ? osg::Vec3(1,0,0) : osg::Vec3(0,0,1),
osg::Vec3(0,0,-1),
osg::Vec3(-1,0,0),
rawCubemap ? osg::Vec3(0,0,1) : osg::Vec3(1,0,0),
osg::Vec3(0,1,0),
osg::Vec3(0,-1,0)};
double rotations[] = {
-osg::PI / 2.0,
osg::PI / 2.0,
osg::PI,
0,
osg::PI / 2.0,
osg::PI / 2.0};
double fovBackup = mFieldOfView;
mFieldOfView = 90.0; // each cubemap side sees 90 degrees
int maskBackup = mPlayerAnimation->getObjectRoot()->getNodeMask();
if (mCamera->isFirstPerson())
mPlayerAnimation->getObjectRoot()->setNodeMask(0);
for (int i = 0; i < 6; ++i) // for each cubemap side
{
osg::Matrixd transform = osg::Matrixd::rotate(osg::Vec3(0,0,-1),directions[i]);
if (!rawCubemap)
transform *= osg::Matrixd::rotate(rotations[i],osg::Vec3(0,0,-1));
osg::Image *sideImage = images[i].get();
screenshot(sideImage,cubeSize,cubeSize,transform);
if (!rawCubemap)
sideImage->flipHorizontal();
}
mPlayerAnimation->getObjectRoot()->setNodeMask(maskBackup);
mFieldOfView = fovBackup;
if (rawCubemap) // for raw cubemap don't run on GPU, just merge the images
{
image->allocateImage(cubeSize * 6,cubeSize,images[0]->r(),images[0]->getPixelFormat(),images[0]->getDataType());
for (int i = 0; i < 6; ++i)
osg::copyImage(images[i].get(),0,0,0,images[i]->s(),images[i]->t(),images[i]->r(),image,i * cubeSize,0,0);
return true;
}
// run on GPU now:
osg::ref_ptr<osg::TextureCubeMap> cubeTexture (new osg::TextureCubeMap);
cubeTexture->setResizeNonPowerOfTwoHint(false);
cubeTexture->setFilter(osg::Texture::MIN_FILTER,osg::Texture::NEAREST);
cubeTexture->setFilter(osg::Texture::MAG_FILTER,osg::Texture::NEAREST);
cubeTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
cubeTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
for (int i = 0; i < 6; ++i)
cubeTexture->setImage(i,images[i].get());
osg::ref_ptr<osg::Camera> screenshotCamera (new osg::Camera);
osg::ref_ptr<osg::ShapeDrawable> quad (new osg::ShapeDrawable(new osg::Box(osg::Vec3(0,0,0),2.0)));
std::map<std::string, std::string> defineMap;
Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager();
osg::ref_ptr<osg::Shader> fragmentShader (shaderMgr.getShader("s360_fragment.glsl",defineMap,osg::Shader::FRAGMENT));
osg::ref_ptr<osg::Shader> vertexShader (shaderMgr.getShader("s360_vertex.glsl", defineMap, osg::Shader::VERTEX));
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
osg::ref_ptr<osg::Program> program (new osg::Program);
program->addShader(fragmentShader);
program->addShader(vertexShader);
stateset->setAttributeAndModes(program, osg::StateAttribute::ON);
stateset->addUniform(new osg::Uniform("cubeMap",0));
stateset->addUniform(new osg::Uniform("mapping",screenshotMapping));
stateset->setTextureAttributeAndModes(0,cubeTexture,osg::StateAttribute::ON);
quad->setStateSet(stateset);
quad->setUpdateCallback(NULL);
screenshotCamera->addChild(quad);
mRootNode->addChild(screenshotCamera);
renderCameraToImage(screenshotCamera,image,screenshotW,screenshotH);
screenshotCamera->removeChildren(0,screenshotCamera->getNumChildren());
mRootNode->removeChild(screenshotCamera);
return true;
}
void RenderingManager::renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h)
{
camera->setNodeMask(Mask_RenderToTexture);
camera->attach(osg::Camera::COLOR_BUFFER, image);
camera->setRenderOrder(osg::Camera::PRE_RENDER);
camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF);
camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT,osg::Camera::PIXEL_BUFFER_RTT);
camera->setViewport(0, 0, w, h);
osg::ref_ptr<osg::Texture2D> texture (new osg::Texture2D);
texture->setInternalFormat(GL_RGB);
texture->setTextureSize(w,h);
texture->setResizeNonPowerOfTwoHint(false);
texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
camera->attach(osg::Camera::COLOR_BUFFER,texture);
image->setDataType(GL_UNSIGNED_BYTE);
image->setPixelFormat(texture->getInternalFormat());
// The draw needs to complete before we can copy back our image.
osg::ref_ptr<NotifyDrawCompletedCallback> callback (new NotifyDrawCompletedCallback);
camera->setFinalDrawCallback(callback);
MWBase::Environment::get().getWindowManager()->getLoadingScreen()->loadingOn(false);
mViewer->eventTraversal();
mViewer->updateTraversal();
mViewer->renderingTraversals();
callback->waitTillDone();
MWBase::Environment::get().getWindowManager()->getLoadingScreen()->loadingOff();
// now that we've "used up" the current frame, get a fresh framenumber for the next frame() following after the screenshot is completed
mViewer->advance(mViewer->getFrameStamp()->getSimulationTime());
}
void RenderingManager::screenshot(osg::Image *image, int w, int h, osg::Matrixd cameraTransform)
{ {
osg::ref_ptr<osg::Camera> rttCamera (new osg::Camera); osg::ref_ptr<osg::Camera> rttCamera (new osg::Camera);
rttCamera->setNodeMask(Mask_RenderToTexture); rttCamera->setNodeMask(Mask_RenderToTexture);
@ -704,7 +917,8 @@ namespace MWRender
rttCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); rttCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF);
rttCamera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); rttCamera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT);
rttCamera->setProjectionMatrixAsPerspective(mFieldOfView, w/float(h), mNearClip, mViewDistance); rttCamera->setProjectionMatrixAsPerspective(mFieldOfView, w/float(h), mNearClip, mViewDistance);
rttCamera->setViewMatrix(mViewer->getCamera()->getViewMatrix()); rttCamera->setViewMatrix(mViewer->getCamera()->getViewMatrix() * cameraTransform);
rttCamera->setViewport(0, 0, w, h); rttCamera->setViewport(0, 0, w, h);
osg::ref_ptr<osg::Texture2D> texture (new osg::Texture2D); osg::ref_ptr<osg::Texture2D> texture (new osg::Texture2D);
@ -720,25 +934,17 @@ namespace MWRender
rttCamera->setUpdateCallback(new NoTraverseCallback); rttCamera->setUpdateCallback(new NoTraverseCallback);
rttCamera->addChild(mSceneRoot); rttCamera->addChild(mSceneRoot);
rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & (~Mask_GUI));
mRootNode->addChild(rttCamera); rttCamera->addChild(mWater->getReflectionCamera());
rttCamera->addChild(mWater->getRefractionCamera());
// The draw needs to complete before we can copy back our image. rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & (~Mask_GUI));
osg::ref_ptr<NotifyDrawCompletedCallback> callback (new NotifyDrawCompletedCallback);
rttCamera->setFinalDrawCallback(callback);
// at the time this function is called we are in the middle of a frame, mRootNode->addChild(rttCamera);
// so out of order calls are necessary to get a correct frameNumber for the next frame.
// refer to the advance() and frame() order in Engine::go()
mViewer->eventTraversal();
mViewer->updateTraversal();
mViewer->renderingTraversals();
callback->waitTillDone(); rttCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// now that we've "used up" the current frame, get a fresh framenumber for the next frame() following after the screenshot is completed renderCameraToImage(rttCamera.get(),image,w,h);
mViewer->advance(mViewer->getFrameStamp()->getSimulationTime());
rttCamera->removeChildren(0, rttCamera->getNumChildren()); rttCamera->removeChildren(0, rttCamera->getNumChildren());
mRootNode->removeChild(rttCamera); mRootNode->removeChild(rttCamera);

@ -3,6 +3,7 @@
#include <osg/ref_ptr> #include <osg/ref_ptr>
#include <osg/Light> #include <osg/Light>
#include <osg/Camera>
#include <components/settings/settings.hpp> #include <components/settings/settings.hpp>
@ -126,7 +127,8 @@ namespace MWRender
void setWaterHeight(float level); void setWaterHeight(float level);
/// Take a screenshot of w*h onto the given image, not including the GUI. /// Take a screenshot of w*h onto the given image, not including the GUI.
void screenshot(osg::Image* image, int w, int h); void screenshot(osg::Image* image, int w, int h, osg::Matrixd cameraTransform=osg::Matrixd());
bool screenshot360(osg::Image* image, std::string settingStr);
struct RayResult struct RayResult
{ {
@ -206,6 +208,8 @@ namespace MWRender
LandManager* getLandManager() const; LandManager* getLandManager() const;
bool toggleBorders();
private: private:
void updateProjectionMatrix(); void updateProjectionMatrix();
void updateTextureFiltering(); void updateTextureFiltering();
@ -214,6 +218,8 @@ namespace MWRender
void reportStats() const; void reportStats() const;
void renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h);
osg::ref_ptr<osgUtil::IntersectionVisitor> getIntersectionVisitor(osgUtil::Intersector* intersector, bool ignorePlayer, bool ignoreActors); osg::ref_ptr<osgUtil::IntersectionVisitor> getIntersectionVisitor(osgUtil::Intersector* intersector, bool ignorePlayer, bool ignoreActors);
osg::ref_ptr<osgUtil::IntersectionVisitor> mIntersectionVisitor; osg::ref_ptr<osgUtil::IntersectionVisitor> mIntersectionVisitor;
@ -263,6 +269,7 @@ namespace MWRender
float mFieldOfViewOverride; float mFieldOfViewOverride;
float mFieldOfView; float mFieldOfView;
float mFirstPersonFieldOfView; float mFirstPersonFieldOfView;
bool mBorders;
void operator = (const RenderingManager&); void operator = (const RenderingManager&);
RenderingManager(const RenderingManager&); RenderingManager(const RenderingManager&);

@ -470,6 +470,16 @@ void Water::updateWaterMaterial()
updateVisible(); updateVisible();
} }
osg::Camera *Water::getReflectionCamera()
{
return mReflection;
}
osg::Camera *Water::getRefractionCamera()
{
return mRefraction;
}
void Water::createSimpleWaterStateSet(osg::Node* node, float alpha) void Water::createSimpleWaterStateSet(osg::Node* node, float alpha)
{ {
osg::ref_ptr<osg::StateSet> stateset = SceneUtil::createSimpleWaterStateSet(alpha, MWRender::RenderBin_Water); osg::ref_ptr<osg::StateSet> stateset = SceneUtil::createSimpleWaterStateSet(alpha, MWRender::RenderBin_Water);

@ -7,6 +7,7 @@
#include <osg/ref_ptr> #include <osg/ref_ptr>
#include <osg/Vec3f> #include <osg/Vec3f>
#include <osg/Uniform> #include <osg/Uniform>
#include <osg/Camera>
#include <components/settings/settings.hpp> #include <components/settings/settings.hpp>
@ -112,6 +113,9 @@ namespace MWRender
void update(float dt); void update(float dt);
osg::Camera *getReflectionCamera();
osg::Camera *getRefractionCamera();
void processChangedSettings(const Settings::CategorySettingVector& settings); void processChangedSettings(const Settings::CategorySettingVector& settings);
osg::Uniform *getRainIntensityUniform(); osg::Uniform *getRainIntensityUniform();

@ -193,10 +193,18 @@ namespace MWScript
Interpreter::Type_Integer time = static_cast<Interpreter::Type_Integer>(runtime[0].mFloat); Interpreter::Type_Integer time = static_cast<Interpreter::Type_Integer>(runtime[0].mFloat);
runtime.pop(); runtime.pop();
// Chance for Idle is unused
if (arg0)
{
--arg0;
runtime.pop();
}
std::vector<unsigned char> idleList; std::vector<unsigned char> idleList;
bool repeat = false; bool repeat = false;
for(int i=1; i < 10 && arg0; ++i) // Chances for Idle2-Idle9
for(int i=2; i<=9 && arg0; ++i)
{ {
if(!repeat) if(!repeat)
repeat = true; repeat = true;

@ -454,5 +454,6 @@ op 0x2000303: Fixme, explicit
op 0x2000304: Show op 0x2000304: Show
op 0x2000305: Show, explicit op 0x2000305: Show, explicit
op 0x2000306: OnActivate, explicit op 0x2000306: OnActivate, explicit
op 0x2000307: ToggleBorders, tb
opcodes 0x2000307-0x3ffffff unused opcodes 0x2000308-0x3ffffff unused

@ -254,6 +254,20 @@ namespace MWScript
} }
}; };
class OpToggleBorders : public Interpreter::Opcode0
{
public:
virtual void execute (Interpreter::Runtime& runtime)
{
bool enabled =
MWBase::Environment::get().getWorld()->toggleBorders();
runtime.getContext().report (enabled ?
"Border Rendering -> On" : "Border Rendering -> Off");
}
};
class OpTogglePathgrid : public Interpreter::Opcode0 class OpTogglePathgrid : public Interpreter::Opcode0
{ {
public: public:
@ -1380,6 +1394,7 @@ namespace MWScript
interpreter.installSegment5 (Compiler::Misc::opcodeRemoveFromLevItem, new OpRemoveFromLevItem); interpreter.installSegment5 (Compiler::Misc::opcodeRemoveFromLevItem, new OpRemoveFromLevItem);
interpreter.installSegment3 (Compiler::Misc::opcodeShowSceneGraph, new OpShowSceneGraph<ImplicitRef>); interpreter.installSegment3 (Compiler::Misc::opcodeShowSceneGraph, new OpShowSceneGraph<ImplicitRef>);
interpreter.installSegment3 (Compiler::Misc::opcodeShowSceneGraphExplicit, new OpShowSceneGraph<ExplicitRef>); interpreter.installSegment3 (Compiler::Misc::opcodeShowSceneGraphExplicit, new OpShowSceneGraph<ExplicitRef>);
interpreter.installSegment5 (Compiler::Misc::opcodeToggleBorders, new OpToggleBorders);
} }
} }
} }

@ -579,26 +579,25 @@ namespace MWScript
Interpreter::Type_Float rotation = osg::DegreesToRadians(runtime[0].mFloat*MWBase::Environment::get().getFrameDuration()); Interpreter::Type_Float rotation = osg::DegreesToRadians(runtime[0].mFloat*MWBase::Environment::get().getFrameDuration());
runtime.pop(); runtime.pop();
const float *objRot = ptr.getRefData().getPosition().rot; if (!ptr.getRefData().getBaseNode())
return;
float ax = objRot[0]; // We can rotate actors only around Z axis
float ay = objRot[1]; if (ptr.getClass().isActor() && (axis == "x" || axis == "y"))
float az = objRot[2]; return;
osg::Quat rot;
if (axis == "x") if (axis == "x")
{ rot = osg::Quat(rotation, -osg::X_AXIS);
MWBase::Environment::get().getWorld()->rotateObject(ptr,ax+rotation,ay,az);
}
else if (axis == "y") else if (axis == "y")
{ rot = osg::Quat(rotation, -osg::Y_AXIS);
MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay+rotation,az);
}
else if (axis == "z") else if (axis == "z")
{ rot = osg::Quat(rotation, -osg::Z_AXIS);
MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,az+rotation);
}
else else
throw std::runtime_error ("invalid rotation axis: " + axis); throw std::runtime_error ("invalid rotation axis: " + axis);
osg::Quat attitude = ptr.getRefData().getBaseNode()->getAttitude();
MWBase::Environment::get().getWorld()->rotateWorldObject(ptr, attitude * rot);
} }
}; };

@ -945,8 +945,13 @@ namespace MWWorld
{ {
const MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); const MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr);
static const float fCorpseClearDelay = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fCorpseClearDelay")->getFloat(); static const float fCorpseClearDelay = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fCorpseClearDelay")->getFloat();
if (creatureStats.isDead() && !ptr.getClass().isPersistent(ptr) && creatureStats.getTimeOfDeath() + fCorpseClearDelay <= MWBase::Environment::get().getWorld()->getTimeStamp()) if (creatureStats.isDead() &&
creatureStats.isDeathAnimationFinished() &&
!ptr.getClass().isPersistent(ptr) &&
creatureStats.getTimeOfDeath() + fCorpseClearDelay <= MWBase::Environment::get().getWorld()->getTimeStamp())
{
MWBase::Environment::get().getWorld()->deleteObject(ptr); MWBase::Environment::get().getWorld()->deleteObject(ptr);
}
} }
void CellStore::respawn() void CellStore::respawn()

@ -47,7 +47,7 @@ namespace
for (typename MWWorld::CellRefList<T>::List::iterator iter (list.mList.begin()); for (typename MWWorld::CellRefList<T>::List::iterator iter (list.mList.begin());
iter!=list.mList.end(); ++iter) iter!=list.mList.end(); ++iter)
{ {
if (Misc::StringUtils::ciEqual(iter->mBase->mId, id2)) if (Misc::StringUtils::ciEqual(iter->mBase->mId, id2) && iter->mData.getCount())
{ {
MWWorld::Ptr ptr (&*iter, 0); MWWorld::Ptr ptr (&*iter, 0);
ptr.setContainerStore (store); ptr.setContainerStore (store);
@ -675,6 +675,30 @@ int MWWorld::ContainerStore::getType (const ConstPtr& ptr)
"Object '" + ptr.getCellRef().getRefId() + "' of type " + ptr.getTypeName() + " can not be placed into a container"); "Object '" + ptr.getCellRef().getRefId() + "' of type " + ptr.getTypeName() + " can not be placed into a container");
} }
MWWorld::Ptr MWWorld::ContainerStore::findReplacement(const std::string& id)
{
MWWorld::Ptr item;
int itemHealth = 1;
for (MWWorld::ContainerStoreIterator iter = begin(); iter != end(); ++iter)
{
int iterHealth = iter->getClass().hasItemHealth(*iter) ? iter->getClass().getItemHealth(*iter) : 1;
if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), id))
{
// Prefer the stack with the lowest remaining uses
// Try to get item with zero durability only if there are no other items found
if (item.isEmpty() ||
(iterHealth > 0 && iterHealth < itemHealth) ||
(itemHealth <= 0 && iterHealth > 0))
{
item = *iter;
itemHealth = iterHealth;
}
}
}
return item;
}
MWWorld::Ptr MWWorld::ContainerStore::search (const std::string& id) MWWorld::Ptr MWWorld::ContainerStore::search (const std::string& id)
{ {
{ {

@ -198,6 +198,9 @@ namespace MWWorld
///< This function throws an exception, if ptr does not point to an object, that can be ///< This function throws an exception, if ptr does not point to an object, that can be
/// put into a container. /// put into a container.
Ptr findReplacement(const std::string& id);
///< Returns replacement for object with given id. Prefer used items (with low durability left).
Ptr search (const std::string& id); Ptr search (const std::string& id);
virtual void writeState (ESM::InventoryState& state) const; virtual void writeState (ESM::InventoryState& state) const;

@ -120,7 +120,7 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener)
} }
} }
void ESMStore::setUp() void ESMStore::setUp(bool validateRecords)
{ {
mIds.clear(); mIds.clear();
@ -142,6 +142,62 @@ void ESMStore::setUp()
mAttributes.setUp(); mAttributes.setUp();
mDialogs.setUp(); mDialogs.setUp();
mStatics.setUp(); mStatics.setUp();
if (validateRecords)
validate();
}
void ESMStore::validate()
{
// Cache first class from store - we will use it if current class is not found
std::string defaultCls = "";
Store<ESM::Class>::iterator it = mClasses.begin();
if (it != mClasses.end())
defaultCls = it->mId;
else
throw std::runtime_error("List of NPC classes is empty!");
// Validate NPCs for non-existing class and faction.
// We will replace invalid entries by fixed ones
std::vector<ESM::NPC> entitiesToReplace;
for (ESM::NPC npc : mNpcs)
{
bool changed = false;
const std::string npcFaction = npc.mFaction;
if (!npcFaction.empty())
{
const ESM::Faction *fact = mFactions.search(npcFaction);
if (!fact)
{
std::cerr << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent faction '" << npc.mFaction << "', ignoring it." << std::endl;
npc.mFaction = "";
npc.mNpdt.mRank = -1;
changed = true;
}
}
std::string npcClass = npc.mClass;
if (!npcClass.empty())
{
const ESM::Class *cls = mClasses.search(npcClass);
if (!cls)
{
std::cerr << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent class '" << npc.mClass << "', using '" << defaultCls << "' class as replacement." << std::endl;
npc.mClass = defaultCls;
changed = true;
}
}
if (changed)
entitiesToReplace.push_back(npc);
}
for (const ESM::NPC &npc : entitiesToReplace)
{
mNpcs.eraseStatic(npc.mId);
mNpcs.insertStatic(npc);
}
} }
int ESMStore::countSavedGameRecords() const int ESMStore::countSavedGameRecords() const

@ -74,6 +74,9 @@ namespace MWWorld
unsigned int mDynamicCount; unsigned int mDynamicCount;
/// Validate entries in store after setup
void validate();
public: public:
/// \todo replace with SharedIterator<StoreBase> /// \todo replace with SharedIterator<StoreBase>
typedef std::map<int, StoreBase *>::const_iterator iterator; typedef std::map<int, StoreBase *>::const_iterator iterator;
@ -228,7 +231,7 @@ namespace MWWorld
// This method must be called once, after loading all master/plugin files. This can only be done // This method must be called once, after loading all master/plugin files. This can only be done
// from the outside, so it must be public. // from the outside, so it must be public.
void setUp(); void setUp(bool validateRecords = false);
int countSavedGameRecords() const; int countSavedGameRecords() const;

@ -287,6 +287,7 @@ namespace MWWorld
mAttackingOrSpell = false; mAttackingOrSpell = false;
mCurrentCrimeId = -1; mCurrentCrimeId = -1;
mPaidCrimeId = -1; mPaidCrimeId = -1;
mPreviousItems.clear();
mLastKnownExteriorPosition = osg::Vec3f(0,0,0); mLastKnownExteriorPosition = osg::Vec3f(0,0,0);
for (int i=0; i<ESM::Skill::Length; ++i) for (int i=0; i<ESM::Skill::Length; ++i)
@ -341,6 +342,8 @@ namespace MWWorld
for (int i=0; i<ESM::Skill::Length; ++i) for (int i=0; i<ESM::Skill::Length; ++i)
mSaveSkills[i].writeState(player.mSaveSkills[i]); mSaveSkills[i].writeState(player.mSaveSkills[i]);
player.mPreviousItems = mPreviousItems;
writer.startRecord (ESM::REC_PLAY); writer.startRecord (ESM::REC_PLAY);
player.save (writer); player.save (writer);
writer.endRecord (ESM::REC_PLAY); writer.endRecord (ESM::REC_PLAY);
@ -441,6 +444,8 @@ namespace MWWorld
mForwardBackward = 0; mForwardBackward = 0;
mTeleported = false; mTeleported = false;
mPreviousItems = player.mPreviousItems;
return true; return true;
} }
@ -461,4 +466,19 @@ namespace MWWorld
{ {
return mPaidCrimeId; return mPaidCrimeId;
} }
void Player::setPreviousItem(const std::string& boundItemId, const std::string& previousItemId)
{
mPreviousItems[boundItemId] = previousItemId;
}
std::string Player::getPreviousItem(const std::string& boundItemId)
{
return mPreviousItems[boundItemId];
}
void Player::erasePreviousItem(const std::string& boundItemId)
{
mPreviousItems.erase(boundItemId);
}
} }

@ -1,6 +1,8 @@
#ifndef GAME_MWWORLD_PLAYER_H #ifndef GAME_MWWORLD_PLAYER_H
#define GAME_MWWORLD_PLAYER_H #define GAME_MWWORLD_PLAYER_H
#include <map>
#include "../mwworld/refdata.hpp" #include "../mwworld/refdata.hpp"
#include "../mwworld/livecellref.hpp" #include "../mwworld/livecellref.hpp"
@ -46,6 +48,9 @@ namespace MWWorld
int mCurrentCrimeId; // the id assigned witnesses int mCurrentCrimeId; // the id assigned witnesses
int mPaidCrimeId; // the last id paid off (0 bounty) int mPaidCrimeId; // the last id paid off (0 bounty)
typedef std::map<std::string, std::string> PreviousItems; // previous equipped items, needed for bound spells
PreviousItems mPreviousItems;
// Saved stats prior to becoming a werewolf // Saved stats prior to becoming a werewolf
MWMechanics::SkillValue mSaveSkills[ESM::Skill::Length]; MWMechanics::SkillValue mSaveSkills[ESM::Skill::Length];
MWMechanics::AttributeValue mSaveAttributes[ESM::Attribute::Length]; MWMechanics::AttributeValue mSaveAttributes[ESM::Attribute::Length];
@ -120,6 +125,10 @@ namespace MWWorld
int getNewCrimeId(); // get new id for witnesses int getNewCrimeId(); // get new id for witnesses
void recordCrimeId(); // record the paid crime id when bounty is 0 void recordCrimeId(); // record the paid crime id when bounty is 0
int getCrimeId() const; // get the last paid crime id int getCrimeId() const; // get the last paid crime id
void setPreviousItem(const std::string& boundItemId, const std::string& previousItemId);
std::string getPreviousItem(const std::string& boundItemId);
void erasePreviousItem(const std::string& boundItemId);
}; };
} }
#endif #endif

@ -6,7 +6,6 @@
#include <components/esm/esmwriter.hpp> #include <components/esm/esmwriter.hpp>
#include <components/esm/savedgame.hpp> #include <components/esm/savedgame.hpp>
#include <components/esm/weatherstate.hpp> #include <components/esm/weatherstate.hpp>
#include <components/fallback/fallback.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/soundmanager.hpp" #include "../mwbase/soundmanager.hpp"
@ -43,49 +42,67 @@ namespace
} }
template <typename T> template <typename T>
T TimeOfDayInterpolator<T>::getValue(const float gameHour, const TimeOfDaySettings& timeSettings) const T TimeOfDayInterpolator<T>::getValue(const float gameHour, const TimeOfDaySettings& timeSettings, const std::string& prefix) const
{ {
// TODO: use pre/post sunset/sunrise time values in [Weather] section WeatherSetting setting = timeSettings.getSetting(prefix);
float preSunriseTime = setting.mPreSunriseTime;
float postSunriseTime = setting.mPostSunriseTime;
float preSunsetTime = setting.mPreSunsetTime;
float postSunsetTime = setting.mPostSunsetTime;
// night // night
if (gameHour <= timeSettings.mNightEnd || gameHour >= timeSettings.mNightStart + 1) if (gameHour < timeSettings.mNightEnd - preSunriseTime || gameHour > timeSettings.mNightStart + postSunsetTime)
return mNightValue; return mNightValue;
// sunrise // sunrise
else if (gameHour >= timeSettings.mNightEnd && gameHour <= timeSettings.mDayStart + 1) else if (gameHour >= timeSettings.mNightEnd - preSunriseTime && gameHour <= timeSettings.mDayStart + postSunriseTime)
{ {
if (gameHour <= timeSettings.mSunriseTime) float duration = timeSettings.mDayStart + postSunriseTime - timeSettings.mNightEnd + preSunriseTime;
float middle = timeSettings.mNightEnd - preSunriseTime + duration / 2.f;
if (gameHour <= middle)
{ {
// fade in // fade in
float advance = timeSettings.mSunriseTime - gameHour; float advance = middle - gameHour;
float factor = advance / 0.5f; float factor = 0.f;
if (duration > 0)
factor = advance / duration * 2;
return lerp(mSunriseValue, mNightValue, factor); return lerp(mSunriseValue, mNightValue, factor);
} }
else else
{ {
// fade out // fade out
float advance = gameHour - timeSettings.mSunriseTime; float advance = gameHour - middle;
float factor = advance / 3.f; float factor = 1.f;
if (duration > 0)
factor = advance / duration * 2;
return lerp(mSunriseValue, mDayValue, factor); return lerp(mSunriseValue, mDayValue, factor);
} }
} }
// day // day
else if (gameHour >= timeSettings.mDayStart + 1 && gameHour <= timeSettings.mDayEnd - 1) else if (gameHour > timeSettings.mDayStart + postSunriseTime && gameHour < timeSettings.mDayEnd - preSunsetTime)
return mDayValue; return mDayValue;
// sunset // sunset
else if (gameHour >= timeSettings.mDayEnd - 1 && gameHour <= timeSettings.mNightStart + 1) else if (gameHour >= timeSettings.mDayEnd - preSunsetTime && gameHour <= timeSettings.mNightStart + postSunsetTime)
{ {
if (gameHour <= timeSettings.mDayEnd + 1) float duration = timeSettings.mNightStart + postSunsetTime - timeSettings.mDayEnd + preSunsetTime;
float middle = timeSettings.mDayEnd - preSunsetTime + duration / 2.f;
if (gameHour <= middle)
{ {
// fade in // fade in
float advance = (timeSettings.mDayEnd + 1) - gameHour; float advance = middle - gameHour;
float factor = (advance / 2); float factor = 0.f;
if (duration > 0)
factor = advance / duration * 2;
return lerp(mSunsetValue, mDayValue, factor); return lerp(mSunsetValue, mDayValue, factor);
} }
else else
{ {
// fade out // fade out
float advance = gameHour - (timeSettings.mDayEnd + 1); float advance = gameHour - middle;
float factor = advance / 2.f; float factor = 1.f;
if (duration > 0)
factor = advance / duration * 2;
return lerp(mSunsetValue, mNightValue, factor); return lerp(mSunsetValue, mNightValue, factor);
} }
} }
@ -539,10 +556,28 @@ WeatherManager::WeatherManager(MWRender::RenderingManager& rendering, const Fall
, mPlayingSoundID() , mPlayingSoundID()
{ {
mTimeSettings.mNightStart = mSunsetTime + mSunsetDuration; mTimeSettings.mNightStart = mSunsetTime + mSunsetDuration;
mTimeSettings.mNightEnd = mSunriseTime - 0.5f; mTimeSettings.mNightEnd = mSunriseTime;
mTimeSettings.mDayStart = mSunriseTime + mSunriseDuration; mTimeSettings.mDayStart = mSunriseTime + mSunriseDuration;
mTimeSettings.mDayEnd = mSunsetTime; mTimeSettings.mDayEnd = mSunsetTime;
mTimeSettings.mSunriseTime = mSunriseTime;
mTimeSettings.addSetting(fallback, "Sky");
mTimeSettings.addSetting(fallback, "Ambient");
mTimeSettings.addSetting(fallback, "Fog");
mTimeSettings.addSetting(fallback, "Sun");
// Morrowind handles stars settings differently for other ones
mTimeSettings.mStarsPostSunsetStart = fallback.getFallbackFloat("Weather_Stars_Post-Sunset_Start");
mTimeSettings.mStarsPreSunriseFinish = fallback.getFallbackFloat("Weather_Stars_Pre-Sunrise_Finish");
mTimeSettings.mStarsFadingDuration = fallback.getFallbackFloat("Weather_Stars_Fading_Duration");
WeatherSetting starSetting = {
mTimeSettings.mStarsPreSunriseFinish,
mTimeSettings.mStarsFadingDuration - mTimeSettings.mStarsPreSunriseFinish,
mTimeSettings.mStarsPostSunsetStart,
mTimeSettings.mStarsFadingDuration - mTimeSettings.mStarsPostSunsetStart
};
mTimeSettings.mSunriseTransitions["Stars"] = starSetting;
mWeatherSettings.reserve(10); mWeatherSettings.reserve(10);
// These distant land fog factor and offset values are the defaults MGE XE provides. Should be // These distant land fog factor and offset values are the defaults MGE XE provides. Should be
@ -698,10 +733,13 @@ void WeatherManager::update(float duration, bool paused, const TimeStamp& time,
const float nightDuration = 24.f - dayDuration; const float nightDuration = 24.f - dayDuration;
double theta; double theta;
if ( !is_night ) { if ( !is_night )
{
theta = static_cast<float>(osg::PI) * (adjustedHour - mSunriseTime) / dayDuration; theta = static_cast<float>(osg::PI) * (adjustedHour - mSunriseTime) / dayDuration;
} else { }
theta = static_cast<float>(osg::PI) * (1.f - (adjustedHour - adjustedNightStart) / nightDuration); else
{
theta = static_cast<float>(osg::PI) + static_cast<float>(osg::PI) * (adjustedHour - adjustedNightStart) / nightDuration;
} }
osg::Vec3f final( osg::Vec3f final(
@ -711,7 +749,7 @@ void WeatherManager::update(float duration, bool paused, const TimeStamp& time,
mRendering.setSunDirection( final * -1 ); mRendering.setSunDirection( final * -1 );
} }
float underwaterFog = mUnderwaterFog.getValue(time.getHour(), mTimeSettings); float underwaterFog = mUnderwaterFog.getValue(time.getHour(), mTimeSettings, "Fog");
float peakHour = mSunriseTime + (mSunsetTime - mSunriseTime) / 2; float peakHour = mSunriseTime + (mSunsetTime - mSunriseTime) / 2;
if (time.getHour() < mSunriseTime || time.getHour() > mSunsetTime) if (time.getHour() < mSunriseTime || time.getHour() > mSunsetTime)
@ -784,7 +822,7 @@ unsigned int WeatherManager::getWeatherID() const
bool WeatherManager::useTorches(float hour) const bool WeatherManager::useTorches(float hour) const
{ {
bool isDark = hour < mSunriseTime || hour > mTimeSettings.mNightStart - 1; bool isDark = hour < mSunriseTime || hour > mTimeSettings.mNightStart;
return isDark && !mPrecipitation; return isDark && !mPrecipitation;
} }
@ -1060,20 +1098,25 @@ inline void WeatherManager::calculateResult(const int weatherID, const float gam
mResult.mParticleEffect = current.mParticleEffect; mResult.mParticleEffect = current.mParticleEffect;
mResult.mRainEffect = current.mRainEffect; mResult.mRainEffect = current.mRainEffect;
mResult.mNight = (gameHour < mSunriseTime || gameHour > mTimeSettings.mNightStart - 1); mResult.mNight = (gameHour < mSunriseTime || gameHour > mTimeSettings.mNightStart + mTimeSettings.mStarsPostSunsetStart - mTimeSettings.mStarsFadingDuration);
mResult.mFogDepth = current.mLandFogDepth.getValue(gameHour, mTimeSettings); mResult.mFogDepth = current.mLandFogDepth.getValue(gameHour, mTimeSettings, "Fog");
mResult.mFogColor = current.mFogColor.getValue(gameHour, mTimeSettings, "Fog");
mResult.mAmbientColor = current.mAmbientColor.getValue(gameHour, mTimeSettings, "Ambient");
mResult.mSunColor = current.mSunColor.getValue(gameHour, mTimeSettings, "Sun");
mResult.mSkyColor = current.mSkyColor.getValue(gameHour, mTimeSettings, "Sky");
mResult.mNightFade = mNightFade.getValue(gameHour, mTimeSettings, "Stars");
mResult.mDLFogFactor = current.mDL.FogFactor; mResult.mDLFogFactor = current.mDL.FogFactor;
mResult.mDLFogOffset = current.mDL.FogOffset; mResult.mDLFogOffset = current.mDL.FogOffset;
mResult.mFogColor = current.mFogColor.getValue(gameHour, mTimeSettings);
mResult.mAmbientColor = current.mAmbientColor.getValue(gameHour, mTimeSettings);
mResult.mSunColor = current.mSunColor.getValue(gameHour, mTimeSettings);
mResult.mSkyColor = current.mSkyColor.getValue(gameHour, mTimeSettings);
mResult.mNightFade = mNightFade.getValue(gameHour, mTimeSettings);
if (gameHour >= mSunsetTime - mSunPreSunsetTime) WeatherSetting setting = mTimeSettings.getSetting("Sun");
float preSunsetTime = setting.mPreSunsetTime;
if (gameHour >= mTimeSettings.mDayEnd - preSunsetTime)
{ {
float factor = (gameHour - (mSunsetTime - mSunPreSunsetTime)) / mSunPreSunsetTime; float factor = 1.f;
if (preSunsetTime > 0)
factor = (gameHour - (mTimeSettings.mDayEnd - preSunsetTime)) / preSunsetTime;
factor = std::min(1.f, factor); factor = std::min(1.f, factor);
mResult.mSunDiscColor = lerp(osg::Vec4f(1,1,1,1), current.mSunDiscSunsetColor, factor); mResult.mSunDiscColor = lerp(osg::Vec4f(1,1,1,1), current.mSunDiscSunsetColor, factor);
// The SunDiscSunsetColor in the INI isn't exactly the resulting color on screen, most likely because // The SunDiscSunsetColor in the INI isn't exactly the resulting color on screen, most likely because
@ -1087,15 +1130,17 @@ inline void WeatherManager::calculateResult(const int weatherID, const float gam
else else
mResult.mSunDiscColor = osg::Vec4f(1,1,1,1); mResult.mSunDiscColor = osg::Vec4f(1,1,1,1);
if (gameHour >= mSunsetTime) if (gameHour >= mTimeSettings.mDayEnd)
{ {
float fade = std::min(1.f, (gameHour - mSunsetTime) / 2.f); // sunset
float fade = std::min(1.f, (gameHour - mTimeSettings.mDayEnd) / (mTimeSettings.mNightStart - mTimeSettings.mDayEnd));
fade = fade*fade; fade = fade*fade;
mResult.mSunDiscColor.a() = 1.f - fade; mResult.mSunDiscColor.a() = 1.f - fade;
} }
else if (gameHour >= mSunriseTime && gameHour <= mSunriseTime + 1) else if (gameHour >= mTimeSettings.mNightEnd && gameHour <= mTimeSettings.mNightEnd + mSunriseDuration / 2.f)
{ {
mResult.mSunDiscColor.a() = gameHour - mSunriseTime; // sunrise
mResult.mSunDiscColor.a() = gameHour - mTimeSettings.mNightEnd;
} }
else else
mResult.mSunDiscColor.a() = 1; mResult.mSunDiscColor.a() = 1;

@ -7,6 +7,8 @@
#include <osg/Vec4f> #include <osg/Vec4f>
#include <components/fallback/fallback.hpp>
#include "../mwbase/soundmanager.hpp" #include "../mwbase/soundmanager.hpp"
#include "../mwrender/sky.hpp" #include "../mwrender/sky.hpp"
@ -38,6 +40,13 @@ namespace MWWorld
{ {
class TimeStamp; class TimeStamp;
struct WeatherSetting
{
float mPreSunriseTime;
float mPostSunriseTime;
float mPreSunsetTime;
float mPostSunsetTime;
};
struct TimeOfDaySettings struct TimeOfDaySettings
{ {
@ -45,7 +54,37 @@ namespace MWWorld
float mNightEnd; float mNightEnd;
float mDayStart; float mDayStart;
float mDayEnd; float mDayEnd;
float mSunriseTime;
std::map<std::string, WeatherSetting> mSunriseTransitions;
float mStarsPostSunsetStart;
float mStarsPreSunriseFinish;
float mStarsFadingDuration;
WeatherSetting getSetting(const std::string& type) const
{
std::map<std::string, WeatherSetting>::const_iterator it = mSunriseTransitions.find(type);
if (it != mSunriseTransitions.end())
{
return it->second;
}
else
{
return { 1.f, 1.f, 1.f, 1.f };
}
}
void addSetting(const Fallback::Map& fallback, const std::string& type)
{
WeatherSetting setting = {
fallback.getFallbackFloat("Weather_" + type + "_Pre-Sunrise_Time"),
fallback.getFallbackFloat("Weather_" + type + "_Post-Sunrise_Time"),
fallback.getFallbackFloat("Weather_" + type + "_Pre-Sunset_Time"),
fallback.getFallbackFloat("Weather_" + type + "_Post-Sunset_Time")
};
mSunriseTransitions[type] = setting;
}
}; };
/// Interpolates between 4 data points (sunrise, day, sunset, night) based on the time of day. /// Interpolates between 4 data points (sunrise, day, sunset, night) based on the time of day.
@ -59,7 +98,7 @@ namespace MWWorld
{ {
} }
T getValue (const float gameHour, const TimeOfDaySettings& timeSettings) const; T getValue (const float gameHour, const TimeOfDaySettings& timeSettings, const std::string& prefix) const;
private: private:
T mSunriseValue, mDayValue, mSunsetValue, mNightValue; T mSunriseValue, mDayValue, mSunsetValue, mNightValue;

@ -184,7 +184,7 @@ namespace MWWorld
fillGlobalVariables(); fillGlobalVariables();
mStore.setUp(); mStore.setUp(true);
mStore.movePlayerRecord(); mStore.movePlayerRecord();
mSwimHeightScale = mStore.get<ESM::GameSetting>().find("fSwimHeightScale")->getFloat(); mSwimHeightScale = mStore.get<ESM::GameSetting>().find("fSwimHeightScale")->getFloat();
@ -1073,20 +1073,29 @@ namespace MWWorld
osg::Quat rot = osg::Quat(posdata.rot[0], osg::Vec3f(-1,0,0)) * osg::Quat(posdata.rot[2], osg::Vec3f(0,0,-1)); osg::Quat rot = osg::Quat(posdata.rot[0], osg::Vec3f(-1,0,0)) * osg::Quat(posdata.rot[2], osg::Vec3f(0,0,-1));
osg::Vec3f pos = ptr.getRefData().getPosition().asVec3(); osg::Vec3f halfExtents = mPhysics->getHalfExtents(ptr);
// the origin of hitbox is an actor's front, not center
distance += halfExtents.y();
// special cased for better aiming with the camera
// if we do not hit anything, will use the default approach as fallback
if (ptr == getPlayerPtr()) if (ptr == getPlayerPtr())
pos = getActorHeadTransform(ptr).getTrans(); // special cased for better aiming with the camera
else
{ {
// general case, compatible with all types of different creatures osg::Vec3f pos = getActorHeadTransform(ptr).getTrans();
// note: we intentionally do *not* use the collision box offset here, this is required to make
// some flying creatures work that have their collision box offset in the air std::pair<MWWorld::Ptr,osg::Vec3f> result = mPhysics->getHitContact(ptr, pos, rot, distance, targets);
osg::Vec3f halfExtents = mPhysics->getHalfExtents(ptr); if(!result.first.isEmpty())
pos.z() += halfExtents.z() * 2 * 0.75; return std::make_pair(result.first, result.second);
distance += halfExtents.y();
} }
osg::Vec3f pos = ptr.getRefData().getPosition().asVec3();
// general case, compatible with all types of different creatures
// note: we intentionally do *not* use the collision box offset here, this is required to make
// some flying creatures work that have their collision box offset in the air
pos.z() += halfExtents.z();
std::pair<MWWorld::Ptr,osg::Vec3f> result = mPhysics->getHitContact(ptr, pos, rot, distance, targets); std::pair<MWWorld::Ptr,osg::Vec3f> result = mPhysics->getHitContact(ptr, pos, rot, distance, targets);
if(result.first.isEmpty()) if(result.first.isEmpty())
return std::make_pair(MWWorld::Ptr(), osg::Vec3f()); return std::make_pair(MWWorld::Ptr(), osg::Vec3f());
@ -1342,6 +1351,15 @@ namespace MWWorld
rotateObjectImp(ptr, osg::Vec3f(x, y, z), adjust); rotateObjectImp(ptr, osg::Vec3f(x, y, z), adjust);
} }
void World::rotateWorldObject (const Ptr& ptr, osg::Quat rotate)
{
if(ptr.getRefData().getBaseNode() != 0)
{
mRendering->rotateObject(ptr, rotate);
mPhysics->updateRotation(ptr);
}
}
MWWorld::Ptr World::placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos) MWWorld::Ptr World::placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos)
{ {
return copyObjectToCell(ptr,cell,pos,ptr.getRefData().getCount(),false); return copyObjectToCell(ptr,cell,pos,ptr.getRefData().getCount(),false);
@ -1919,6 +1937,11 @@ namespace MWWorld
return mRendering->toggleRenderMode(MWRender::Render_Scene); return mRendering->toggleRenderMode(MWRender::Render_Scene);
} }
bool World::toggleBorders()
{
return mRendering->toggleBorders();
}
void World::PCDropped (const Ptr& item) void World::PCDropped (const Ptr& item)
{ {
std::string script = item.getClass().getScript(item); std::string script = item.getClass().getScript(item);
@ -2282,6 +2305,11 @@ namespace MWWorld
{ {
mRendering->screenshot(image, w, h); mRendering->screenshot(image, w, h);
} }
bool World::screenshot360(osg::Image* image, std::string settingStr)
{
return mRendering->screenshot360(image,settingStr);
}
void World::activateDoor(const MWWorld::Ptr& door) void World::activateDoor(const MWWorld::Ptr& door)
{ {
@ -2601,11 +2629,11 @@ namespace MWWorld
int y = std::stoi(name.substr(name.find(',')+1)); int y = std::stoi(name.substr(name.find(',')+1));
ext = getExterior(x, y)->getCell(); ext = getExterior(x, y)->getCell();
} }
catch (std::invalid_argument) catch (const std::invalid_argument&)
{ {
// This exception can be ignored, as this means that name probably refers to a interior cell instead of comma separated coordinates // This exception can be ignored, as this means that name probably refers to a interior cell instead of comma separated coordinates
} }
catch (std::out_of_range) catch (const std::out_of_range&)
{ {
throw std::runtime_error("Cell coordinates out of range."); throw std::runtime_error("Cell coordinates out of range.");
} }
@ -2743,64 +2771,66 @@ namespace MWWorld
{ {
MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
// Get the target to use for "on touch" effects, using the facing direction from Head node
MWWorld::Ptr target;
float distance = getActivationDistancePlusTelekinesis();
osg::Vec3f hitPosition = actor.getRefData().getPosition().asVec3();
osg::Vec3f origin = getActorHeadTransform(actor).getTrans();
osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1,0,0))
* osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0,0,-1));
osg::Vec3f direction = orient * osg::Vec3f(0,1,0);
osg::Vec3f dest = origin + direction * distance;
// For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result.
std::vector<MWWorld::Ptr> targetActors; std::vector<MWWorld::Ptr> targetActors;
if (!actor.isEmpty() && actor != MWMechanics::getPlayer()) if (!actor.isEmpty() && actor != MWMechanics::getPlayer())
actor.getClass().getCreatureStats(actor).getAiSequence().getCombatTargets(targetActors); actor.getClass().getCreatureStats(actor).getAiSequence().getCombatTargets(targetActors);
// For actor targets, we want to use bounding boxes (physics raycast). const float fCombatDistance = getStore().get<ESM::GameSetting>().find("fCombatDistance")->getFloat();
// This is to give a slight tolerance for errors, especially with creatures like the Skeleton that would be very hard to aim at otherwise.
// For object targets, we want the detailed shapes (rendering raycast).
// If we used the bounding boxes for static objects, then we would not be able to target e.g. objects lying on a shelf.
MWPhysics::PhysicsSystem::RayResult result1 = mPhysics->castRay(origin, dest, actor, targetActors, MWPhysics::CollisionType_Actor);
MWRender::RenderingManager::RayResult result2 = mRendering->castRay(origin, dest, true, true); osg::Vec3f hitPosition = actor.getRefData().getPosition().asVec3();
float dist1 = std::numeric_limits<float>::max(); // for player we can take faced object first
float dist2 = std::numeric_limits<float>::max(); MWWorld::Ptr target;
if (actor == MWMechanics::getPlayer())
target = getFacedObject();
if (result1.mHit) // if the faced object can not be activated, do not use it
dist1 = (origin - result1.mHitPos).length(); if (!target.isEmpty() && !target.getClass().canBeActivated(target))
if (result2.mHit) target = NULL;
dist2 = (origin - result2.mHitPointWorld).length();
if (result1.mHit) if (target.isEmpty())
{ {
target = result1.mHitObject; // For actor targets, we want to use hit contact with bounding boxes.
hitPosition = result1.mHitPos; // This is to give a slight tolerance for errors, especially with creatures like the Skeleton that would be very hard to aim at otherwise.
if (dist1 > getMaxActivationDistance() && !target.isEmpty() && (target.getClass().isActor() || !target.getClass().canBeActivated(target))) // For object targets, we want the detailed shapes (rendering raycast).
target = NULL; // If we used the bounding boxes for static objects, then we would not be able to target e.g. objects lying on a shelf.
} std::pair<MWWorld::Ptr,osg::Vec3f> result1 = getHitContact(actor, fCombatDistance, targetActors);
else if (result2.mHit)
{
target = result2.mHitObject;
hitPosition = result2.mHitPointWorld;
if (dist2 > getMaxActivationDistance() && !target.isEmpty() && !target.getClass().canBeActivated(target))
target = NULL;
}
// When targeting an actor that is in combat with an "on touch" spell, // Get the target to use for "on touch" effects, using the facing direction from Head node
// compare against the minimum of activation distance and combat distance. osg::Vec3f origin = getActorHeadTransform(actor).getTrans();
if (!target.isEmpty() && target.getClass().isActor() && target.getClass().getCreatureStats (target).getAiSequence().isInCombat()) osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1,0,0))
{ * osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0,0,-1));
distance = std::min (distance, getStore().get<ESM::GameSetting>().find("fCombatDistance")->getFloat());
if (distance < dist1) osg::Vec3f direction = orient * osg::Vec3f(0,1,0);
target = NULL; float distance = getMaxActivationDistance();
osg::Vec3f dest = origin + direction * distance;
MWRender::RenderingManager::RayResult result2 = mRendering->castRay(origin, dest, true, true);
float dist1 = std::numeric_limits<float>::max();
float dist2 = std::numeric_limits<float>::max();
if (!result1.first.isEmpty() && result1.first.getClass().isActor())
dist1 = (origin - result1.second).length();
if (result2.mHit)
dist2 = (origin - result2.mHitPointWorld).length();
if (!result1.first.isEmpty() && result1.first.getClass().isActor())
{
target = result1.first;
hitPosition = result1.second;
if (dist1 > getMaxActivationDistance())
target = NULL;
}
else if (result2.mHit)
{
target = result2.mHitObject;
hitPosition = result2.mHitPointWorld;
if (dist2 > getMaxActivationDistance() && !target.isEmpty() && !target.getClass().canBeActivated(target))
target = NULL;
}
} }
std::string selectedSpell = stats.getSpells().getSelectedSpell(); std::string selectedSpell = stats.getSpells().getSelectedSpell();
@ -3457,7 +3487,7 @@ namespace MWWorld
osg::Vec3f World::aimToTarget(const ConstPtr &actor, const MWWorld::ConstPtr& target) osg::Vec3f World::aimToTarget(const ConstPtr &actor, const MWWorld::ConstPtr& target)
{ {
osg::Vec3f weaponPos = actor.getRefData().getPosition().asVec3(); osg::Vec3f weaponPos = actor.getRefData().getPosition().asVec3();
weaponPos.z() += mPhysics->getHalfExtents(actor).z() * 2 * 0.75; weaponPos.z() += mPhysics->getHalfExtents(actor).z();
osg::Vec3f targetPos = mPhysics->getCollisionObjectPosition(target); osg::Vec3f targetPos = mPhysics->getCollisionObjectPosition(target);
return (targetPos - weaponPos); return (targetPos - weaponPos);
} }
@ -3466,7 +3496,7 @@ namespace MWWorld
{ {
osg::Vec3f weaponPos = actor.getRefData().getPosition().asVec3(); osg::Vec3f weaponPos = actor.getRefData().getPosition().asVec3();
osg::Vec3f halfExtents = mPhysics->getHalfExtents(actor); osg::Vec3f halfExtents = mPhysics->getHalfExtents(actor);
weaponPos.z() += halfExtents.z() * 2 * 0.75; weaponPos.z() += halfExtents.z();
return mPhysics->getHitDistance(weaponPos, target) - halfExtents.y(); return mPhysics->getHitDistance(weaponPos, target) - halfExtents.y();
} }

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

Loading…
Cancel
Save