1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-04-01 16:06:41 +00:00

Update to upstream/master. Resolve merge conflicts

This commit is contained in:
Stanislav Bas 2015-07-27 13:54:13 +03:00
commit 03c2e11961
150 changed files with 3970 additions and 2477 deletions

1
.gitignore vendored
View file

@ -41,7 +41,6 @@ resources
## generated objects ## generated objects
apps/openmw/config.hpp apps/openmw/config.hpp
components/version/version.hpp
docs/mainpage.hpp docs/mainpage.hpp
moc_*.cxx moc_*.cxx
*.cxx_parameters *.cxx_parameters

View file

@ -18,8 +18,8 @@ addons:
name: "OpenMW/openmw" name: "OpenMW/openmw"
description: "<Your project description here>" description: "<Your project description here>"
notification_email: scrawl@baseoftrash.de notification_email: scrawl@baseoftrash.de
build_command_prepend: "cmake ." build_command_prepend: "cmake . -DBUILD_UNITTESTS=FALSE -DBUILD_OPENCS=FALSE"
build_command: "make -j3" build_command: "make"
branch_pattern: coverity_scan branch_pattern: coverity_scan
matrix: matrix:
include: include:

View file

@ -52,6 +52,7 @@ Programmers
jeaye jeaye
Jeffrey Haines (Jyby) Jeffrey Haines (Jyby)
Jengerer Jengerer
Jiří Kuneš (kunesj)
Joel Graff (graffy) Joel Graff (graffy)
John Blomberg (fstp) John Blomberg (fstp)
Jordan Ayers Jordan Ayers
@ -59,6 +60,7 @@ Programmers
Julien Voisin (jvoisin/ap0) Julien Voisin (jvoisin/ap0)
Karl-Felix Glatzer (k1ll) Karl-Felix Glatzer (k1ll)
Kevin Poitra (PuppyKevin) Kevin Poitra (PuppyKevin)
Koncord
Lars Söderberg (Lazaroth) Lars Söderberg (Lazaroth)
lazydev lazydev
Leon Saunders (emoose) Leon Saunders (emoose)
@ -86,6 +88,7 @@ Programmers
Nolan Poe (nopoe) Nolan Poe (nopoe)
Paul McElroy (Greendogo) Paul McElroy (Greendogo)
Pieter van der Kloet (pvdk) Pieter van der Kloet (pvdk)
pkubik
Radu-Marius Popovici (rpopovici) Radu-Marius Popovici (rpopovici)
rdimesio rdimesio
riothamus riothamus

View file

@ -1,3 +1,8 @@
0.36.1
------
Bug #2590: Start scripts not added correctly
0.36.0 0.36.0
------ ------

View file

@ -10,9 +10,10 @@ fi
echo "yes" | sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu `lsb_release -sc` main universe restricted multiverse" echo "yes" | sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu `lsb_release -sc` main universe restricted multiverse"
echo "yes" | sudo apt-add-repository ppa:openmw/openmw echo "yes" | sudo apt-add-repository ppa:openmw/openmw
echo "yes" | sudo apt-add-repository ppa:boost-latest/ppa
sudo apt-get update -qq sudo apt-get update -qq
sudo apt-get install -qq libgtest-dev google-mock sudo apt-get install -qq libgtest-dev google-mock
sudo apt-get install -qq libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-thread-dev sudo apt-get install -qq libboost-filesystem1.55-dev libboost-program-options1.55-dev libboost-system1.55-dev libboost-thread1.55-dev
sudo apt-get install -qq libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavresample-dev sudo apt-get install -qq libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavresample-dev
sudo apt-get install -qq libbullet-dev libopenscenegraph-dev libmygui-dev libsdl2-dev libunshield-dev libtinyxml-dev libopenal-dev libqt4-dev sudo apt-get install -qq libbullet-dev libopenscenegraph-dev libmygui-dev libsdl2-dev libunshield-dev libtinyxml-dev libopenal-dev libqt4-dev
sudo apt-get install -qq cmake-data #workaround for broken osgqt cmake script in ubuntu 12.04 sudo apt-get install -qq cmake-data #workaround for broken osgqt cmake script in ubuntu 12.04

View file

@ -20,7 +20,7 @@ message(STATUS "Configuring OpenMW...")
set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MAJOR 0)
set(OPENMW_VERSION_MINOR 36) set(OPENMW_VERSION_MINOR 36)
set(OPENMW_VERSION_RELEASE 0) set(OPENMW_VERSION_RELEASE 1)
set(OPENMW_VERSION_COMMITHASH "") set(OPENMW_VERSION_COMMITHASH "")
set(OPENMW_VERSION_TAGHASH "") set(OPENMW_VERSION_TAGHASH "")
@ -56,6 +56,8 @@ configure_file ("${OpenMW_SOURCE_DIR}/docs/mainpage.hpp.cmake" "${OpenMW_BINARY_
option(MYGUI_STATIC "Link static build of Mygui into the binaries" FALSE) option(MYGUI_STATIC "Link static build of Mygui into the binaries" FALSE)
option(BOOST_STATIC "Link static build of Boost into the binaries" FALSE) option(BOOST_STATIC "Link static build of Boost into the binaries" FALSE)
option(SDL2_STATIC "Link static build of SDL into the binaries" FALSE) option(SDL2_STATIC "Link static build of SDL into the binaries" FALSE)
option(OSG_STATIC "Link static build of OpenSceneGraph into the binaries" FALSE)
option(QT_STATIC "Link static build of QT into the binaries" FALSE)
option(OPENMW_UNITY_BUILD "Use fewer compilation units to speed up compile time" FALSE) option(OPENMW_UNITY_BUILD "Use fewer compilation units to speed up compile time" FALSE)
@ -199,9 +201,67 @@ IF(BOOST_STATIC)
set(Boost_USE_STATIC_LIBS ON) set(Boost_USE_STATIC_LIBS ON)
endif() endif()
find_package(OpenSceneGraph 3.2.0 REQUIRED osgDB osgViewer osgGA osgAnimation osgParticle osgQt osgUtil osgFX) find_package(OpenSceneGraph 3.2.0 REQUIRED osgDB osgViewer osgText osgGA osgAnimation osgParticle osgQt osgUtil osgFX)
include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS}) include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS})
if(OSG_STATIC)
macro(use_static_osg_plugin_library PLUGIN_NAME)
set(PLUGIN_NAME_DBG ${PLUGIN_NAME}d ${PLUGIN_NAME}D ${PLUGIN_NAME}_d ${PLUGIN_NAME}_D ${PLUGIN_NAME}_debug ${PLUGIN_NAME})
# For now, users wishing to do a static build will need to pass the path to where the plugins reside
# More clever logic would need to deduce the path, probably installed under <OpenSceneGraph>/lib/osgPlugins-<X.X.X>
find_library(${PLUGIN_NAME}_LIBRARY_REL NAMES ${PLUGIN_NAME} HINTS ${OSG_PLUGIN_LIB_SEARCH_PATH})
find_library(${PLUGIN_NAME}_LIBRARY_DBG NAMES ${PLUGIN_NAME_DBG} HINTS ${OSG_PLUGIN_LIB_SEARCH_PATH})
make_library_set(${PLUGIN_NAME}_LIBRARY)
if("${${PLUGIN_NAME}_LIBRARY}" STREQUAL "")
message(FATAL_ERROR "Unable to find static OpenSceneGraph plugin: ${PLUGIN_NAME}")
endif()
set(OPENSCENEGRAPH_LIBRARIES ${OPENSCENEGRAPH_LIBRARIES} ${${PLUGIN_NAME}_LIBRARY})
endmacro()
macro(use_static_osg_plugin_dep DEPENDENCY)
find_package(${DEPENDENCY} REQUIRED)
set(OPENSCENEGRAPH_LIBRARIES ${OPENSCENEGRAPH_LIBRARIES} ${${DEPENDENCY}_LIBRARIES})
endmacro()
add_definitions(-DOSG_LIBRARY_STATIC)
set(PLUGIN_LIST
osgdb_png # depends on libpng, zlib
osgdb_tga
osgdb_dds
osgdb_jpeg # depends on libjpeg
)
foreach(PLUGIN ${PLUGIN_LIST})
use_static_osg_plugin_library(${PLUGIN})
endforeach()
# OSG static plugins need to linked against their respective dependencies
set(PLUGIN_DEPS_LIST
PNG # needed by osgdb_png
ZLIB # needed by osgdb_png
JPEG # needed by osgdb_jpeg
)
foreach(DEPENDENCY ${PLUGIN_DEPS_LIST})
use_static_osg_plugin_dep(${DEPENDENCY})
endforeach()
endif()
if(QT_STATIC)
if(WIN32)
if(DESIRED_QT_VERSION MATCHES 4)
# QtCore needs WSAAsyncSelect from Ws2_32.lib
set(QT_QTCORE_LIBRARY ${QT_QTCORE_LIBRARY} Ws2_32.lib)
message("QT_QTCORE_LIBRARY: ${QT_QTCORE_LIBRARY}")
endif()
endif()
endif()
find_package(MyGUI REQUIRED) find_package(MyGUI REQUIRED)
if (${MYGUI_VERSION} VERSION_LESS "3.2.1") if (${MYGUI_VERSION} VERSION_LESS "3.2.1")
message(FATAL_ERROR "OpenMW requires MyGUI 3.2.1 or later, please install the latest version from http://mygui.info") message(FATAL_ERROR "OpenMW requires MyGUI 3.2.1 or later, please install the latest version from http://mygui.info")
@ -366,6 +426,7 @@ IF(NOT WIN32 AND NOT APPLE)
# Install global configuration files # Install global configuration files
INSTALL(FILES "${OpenMW_BINARY_DIR}/settings-default.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") INSTALL(FILES "${OpenMW_BINARY_DIR}/settings-default.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" DESTINATION "${SYSCONFDIR}" RENAME "openmw.cfg" COMPONENT "openmw") INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" DESTINATION "${SYSCONFDIR}" RENAME "openmw.cfg" COMPONENT "openmw")
INSTALL(FILES "${OpenMW_BINARY_DIR}/resources/version" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
INSTALL(FILES "${OpenMW_BINARY_DIR}/gamecontrollerdb.txt" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") INSTALL(FILES "${OpenMW_BINARY_DIR}/gamecontrollerdb.txt" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
IF(BUILD_OPENCS) IF(BUILD_OPENCS)
@ -381,6 +442,7 @@ if(WIN32)
FILE(GLOB dll_files "${OpenMW_BINARY_DIR}/Release/*.dll") FILE(GLOB dll_files "${OpenMW_BINARY_DIR}/Release/*.dll")
INSTALL(FILES ${dll_files} DESTINATION ".") INSTALL(FILES ${dll_files} DESTINATION ".")
INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" DESTINATION "." RENAME "openmw.cfg") INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" DESTINATION "." RENAME "openmw.cfg")
INSTALL(FILES "${OpenMW_BINARY_DIR}/resources/version" DESTINATION ".")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/CHANGELOG.md" DESTINATION "." RENAME "CHANGELOG.txt") INSTALL(FILES "${OpenMW_SOURCE_DIR}/CHANGELOG.md" DESTINATION "." RENAME "CHANGELOG.txt")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/README.md" DESTINATION "." RENAME "README.txt") INSTALL(FILES "${OpenMW_SOURCE_DIR}/README.md" DESTINATION "." RENAME "README.txt")
INSTALL(FILES INSTALL(FILES

View file

@ -3,10 +3,11 @@ OpenMW
[![Build Status](https://img.shields.io/travis/OpenMW/openmw.svg)](https://travis-ci.org/OpenMW/openmw) [![Coverity Scan Build Status](https://scan.coverity.com/projects/3740/badge.svg)](https://scan.coverity.com/projects/3740) [![Build Status](https://img.shields.io/travis/OpenMW/openmw.svg)](https://travis-ci.org/OpenMW/openmw) [![Coverity Scan Build Status](https://scan.coverity.com/projects/3740/badge.svg)](https://scan.coverity.com/projects/3740)
OpenMW is an attempt at recreating the engine for the popular role-playing game 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.
Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work.
* Version: 0.36.0 OpenMW also comes with OpenMW-CS, a replacement for Morrowind's TES Construction Set.
* Version: 0.36.1
* License: GPL (see docs/license/GPL3.txt for more information) * License: GPL (see docs/license/GPL3.txt for more information)
* Website: http://www.openmw.org * Website: http://www.openmw.org
* IRC: #openmw on irc.freenode.net * IRC: #openmw on irc.freenode.net
@ -14,6 +15,13 @@ Morrowind by Bethesda Softworks. You need to own and install the original game f
Font Licenses: Font Licenses:
* DejaVuLGCSansMono.ttf: custom (see docs/license/DejaVu Font License.txt for more information) * DejaVuLGCSansMono.ttf: custom (see docs/license/DejaVu Font License.txt for more information)
Current Status
--------------
The main quests in Morrowind, Tribunal and Bloodmoon are all completable. Some issues with side quests are to be expected (but rare). Check the [bug tracker](https://bugs.openmw.org/versions/21) for a list of issues we need to resolve before the "1.0" release. Even before the "1.0" release however, OpenMW boasts some new [features](https://wiki.openmw.org/index.php?title=Features), such as improved graphics and user interfaces.
Pre-existing modifications created for the original Morrowind engine can be hit-and-miss. The OpenMW script compiler performs more thorough error-checking than Morrowind does, meaning that a mod created for Morrowind may not necessarily run in OpenMW. Some mods also rely on quirky behaviour or engine bugs in order to work. We are considering such compatibility issues on a case-by-case basis - in some cases adding a workaround to OpenMW may be feasible, in other cases fixing the mod will be the only option. If you know of any mods that work or don't work, feel free to add them to the [Mod status](https://wiki.openmw.org/index.php?title=Mod_status) wiki page.
Getting Started Getting Started
--------------- ---------------

View file

@ -1,5 +1,7 @@
#include "convertplayer.hpp" #include "convertplayer.hpp"
#include <components/misc/stringops.hpp>
namespace ESSImport namespace ESSImport
{ {

View file

@ -166,7 +166,9 @@ namespace ESSImport
if (i >= file2.mRecords.size()) if (i >= file2.mRecords.size())
{ {
std::ios::fmtflags f(std::cout.flags());
std::cout << "Record in file1 not present in file2: (1) 0x" << std::hex << rec.mFileOffset << std::endl; std::cout << "Record in file1 not present in file2: (1) 0x" << std::hex << rec.mFileOffset << std::endl;
std::cout.flags(f);
return; return;
} }
@ -174,7 +176,9 @@ namespace ESSImport
if (rec.mName != rec2.mName) if (rec.mName != rec2.mName)
{ {
std::ios::fmtflags f(std::cout.flags());
std::cout << "Different record name at (2) 0x" << std::hex << rec2.mFileOffset << std::endl; std::cout << "Different record name at (2) 0x" << std::hex << rec2.mFileOffset << std::endl;
std::cout.flags(f);
return; // TODO: try to recover return; // TODO: try to recover
} }
@ -185,7 +189,9 @@ namespace ESSImport
if (j >= rec2.mSubrecords.size()) if (j >= rec2.mSubrecords.size())
{ {
std::ios::fmtflags f(std::cout.flags());
std::cout << "Subrecord in file1 not present in file2: (1) 0x" << std::hex << sub.mFileOffset << std::endl; std::cout << "Subrecord in file1 not present in file2: (1) 0x" << std::hex << sub.mFileOffset << std::endl;
std::cout.flags(f);
return; return;
} }
@ -193,8 +199,10 @@ namespace ESSImport
if (sub.mName != sub2.mName) if (sub.mName != sub2.mName)
{ {
std::ios::fmtflags f(std::cout.flags());
std::cout << "Different subrecord name (" << rec.mName << "." << sub.mName << " vs. " << sub2.mName << ") at (1) 0x" << std::hex << sub.mFileOffset std::cout << "Different subrecord name (" << rec.mName << "." << sub.mName << " vs. " << sub2.mName << ") at (1) 0x" << std::hex << sub.mFileOffset
<< " (2) 0x" << sub2.mFileOffset << std::endl; << " (2) 0x" << sub2.mFileOffset << std::endl;
std::cout.flags(f);
break; // TODO: try to recover break; // TODO: try to recover
} }
@ -203,6 +211,8 @@ namespace ESSImport
if (blacklist.find(std::make_pair(rec.mName, sub.mName)) != blacklist.end()) if (blacklist.find(std::make_pair(rec.mName, sub.mName)) != blacklist.end())
continue; continue;
std::ios::fmtflags f(std::cout.flags());
std::cout << "Different subrecord data for " << rec.mName << "." << sub.mName << " at (1) 0x" << std::hex << sub.mFileOffset std::cout << "Different subrecord data for " << rec.mName << "." << sub.mName << " at (1) 0x" << std::hex << sub.mFileOffset
<< " (2) 0x" << sub2.mFileOffset << std::endl; << " (2) 0x" << sub2.mFileOffset << std::endl;
@ -235,6 +245,7 @@ namespace ESSImport
std::cout << "\033[0m"; std::cout << "\033[0m";
} }
std::cout << std::endl; std::cout << std::endl;
std::cout.flags(f);
} }
} }
} }
@ -319,7 +330,11 @@ namespace ESSImport
else else
{ {
if (unknownRecords.insert(n.val).second) if (unknownRecords.insert(n.val).second)
{
std::ios::fmtflags f(std::cerr.flags());
std::cerr << "unknown record " << n.toString() << " (0x" << std::hex << esm.getFileOffset() << ")" << std::endl; std::cerr << "unknown record " << n.toString() << " (0x" << std::hex << esm.getFileOffset() << ")" << std::endl;
std::cerr.flags(f);
}
esm.skipRecord(); esm.skipRecord();
} }

View file

@ -49,6 +49,10 @@ namespace ESSImport
std::map<std::string, ESM::NPC> mNpcs; std::map<std::string, ESM::NPC> mNpcs;
Context() Context()
: mDay(0)
, mMonth(0)
, mYear(0)
, mHour(0.f)
{ {
mPlayer.mAutoMove = 0; mPlayer.mAutoMove = 0;
ESM::CellId playerCellId; ESM::CellId playerCellId;

View file

@ -57,26 +57,6 @@ Launcher::MainDialog::MainDialog(QWidget *parent)
// Remove what's this? button // Remove what's this? button
setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
// Add version information to bottom of the window
QString revision(OPENMW_VERSION_COMMITHASH);
QString tag(OPENMW_VERSION_TAGHASH);
versionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
if (!revision.isEmpty() && !tag.isEmpty())
{
if (revision == tag) {
versionLabel->setText(tr("OpenMW %1 release").arg(OPENMW_VERSION));
} else {
versionLabel->setText(tr("OpenMW development (%1)").arg(revision.left(10)));
}
// Add the compile date and time
versionLabel->setToolTip(tr("Compiled on %1 %2").arg(QLocale(QLocale::C).toDate(QString(__DATE__).simplified(),
QLatin1String("MMM d yyyy")).toString(Qt::SystemLocaleLongDate),
QLocale(QLocale::C).toTime(QString(__TIME__).simplified(),
QLatin1String("hh:mm:ss")).toString(Qt::SystemLocaleShortDate)));
}
createIcons(); createIcons();
} }
@ -186,11 +166,38 @@ Launcher::FirstRunDialogResult Launcher::MainDialog::showFirstRunDialog()
return setup() ? FirstRunDialogResultContinue : FirstRunDialogResultFailure; return setup() ? FirstRunDialogResultContinue : FirstRunDialogResultFailure;
} }
void Launcher::MainDialog::setVersionLabel()
{
// Add version information to bottom of the window
Version::Version v = Version::getOpenmwVersion(mGameSettings.value("resources").toUtf8().constData());
QString revision(QString::fromUtf8(v.mCommitHash.c_str()));
QString tag(QString::fromUtf8(v.mTagHash.c_str()));
versionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
if (!revision.isEmpty() && !tag.isEmpty())
{
if (revision == tag) {
versionLabel->setText(tr("OpenMW %1 release").arg(QString::fromUtf8(v.mVersion.c_str())));
} else {
versionLabel->setText(tr("OpenMW development (%1)").arg(revision.left(10)));
}
// Add the compile date and time
versionLabel->setToolTip(tr("Compiled on %1 %2").arg(QLocale(QLocale::C).toDate(QString(__DATE__).simplified(),
QLatin1String("MMM d yyyy")).toString(Qt::SystemLocaleLongDate),
QLocale(QLocale::C).toTime(QString(__TIME__).simplified(),
QLatin1String("hh:mm:ss")).toString(Qt::SystemLocaleShortDate)));
}
}
bool Launcher::MainDialog::setup() bool Launcher::MainDialog::setup()
{ {
if (!setupGameSettings()) if (!setupGameSettings())
return false; return false;
setVersionLabel();
mLauncherSettings.setContentList(mGameSettings); mLauncherSettings.setContentList(mGameSettings);
if (!setupGraphicsSettings()) if (!setupGraphicsSettings())
@ -309,11 +316,11 @@ bool Launcher::MainDialog::setupGameSettings()
mGameSettings.readUserFile(stream); mGameSettings.readUserFile(stream);
} }
// Now the rest // Now the rest - priority: user > local > global
QStringList paths; QStringList paths;
paths.append(userPath + QString("openmw.cfg"));
paths.append(QString("openmw.cfg"));
paths.append(globalPath + QString("openmw.cfg")); paths.append(globalPath + QString("openmw.cfg"));
paths.append(QString("openmw.cfg"));
paths.append(userPath + QString("openmw.cfg"));
foreach (const QString &path, paths) { foreach (const QString &path, paths) {
qDebug() << "Loading config file:" << qPrintable(path); qDebug() << "Loading config file:" << qPrintable(path);

View file

@ -72,6 +72,8 @@ namespace Launcher
bool setupGameSettings(); bool setupGameSettings();
bool setupGraphicsSettings(); bool setupGraphicsSettings();
void setVersionLabel();
void loadSettings(); void loadSettings();
void saveSettings(); void saveSettings();

View file

@ -64,7 +64,7 @@ opencs_units (view/world
table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator
cellcreator referenceablecreator referencecreator scenesubview cellcreator referenceablecreator referencecreator scenesubview
infocreator scriptedit dialoguesubview previewsubview regionmap dragrecordtable nestedtable infocreator scriptedit dialoguesubview previewsubview regionmap dragrecordtable nestedtable
dialoguespinbox recordbuttonbar tableeditidaction extendedcommandconfigurator dialoguespinbox recordbuttonbar tableeditidaction scripterrortable extendedcommandconfigurator
) )
opencs_units_noqt (view/world opencs_units_noqt (view/world

View file

@ -798,9 +798,9 @@ void CSMDoc::Document::addGmsts()
"sBookSkillMessage", "sBookSkillMessage",
"sBounty", "sBounty",
"sBreath", "sBreath",
"sBribe", "sBribe 10 Gold",
"sBribe", "sBribe 100 Gold",
"sBribe", "sBribe 1000 Gold",
"sBribeFail", "sBribeFail",
"sBribeSuccess", "sBribeSuccess",
"sBuy", "sBuy",

View file

@ -8,6 +8,20 @@ CSMDoc::Message::Message (const CSMWorld::UniversalId& id, const std::string& me
: mId (id), mMessage (message), mHint (hint), mSeverity (severity) : mId (id), mMessage (message), mHint (hint), mSeverity (severity)
{} {}
std::string CSMDoc::Message::toString (Severity severity)
{
switch (severity)
{
case CSMDoc::Message::Severity_Info: return "Information";
case CSMDoc::Message::Severity_Warning: return "Warning";
case CSMDoc::Message::Severity_Error: return "Error";
case CSMDoc::Message::Severity_SeriousError: return "Serious Error";
case CSMDoc::Message::Severity_Default: break;
}
return "";
}
CSMDoc::Messages::Messages (Message::Severity default_) CSMDoc::Messages::Messages (Message::Severity default_)
: mDefault (default_) : mDefault (default_)
@ -18,7 +32,7 @@ void CSMDoc::Messages::add (const CSMWorld::UniversalId& id, const std::string&
{ {
if (severity==Message::Severity_Default) if (severity==Message::Severity_Default)
severity = mDefault; severity = mDefault;
mMessages.push_back (Message (id, message, hint, severity)); mMessages.push_back (Message (id, message, hint, severity));
} }

View file

@ -21,18 +21,20 @@ namespace CSMDoc
// reporting it correctly // reporting it correctly
Severity_Default = 4 Severity_Default = 4
}; };
CSMWorld::UniversalId mId; CSMWorld::UniversalId mId;
std::string mMessage; std::string mMessage;
std::string mHint; std::string mHint;
Severity mSeverity; Severity mSeverity;
Message(); Message();
Message (const CSMWorld::UniversalId& id, const std::string& message, Message (const CSMWorld::UniversalId& id, const std::string& message,
const std::string& hint, Severity severity); const std::string& hint, Severity severity);
static std::string toString (Severity severity);
}; };
class Messages class Messages
{ {
public: public:

View file

@ -162,7 +162,7 @@ void CSMSettings::UserSettings::buildSettingModelDefaults()
ritd->setDeclaredValues (values); ritd->setDeclaredValues (values);
} }
declareSection ("table-input", "Table Input"); declareSection ("table-input", "ID Tables");
{ {
QString inPlaceEdit ("Edit in Place"); QString inPlaceEdit ("Edit in Place");
QString editRecord ("Edit Record"); QString editRecord ("Edit Record");
@ -226,7 +226,13 @@ void CSMSettings::UserSettings::buildSettingModelDefaults()
"records are deleted/reverted immediately."); "records are deleted/reverted immediately.");
} }
declareSection ("report-input", "Report Input"); declareSection ("dialogues", "ID Dialogues");
{
Setting *toolbar = createSetting (Type_CheckBox, "toolbar", "Show toolbar");
toolbar->setDefaultValue ("true");
}
declareSection ("report-input", "Reports");
{ {
QString none ("None"); QString none ("None");
QString edit ("Edit"); QString edit ("Edit");
@ -266,7 +272,7 @@ void CSMSettings::UserSettings::buildSettingModelDefaults()
shiftCtrlDoubleClick->setDefaultValue (none); shiftCtrlDoubleClick->setDefaultValue (none);
shiftCtrlDoubleClick->setToolTip ("Action on shift control double click in report table:<p>" + toolTip); shiftCtrlDoubleClick->setToolTip ("Action on shift control double click in report table:<p>" + toolTip);
} }
declareSection ("search", "Search & Replace"); declareSection ("search", "Search & Replace");
{ {
Setting *before = createSetting (Type_SpinBox, "char-before", Setting *before = createSetting (Type_SpinBox, "char-before",
@ -308,7 +314,7 @@ void CSMSettings::UserSettings::buildSettingModelDefaults()
QStringList modes; QStringList modes;
modes << "Ignore" << modeNormal << "Strict"; modes << "Ignore" << modeNormal << "Strict";
Setting *warnings = createSetting (Type_ComboBox, "warnings", Setting *warnings = createSetting (Type_ComboBox, "warnings",
"Warning Mode"); "Warning Mode");
warnings->setDeclaredValues (modes); warnings->setDeclaredValues (modes);
@ -318,7 +324,16 @@ void CSMSettings::UserSettings::buildSettingModelDefaults()
"<li>Normal: Report warning as a warning</li>" "<li>Normal: Report warning as a warning</li>"
"<li>Strict: Promote warning to an error</li>" "<li>Strict: Promote warning to an error</li>"
"</ul>"); "</ul>");
Setting *toolbar = createSetting (Type_CheckBox, "toolbar", "Show toolbar");
toolbar->setDefaultValue ("true");
Setting *delay = createSetting (Type_SpinBox, "compile-delay",
"Delay between updating of source errors");
delay->setDefaultValue (100);
delay->setRange (0, 10000);
delay->setToolTip ("Delay in milliseconds");
Setting *formatInt = createSetting (Type_LineEdit, "colour-int", "Highlight Colour: Int"); Setting *formatInt = createSetting (Type_LineEdit, "colour-int", "Highlight Colour: Int");
formatInt->setDefaultValues (QStringList() << "Dark magenta"); formatInt->setDefaultValues (QStringList() << "Dark magenta");
formatInt->setToolTip ("(Default: Green) Use one of the following formats:" + tooltip); formatInt->setToolTip ("(Default: Green) Use one of the following formats:" + tooltip);
@ -355,7 +370,7 @@ void CSMSettings::UserSettings::buildSettingModelDefaults()
cycle->setToolTip ("When using next/previous functions at the last/first item of a " cycle->setToolTip ("When using next/previous functions at the last/first item of a "
"list go to the first/last item"); "list go to the first/last item");
} }
{ {
/****************************************************************** /******************************************************************
* There are three types of values: * There are three types of values:

View file

@ -13,7 +13,7 @@ CSMTools::ReportModel::ReportModel (bool fieldColumn, bool severityColumn)
if (severityColumn) if (severityColumn)
mColumnSeverity = index++; mColumnSeverity = index++;
if (fieldColumn) if (fieldColumn)
mColumnField = index++; mColumnField = index++;
@ -46,7 +46,7 @@ QVariant CSMTools::ReportModel::data (const QModelIndex & index, int role) const
case Column_Type: case Column_Type:
return static_cast<int> (mRows.at (index.row()).mId.getType()); return static_cast<int> (mRows.at (index.row()).mId.getType());
case Column_Id: case Column_Id:
{ {
CSMWorld::UniversalId id = mRows.at (index.row()).mId; CSMWorld::UniversalId id = mRows.at (index.row()).mId;
@ -56,7 +56,7 @@ QVariant CSMTools::ReportModel::data (const QModelIndex & index, int role) const
return QString ("-"); return QString ("-");
} }
case Column_Hint: case Column_Hint:
return QString::fromUtf8 (mRows.at (index.row()).mHint.c_str()); return QString::fromUtf8 (mRows.at (index.row()).mHint.c_str());
@ -85,16 +85,10 @@ QVariant CSMTools::ReportModel::data (const QModelIndex & index, int role) const
if (index.column()==mColumnSeverity) if (index.column()==mColumnSeverity)
{ {
switch (mRows.at (index.row()).mSeverity) return QString::fromUtf8 (
{ CSMDoc::Message::toString (mRows.at (index.row()).mSeverity).c_str());
case CSMDoc::Message::Severity_Info: return "Information";
case CSMDoc::Message::Severity_Warning: return "Warning";
case CSMDoc::Message::Severity_Error: return "Error";
case CSMDoc::Message::Severity_SeriousError: return "Serious Error";
case CSMDoc::Message::Severity_Default: break;
}
} }
return QVariant(); return QVariant();
} }
@ -144,7 +138,7 @@ bool CSMTools::ReportModel::removeRows (int row, int count, const QModelIndex& p
void CSMTools::ReportModel::add (const CSMDoc::Message& message) void CSMTools::ReportModel::add (const CSMDoc::Message& message)
{ {
beginInsertRows (QModelIndex(), mRows.size(), mRows.size()); beginInsertRows (QModelIndex(), mRows.size(), mRows.size());
mRows.push_back (message); mRows.push_back (message);
endInsertRows(); endInsertRows();

View file

@ -62,7 +62,7 @@ int CSMWorld::Data::count (RecordBase::State state, const CollectionBase& collec
CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager) CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager)
: mEncoder (encoding), mPathgrids (mCells), mRefs (mCells), : mEncoder (encoding), mPathgrids (mCells), mRefs (mCells),
mResourcesManager (resourcesManager), mReader (0), mDialogue (0), mReaderIndex(0), mResourceSystem(resourcesManager.getVFS()) mResourcesManager (resourcesManager), mReader (0), mDialogue (0), mReaderIndex(0), mResourceSystem(new Resource::ResourceSystem(resourcesManager.getVFS()))
{ {
int index = 0; int index = 0;
@ -537,14 +537,14 @@ CSMWorld::Data::~Data()
delete mReader; delete mReader;
} }
Resource::ResourceSystem* CSMWorld::Data::getResourceSystem() boost::shared_ptr<Resource::ResourceSystem> CSMWorld::Data::getResourceSystem()
{ {
return &mResourceSystem; return mResourceSystem;
} }
const Resource::ResourceSystem* CSMWorld::Data::getResourceSystem() const boost::shared_ptr<const Resource::ResourceSystem> CSMWorld::Data::getResourceSystem() const
{ {
return &mResourceSystem; return mResourceSystem;
} }
const CSMWorld::IdCollection<ESM::Global>& CSMWorld::Data::getGlobals() const const CSMWorld::IdCollection<ESM::Global>& CSMWorld::Data::getGlobals() const

View file

@ -113,7 +113,7 @@ namespace CSMWorld
std::map<std::string, std::map<ESM::RefNum, std::string> > mRefLoadCache; std::map<std::string, std::map<ESM::RefNum, std::string> > mRefLoadCache;
int mReaderIndex; int mReaderIndex;
Resource::ResourceSystem mResourceSystem; boost::shared_ptr<Resource::ResourceSystem> mResourceSystem;
std::vector<boost::shared_ptr<ESM::ESMReader> > mReaders; std::vector<boost::shared_ptr<ESM::ESMReader> > mReaders;
@ -138,9 +138,9 @@ namespace CSMWorld
const VFS::Manager* getVFS() const; const VFS::Manager* getVFS() const;
Resource::ResourceSystem* getResourceSystem(); boost::shared_ptr<Resource::ResourceSystem> getResourceSystem();
const Resource::ResourceSystem* getResourceSystem() const; boost::shared_ptr<const Resource::ResourceSystem> getResourceSystem() const;
const IdCollection<ESM::Global>& getGlobals() const; const IdCollection<ESM::Global>& getGlobals() const;

View file

@ -50,6 +50,24 @@ QModelIndex CSMWorld::IdTableProxyModel::getModelIndex (const std::string& id, i
return mapFromSource (dynamic_cast<IdTableBase&> (*sourceModel()).getModelIndex (id, column)); return mapFromSource (dynamic_cast<IdTableBase&> (*sourceModel()).getModelIndex (id, column));
} }
void CSMWorld::IdTableProxyModel::setSourceModel(QAbstractItemModel *model)
{
QSortFilterProxyModel::setSourceModel(model);
connect(sourceModel(),
SIGNAL(rowsRemoved(const QModelIndex &, int, int)),
this,
SLOT(sourceRowsChanged(const QModelIndex &, int, int)));
connect(sourceModel(),
SIGNAL(rowsInserted(const QModelIndex &, int, int)),
this,
SLOT(sourceRowsChanged(const QModelIndex &, int, int)));
connect(sourceModel(),
SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)),
this,
SLOT(sourceDataChanged(const QModelIndex &, const QModelIndex &)));
}
void CSMWorld::IdTableProxyModel::setFilter (const boost::shared_ptr<CSMFilter::Node>& filter) void CSMWorld::IdTableProxyModel::setFilter (const boost::shared_ptr<CSMFilter::Node>& filter)
{ {
beginResetModel(); beginResetModel();
@ -60,6 +78,20 @@ void CSMWorld::IdTableProxyModel::setFilter (const boost::shared_ptr<CSMFilter::
bool CSMWorld::IdTableProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const bool CSMWorld::IdTableProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
{ {
Columns::ColumnId id = static_cast<Columns::ColumnId>(left.data(ColumnBase::Role_ColumnId).toInt());
EnumColumnCache::const_iterator valuesIt = mEnumColumnCache.find(id);
if (valuesIt == mEnumColumnCache.end())
{
if (Columns::hasEnums(id))
{
valuesIt = mEnumColumnCache.insert(std::make_pair(id, Columns::getEnums(id))).first;
}
}
if (valuesIt != mEnumColumnCache.end())
{
return valuesIt->second[left.data().toInt()] < valuesIt->second[right.data().toInt()];
}
return QSortFilterProxyModel::lessThan(left, right); return QSortFilterProxyModel::lessThan(left, right);
} }
@ -68,3 +100,13 @@ void CSMWorld::IdTableProxyModel::refreshFilter()
updateColumnMap(); updateColumnMap();
invalidateFilter(); invalidateFilter();
} }
void CSMWorld::IdTableProxyModel::sourceRowsChanged(const QModelIndex &/*parent*/, int /*start*/, int /*end*/)
{
refreshFilter();
}
void CSMWorld::IdTableProxyModel::sourceDataChanged(const QModelIndex &/*topLeft*/, const QModelIndex &/*bottomRight*/)
{
refreshFilter();
}

View file

@ -11,6 +11,8 @@
#include "../filter/node.hpp" #include "../filter/node.hpp"
#include "columns.hpp"
namespace CSMWorld namespace CSMWorld
{ {
class IdTableProxyModel : public QSortFilterProxyModel class IdTableProxyModel : public QSortFilterProxyModel
@ -20,6 +22,11 @@ namespace CSMWorld
boost::shared_ptr<CSMFilter::Node> mFilter; boost::shared_ptr<CSMFilter::Node> mFilter;
std::map<int, int> mColumnMap; // column ID, column index in this model (or -1) std::map<int, int> mColumnMap; // column ID, column index in this model (or -1)
// Cache of enum values for enum columns (e.g. Modified, Record Type).
// Used to speed up comparisons during the sort by such columns.
typedef std::map<Columns::ColumnId, std::vector<std::string> > EnumColumnCache;
mutable EnumColumnCache mEnumColumnCache;
private: private:
void updateColumnMap(); void updateColumnMap();
@ -30,6 +37,8 @@ namespace CSMWorld
virtual QModelIndex getModelIndex (const std::string& id, int column) const; virtual QModelIndex getModelIndex (const std::string& id, int column) const;
virtual void setSourceModel(QAbstractItemModel *model);
void setFilter (const boost::shared_ptr<CSMFilter::Node>& filter); void setFilter (const boost::shared_ptr<CSMFilter::Node>& filter);
void refreshFilter(); void refreshFilter();
@ -39,6 +48,12 @@ namespace CSMWorld
bool lessThan(const QModelIndex &left, const QModelIndex &right) const; bool lessThan(const QModelIndex &left, const QModelIndex &right) const;
virtual bool filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) const; virtual bool filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) const;
private slots:
void sourceRowsChanged(const QModelIndex &parent, int start, int end);
void sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
}; };
} }

View file

@ -171,10 +171,10 @@ QModelIndex CSMWorld::IdTree::index (int row, int column, const QModelIndex& par
encodedId = this->foldIndexAddress(parent); encodedId = this->foldIndexAddress(parent);
} }
if (row<0 || row>=idCollection()->getSize()) if (row < 0 || row >= rowCount(parent))
return QModelIndex(); return QModelIndex();
if (column<0 || column>=idCollection()->getColumns()) if (column < 0 || column >= columnCount(parent))
return QModelIndex(); return QModelIndex();
return createIndex(row, column, encodedId); // store internal id return createIndex(row, column, encodedId); // store internal id

View file

@ -161,8 +161,19 @@ namespace CSMWorld
template<typename ESXRecordT, typename IdAccessorT> template<typename ESXRecordT, typename IdAccessorT>
int NestedIdCollection<ESXRecordT, IdAccessorT>::getNestedColumnsCount(int row, int column) const int NestedIdCollection<ESXRecordT, IdAccessorT>::getNestedColumnsCount(int row, int column) const
{ {
return getAdapter(Collection<ESXRecordT, IdAccessorT>::getColumn(column)).getColumnsCount( const ColumnBase &nestedColumn = Collection<ESXRecordT, IdAccessorT>::getColumn(column);
Collection<ESXRecordT, IdAccessorT>::getRecord(row)); int numRecords = Collection<ESXRecordT, IdAccessorT>::getSize();
if (row >= 0 && row < numRecords)
{
const Record<ESXRecordT>& record = Collection<ESXRecordT, IdAccessorT>::getRecord(row);
return getAdapter(nestedColumn).getColumnsCount(record);
}
else
{
// If the row is invalid (or there no records), retrieve the column count using a blank record
const Record<ESXRecordT> record;
return getAdapter(nestedColumn).getColumnsCount(record);
}
} }
template<typename ESXRecordT, typename IdAccessorT> template<typename ESXRecordT, typename IdAccessorT>

View file

@ -75,10 +75,10 @@ QModelIndex CSMWorld::NestedTableProxyModel::index(int row, int column, const QM
{ {
assert (!parent.isValid()); assert (!parent.isValid());
int rows = mMainModel->rowCount(parent); int numRows = rowCount(parent);
int columns = mMainModel->columnCount(parent); int numColumns = columnCount(parent);
if (row < 0 || row >= rows || column < 0 || column >= columns) if (row < 0 || row >= numRows || column < 0 || column >= numColumns)
return QModelIndex(); return QModelIndex();
return createIndex(row, column); return createIndex(row, column);

View file

@ -71,7 +71,7 @@ CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::st
const ESM::Land* esmLand = land.getRecord(mId).get().mLand.get(); const ESM::Land* esmLand = land.getRecord(mId).get().mLand.get();
if(esmLand && esmLand->mDataTypes&ESM::Land::DATA_VHGT) if(esmLand && esmLand->mDataTypes&ESM::Land::DATA_VHGT)
{ {
mTerrain.reset(new Terrain::TerrainGrid(mCellNode, data.getResourceSystem(), NULL, new TerrainStorage(mData), Element_Terrain<<1)); mTerrain.reset(new Terrain::TerrainGrid(mCellNode, data.getResourceSystem().get(), NULL, new TerrainStorage(mData), Element_Terrain<<1));
mTerrain->loadCell(esmLand->mX, mTerrain->loadCell(esmLand->mX,
esmLand->mY); esmLand->mY);

View file

@ -116,7 +116,7 @@ const CSMWorld::CellRef& CSVRender::Object::getReference() const
CSVRender::Object::Object (CSMWorld::Data& data, osg::Group* parentNode, CSVRender::Object::Object (CSMWorld::Data& data, osg::Group* parentNode,
const std::string& id, bool referenceable, bool forceBaseToZero) const std::string& id, bool referenceable, bool forceBaseToZero)
: mData (data), mBaseNode(0), mParentNode(parentNode), mResourceSystem(data.getResourceSystem()), mForceBaseToZero (forceBaseToZero) : mData (data), mBaseNode(0), mParentNode(parentNode), mResourceSystem(data.getResourceSystem().get()), mForceBaseToZero (forceBaseToZero)
{ {
mBaseNode = new osg::PositionAttitudeTransform; mBaseNode = new osg::PositionAttitudeTransform;
parentNode->addChild(mBaseNode); parentNode->addChild(mBaseNode);

View file

@ -8,7 +8,7 @@
CSVRender::PreviewWidget::PreviewWidget (CSMWorld::Data& data, CSVRender::PreviewWidget::PreviewWidget (CSMWorld::Data& data,
const std::string& id, bool referenceable, QWidget *parent) const std::string& id, bool referenceable, QWidget *parent)
: SceneWidget (data.getResourceSystem()->getSceneManager(), parent), mData (data), mObject(data, mRootNode, id, referenceable) : SceneWidget (data.getResourceSystem(), parent), mData (data), mObject(data, mRootNode, id, referenceable)
{ {
mView->setCameraManipulator(new osgGA::TrackballManipulator); mView->setCameraManipulator(new osgGA::TrackballManipulator);

View file

@ -13,6 +13,7 @@
#include <osg/LightModel> #include <osg/LightModel>
#include <components/resource/scenemanager.hpp> #include <components/resource/scenemanager.hpp>
#include <components/resource/resourcesystem.hpp>
#include "../widget/scenetoolmode.hpp" #include "../widget/scenetoolmode.hpp"
#include "../../model/settings/usersettings.hpp" #include "../../model/settings/usersettings.hpp"
@ -132,9 +133,9 @@ void CompositeViewer::update()
// --------------------------------------------------- // ---------------------------------------------------
SceneWidget::SceneWidget(Resource::SceneManager* sceneManager, QWidget *parent, Qt::WindowFlags f) SceneWidget::SceneWidget(boost::shared_ptr<Resource::ResourceSystem> resourceSystem, QWidget *parent, Qt::WindowFlags f)
: RenderWidget(parent, f) : RenderWidget(parent, f)
, mSceneManager(sceneManager) , mResourceSystem(resourceSystem)
, mLighting(NULL) , mLighting(NULL)
, mHasDefaultAmbient(false) , mHasDefaultAmbient(false)
{ {
@ -147,7 +148,7 @@ SceneWidget::SceneWidget(Resource::SceneManager* sceneManager, QWidget *parent,
SceneWidget::~SceneWidget() SceneWidget::~SceneWidget()
{ {
// Since we're holding on to the scene templates past the existance of this graphics context, we'll need to manually release the created objects // Since we're holding on to the scene templates past the existance of this graphics context, we'll need to manually release the created objects
mSceneManager->releaseGLObjects(mView->getCamera()->getGraphicsContext()->getState()); mResourceSystem->getSceneManager()->releaseGLObjects(mView->getCamera()->getGraphicsContext()->getState());
} }
void SceneWidget::setLighting(Lighting *lighting) void SceneWidget::setLighting(Lighting *lighting)

View file

@ -4,6 +4,8 @@
#include <QWidget> #include <QWidget>
#include <QTimer> #include <QTimer>
#include <boost/shared_ptr.hpp>
#include "lightingday.hpp" #include "lightingday.hpp"
#include "lightingnight.hpp" #include "lightingnight.hpp"
#include "lightingbright.hpp" #include "lightingbright.hpp"
@ -13,7 +15,7 @@
namespace Resource namespace Resource
{ {
class SceneManager; class ResourceSystem;
} }
namespace osg namespace osg
@ -57,7 +59,7 @@ namespace CSVRender
{ {
Q_OBJECT Q_OBJECT
public: public:
SceneWidget(Resource::SceneManager* sceneManager, QWidget* parent = 0, Qt::WindowFlags f = 0); SceneWidget(boost::shared_ptr<Resource::ResourceSystem> resourceSystem, QWidget* parent = 0, Qt::WindowFlags f = 0);
virtual ~SceneWidget(); virtual ~SceneWidget();
CSVWidget::SceneToolMode *makeLightingSelector (CSVWidget::SceneToolbar *parent); CSVWidget::SceneToolMode *makeLightingSelector (CSVWidget::SceneToolbar *parent);
@ -73,7 +75,7 @@ namespace CSVRender
void setAmbient(const osg::Vec4f& ambient); void setAmbient(const osg::Vec4f& ambient);
Resource::SceneManager* mSceneManager; boost::shared_ptr<Resource::ResourceSystem> mResourceSystem;
Lighting* mLighting; Lighting* mLighting;

View file

@ -24,7 +24,7 @@
#include "editmode.hpp" #include "editmode.hpp"
CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidget* parent) CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidget* parent)
: SceneWidget (document.getData().getResourceSystem()->getSceneManager(), parent), mSceneElements(0), mRun(0), mDocument(document), : SceneWidget (document.getData().getResourceSystem(), parent), mSceneElements(0), mRun(0), mDocument(document),
mInteractionMask (0) mInteractionMask (0)
{ {
setAcceptDrops(true); setAcceptDrops(true);

View file

@ -16,7 +16,7 @@ void CSVTools::SearchSubView::replace (bool selection)
{ {
if (mLocked) if (mLocked)
return; return;
std::vector<int> indices = mTable->getReplaceIndices (selection); std::vector<int> indices = mTable->getReplaceIndices (selection);
std::string replace = mSearchBox.getReplaceText(); std::string replace = mSearchBox.getReplaceText();
@ -29,7 +29,7 @@ void CSVTools::SearchSubView::replace (bool selection)
CSMTools::Search search (mSearch); CSMTools::Search search (mSearch);
CSMWorld::IdTableBase *currentTable = 0; CSMWorld::IdTableBase *currentTable = 0;
// We are running through the indices in reverse order to avoid messing up multiple results // We are running through the indices in reverse order to avoid messing up multiple results
// in a single string. // in a single string.
for (std::vector<int>::const_reverse_iterator iter (indices.rbegin()); iter!=indices.rend(); ++iter) for (std::vector<int>::const_reverse_iterator iter (indices.rbegin()); iter!=indices.rend(); ++iter)
@ -46,7 +46,7 @@ void CSVTools::SearchSubView::replace (bool selection)
search.configure (table); search.configure (table);
currentTable = table; currentTable = table;
} }
std::string hint = model.getHint (*iter); std::string hint = model.getHint (*iter);
if (search.verify (mDocument, table, id, hint)) if (search.verify (mDocument, table, id, hint))
@ -63,7 +63,7 @@ void CSVTools::SearchSubView::replace (bool selection)
void CSVTools::SearchSubView::showEvent (QShowEvent *event) void CSVTools::SearchSubView::showEvent (QShowEvent *event)
{ {
CSVDoc::SubView::showEvent (event); CSVDoc::SubView::showEvent (event);
mSearchBox.focus(); mSearchBox.focus();
} }
CSVTools::SearchSubView::SearchSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) CSVTools::SearchSubView::SearchSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document)
@ -71,25 +71,23 @@ CSVTools::SearchSubView::SearchSubView (const CSMWorld::UniversalId& id, CSMDoc:
{ {
QVBoxLayout *layout = new QVBoxLayout; QVBoxLayout *layout = new QVBoxLayout;
layout->setContentsMargins (QMargins (0, 0, 0, 0));
layout->addWidget (&mSearchBox); layout->addWidget (&mSearchBox);
layout->addWidget (mTable = new ReportTable (document, id, true), 2); layout->addWidget (mTable = new ReportTable (document, id, true), 2);
QWidget *widget = new QWidget; QWidget *widget = new QWidget;
widget->setLayout (layout); widget->setLayout (layout);
setWidget (widget); setWidget (widget);
stateChanged (document.getState(), &document); stateChanged (document.getState(), &document);
connect (mTable, SIGNAL (editRequest (const CSMWorld::UniversalId&, const std::string&)), connect (mTable, SIGNAL (editRequest (const CSMWorld::UniversalId&, const std::string&)),
SIGNAL (focusId (const CSMWorld::UniversalId&, const std::string&))); SIGNAL (focusId (const CSMWorld::UniversalId&, const std::string&)));
connect (mTable, SIGNAL (replaceRequest()), this, SLOT (replaceRequest())); connect (mTable, SIGNAL (replaceRequest()), this, SLOT (replaceRequest()));
connect (&document, SIGNAL (stateChanged (int, CSMDoc::Document *)), connect (&document, SIGNAL (stateChanged (int, CSMDoc::Document *)),
this, SLOT (stateChanged (int, CSMDoc::Document *))); this, SLOT (stateChanged (int, CSMDoc::Document *)));
@ -124,7 +122,7 @@ void CSVTools::SearchSubView::startSearch (const CSMTools::Search& search)
mSearch = search; mSearch = search;
mSearch.setPadding (paddingBefore, paddingAfter); mSearch.setPadding (paddingBefore, paddingAfter);
mTable->clear(); mTable->clear();
mDocument.runSearch (getUniversalId(), mSearch); mDocument.runSearch (getUniversalId(), mSearch);
} }

View file

@ -31,6 +31,7 @@
#include "../../model/world/idtree.hpp" #include "../../model/world/idtree.hpp"
#include "../../model/world/commands.hpp" #include "../../model/world/commands.hpp"
#include "../../model/doc/document.hpp" #include "../../model/doc/document.hpp"
#include "../../model/settings/usersettings.hpp"
#include "../widget/coloreditor.hpp" #include "../widget/coloreditor.hpp"
#include "../widget/droplineedit.hpp" #include "../widget/droplineedit.hpp"
@ -66,7 +67,7 @@ void CSVWorld::NotEditableSubDelegate::setEditorData (QWidget* editor, const QMo
CSMWorld::Columns::ColumnId columnId = static_cast<CSMWorld::Columns::ColumnId> ( CSMWorld::Columns::ColumnId columnId = static_cast<CSMWorld::Columns::ColumnId> (
mTable->getColumnId (index.column())); mTable->getColumnId (index.column()));
if (QVariant::String == v.type()) if (QVariant::String == v.type())
{ {
label->setText(v.toString()); label->setText(v.toString());
@ -75,7 +76,7 @@ void CSVWorld::NotEditableSubDelegate::setEditorData (QWidget* editor, const QMo
{ {
int data = v.toInt(); int data = v.toInt();
std::vector<std::string> enumNames (CSMWorld::Columns::getEnums (columnId)); std::vector<std::string> enumNames (CSMWorld::Columns::getEnums (columnId));
label->setText(QString::fromUtf8(enumNames.at(data).c_str())); label->setText(QString::fromUtf8(enumNames.at(data).c_str()));
} }
else else
@ -324,11 +325,11 @@ CSVWorld::IdContextMenu::IdContextMenu(QWidget *widget, CSMWorld::ColumnBase::Di
Q_ASSERT(mWidget != NULL); Q_ASSERT(mWidget != NULL);
Q_ASSERT(CSMWorld::ColumnBase::isId(display)); Q_ASSERT(CSMWorld::ColumnBase::isId(display));
Q_ASSERT(mIdType != CSMWorld::UniversalId::Type_None); Q_ASSERT(mIdType != CSMWorld::UniversalId::Type_None);
mWidget->setContextMenuPolicy(Qt::CustomContextMenu); mWidget->setContextMenuPolicy(Qt::CustomContextMenu);
connect(mWidget, connect(mWidget,
SIGNAL(customContextMenuRequested(const QPoint &)), SIGNAL(customContextMenuRequested(const QPoint &)),
this, this,
SLOT(showContextMenu(const QPoint &))); SLOT(showContextMenu(const QPoint &)));
mEditIdAction = new QAction(this); mEditIdAction = new QAction(this);
@ -352,7 +353,7 @@ void CSVWorld::IdContextMenu::excludeId(const std::string &id)
QString CSVWorld::IdContextMenu::getWidgetValue() const QString CSVWorld::IdContextMenu::getWidgetValue() const
{ {
QLineEdit *lineEdit = qobject_cast<QLineEdit *>(mWidget); QLineEdit *lineEdit = qobject_cast<QLineEdit *>(mWidget);
QLabel *label = qobject_cast<QLabel *>(mWidget); QLabel *label = qobject_cast<QLabel *>(mWidget);
QString value = ""; QString value = "";
@ -411,7 +412,7 @@ void CSVWorld::IdContextMenu::showContextMenu(const QPoint &pos)
{ {
removeEditIdActionFromMenu(); removeEditIdActionFromMenu();
} }
if (!mContextMenu->actions().isEmpty()) if (!mContextMenu->actions().isEmpty())
{ {
mContextMenu->exec(mWidget->mapToGlobal(pos)); mContextMenu->exec(mWidget->mapToGlobal(pos));
@ -588,9 +589,9 @@ void CSVWorld::EditWidget::remake(int row)
tablesLayout->addWidget(label); tablesLayout->addWidget(label);
tablesLayout->addWidget(table); tablesLayout->addWidget(table);
connect(table, connect(table,
SIGNAL(editRequest(const CSMWorld::UniversalId &, const std::string &)), SIGNAL(editRequest(const CSMWorld::UniversalId &, const std::string &)),
this, this,
SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &))); SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &)));
} }
else if (!(flags & CSMWorld::ColumnBase::Flag_Dialogue_List)) else if (!(flags & CSMWorld::ColumnBase::Flag_Dialogue_List))
@ -830,45 +831,74 @@ void CSVWorld::SimpleDialogueSubView::updateCurrentId()
} }
CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, void CSVWorld::DialogueSubView::addButtonBar()
CSMDoc::Document& document, const CreatorFactoryBase& creatorFactory, bool sorting)
: SimpleDialogueSubView (id, document)
{ {
// bottom box if (mButtons)
mBottom = new TableBottomBox (creatorFactory, document, id, this); return;
mBottom->setSizePolicy (QSizePolicy::Ignored, QSizePolicy::Fixed); mButtons = new RecordButtonBar (getUniversalId(), getTable(), mBottom,
connect (mBottom, SIGNAL (requestFocus (const std::string&)),
this, SLOT (requestFocus (const std::string&)));
// button bar
mButtons = new RecordButtonBar (id, getTable(), mBottom,
&getCommandDispatcher(), this); &getCommandDispatcher(), this);
// layout getMainLayout().insertWidget (1, mButtons);
getMainLayout().addWidget (mButtons);
getMainLayout().addWidget (mBottom);
// connections // connections
connect (mButtons, SIGNAL (showPreview()), this, SLOT (showPreview())); connect (mButtons, SIGNAL (showPreview()), this, SLOT (showPreview()));
connect (mButtons, SIGNAL (viewRecord()), this, SLOT (viewRecord())); connect (mButtons, SIGNAL (viewRecord()), this, SLOT (viewRecord()));
connect (mButtons, SIGNAL (switchToRow (int)), this, SLOT (switchToRow (int))); connect (mButtons, SIGNAL (switchToRow (int)), this, SLOT (switchToRow (int)));
connect (this, SIGNAL (universalIdChanged (const CSMWorld::UniversalId&)), connect (this, SIGNAL (universalIdChanged (const CSMWorld::UniversalId&)),
mButtons, SLOT (universalIdChanged (const CSMWorld::UniversalId&))); mButtons, SLOT (universalIdChanged (const CSMWorld::UniversalId&)));
} }
CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id,
CSMDoc::Document& document, const CreatorFactoryBase& creatorFactory, bool sorting)
: SimpleDialogueSubView (id, document), mButtons (0)
{
// bottom box
mBottom = new TableBottomBox (creatorFactory, document, id, this);
connect (mBottom, SIGNAL (requestFocus (const std::string&)),
this, SLOT (requestFocus (const std::string&)));
// button bar
if (CSMSettings::UserSettings::instance().setting ("dialogues/toolbar", QString("true")) == "true")
addButtonBar();
// layout
getMainLayout().addWidget (mBottom);
}
void CSVWorld::DialogueSubView::setEditLock (bool locked) void CSVWorld::DialogueSubView::setEditLock (bool locked)
{ {
SimpleDialogueSubView::setEditLock (locked); SimpleDialogueSubView::setEditLock (locked);
mButtons->setEditLock (locked);
if (mButtons)
mButtons->setEditLock (locked);
} }
void CSVWorld::DialogueSubView::updateUserSetting (const QString& name, const QStringList& value) void CSVWorld::DialogueSubView::updateUserSetting (const QString& name, const QStringList& value)
{ {
SimpleDialogueSubView::updateUserSetting (name, value); SimpleDialogueSubView::updateUserSetting (name, value);
mButtons->updateUserSetting (name, value);
if (name=="dialogues/toolbar")
{
if (value.at(0)==QString ("true"))
{
addButtonBar();
}
else
{
if (mButtons)
{
getMainLayout().removeWidget (mButtons);
delete mButtons;
mButtons = 0;
}
}
}
if (mButtons)
mButtons->updateUserSetting (name, value);
} }
void CSVWorld::DialogueSubView::showPreview () void CSVWorld::DialogueSubView::showPreview ()
@ -908,7 +938,7 @@ void CSVWorld::DialogueSubView::switchToRow (int row)
setUniversalId (CSMWorld::UniversalId (type, id)); setUniversalId (CSMWorld::UniversalId (type, id));
updateCurrentId(); updateCurrentId();
getEditWidget().remake (row); getEditWidget().remake (row);
int stateColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Modification); int stateColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Modification);
@ -923,5 +953,5 @@ void CSVWorld::DialogueSubView::requestFocus (const std::string& id)
QModelIndex index = getTable().getModelIndex (id, 0); QModelIndex index = getTable().getModelIndex (id, 0);
if (index.isValid()) if (index.isValid())
switchToRow (index.row()); switchToRow (index.row());
} }

View file

@ -195,8 +195,8 @@ namespace CSVWorld
CSMDoc::Document& mDocument; CSMDoc::Document& mDocument;
std::vector<CSMWorld::NestedTableProxyModel*> mNestedModels; //Plain, raw C pointers, deleted in the dtor std::vector<CSMWorld::NestedTableProxyModel*> mNestedModels; //Plain, raw C pointers, deleted in the dtor
void createEditorContextMenu(QWidget *editor, void createEditorContextMenu(QWidget *editor,
CSMWorld::ColumnBase::Display display, CSMWorld::ColumnBase::Display display,
int currentRow) const; int currentRow) const;
public: public:
@ -236,7 +236,7 @@ namespace CSVWorld
void updateCurrentId(); void updateCurrentId();
bool isLocked() const; bool isLocked() const;
public: public:
SimpleDialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); SimpleDialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document);
@ -256,10 +256,14 @@ namespace CSVWorld
class DialogueSubView : public SimpleDialogueSubView class DialogueSubView : public SimpleDialogueSubView
{ {
Q_OBJECT Q_OBJECT
TableBottomBox* mBottom; TableBottomBox* mBottom;
RecordButtonBar *mButtons; RecordButtonBar *mButtons;
private:
void addButtonBar();
public: public:
DialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, DialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document,
@ -268,14 +272,14 @@ namespace CSVWorld
virtual void setEditLock (bool locked); virtual void setEditLock (bool locked);
virtual void updateUserSetting (const QString& name, const QStringList& value); virtual void updateUserSetting (const QString& name, const QStringList& value);
private slots: private slots:
void showPreview(); void showPreview();
void viewRecord(); void viewRecord();
void switchToRow (int row); void switchToRow (int row);
void requestFocus (const std::string& id); void requestFocus (const std::string& id);
}; };

View file

@ -13,8 +13,6 @@ CSVWorld::PreviewSubView::PreviewSubView (const CSMWorld::UniversalId& id, CSMDo
{ {
QHBoxLayout *layout = new QHBoxLayout; QHBoxLayout *layout = new QHBoxLayout;
layout->setContentsMargins (QMargins (0, 0, 0, 0));
if (document.getData().getReferenceables().searchId (id.getId())==-1) if (document.getData().getReferenceables().searchId (id.getId())==-1)
{ {
std::string referenceableId = std::string referenceableId =

View file

@ -17,18 +17,17 @@ void CSVWorld::RecordButtonBar::updateModificationButtons()
mCloneButton->setDisabled (createAndDeleteDisabled); mCloneButton->setDisabled (createAndDeleteDisabled);
mAddButton->setDisabled (createAndDeleteDisabled); mAddButton->setDisabled (createAndDeleteDisabled);
mDeleteButton->setDisabled (createAndDeleteDisabled);
bool commandDisabled = !mCommandDispatcher || mLocked; bool commandDisabled = !mCommandDispatcher || mLocked;
mRevertButton->setDisabled (commandDisabled); mRevertButton->setDisabled (commandDisabled);
mDeleteButton->setDisabled (commandDisabled); mDeleteButton->setDisabled (commandDisabled || createAndDeleteDisabled);
} }
void CSVWorld::RecordButtonBar::updatePrevNextButtons() void CSVWorld::RecordButtonBar::updatePrevNextButtons()
{ {
int rows = mTable.rowCount(); int rows = mTable.rowCount();
if (rows<=1) if (rows<=1)
{ {
mPrevButton->setDisabled (true); mPrevButton->setDisabled (true);
@ -62,12 +61,12 @@ CSVWorld::RecordButtonBar::RecordButtonBar (const CSMWorld::UniversalId& id,
mPrevButton->setIcon(QIcon(":/go-previous.png")); mPrevButton->setIcon(QIcon(":/go-previous.png"));
mPrevButton->setToolTip ("Switch to previous record"); mPrevButton->setToolTip ("Switch to previous record");
buttonsLayout->addWidget (mPrevButton, 0); buttonsLayout->addWidget (mPrevButton, 0);
mNextButton = new QToolButton (this); mNextButton = new QToolButton (this);
mNextButton->setIcon(QIcon(":/go-next.png")); mNextButton->setIcon(QIcon(":/go-next.png"));
mNextButton->setToolTip ("Switch to next record"); mNextButton->setToolTip ("Switch to next record");
buttonsLayout->addWidget (mNextButton, 1); buttonsLayout->addWidget (mNextButton, 1);
buttonsLayout->addStretch(2); buttonsLayout->addStretch(2);
// optional buttons of the right section // optional buttons of the right section
@ -94,22 +93,22 @@ CSVWorld::RecordButtonBar::RecordButtonBar (const CSMWorld::UniversalId& id,
mCloneButton->setIcon(QIcon(":/edit-clone.png")); mCloneButton->setIcon(QIcon(":/edit-clone.png"));
mCloneButton->setToolTip ("Clone record"); mCloneButton->setToolTip ("Clone record");
buttonsLayout->addWidget(mCloneButton); buttonsLayout->addWidget(mCloneButton);
mAddButton = new QToolButton (this); mAddButton = new QToolButton (this);
mAddButton->setIcon(QIcon(":/add.png")); mAddButton->setIcon(QIcon(":/add.png"));
mAddButton->setToolTip ("Add new record"); mAddButton->setToolTip ("Add new record");
buttonsLayout->addWidget(mAddButton); buttonsLayout->addWidget(mAddButton);
mDeleteButton = new QToolButton (this); mDeleteButton = new QToolButton (this);
mDeleteButton->setIcon(QIcon(":/edit-delete.png")); mDeleteButton->setIcon(QIcon(":/edit-delete.png"));
mDeleteButton->setToolTip ("Delete record"); mDeleteButton->setToolTip ("Delete record");
buttonsLayout->addWidget(mDeleteButton); buttonsLayout->addWidget(mDeleteButton);
mRevertButton = new QToolButton (this); mRevertButton = new QToolButton (this);
mRevertButton->setIcon(QIcon(":/edit-undo.png")); mRevertButton->setIcon(QIcon(":/edit-undo.png"));
mRevertButton->setToolTip ("Revert record"); mRevertButton->setToolTip ("Revert record");
buttonsLayout->addWidget(mRevertButton); buttonsLayout->addWidget(mRevertButton);
setLayout (buttonsLayout); setLayout (buttonsLayout);
// connections // connections
@ -132,7 +131,7 @@ CSVWorld::RecordButtonBar::RecordButtonBar (const CSMWorld::UniversalId& id,
this, SLOT (rowNumberChanged (const QModelIndex&, int, int))); this, SLOT (rowNumberChanged (const QModelIndex&, int, int)));
connect (&mTable, SIGNAL (rowsRemoved (const QModelIndex&, int, int)), connect (&mTable, SIGNAL (rowsRemoved (const QModelIndex&, int, int)),
this, SLOT (rowNumberChanged (const QModelIndex&, int, int))); this, SLOT (rowNumberChanged (const QModelIndex&, int, int)));
updateModificationButtons(); updateModificationButtons();
updatePrevNextButtons(); updatePrevNextButtons();
} }
@ -170,7 +169,7 @@ void CSVWorld::RecordButtonBar::cloneRequest()
} }
void CSVWorld::RecordButtonBar::nextId() void CSVWorld::RecordButtonBar::nextId()
{ {
int newRow = mTable.getModelIndex (mId.getId(), 0).row() + 1; int newRow = mTable.getModelIndex (mId.getId(), 0).row() + 1;
if (newRow >= mTable.rowCount()) if (newRow >= mTable.rowCount())
@ -180,8 +179,8 @@ void CSVWorld::RecordButtonBar::nextId()
newRow = 0; newRow = 0;
else else
return; return;
} }
emit switchToRow (newRow); emit switchToRow (newRow);
} }
@ -197,7 +196,7 @@ void CSVWorld::RecordButtonBar::prevId()
else else
return; return;
} }
emit switchToRow (newRow); emit switchToRow (newRow);
} }

View file

@ -31,8 +31,6 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D
{ {
QVBoxLayout *layout = new QVBoxLayout; QVBoxLayout *layout = new QVBoxLayout;
layout->setContentsMargins (QMargins (0, 0, 0, 0));
layout->addWidget (mBottom = new TableBottomBox (NullCreatorFactory(), document, id, this), 0); layout->addWidget (mBottom = new TableBottomBox (NullCreatorFactory(), document, id, this), 0);
mLayout->setContentsMargins (QMargins (0, 0, 0, 0)); mLayout->setContentsMargins (QMargins (0, 0, 0, 0));

View file

@ -276,11 +276,11 @@ void CSVWorld::ScriptEdit::lineNumberAreaPaintEvent(QPaintEvent *event)
if(textCursor().hasSelection()) if(textCursor().hasSelection())
{ {
QString str = textCursor().selection().toPlainText(); QString str = textCursor().selection().toPlainText();
int selectedLines = str.count("\n")+1; int offset = str.count("\n");
if(textCursor().position() < textCursor().anchor()) if(textCursor().position() < textCursor().anchor())
endBlock += selectedLines; endBlock += offset;
else else
startBlock -= selectedLines; startBlock -= offset;
} }
painter.setBackgroundMode(Qt::OpaqueMode); painter.setBackgroundMode(Qt::OpaqueMode);
QFont font = painter.font(); QFont font = painter.font();

View file

@ -0,0 +1,143 @@
#include "scripterrortable.hpp"
#include <QHeaderView>
#include <components/compiler/tokenloc.hpp>
#include <components/compiler/scanner.hpp>
#include <components/compiler/fileparser.hpp>
#include <components/compiler/exception.hpp>
#include <components/compiler/extensions0.hpp>
#include "../../model/doc/document.hpp"
#include "../../model/settings/usersettings.hpp"
void CSVWorld::ScriptErrorTable::report (const std::string& message, const Compiler::TokenLoc& loc, Type type)
{
std::ostringstream stream;
stream << message << " (" << loc.mLiteral << ")";
addMessage (stream.str(), type==Compiler::ErrorHandler::WarningMessage ?
CSMDoc::Message::Severity_Warning : CSMDoc::Message::Severity_Error,
loc.mLine, loc.mColumn-loc.mLiteral.length());
}
void CSVWorld::ScriptErrorTable::report (const std::string& message, Type type)
{
addMessage (message, type==Compiler::ErrorHandler::WarningMessage ?
CSMDoc::Message::Severity_Warning : CSMDoc::Message::Severity_Error);
}
void CSVWorld::ScriptErrorTable::addMessage (const std::string& message,
CSMDoc::Message::Severity severity, int line, int column)
{
int row = rowCount();
setRowCount (row+1);
QTableWidgetItem *severityItem = new QTableWidgetItem (
QString::fromUtf8 (CSMDoc::Message::toString (severity).c_str()));
severityItem->setFlags (severityItem->flags() ^ Qt::ItemIsEditable);
setItem (row, 0, severityItem);
if (line!=-1)
{
QTableWidgetItem *lineItem = new QTableWidgetItem;
lineItem->setData (Qt::DisplayRole, line+1);
lineItem->setFlags (lineItem->flags() ^ Qt::ItemIsEditable);
setItem (row, 1, lineItem);
QTableWidgetItem *columnItem = new QTableWidgetItem;
columnItem->setData (Qt::DisplayRole, column);
columnItem->setFlags (columnItem->flags() ^ Qt::ItemIsEditable);
setItem (row, 3, columnItem);
}
QTableWidgetItem *messageItem = new QTableWidgetItem (QString::fromUtf8 (message.c_str()));
messageItem->setFlags (messageItem->flags() ^ Qt::ItemIsEditable);
setItem (row, 2, messageItem);
}
void CSVWorld::ScriptErrorTable::setWarningsMode (const QString& value)
{
if (value=="Ignore")
Compiler::ErrorHandler::setWarningsMode (0);
else if (value=="Normal")
Compiler::ErrorHandler::setWarningsMode (1);
else if (value=="Strict")
Compiler::ErrorHandler::setWarningsMode (2);
}
CSVWorld::ScriptErrorTable::ScriptErrorTable (const CSMDoc::Document& document, QWidget *parent)
: QTableWidget (parent), mContext (document.getData())
{
setColumnCount (4);
QStringList headers;
headers << "Severity" << "Line" << "Description";
setHorizontalHeaderLabels (headers);
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
horizontalHeader()->setSectionResizeMode (0, QHeaderView::ResizeToContents);
horizontalHeader()->setSectionResizeMode (1, QHeaderView::ResizeToContents);
#else
horizontalHeader()->setResizeMode (0, QHeaderView::ResizeToContents);
horizontalHeader()->setResizeMode (1, QHeaderView::ResizeToContents);
#endif
horizontalHeader()->setStretchLastSection (true);
verticalHeader()->hide();
setColumnHidden (3, true);
setSelectionMode (QAbstractItemView::NoSelection);
Compiler::registerExtensions (mExtensions);
mContext.setExtensions (&mExtensions);
setWarningsMode (CSMSettings::UserSettings::instance().settingValue ("script-editor/warnings"));
connect (this, SIGNAL (cellClicked (int, int)), this, SLOT (cellClicked (int, int)));
}
void CSVWorld::ScriptErrorTable::updateUserSetting (const QString& name, const QStringList& value)
{
if (name=="script-editor/warnings" && !value.isEmpty())
setWarningsMode (value.at (0));
}
void CSVWorld::ScriptErrorTable::update (const std::string& source)
{
clear();
try
{
std::istringstream input (source);
Compiler::Scanner scanner (*this, input, mContext.getExtensions());
Compiler::FileParser parser (*this, mContext);
scanner.scan (parser);
}
catch (const Compiler::SourceException&)
{
// error has already been reported via error handler
}
catch (const std::exception& error)
{
addMessage (error.what(), CSMDoc::Message::Severity_SeriousError);
}
}
void CSVWorld::ScriptErrorTable::clear()
{
setRowCount (0);
}
void CSVWorld::ScriptErrorTable::cellClicked (int row, int column)
{
if (item (row, 1))
{
int scriptLine = item (row, 1)->data (Qt::DisplayRole).toInt();
int scriptColumn = item (row, 3)->data (Qt::DisplayRole).toInt();
emit highlightError (scriptLine-1, scriptColumn);
}
}

View file

@ -0,0 +1,57 @@
#ifndef CSV_WORLD_SCRIPTERRORTABLE_H
#define CSV_WORLD_SCRIPTERRORTABLE_H
#include <QTableWidget>
#include <components/compiler/errorhandler.hpp>
#include <components/compiler/extensions.hpp>
#include "../../model/world/scriptcontext.hpp"
#include "../../model/doc/messages.hpp"
namespace CSMDoc
{
class Document;
}
namespace CSVWorld
{
class ScriptErrorTable : public QTableWidget, private Compiler::ErrorHandler
{
Q_OBJECT
Compiler::Extensions mExtensions;
CSMWorld::ScriptContext mContext;
virtual void report (const std::string& message, const Compiler::TokenLoc& loc, Type type);
///< Report error to the user.
virtual void report (const std::string& message, Type type);
///< Report a file related error
void addMessage (const std::string& message, CSMDoc::Message::Severity severity,
int line = -1, int column = -1);
void setWarningsMode (const QString& value);
public:
ScriptErrorTable (const CSMDoc::Document& document, QWidget *parent = 0);
void updateUserSetting (const QString& name, const QStringList& value);
void update (const std::string& source);
void clear();
private slots:
void cellClicked (int row, int column);
signals:
void highlightError (int line, int column);
};
}
#endif

View file

@ -4,7 +4,8 @@
#include <QStatusBar> #include <QStatusBar>
#include <QStackedLayout> #include <QStackedLayout>
#include <QLabel> #include <QSplitter>
#include <QTimer>
#include "../../model/doc/document.hpp" #include "../../model/doc/document.hpp"
#include "../../model/world/universalid.hpp" #include "../../model/world/universalid.hpp"
@ -15,45 +16,96 @@
#include "../../model/settings/usersettings.hpp" #include "../../model/settings/usersettings.hpp"
#include "scriptedit.hpp" #include "scriptedit.hpp"
#include "recordbuttonbar.hpp"
#include "tablebottombox.hpp"
#include "genericcreator.hpp"
#include "scripterrortable.hpp"
void CSVWorld::ScriptSubView::addButtonBar()
{
if (mButtons)
return;
mButtons = new RecordButtonBar (getUniversalId(), *mModel, mBottom, &mCommandDispatcher, this);
mLayout.insertWidget (1, mButtons);
connect (mButtons, SIGNAL (switchToRow (int)), this, SLOT (switchToRow (int)));
connect (this, SIGNAL (universalIdChanged (const CSMWorld::UniversalId&)),
mButtons, SLOT (universalIdChanged (const CSMWorld::UniversalId&)));
}
void CSVWorld::ScriptSubView::recompile()
{
if (!mCompileDelay->isActive() && !isDeleted())
mCompileDelay->start (
CSMSettings::UserSettings::instance().setting ("script-editor/compile-delay").toInt());
}
bool CSVWorld::ScriptSubView::isDeleted() const
{
return mModel->data (mModel->getModelIndex (getUniversalId().getId(), mStateColumn)).toInt()
==CSMWorld::RecordBase::State_Deleted;
}
void CSVWorld::ScriptSubView::updateDeletedState()
{
if (isDeleted())
{
mErrors->clear();
mEditor->setEnabled (false);
}
else
{
mEditor->setEnabled (true);
recompile();
}
}
CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document)
: SubView (id), mDocument (document), mColumn (-1), mBottom(0), mStatus(0) : SubView (id), mDocument (document), mColumn (-1), mBottom(0), mButtons (0),
mCommandDispatcher (document, CSMWorld::UniversalId::getParentType (id.getType()))
{ {
QVBoxLayout *layout = new QVBoxLayout; std::vector<std::string> selection (1, id.getId());
layout->setContentsMargins (QMargins (0, 0, 0, 0)); mCommandDispatcher.setSelection (selection);
mBottom = new QWidget(this); mMain = new QSplitter (this);
QStackedLayout *bottmLayout = new QStackedLayout(mBottom); mMain->setOrientation (Qt::Vertical);
bottmLayout->setContentsMargins (0, 0, 0, 0); mLayout.addWidget (mMain, 2);
QStatusBar *statusBar = new QStatusBar(mBottom);
mStatus = new QLabel(mBottom);
statusBar->addWidget (mStatus);
bottmLayout->addWidget (statusBar);
mBottom->setLayout (bottmLayout);
layout->addWidget (mBottom, 0); mEditor = new ScriptEdit (mDocument, ScriptHighlighter::Mode_General, this);
layout->insertWidget (0, mEditor = new ScriptEdit (mDocument, ScriptHighlighter::Mode_General, this), 2); mMain->addWidget (mEditor);
mMain->setCollapsible (0, false);
QWidget *widget = new QWidget; mErrors = new ScriptErrorTable (document, this);
widget->setLayout (layout); mMain->addWidget (mErrors);
QWidget *widget = new QWidget (this);;
widget->setLayout (&mLayout);
setWidget (widget); setWidget (widget);
mModel = &dynamic_cast<CSMWorld::IdTable&> ( mModel = &dynamic_cast<CSMWorld::IdTable&> (
*document.getData().getTableModel (CSMWorld::UniversalId::Type_Scripts)); *document.getData().getTableModel (CSMWorld::UniversalId::Type_Scripts));
for (int i=0; i<mModel->columnCount(); ++i) mColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_ScriptText);
if (mModel->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display)== mStateColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Modification);
CSMWorld::ColumnBase::Display_ScriptFile)
{
mColumn = i;
break;
}
if (mColumn==-1) QString source = mModel->data (mModel->getModelIndex (id.getId(), mColumn)).toString();
throw std::logic_error ("Can't find script column");
mEditor->setPlainText (mModel->data (mModel->getModelIndex (id.getId(), mColumn)).toString()); mEditor->setPlainText (source);
// bottom box and buttons
mBottom = new TableBottomBox (CreatorFactory<GenericCreator>(), document, id, this);
if (CSMSettings::UserSettings::instance().setting ("script-editor/toolbar", QString("true")) == "true")
addButtonBar();
connect (mBottom, SIGNAL (requestFocus (const std::string&)),
this, SLOT (switchToId (const std::string&)));
mLayout.addWidget (mBottom);
// signals
connect (mEditor, SIGNAL (textChanged()), this, SLOT (textChanged())); connect (mEditor, SIGNAL (textChanged()), this, SLOT (textChanged()));
connect (mModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), connect (mModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)),
@ -64,35 +116,80 @@ CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc:
updateStatusBar(); updateStatusBar();
connect(mEditor, SIGNAL(cursorPositionChanged()), this, SLOT(updateStatusBar())); connect(mEditor, SIGNAL(cursorPositionChanged()), this, SLOT(updateStatusBar()));
mErrors->update (source.toUtf8().constData());
connect (mErrors, SIGNAL (highlightError (int, int)),
this, SLOT (highlightError (int, int)));
mCompileDelay = new QTimer (this);
mCompileDelay->setSingleShot (true);
connect (mCompileDelay, SIGNAL (timeout()), this, SLOT (updateRequest()));
updateDeletedState();
} }
void CSVWorld::ScriptSubView::updateUserSetting (const QString& name, const QStringList& value) void CSVWorld::ScriptSubView::updateUserSetting (const QString& name, const QStringList& value)
{ {
if (name == "script-editor/show-linenum") if (name == "script-editor/show-linenum")
{ {
std::string showLinenum = value.at(0).toStdString(); std::string showLinenum = value.at(0).toUtf8().constData();
mEditor->showLineNum(showLinenum == "true"); mEditor->showLineNum(showLinenum == "true");
mBottom->setVisible(showLinenum == "true"); mBottom->setVisible(showLinenum == "true");
} }
else if (name == "script-editor/mono-font") else if (name == "script-editor/mono-font")
{ {
mEditor->setMonoFont(value.at(0).toStdString() == "true"); mEditor->setMonoFont (value.at(0)==QString ("true"));
} }
else if (name=="script-editor/toolbar")
{
if (value.at(0)==QString ("true"))
{
addButtonBar();
}
else
{
if (mButtons)
{
mLayout.removeWidget (mButtons);
delete mButtons;
mButtons = 0;
}
}
}
else if (name=="script-editor/compile-delay")
{
mCompileDelay->setInterval (value.at (0).toInt());
}
if (mButtons)
mButtons->updateUserSetting (name, value);
mErrors->updateUserSetting (name, value);
if (name=="script-editor/warnings")
recompile();
}
void CSVWorld::ScriptSubView::setStatusBar (bool show)
{
mBottom->setStatusBar (show);
} }
void CSVWorld::ScriptSubView::updateStatusBar () void CSVWorld::ScriptSubView::updateStatusBar ()
{ {
std::ostringstream stream; mBottom->positionChanged (mEditor->textCursor().blockNumber() + 1,
mEditor->textCursor().columnNumber() + 1);
stream << "(" << mEditor->textCursor().blockNumber() + 1 << ", "
<< mEditor->textCursor().columnNumber() + 1 << ")";
mStatus->setText (QString::fromUtf8 (stream.str().c_str()));
} }
void CSVWorld::ScriptSubView::setEditLock (bool locked) void CSVWorld::ScriptSubView::setEditLock (bool locked)
{ {
mEditor->setReadOnly (locked); mEditor->setReadOnly (locked);
if (mButtons)
mButtons->setEditLock (locked);
mCommandDispatcher.setEditLock (locked);
} }
void CSVWorld::ScriptSubView::useHint (const std::string& hint) void CSVWorld::ScriptSubView::useHint (const std::string& hint)
@ -129,8 +226,12 @@ void CSVWorld::ScriptSubView::textChanged()
ScriptEdit::ChangeLock lock (*mEditor); ScriptEdit::ChangeLock lock (*mEditor);
QString source = mEditor->toPlainText();
mDocument.getUndoStack().push (new CSMWorld::ModifyCommand (*mModel, mDocument.getUndoStack().push (new CSMWorld::ModifyCommand (*mModel,
mModel->getModelIndex (getUniversalId().getId(), mColumn), mEditor->toPlainText())); mModel->getModelIndex (getUniversalId().getId(), mColumn), source));
recompile();
} }
void CSVWorld::ScriptSubView::dataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) void CSVWorld::ScriptSubView::dataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight)
@ -142,12 +243,21 @@ void CSVWorld::ScriptSubView::dataChanged (const QModelIndex& topLeft, const QMo
QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn); QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn);
if (index.row()>=topLeft.row() && index.row()<=bottomRight.row() && if (index.row()>=topLeft.row() && index.row()<=bottomRight.row())
index.column()>=topLeft.column() && index.column()<=bottomRight.column())
{ {
QTextCursor cursor = mEditor->textCursor(); if (mStateColumn>=topLeft.column() && mStateColumn<=bottomRight.column())
mEditor->setPlainText (mModel->data (index).toString()); updateDeletedState();
mEditor->setTextCursor (cursor);
if (mColumn>=topLeft.column() && mColumn<=bottomRight.column())
{
QString source = mModel->data (index).toString();
QTextCursor cursor = mEditor->textCursor();
mEditor->setPlainText (source);
mEditor->setTextCursor (cursor);
recompile();
}
} }
} }
@ -159,3 +269,42 @@ void CSVWorld::ScriptSubView::rowsAboutToBeRemoved (const QModelIndex& parent, i
emit closeRequest(); emit closeRequest();
} }
void CSVWorld::ScriptSubView::switchToRow (int row)
{
int idColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id);
std::string id = mModel->data (mModel->index (row, idColumn)).toString().toUtf8().constData();
setUniversalId (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Script, id));
mEditor->setPlainText (mModel->data (mModel->index (row, mColumn)).toString());
std::vector<std::string> selection (1, id);
mCommandDispatcher.setSelection (selection);
updateDeletedState();
}
void CSVWorld::ScriptSubView::switchToId (const std::string& id)
{
switchToRow (mModel->getModelIndex (id, 0).row());
}
void CSVWorld::ScriptSubView::highlightError (int line, int column)
{
QTextCursor cursor = mEditor->textCursor();
cursor.movePosition (QTextCursor::Start);
if (cursor.movePosition (QTextCursor::Down, QTextCursor::MoveAnchor, line))
cursor.movePosition (QTextCursor::Right, QTextCursor::MoveAnchor, column);
mEditor->setFocus();
mEditor->setTextCursor (cursor);
}
void CSVWorld::ScriptSubView::updateRequest()
{
QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn);
QString source = mModel->data (index).toString();
mErrors->update (source.toUtf8().constData());
}

View file

@ -1,10 +1,17 @@
#ifndef CSV_WORLD_SCRIPTSUBVIEW_H #ifndef CSV_WORLD_SCRIPTSUBVIEW_H
#define CSV_WORLD_SCRIPTSUBVIEW_H #define CSV_WORLD_SCRIPTSUBVIEW_H
#include <QVBoxLayout>
#include "../../model/world/commanddispatcher.hpp"
#include "../doc/subview.hpp" #include "../doc/subview.hpp"
class QModelIndex; class QModelIndex;
class QLabel; class QLabel;
class QVBoxLayout;
class QSplitter;
class QTime;
namespace CSMDoc namespace CSMDoc
{ {
@ -19,6 +26,9 @@ namespace CSMWorld
namespace CSVWorld namespace CSVWorld
{ {
class ScriptEdit; class ScriptEdit;
class RecordButtonBar;
class TableBottomBox;
class ScriptErrorTable;
class ScriptSubView : public CSVDoc::SubView class ScriptSubView : public CSVDoc::SubView
{ {
@ -28,8 +38,24 @@ namespace CSVWorld
CSMDoc::Document& mDocument; CSMDoc::Document& mDocument;
CSMWorld::IdTable *mModel; CSMWorld::IdTable *mModel;
int mColumn; int mColumn;
QWidget *mBottom; int mStateColumn;
QLabel *mStatus; TableBottomBox *mBottom;
RecordButtonBar *mButtons;
CSMWorld::CommandDispatcher mCommandDispatcher;
QVBoxLayout mLayout;
QSplitter *mMain;
ScriptErrorTable *mErrors;
QTimer *mCompileDelay;
private:
void addButtonBar();
void recompile();
bool isDeleted() const;
void updateDeletedState();
public: public:
@ -41,6 +67,8 @@ namespace CSVWorld
virtual void updateUserSetting (const QString& name, const QStringList& value); virtual void updateUserSetting (const QString& name, const QStringList& value);
virtual void setStatusBar (bool show);
public slots: public slots:
void textChanged(); void textChanged();
@ -52,6 +80,14 @@ namespace CSVWorld
private slots: private slots:
void updateStatusBar(); void updateStatusBar();
void switchToRow (int row);
void switchToId (const std::string& id);
void highlightError (int line, int column);
void updateRequest();
}; };
} }

View file

@ -679,10 +679,6 @@ void CSVWorld::Table::tableSizeUpdate()
} }
emit tableSizeChanged (size, deleted, modified); emit tableSizeChanged (size, deleted, modified);
// not really related to tableSizeUpdate() but placed here for convenience rather than
// creating a bunch of extra connections & slot
mProxyModel->refreshFilter();
} }
void CSVWorld::Table::selectionSizeUpdate() void CSVWorld::Table::selectionSizeUpdate()

View file

@ -51,6 +51,14 @@ void CSVWorld::TableBottomBox::updateStatus()
} }
} }
if (mHasPosition)
{
if (!first)
stream << " -- ";
stream << "(" << mRow << ", " << mColumn << ")";
}
mStatus->setText (QString::fromUtf8 (stream.str().c_str())); mStatus->setText (QString::fromUtf8 (stream.str().c_str()));
} }
} }
@ -69,7 +77,7 @@ CSVWorld::TableBottomBox::TableBottomBox (const CreatorFactoryBase& creatorFacto
CSMDoc::Document& document, CSMDoc::Document& document,
const CSMWorld::UniversalId& id, const CSMWorld::UniversalId& id,
QWidget *parent) QWidget *parent)
: QWidget (parent), mShowStatusBar (false), mEditMode(EditMode_None) : QWidget (parent), mShowStatusBar (false), mEditMode(EditMode_None), mHasPosition(false)
{ {
for (int i=0; i<4; ++i) for (int i=0; i<4; ++i)
mStatusCount[i] = 0; mStatusCount[i] = 0;
@ -206,6 +214,20 @@ void CSVWorld::TableBottomBox::tableSizeChanged (int size, int deleted, int modi
updateStatus(); updateStatus();
} }
void CSVWorld::TableBottomBox::positionChanged (int row, int column)
{
mRow = row;
mColumn = column;
mHasPosition = true;
updateStatus();
}
void CSVWorld::TableBottomBox::noMorePosition()
{
mHasPosition = false;
updateStatus();
}
void CSVWorld::TableBottomBox::createRequest() void CSVWorld::TableBottomBox::createRequest()
{ {
mCreator->reset(); mCreator->reset();
@ -216,8 +238,8 @@ void CSVWorld::TableBottomBox::createRequest()
mCreator->focus(); mCreator->focus();
} }
void CSVWorld::TableBottomBox::cloneRequest(const std::string& id, void CSVWorld::TableBottomBox::cloneRequest(const std::string& id,
const CSMWorld::UniversalId::Type type) const CSMWorld::UniversalId::Type type)
{ {
mCreator->reset(); mCreator->reset();
mCreator->cloneMode(id, type); mCreator->cloneMode(id, type);

View file

@ -36,6 +36,9 @@ namespace CSVWorld
ExtendedCommandConfigurator *mExtendedConfigurator; ExtendedCommandConfigurator *mExtendedConfigurator;
QStackedLayout *mLayout; QStackedLayout *mLayout;
bool mHasPosition;
int mRow;
int mColumn;
private: private:
@ -52,9 +55,9 @@ namespace CSVWorld
public: public:
TableBottomBox (const CreatorFactoryBase& creatorFactory, TableBottomBox (const CreatorFactoryBase& creatorFactory,
CSMDoc::Document& document, CSMDoc::Document& document,
const CSMWorld::UniversalId& id, const CSMWorld::UniversalId& id,
QWidget *parent = 0); QWidget *parent = 0);
virtual ~TableBottomBox(); virtual ~TableBottomBox();
@ -92,6 +95,10 @@ namespace CSVWorld
/// \param deleted Number of deleted records /// \param deleted Number of deleted records
/// \param modified Number of added and modified records /// \param modified Number of added and modified records
void positionChanged (int row, int column);
void noMorePosition();
void createRequest(); void createRequest();
void cloneRequest(const std::string& id, void cloneRequest(const std::string& id,
const CSMWorld::UniversalId::Type type); const CSMWorld::UniversalId::Type type);

View file

@ -23,8 +23,6 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D
{ {
QVBoxLayout *layout = new QVBoxLayout; QVBoxLayout *layout = new QVBoxLayout;
layout->setContentsMargins (QMargins (0, 0, 0, 0));
layout->addWidget (mBottom = layout->addWidget (mBottom =
new TableBottomBox (creatorFactory, document, id, this), 0); new TableBottomBox (creatorFactory, document, id, this), 0);
@ -171,4 +169,3 @@ bool CSVWorld::TableSubView::eventFilter (QObject* object, QEvent* event)
} }
return false; return false;
} }

View file

@ -27,7 +27,7 @@
#include <components/files/configurationmanager.hpp> #include <components/files/configurationmanager.hpp>
#include <components/translation/translation.hpp> #include <components/translation/translation.hpp>
#include <components/esm/loadcell.hpp> #include <components/version/version.hpp>
#include "mwinput/inputmanagerimp.hpp" #include "mwinput/inputmanagerimp.hpp"
@ -414,7 +414,7 @@ void OMW::Engine::setWindowIcon()
{ {
boost::filesystem::ifstream windowIconStream; boost::filesystem::ifstream windowIconStream;
std::string windowIcon = (mResDir / "mygui" / "openmw.png").string(); std::string windowIcon = (mResDir / "mygui" / "openmw.png").string();
windowIconStream.open(windowIcon); windowIconStream.open(windowIcon, std::ios_base::in | std::ios_base::binary);
if (windowIconStream.fail()) if (windowIconStream.fail())
std::cerr << "Failed to open " << windowIcon << std::endl; std::cerr << "Failed to open " << windowIcon << std::endl;
osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension("png"); osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension("png");
@ -493,7 +493,8 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
rootNode->addChild(guiRoot); rootNode->addChild(guiRoot);
MWGui::WindowManager* window = new MWGui::WindowManager(mViewer, guiRoot, mResourceSystem.get(), MWGui::WindowManager* window = new MWGui::WindowManager(mViewer, guiRoot, mResourceSystem.get(),
mCfgMgr.getLogPath().string() + std::string("/"), myguiResources, mCfgMgr.getLogPath().string() + std::string("/"), myguiResources,
mScriptConsoleMode, mTranslationDataStorage, mEncoding, mExportFonts, mFallbackMap); mScriptConsoleMode, mTranslationDataStorage, mEncoding, mExportFonts, mFallbackMap,
Version::getOpenmwVersionDescription(mResDir.string()));
mEnvironment.setWindowManager (window); mEnvironment.setWindowManager (window);
// Create sound system // Create sound system
@ -645,9 +646,6 @@ void OMW::Engine::go()
prepareEngine (settings); prepareEngine (settings);
// Play some good 'ol tunes
MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore"));
if (!mSaveGameFile.empty()) if (!mSaveGameFile.empty())
{ {
MWBase::Environment::get().getStateManager()->loadGame(mSaveGameFile); MWBase::Environment::get().getStateManager()->loadGame(mSaveGameFile);

View file

@ -14,7 +14,9 @@
#if defined(_WIN32) #if defined(_WIN32)
// For OutputDebugString // For OutputDebugString
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h> #include <windows.h>
// makes __argc and __argv available on windows // makes __argc and __argv available on windows
#include <cstdlib> #include <cstdlib>
@ -197,18 +199,14 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
return false; return false;
} }
std::cout << "OpenMW version " << OPENMW_VERSION;
std::string rev = OPENMW_VERSION_COMMITHASH;
std::string tag = OPENMW_VERSION_TAGHASH;
if (!rev.empty() && !tag.empty())
{
rev = rev.substr(0, 10);
std::cout << " (revision " << rev << ")";
}
std::cout << std::endl;
if (variables.count ("version")) if (variables.count ("version"))
{
cfgMgr.readConfiguration(variables, desc, true);
Version::Version v = Version::getOpenmwVersion(variables["resources"].as<std::string>());
std::cout << v.describe() << std::endl;
return false; return false;
}
cfgMgr.readConfiguration(variables, desc); cfgMgr.readConfiguration(variables, desc);

View file

@ -23,6 +23,7 @@ namespace MWWorld
{ {
class Ptr; class Ptr;
class CellStore; class CellStore;
class CellRef;
} }
namespace Loading namespace Loading
@ -128,6 +129,11 @@ namespace MWBase
OffenseType type, int arg=0, bool victimAware=false) = 0; OffenseType type, int arg=0, bool victimAware=false) = 0;
/// @return false if the attack was considered a "friendly hit" and forgiven /// @return false if the attack was considered a "friendly hit" and forgiven
virtual bool actorAttacked (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker) = 0; virtual bool actorAttacked (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker) = 0;
/// Notify that actor was killed, add a murder bounty if applicable
/// @note No-op for non-player attackers
virtual void actorKilled (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker) = 0;
/// Utility to check if taking this item is illegal and calling commitCrime if so /// Utility to check if taking this item is illegal and calling commitCrime if so
/// @param container The container the item is in; may be empty for an item in the world /// @param container The container the item is in; may be empty for an item in the world
virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, const MWWorld::Ptr& container, virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, const MWWorld::Ptr& container,
@ -211,6 +217,8 @@ namespace MWBase
/// 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 std::string& ownerid) = 0;
virtual bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::CellRef& cellref, MWWorld::Ptr& victim) = 0;
}; };
} }

View file

@ -25,20 +25,16 @@
#include <boost/lexical_cast.hpp> #include <boost/lexical_cast.hpp>
namespace
{
bool isGold (const MWWorld::Ptr& ptr)
{
return Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_001")
|| Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_005")
|| Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_010")
|| Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_025")
|| Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_100");
}
}
namespace MWClass namespace MWClass
{ {
bool Miscellaneous::isGold (const MWWorld::Ptr& ptr) const
{
return Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_001")
|| Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_005")
|| Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_010")
|| Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_025")
|| Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_100");
}
std::string Miscellaneous::getId (const MWWorld::Ptr& ptr) const std::string Miscellaneous::getId (const MWWorld::Ptr& ptr) const
{ {
return ptr.get<ESM::Miscellaneous>()->mBase->mId; return ptr.get<ESM::Miscellaneous>()->mBase->mId;

View file

@ -62,6 +62,8 @@ namespace MWClass
virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const;
virtual bool isKey (const MWWorld::Ptr &ptr) const; virtual bool isKey (const MWWorld::Ptr &ptr) const;
virtual bool isGold (const MWWorld::Ptr& ptr) const;
}; };
} }

View file

@ -751,12 +751,7 @@ namespace MWClass
attacker.getClass().getNpcStats(attacker).addWerewolfKill(); attacker.getClass().getNpcStats(attacker).addWerewolfKill();
} }
// Simple check for who attacked first: if the player attacked first, a crimeId should be set MWBase::Environment::get().getMechanicsManager()->actorKilled(ptr, attacker);
// 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.
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
if (attacker == player && ptr.getClass().getNpcStats(ptr).getCrimeId() != -1 && ptr != player)
MWBase::Environment::get().getMechanicsManager()->commitCrime(player, ptr, MWBase::MechanicsManager::OT_Murder);
} }
} }

View file

@ -10,6 +10,7 @@
#include <components/esm/loaddial.hpp> #include <components/esm/loaddial.hpp>
#include <components/esm/loadinfo.hpp> #include <components/esm/loadinfo.hpp>
#include <components/esm/dialoguestate.hpp> #include <components/esm/dialoguestate.hpp>
#include <components/esm/esmwriter.hpp>
#include <components/compiler/exception.hpp> #include <components/compiler/exception.hpp>
#include <components/compiler/errorhandler.hpp> #include <components/compiler/errorhandler.hpp>

View file

@ -203,6 +203,8 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c
return false; // script does not have a variable of this name. return false; // script does not have a variable of this name.
int index = localDefs.getIndex (name); int index = localDefs.getIndex (name);
if (index < 0)
return false; // shouldn't happen, we checked that variable has a type above, so must exist
const MWScript::Locals& locals = mActor.getRefData().getLocals(); const MWScript::Locals& locals = mActor.getRefData().getLocals();

View file

@ -4,8 +4,6 @@
#include <MyGUI_ImageBox.h> #include <MyGUI_ImageBox.h>
#include <MyGUI_Gui.h> #include <MyGUI_Gui.h>
#include <components/esm/records.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"

View file

@ -132,134 +132,141 @@ namespace MWGui
void CharacterCreation::spawnDialog(const char id) void CharacterCreation::spawnDialog(const char id)
{ {
switch (id) try
{ {
case GM_Name: switch (id)
MWBase::Environment::get().getWindowManager()->removeDialog(mNameDialog); {
mNameDialog = 0; case GM_Name:
mNameDialog = new TextInputDialog(); MWBase::Environment::get().getWindowManager()->removeDialog(mNameDialog);
mNameDialog->setTextLabel(MWBase::Environment::get().getWindowManager()->getGameSettingString("sName", "Name")); mNameDialog = 0;
mNameDialog->setTextInput(mPlayerName); mNameDialog = new TextInputDialog();
mNameDialog->setNextButtonShow(mCreationStage >= CSE_NameChosen); mNameDialog->setTextLabel(MWBase::Environment::get().getWindowManager()->getGameSettingString("sName", "Name"));
mNameDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onNameDialogDone); mNameDialog->setTextInput(mPlayerName);
mNameDialog->setVisible(true); mNameDialog->setNextButtonShow(mCreationStage >= CSE_NameChosen);
break; mNameDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onNameDialogDone);
mNameDialog->setVisible(true);
break;
case GM_Race: case GM_Race:
MWBase::Environment::get().getWindowManager()->removeDialog(mRaceDialog); MWBase::Environment::get().getWindowManager()->removeDialog(mRaceDialog);
mRaceDialog = 0; mRaceDialog = 0;
mRaceDialog = new RaceDialog(mViewer, mResourceSystem); mRaceDialog = new RaceDialog(mViewer, mResourceSystem);
mRaceDialog->setNextButtonShow(mCreationStage >= CSE_RaceChosen); mRaceDialog->setNextButtonShow(mCreationStage >= CSE_RaceChosen);
mRaceDialog->setRaceId(mPlayerRaceId); mRaceDialog->setRaceId(mPlayerRaceId);
mRaceDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onRaceDialogDone); mRaceDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onRaceDialogDone);
mRaceDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onRaceDialogBack); mRaceDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onRaceDialogBack);
mRaceDialog->setVisible(true); mRaceDialog->setVisible(true);
if (mCreationStage < CSE_NameChosen) if (mCreationStage < CSE_NameChosen)
mCreationStage = CSE_NameChosen; mCreationStage = CSE_NameChosen;
break; break;
case GM_Class: case GM_Class:
MWBase::Environment::get().getWindowManager()->removeDialog(mClassChoiceDialog); MWBase::Environment::get().getWindowManager()->removeDialog(mClassChoiceDialog);
mClassChoiceDialog = 0; mClassChoiceDialog = 0;
mClassChoiceDialog = new ClassChoiceDialog(); mClassChoiceDialog = new ClassChoiceDialog();
mClassChoiceDialog->eventButtonSelected += MyGUI::newDelegate(this, &CharacterCreation::onClassChoice); mClassChoiceDialog->eventButtonSelected += MyGUI::newDelegate(this, &CharacterCreation::onClassChoice);
mClassChoiceDialog->setVisible(true); mClassChoiceDialog->setVisible(true);
if (mCreationStage < CSE_RaceChosen) if (mCreationStage < CSE_RaceChosen)
mCreationStage = CSE_RaceChosen; mCreationStage = CSE_RaceChosen;
break; break;
case GM_ClassPick: case GM_ClassPick:
MWBase::Environment::get().getWindowManager()->removeDialog(mPickClassDialog); MWBase::Environment::get().getWindowManager()->removeDialog(mPickClassDialog);
mPickClassDialog = 0; mPickClassDialog = 0;
mPickClassDialog = new PickClassDialog(); mPickClassDialog = new PickClassDialog();
mPickClassDialog->setNextButtonShow(mCreationStage >= CSE_ClassChosen); mPickClassDialog->setNextButtonShow(mCreationStage >= CSE_ClassChosen);
mPickClassDialog->setClassId(mPlayerClass.mName); mPickClassDialog->setClassId(mPlayerClass.mName);
mPickClassDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onPickClassDialogDone); mPickClassDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onPickClassDialogDone);
mPickClassDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onPickClassDialogBack); mPickClassDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onPickClassDialogBack);
mPickClassDialog->setVisible(true); mPickClassDialog->setVisible(true);
if (mCreationStage < CSE_RaceChosen) if (mCreationStage < CSE_RaceChosen)
mCreationStage = CSE_RaceChosen; mCreationStage = CSE_RaceChosen;
break; break;
case GM_Birth: case GM_Birth:
MWBase::Environment::get().getWindowManager()->removeDialog(mBirthSignDialog); MWBase::Environment::get().getWindowManager()->removeDialog(mBirthSignDialog);
mBirthSignDialog = 0; mBirthSignDialog = 0;
mBirthSignDialog = new BirthDialog(); mBirthSignDialog = new BirthDialog();
mBirthSignDialog->setNextButtonShow(mCreationStage >= CSE_BirthSignChosen); mBirthSignDialog->setNextButtonShow(mCreationStage >= CSE_BirthSignChosen);
mBirthSignDialog->setBirthId(mPlayerBirthSignId); mBirthSignDialog->setBirthId(mPlayerBirthSignId);
mBirthSignDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onBirthSignDialogDone); mBirthSignDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onBirthSignDialogDone);
mBirthSignDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onBirthSignDialogBack); mBirthSignDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onBirthSignDialogBack);
mBirthSignDialog->setVisible(true); mBirthSignDialog->setVisible(true);
if (mCreationStage < CSE_ClassChosen) if (mCreationStage < CSE_ClassChosen)
mCreationStage = CSE_ClassChosen; mCreationStage = CSE_ClassChosen;
break; break;
case GM_ClassCreate: case GM_ClassCreate:
if (!mCreateClassDialog) if (!mCreateClassDialog)
{
mCreateClassDialog = new CreateClassDialog();
mCreateClassDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onCreateClassDialogDone);
mCreateClassDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onCreateClassDialogBack);
}
mCreateClassDialog->setNextButtonShow(mCreationStage >= CSE_ClassChosen);
mCreateClassDialog->setVisible(true);
if (mCreationStage < CSE_RaceChosen)
mCreationStage = CSE_RaceChosen;
break;
case GM_ClassGenerate:
mGenerateClassStep = 0;
mGenerateClass = "";
mGenerateClassSpecializations[0] = 0;
mGenerateClassSpecializations[1] = 0;
mGenerateClassSpecializations[2] = 0;
showClassQuestionDialog();
if (mCreationStage < CSE_RaceChosen)
mCreationStage = CSE_RaceChosen;
break;
case GM_Review:
MWBase::Environment::get().getWindowManager()->removeDialog(mReviewDialog);
mReviewDialog = 0;
mReviewDialog = new ReviewDialog();
mReviewDialog->setPlayerName(mPlayerName);
mReviewDialog->setRace(mPlayerRaceId);
mReviewDialog->setClass(mPlayerClass);
mReviewDialog->setBirthSign(mPlayerBirthSignId);
{
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
const MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player);
mReviewDialog->setHealth ( stats.getHealth() );
mReviewDialog->setMagicka( stats.getMagicka() );
mReviewDialog->setFatigue( stats.getFatigue() );
}
{
std::map<int, MWMechanics::AttributeValue > attributes = MWBase::Environment::get().getWindowManager()->getPlayerAttributeValues();
for (std::map<int, MWMechanics::AttributeValue >::iterator it = attributes.begin();
it != attributes.end(); ++it)
{ {
mReviewDialog->setAttribute(static_cast<ESM::Attribute::AttributeID> (it->first), it->second); mCreateClassDialog = new CreateClassDialog();
mCreateClassDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onCreateClassDialogDone);
mCreateClassDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onCreateClassDialogBack);
} }
} mCreateClassDialog->setNextButtonShow(mCreationStage >= CSE_ClassChosen);
mCreateClassDialog->setVisible(true);
if (mCreationStage < CSE_RaceChosen)
mCreationStage = CSE_RaceChosen;
break;
case GM_ClassGenerate:
mGenerateClassStep = 0;
mGenerateClass = "";
mGenerateClassSpecializations[0] = 0;
mGenerateClassSpecializations[1] = 0;
mGenerateClassSpecializations[2] = 0;
showClassQuestionDialog();
if (mCreationStage < CSE_RaceChosen)
mCreationStage = CSE_RaceChosen;
break;
case GM_Review:
MWBase::Environment::get().getWindowManager()->removeDialog(mReviewDialog);
mReviewDialog = 0;
mReviewDialog = new ReviewDialog();
mReviewDialog->setPlayerName(mPlayerName);
mReviewDialog->setRace(mPlayerRaceId);
mReviewDialog->setClass(mPlayerClass);
mReviewDialog->setBirthSign(mPlayerBirthSignId);
{
std::map<int, MWMechanics::SkillValue > skills = MWBase::Environment::get().getWindowManager()->getPlayerSkillValues();
for (std::map<int, MWMechanics::SkillValue >::iterator it = skills.begin();
it != skills.end(); ++it)
{ {
mReviewDialog->setSkillValue(static_cast<ESM::Skill::SkillEnum> (it->first), it->second); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
} const MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player);
mReviewDialog->configureSkills(MWBase::Environment::get().getWindowManager()->getPlayerMajorSkills(), MWBase::Environment::get().getWindowManager()->getPlayerMinorSkills());
}
mReviewDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onReviewDialogDone); mReviewDialog->setHealth ( stats.getHealth() );
mReviewDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onReviewDialogBack); mReviewDialog->setMagicka( stats.getMagicka() );
mReviewDialog->eventActivateDialog += MyGUI::newDelegate(this, &CharacterCreation::onReviewActivateDialog); mReviewDialog->setFatigue( stats.getFatigue() );
mReviewDialog->setVisible(true); }
if (mCreationStage < CSE_BirthSignChosen)
mCreationStage = CSE_BirthSignChosen; {
break; std::map<int, MWMechanics::AttributeValue > attributes = MWBase::Environment::get().getWindowManager()->getPlayerAttributeValues();
for (std::map<int, MWMechanics::AttributeValue >::iterator it = attributes.begin();
it != attributes.end(); ++it)
{
mReviewDialog->setAttribute(static_cast<ESM::Attribute::AttributeID> (it->first), it->second);
}
}
{
std::map<int, MWMechanics::SkillValue > skills = MWBase::Environment::get().getWindowManager()->getPlayerSkillValues();
for (std::map<int, MWMechanics::SkillValue >::iterator it = skills.begin();
it != skills.end(); ++it)
{
mReviewDialog->setSkillValue(static_cast<ESM::Skill::SkillEnum> (it->first), it->second);
}
mReviewDialog->configureSkills(MWBase::Environment::get().getWindowManager()->getPlayerMajorSkills(), MWBase::Environment::get().getWindowManager()->getPlayerMinorSkills());
}
mReviewDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onReviewDialogDone);
mReviewDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onReviewDialogBack);
mReviewDialog->eventActivateDialog += MyGUI::newDelegate(this, &CharacterCreation::onReviewActivateDialog);
mReviewDialog->setVisible(true);
if (mCreationStage < CSE_BirthSignChosen)
mCreationStage = CSE_BirthSignChosen;
break;
}
}
catch (std::exception& e)
{
std::cerr << "Failed to create chargen window: " << e.what() << std::endl;
} }
} }

View file

@ -1,6 +1,5 @@
#include "companionitemmodel.hpp" #include "companionitemmodel.hpp"
#include "../mwmechanics/npcstats.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
namespace namespace

View file

@ -3,11 +3,8 @@
#include <MyGUI_InputManager.h> #include <MyGUI_InputManager.h>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/dialoguemanager.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
#include "../mwmechanics/npcstats.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "messagebox.hpp" #include "messagebox.hpp"

View file

@ -11,13 +11,11 @@
#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/mechanicsmanager.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/containerstore.hpp"
#include "../mwmechanics/pickpocket.hpp" #include "../mwmechanics/pickpocket.hpp"
#include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/creaturestats.hpp"
#include "countdialog.hpp" #include "countdialog.hpp"
#include "tradewindow.hpp"
#include "inventorywindow.hpp" #include "inventorywindow.hpp"
#include "itemview.hpp" #include "itemview.hpp"

View file

@ -16,12 +16,12 @@
#include "../mwbase/soundmanager.hpp" #include "../mwbase/soundmanager.hpp"
#include "../mwbase/dialoguemanager.hpp" #include "../mwbase/dialoguemanager.hpp"
#include "../mwmechanics/npcstats.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/containerstore.hpp" #include "../mwworld/containerstore.hpp"
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "widgets.hpp" #include "widgets.hpp"
#include "bookpage.hpp" #include "bookpage.hpp"

View file

@ -7,7 +7,6 @@
#include <MyGUI_Button.h> #include <MyGUI_Button.h>
#include <MyGUI_ScrollView.h> #include <MyGUI_ScrollView.h>
#include <components/esm/records.hpp>
#include <components/widgets/list.hpp> #include <components/widgets/list.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"

View file

@ -10,7 +10,6 @@
#include <components/settings/settings.hpp> #include <components/settings/settings.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
@ -518,7 +517,19 @@ namespace MWGui
{ {
mCrosshair->setVisible (visible); mCrosshair->setVisible (visible);
} }
void HUD::setCrosshairOwned(bool owned)
{
if(owned)
{
mCrosshair->changeWidgetSkin("HUD_Crosshair_Owned");
}
else
{
mCrosshair->changeWidgetSkin("HUD_Crosshair");
}
}
void HUD::setHmsVisible(bool visible) void HUD::setHmsVisible(bool visible)
{ {
mHealth->setVisible(visible); mHealth->setVisible(visible);

View file

@ -47,6 +47,7 @@ namespace MWGui
void unsetSelectedWeapon(); void unsetSelectedWeapon();
void setCrosshairVisible(bool visible); void setCrosshairVisible(bool visible);
void setCrosshairOwned(bool owned);
void onFrame(float dt); void onFrame(float dt);

View file

@ -4,8 +4,6 @@
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/inventorystore.hpp" #include "../mwworld/inventorystore.hpp"
#include "../mwmechanics/creaturestats.hpp"
namespace MWGui namespace MWGui
{ {

View file

@ -10,6 +10,8 @@
#include <osg/Texture2D> #include <osg/Texture2D>
#include <components/misc/stringops.hpp>
#include <components/myguiplatform/myguitexture.hpp> #include <components/myguiplatform/myguitexture.hpp>
#include <components/settings/settings.hpp> #include <components/settings/settings.hpp>
@ -19,12 +21,12 @@
#include "../mwbase/soundmanager.hpp" #include "../mwbase/soundmanager.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/scriptmanager.hpp"
#include "../mwworld/inventorystore.hpp" #include "../mwworld/inventorystore.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/action.hpp" #include "../mwworld/action.hpp"
#include "../mwscript/interpretercontext.hpp" #include "../mwscript/interpretercontext.hpp"
#include "../mwbase/scriptmanager.hpp"
#include "../mwrender/characterpreview.hpp" #include "../mwrender/characterpreview.hpp"
#include "itemview.hpp" #include "itemview.hpp"
@ -432,6 +434,20 @@ namespace MWGui
{ {
const std::string& script = ptr.getClass().getScript(ptr); const std::string& script = ptr.getClass().getScript(ptr);
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
// early-out for items that need to be equipped, but can't be equipped: we don't want to set OnPcEquip in that case
if (!ptr.getClass().getEquipmentSlots(ptr).first.empty())
{
std::pair<int, std::string> canEquip = ptr.getClass().canBeEquipped(ptr, player);
if (canEquip.first == 0)
{
MWBase::Environment::get().getWindowManager()->messageBox(canEquip.second);
updateItemView();
return;
}
}
// If the item has a script, set its OnPcEquip to 1 // If the item has a script, set its OnPcEquip to 1
if (!script.empty() if (!script.empty()
// Another morrowind oddity: when an item has skipped equipping and pcskipequip is reset to 0 afterwards, // Another morrowind oddity: when an item has skipped equipping and pcskipequip is reset to 0 afterwards,
@ -451,7 +467,7 @@ namespace MWGui
{ {
boost::shared_ptr<MWWorld::Action> action = ptr.getClass().use(ptr); boost::shared_ptr<MWWorld::Action> action = ptr.getClass().use(ptr);
action->execute (MWBase::Environment::get().getWorld()->getPlayerPtr()); action->execute (player);
mSkippedToEquip = MWWorld::Ptr(); mSkippedToEquip = MWWorld::Ptr();
} }

View file

@ -17,9 +17,7 @@
#include <components/vfs/manager.hpp> #include <components/vfs/manager.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/statemanager.hpp" #include "../mwbase/statemanager.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
#include "../mwbase/inputmanager.hpp" #include "../mwbase/inputmanager.hpp"

View file

@ -4,8 +4,6 @@
#include <MyGUI_Gui.h> #include <MyGUI_Gui.h>
#include <MyGUI_RenderManager.h> #include <MyGUI_RenderManager.h>
#include <components/version/version.hpp>
#include <components/widgets/imagebutton.hpp> #include <components/widgets/imagebutton.hpp>
#include <components/settings/settings.hpp> #include <components/settings/settings.hpp>
#include <components/vfs/manager.hpp> #include <components/vfs/manager.hpp>
@ -14,12 +12,8 @@
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
#include "../mwbase/soundmanager.hpp" #include "../mwbase/soundmanager.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/journal.hpp"
#include "../mwbase/dialoguemanager.hpp"
#include "../mwbase/statemanager.hpp" #include "../mwbase/statemanager.hpp"
#include "../mwstate/character.hpp"
#include "savegamedialog.hpp" #include "savegamedialog.hpp"
#include "confirmationdialog.hpp" #include "confirmationdialog.hpp"
#include "backgroundimage.hpp" #include "backgroundimage.hpp"
@ -28,7 +22,7 @@
namespace MWGui namespace MWGui
{ {
MainMenu::MainMenu(int w, int h, const VFS::Manager* vfs) MainMenu::MainMenu(int w, int h, const VFS::Manager* vfs, const std::string& versionDescription)
: Layout("openmw_mainmenu.layout") : Layout("openmw_mainmenu.layout")
, mWidth (w), mHeight (h) , mWidth (w), mHeight (h)
, mVFS(vfs), mButtonBox(0) , mVFS(vfs), mButtonBox(0)
@ -38,20 +32,7 @@ namespace MWGui
, mSaveGameDialog(NULL) , mSaveGameDialog(NULL)
{ {
getWidget(mVersionText, "VersionText"); getWidget(mVersionText, "VersionText");
std::stringstream sstream; mVersionText->setCaption(versionDescription);
sstream << "OpenMW Version: " << OPENMW_VERSION;
// adding info about git hash if available
std::string rev = OPENMW_VERSION_COMMITHASH;
std::string tag = OPENMW_VERSION_TAGHASH;
if (!rev.empty() && !tag.empty())
{
rev = rev.substr(0,10);
sstream << "\nRevision: " << rev;
}
std::string output = sstream.str();
mVersionText->setCaption(output);
mHasAnimatedMenu = mVFS->exists("video/menu_background.bik"); mHasAnimatedMenu = mVFS->exists("video/menu_background.bik");

View file

@ -29,7 +29,7 @@ namespace MWGui
public: public:
MainMenu(int w, int h, const VFS::Manager* vfs); MainMenu(int w, int h, const VFS::Manager* vfs, const std::string& versionDescription);
~MainMenu(); ~MainMenu();
void onResChange(int w, int h); void onResChange(int w, int h);

View file

@ -12,6 +12,7 @@
#include <MyGUI_FactoryManager.h> #include <MyGUI_FactoryManager.h>
#include <components/esm/globalmap.hpp> #include <components/esm/globalmap.hpp>
#include <components/esm/esmwriter.hpp>
#include <components/settings/settings.hpp> #include <components/settings/settings.hpp>
#include <components/myguiplatform/myguitexture.hpp> #include <components/myguiplatform/myguitexture.hpp>
@ -26,7 +27,6 @@
#include "../mwrender/globalmap.hpp" #include "../mwrender/globalmap.hpp"
#include "../mwrender/localmap.hpp" #include "../mwrender/localmap.hpp"
#include "widgets.hpp"
#include "confirmationdialog.hpp" #include "confirmationdialog.hpp"
#include "tooltips.hpp" #include "tooltips.hpp"
@ -37,8 +37,8 @@ namespace
enum LocalMapWidgetDepth enum LocalMapWidgetDepth
{ {
Local_CompassLayer = 0, Local_MarkerAboveFogLayer = 0,
Local_MarkerAboveFogLayer = 1, Local_CompassLayer = 1,
Local_FogLayer = 2, Local_FogLayer = 2,
Local_MarkerLayer = 3, Local_MarkerLayer = 3,
Local_MapLayer = 4 Local_MapLayer = 4
@ -91,31 +91,41 @@ namespace MWGui
void CustomMarkerCollection::addMarker(const ESM::CustomMarker &marker, bool triggerEvent) void CustomMarkerCollection::addMarker(const ESM::CustomMarker &marker, bool triggerEvent)
{ {
mMarkers.push_back(marker); mMarkers.insert(std::make_pair(marker.mCell, marker));
if (triggerEvent) if (triggerEvent)
eventMarkersChanged(); eventMarkersChanged();
} }
void CustomMarkerCollection::deleteMarker(const ESM::CustomMarker &marker) void CustomMarkerCollection::deleteMarker(const ESM::CustomMarker &marker)
{ {
std::vector<ESM::CustomMarker>::iterator it = std::find(mMarkers.begin(), mMarkers.end(), marker); std::pair<ContainerType::iterator, ContainerType::iterator> range = mMarkers.equal_range(marker.mCell);
if (it != mMarkers.end())
mMarkers.erase(it);
else
throw std::runtime_error("can't find marker to delete");
eventMarkersChanged(); for (ContainerType::iterator it = range.first; it != range.second; ++it)
{
if (it->second == marker)
{
mMarkers.erase(it);
eventMarkersChanged();
return;
}
}
throw std::runtime_error("can't find marker to delete");
} }
void CustomMarkerCollection::updateMarker(const ESM::CustomMarker &marker, const std::string &newNote) void CustomMarkerCollection::updateMarker(const ESM::CustomMarker &marker, const std::string &newNote)
{ {
std::vector<ESM::CustomMarker>::iterator it = std::find(mMarkers.begin(), mMarkers.end(), marker); std::pair<ContainerType::iterator, ContainerType::iterator> range = mMarkers.equal_range(marker.mCell);
if (it != mMarkers.end())
it->mNote = newNote;
else
throw std::runtime_error("can't find marker to update");
eventMarkersChanged(); for (ContainerType::iterator it = range.first; it != range.second; ++it)
{
if (it->second == marker)
{
it->second.mNote = newNote;
eventMarkersChanged();
return;
}
}
throw std::runtime_error("can't find marker to update");
} }
void CustomMarkerCollection::clear() void CustomMarkerCollection::clear()
@ -124,16 +134,21 @@ namespace MWGui
eventMarkersChanged(); eventMarkersChanged();
} }
std::vector<ESM::CustomMarker>::const_iterator CustomMarkerCollection::begin() const CustomMarkerCollection::ContainerType::const_iterator CustomMarkerCollection::begin() const
{ {
return mMarkers.begin(); return mMarkers.begin();
} }
std::vector<ESM::CustomMarker>::const_iterator CustomMarkerCollection::end() const CustomMarkerCollection::ContainerType::const_iterator CustomMarkerCollection::end() const
{ {
return mMarkers.end(); return mMarkers.end();
} }
CustomMarkerCollection::RangeType CustomMarkerCollection::getMarkers(const ESM::CellId &cellId) const
{
return mMarkers.equal_range(cellId);
}
size_t CustomMarkerCollection::size() const size_t CustomMarkerCollection::size() const
{ {
return mMarkers.size(); return mMarkers.size();
@ -298,44 +313,43 @@ namespace MWGui
MyGUI::Gui::getInstance().destroyWidget(*it); MyGUI::Gui::getInstance().destroyWidget(*it);
mCustomMarkerWidgets.clear(); mCustomMarkerWidgets.clear();
for (std::vector<ESM::CustomMarker>::const_iterator it = mCustomMarkers.begin(); it != mCustomMarkers.end(); ++it) for (int dX = -1; dX <= 1; ++dX)
{ {
const ESM::CustomMarker& marker = *it; for (int dY =-1; dY <= 1; ++dY)
if (marker.mCell.mPaged != !mInterior)
continue;
if (mInterior)
{ {
if (marker.mCell.mWorldspace != mPrefix) ESM::CellId cellId;
continue; cellId.mPaged = !mInterior;
} cellId.mWorldspace = (mInterior ? mPrefix : "sys::default");
else cellId.mIndex.mX = mCurX+dX;
{ cellId.mIndex.mY = mCurY+dY;
if (std::abs(marker.mCell.mIndex.mX - mCurX) > 1)
continue;
if (std::abs(marker.mCell.mIndex.mY - mCurY) > 1)
continue;
}
MarkerUserData markerPos (mLocalMapRender); CustomMarkerCollection::RangeType markers = mCustomMarkers.getMarkers(cellId);
MyGUI::IntPoint widgetPos = getMarkerPosition(marker.mWorldX, marker.mWorldY, markerPos); for (CustomMarkerCollection::ContainerType::const_iterator it = markers.first; it != markers.second; ++it)
{
const ESM::CustomMarker& marker = it->second;
MyGUI::IntCoord widgetCoord(widgetPos.left - 8, MarkerUserData markerPos (mLocalMapRender);
widgetPos.top - 8, MyGUI::IntPoint widgetPos = getMarkerPosition(marker.mWorldX, marker.mWorldY, markerPos);
16, 16);
MarkerWidget* markerWidget = mLocalMap->createWidget<MarkerWidget>("CustomMarkerButton", MyGUI::IntCoord widgetCoord(widgetPos.left - 8,
widgetCoord, MyGUI::Align::Default); widgetPos.top - 8,
markerWidget->setDepth(Local_MarkerAboveFogLayer); 16, 16);
markerWidget->setUserString("ToolTipType", "Layout"); MarkerWidget* markerWidget = mLocalMap->createWidget<MarkerWidget>("CustomMarkerButton",
markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine"); widgetCoord, MyGUI::Align::Default);
markerWidget->setUserString("Caption_TextOneLine", MyGUI::TextIterator::toTagsString(marker.mNote)); markerWidget->setDepth(Local_MarkerAboveFogLayer);
markerWidget->setNormalColour(MyGUI::Colour(0.6f, 0.6f, 0.6f)); markerWidget->setUserString("ToolTipType", "Layout");
markerWidget->setHoverColour(MyGUI::Colour(1.0f, 1.0f, 1.0f)); markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine");
markerWidget->setUserData(marker); markerWidget->setUserString("Caption_TextOneLine", MyGUI::TextIterator::toTagsString(marker.mNote));
markerWidget->setNeedMouseFocus(true); markerWidget->setNormalColour(MyGUI::Colour(0.6f, 0.6f, 0.6f));
customMarkerCreated(markerWidget); markerWidget->setHoverColour(MyGUI::Colour(1.0f, 1.0f, 1.0f));
mCustomMarkerWidgets.push_back(markerWidget); markerWidget->setUserData(marker);
markerWidget->setNeedMouseFocus(true);
customMarkerCreated(markerWidget);
mCustomMarkerWidgets.push_back(markerWidget);
}
}
} }
redraw(); redraw();
} }
@ -410,11 +424,9 @@ namespace MWGui
MWBase::World::DoorMarker marker = *it; MWBase::World::DoorMarker marker = *it;
std::vector<std::string> destNotes; std::vector<std::string> destNotes;
for (std::vector<ESM::CustomMarker>::const_iterator it = mCustomMarkers.begin(); it != mCustomMarkers.end(); ++it) CustomMarkerCollection::RangeType markers = mCustomMarkers.getMarkers(marker.dest);
{ for (CustomMarkerCollection::ContainerType::const_iterator it = markers.first; it != markers.second; ++it)
if (it->mCell == marker.dest) destNotes.push_back(it->second.mNote);
destNotes.push_back(it->mNote);
}
MarkerUserData data (mLocalMapRender); MarkerUserData data (mLocalMapRender);
data.notes = destNotes; data.notes = destNotes;
@ -770,15 +782,19 @@ namespace MWGui
MyGUI::Widget* markerWidget = mGlobalMap->createWidget<MyGUI::Widget>("MarkerButton", MyGUI::Widget* markerWidget = mGlobalMap->createWidget<MyGUI::Widget>("MarkerButton",
widgetCoord, MyGUI::Align::Default); widgetCoord, MyGUI::Align::Default);
markerWidget->setUserString("Caption_TextOneLine", name);
setGlobalMapMarkerTooltip(markerWidget, x, y);
markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine");
markerWidget->setNeedMouseFocus(true); markerWidget->setNeedMouseFocus(true);
markerWidget->setColour(MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=normal}"))); markerWidget->setColour(MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=normal}")));
markerWidget->setUserString("ToolTipType", "Layout");
markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine");
markerWidget->setUserString("Caption_TextOneLine", name);
markerWidget->setDepth(Global_MarkerLayer); markerWidget->setDepth(Global_MarkerLayer);
markerWidget->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); markerWidget->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag);
markerWidget->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); markerWidget->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart);
mGlobalMapMarkers.push_back(markerWidget); mGlobalMapMarkers[std::make_pair(x,y)] = markerWidget;
} }
} }
@ -803,6 +819,45 @@ namespace MWGui
NoDrop::onFrame(dt); NoDrop::onFrame(dt);
} }
void MapWindow::setGlobalMapMarkerTooltip(MyGUI::Widget* markerWidget, int x, int y)
{
ESM::CellId cellId;
cellId.mIndex.mX = x;
cellId.mIndex.mY = y;
cellId.mWorldspace = "sys::default";
cellId.mPaged = true;
CustomMarkerCollection::RangeType markers = mCustomMarkers.getMarkers(cellId);
std::vector<std::string> destNotes;
for (CustomMarkerCollection::ContainerType::const_iterator it = markers.first; it != markers.second; ++it)
destNotes.push_back(it->second.mNote);
if (!destNotes.empty())
{
MarkerUserData data (NULL);
data.notes = destNotes;
data.caption = markerWidget->getUserString("Caption_TextOneLine");
markerWidget->setUserData(data);
markerWidget->setUserString("ToolTipType", "MapMarker");
}
else
{
markerWidget->setUserString("ToolTipType", "Layout");
}
}
void MapWindow::updateCustomMarkers()
{
LocalMapBase::updateCustomMarkers();
for (std::map<std::pair<int, int>, MyGUI::Widget*>::iterator widgetIt = mGlobalMapMarkers.begin(); widgetIt != mGlobalMapMarkers.end(); ++widgetIt)
{
int x = widgetIt->first.first;
int y = widgetIt->first.second;
MyGUI::Widget* markerWidget = widgetIt->second;
setGlobalMapMarkerTooltip(markerWidget, x, y);
}
}
void MapWindow::onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) void MapWindow::onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id)
{ {
if (_id!=MyGUI::MouseButton::Left) return; if (_id!=MyGUI::MouseButton::Left) return;
@ -900,8 +955,8 @@ namespace MWGui
mGlobalMapRender->clear(); mGlobalMapRender->clear();
mChanged = true; mChanged = true;
for (std::vector<MyGUI::Widget*>::iterator it = mGlobalMapMarkers.begin(); it != mGlobalMapMarkers.end(); ++it) for (std::map<std::pair<int, int>, MyGUI::Widget*>::iterator it = mGlobalMapMarkers.begin(); it != mGlobalMapMarkers.end(); ++it)
MyGUI::Gui::getInstance().destroyWidget(*it); MyGUI::Gui::getInstance().destroyWidget(it->second);
mGlobalMapMarkers.clear(); mGlobalMapMarkers.clear();
} }
@ -1021,6 +1076,8 @@ namespace MWGui
bool LocalMapBase::MarkerUserData::isPositionExplored() const bool LocalMapBase::MarkerUserData::isPositionExplored() const
{ {
if (!mLocalMapRender)
return true;
return mLocalMapRender->isPositionExplored(nX, nY, cellX, cellY, interior); return mLocalMapRender->isPositionExplored(nX, nY, cellX, cellY, interior);
} }

View file

@ -42,14 +42,20 @@ namespace MWGui
size_t size() const; size_t size() const;
std::vector<ESM::CustomMarker>::const_iterator begin() const; typedef std::multimap<ESM::CellId, ESM::CustomMarker> ContainerType;
std::vector<ESM::CustomMarker>::const_iterator end() const;
typedef std::pair<ContainerType::const_iterator, ContainerType::const_iterator> RangeType;
ContainerType::const_iterator begin() const;
ContainerType::const_iterator end() const;
RangeType getMarkers(const ESM::CellId& cellId) const;
typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void;
EventHandle_Void eventMarkersChanged; EventHandle_Void eventMarkersChanged;
private: private:
std::vector<ESM::CustomMarker> mMarkers; ContainerType mMarkers;
}; };
class LocalMapBase class LocalMapBase
@ -120,7 +126,7 @@ namespace MWGui
std::vector<MyGUI::Widget*> mMagicMarkerWidgets; std::vector<MyGUI::Widget*> mMagicMarkerWidgets;
std::vector<MyGUI::Widget*> mCustomMarkerWidgets; std::vector<MyGUI::Widget*> mCustomMarkerWidgets;
void updateCustomMarkers(); virtual void updateCustomMarkers();
void applyFogOfWar(); void applyFogOfWar();
@ -197,6 +203,8 @@ namespace MWGui
void onFrame(float dt); void onFrame(float dt);
virtual void updateCustomMarkers();
/// Clear all savegame-specific data /// Clear all savegame-specific data
void clear(); void clear();
@ -215,6 +223,7 @@ namespace MWGui
void onNoteDoubleClicked(MyGUI::Widget* sender); void onNoteDoubleClicked(MyGUI::Widget* sender);
void onChangeScrollWindowCoord(MyGUI::Widget* sender); void onChangeScrollWindowCoord(MyGUI::Widget* sender);
void globalMapUpdatePlayer(); void globalMapUpdatePlayer();
void setGlobalMapMarkerTooltip(MyGUI::Widget* widget, int x, int y);
MyGUI::ScrollView* mGlobalMap; MyGUI::ScrollView* mGlobalMap;
std::auto_ptr<MyGUI::ITexture> mGlobalMapTexture; std::auto_ptr<MyGUI::ITexture> mGlobalMapTexture;
@ -242,7 +251,7 @@ namespace MWGui
MWRender::GlobalMap* mGlobalMapRender; MWRender::GlobalMap* mGlobalMapRender;
std::vector<MyGUI::Widget*> mGlobalMapMarkers; std::map<std::pair<int, int>, MyGUI::Widget*> mGlobalMapMarkers;
EditNoteDialog mEditNoteDialog; EditNoteDialog mEditNoteDialog;
ESM::CustomMarker mEditingMarker; ESM::CustomMarker mEditingMarker;

View file

@ -1,8 +1,8 @@
#include "pickpocketitemmodel.hpp" #include "pickpocketitemmodel.hpp"
#include <components/misc/rng.hpp> #include <components/misc/rng.hpp>
#include <components/esm/loadskil.hpp>
#include "../mwmechanics/npcstats.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
namespace MWGui namespace MWGui

View file

@ -5,6 +5,7 @@
#include <MyGUI_Gui.h> #include <MyGUI_Gui.h>
#include <MyGUI_ImageBox.h> #include <MyGUI_ImageBox.h>
#include <components/esm/esmwriter.hpp>
#include <components/esm/quickkeys.hpp> #include <components/esm/quickkeys.hpp>
#include "../mwworld/inventorystore.hpp" #include "../mwworld/inventorystore.hpp"
@ -14,18 +15,15 @@
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/spellcasting.hpp"
#include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/creaturestats.hpp"
#include "../mwgui/inventorywindow.hpp" #include "../mwgui/inventorywindow.hpp"
#include "windowmanagerimp.hpp"
#include "itemselection.hpp" #include "itemselection.hpp"
#include "spellview.hpp" #include "spellview.hpp"
#include "itemwidget.hpp" #include "itemwidget.hpp"
#include "sortfilteritemmodel.hpp" #include "sortfilteritemmodel.hpp"

View file

@ -7,8 +7,6 @@
#include <components/misc/rng.hpp> #include <components/misc/rng.hpp>
#include <components/esm/records.hpp>
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"

View file

@ -233,6 +233,7 @@ namespace MWGui
if (mResolutionList->findItemIndexWith(str) == MyGUI::ITEM_NONE) if (mResolutionList->findItemIndexWith(str) == MyGUI::ITEM_NONE)
mResolutionList->addItem(str); mResolutionList->addItem(str);
} }
highlightCurrentResolution();
std::string tf = Settings::Manager::getString("texture filtering", "General"); std::string tf = Settings::Manager::getString("texture filtering", "General");
mTextureFilteringButton->setCaption(textureFilteringToStr(tf)); mTextureFilteringButton->setCaption(textureFilteringToStr(tf));
@ -299,8 +300,28 @@ namespace MWGui
} }
void SettingsWindow::onResolutionCancel() void SettingsWindow::onResolutionCancel()
{
highlightCurrentResolution();
}
void SettingsWindow::highlightCurrentResolution()
{ {
mResolutionList->setIndexSelected(MyGUI::ITEM_NONE); mResolutionList->setIndexSelected(MyGUI::ITEM_NONE);
int currentX = Settings::Manager::getInt("resolution x", "Video");
int currentY = Settings::Manager::getInt("resolution y", "Video");
for (size_t i=0; i<mResolutionList->getItemCount(); ++i)
{
int resX, resY;
parseResolution (resX, resY, mResolutionList->getItemNameAt(i));
if (resX == currentX && resY == currentY)
{
mResolutionList->setIndexSelected(i);
break;
}
}
} }
void SettingsWindow::onShadowTextureSizeChanged(MyGUI::ComboBox *_sender, size_t pos) void SettingsWindow::onShadowTextureSizeChanged(MyGUI::ComboBox *_sender, size_t pos)

View file

@ -59,6 +59,7 @@ namespace MWGui
void onResolutionSelected(MyGUI::ListBox* _sender, size_t index); void onResolutionSelected(MyGUI::ListBox* _sender, size_t index);
void onResolutionAccept(); void onResolutionAccept();
void onResolutionCancel(); void onResolutionCancel();
void highlightCurrentResolution();
void onShadowTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos); void onShadowTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos);

View file

@ -12,7 +12,6 @@
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/containerstore.hpp" #include "../mwworld/containerstore.hpp"
#include "../mwworld/containerstore.hpp"
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/creaturestats.hpp"

View file

@ -38,6 +38,7 @@ namespace MWGui
, mLastMouseY(0) , mLastMouseY(0)
, mEnabled(true) , mEnabled(true)
, mFullHelp(false) , mFullHelp(false)
, mShowOwned(0)
{ {
getWidget(mDynamicToolTipBox, "DynamicToolTipBox"); getWidget(mDynamicToolTipBox, "DynamicToolTipBox");
@ -55,6 +56,8 @@ namespace MWGui
{ {
mMainWidget->getChildAt(i)->setVisible(false); mMainWidget->getChildAt(i)->setVisible(false);
} }
mShowOwned = Settings::Manager::getInt("show owned", "Game");
} }
void ToolTips::setEnabled(bool enabled) void ToolTips::setEnabled(bool enabled)
@ -346,10 +349,41 @@ namespace MWGui
return tooltipSize; return tooltipSize;
} }
bool ToolTips::checkOwned()
{
if(!mFocusObject.isEmpty())
{
const MWWorld::CellRef& cellref = mFocusObject.getCellRef();
MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWWorld::Ptr victim;
MWBase::MechanicsManager* mm = MWBase::Environment::get().getMechanicsManager();
bool allowed = mm->isAllowedToUse(ptr, cellref, victim);
return !allowed;
}
else
{
return false;
}
}
MyGUI::IntSize ToolTips::createToolTip(const MWGui::ToolTipInfo& info) MyGUI::IntSize ToolTips::createToolTip(const MWGui::ToolTipInfo& info)
{ {
mDynamicToolTipBox->setVisible(true); mDynamicToolTipBox->setVisible(true);
if(mShowOwned == 1 || mShowOwned == 3)
{
if(checkOwned())
{
mDynamicToolTipBox->changeWidgetSkin("HUD_Box_NoTransp_Owned");
}
else
{
mDynamicToolTipBox->changeWidgetSkin("HUD_Box_NoTransp");
}
}
std::string caption = info.caption; std::string caption = info.caption;
std::string image = info.icon; std::string image = info.icon;

View file

@ -87,7 +87,10 @@ namespace MWGui
static void createRaceToolTip(MyGUI::Widget* widget, const ESM::Race* playerRace); static void createRaceToolTip(MyGUI::Widget* widget, const ESM::Race* playerRace);
static void createClassToolTip(MyGUI::Widget* widget, const ESM::Class& playerClass); static void createClassToolTip(MyGUI::Widget* widget, const ESM::Class& playerClass);
static void createMagicEffectToolTip(MyGUI::Widget* widget, short id); static void createMagicEffectToolTip(MyGUI::Widget* widget, short id);
bool checkOwned();
/// Returns True if taking mFocusObject would be crime
private: private:
MyGUI::Widget* mDynamicToolTipBox; MyGUI::Widget* mDynamicToolTipBox;
@ -119,6 +122,8 @@ namespace MWGui
bool mEnabled; bool mEnabled;
bool mFullHelp; bool mFullHelp;
int mShowOwned;
}; };
} }
#endif #endif

View file

@ -15,7 +15,6 @@
#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/dialoguemanager.hpp" #include "../mwbase/dialoguemanager.hpp"
#include "../mwworld/manualref.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/containerstore.hpp" #include "../mwworld/containerstore.hpp"
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"

View file

@ -539,6 +539,9 @@ namespace MWGui
, mRepeatStepTime(0.1f) , mRepeatStepTime(0.1f)
, mIsIncreasing(true) , mIsIncreasing(true)
{ {
#if MYGUI_VERSION >= MYGUI_DEFINE_VERSION(3,2,2)
ScrollBar::setRepeatEnabled(false);
#endif
} }
MWScrollBar::~MWScrollBar() MWScrollBar::~MWScrollBar()

View file

@ -21,16 +21,16 @@ namespace MWGui
// Events // Events
typedef MyGUI::delegates::CMultiDelegate1<WindowBase*> EventHandle_WindowBase; typedef MyGUI::delegates::CMultiDelegate1<WindowBase*> EventHandle_WindowBase;
///Unhides the window /// Notify that window has been made visible
virtual void open() {} virtual void open() {}
///Hides the window /// Notify that window has been hidden
virtual void close () {} virtual void close () {}
///Gracefully exits the window /// Gracefully exits the window
virtual void exit() {} virtual void exit() {}
///Sets the visibility of the window /// Sets the visibility of the window
virtual void setVisible(bool visible); virtual void setVisible(bool visible);
///Returns the visibility state of the window /// Returns the visibility state of the window
virtual bool isVisible(); bool isVisible();
void center(); void center();
}; };

View file

@ -22,6 +22,9 @@
#include <components/sdlutil/sdlcursormanager.hpp> #include <components/sdlutil/sdlcursormanager.hpp>
#include <components/esm/esmreader.hpp>
#include <components/esm/esmwriter.hpp>
#include <components/fontloader/fontloader.hpp> #include <components/fontloader/fontloader.hpp>
#include <components/resource/resourcesystem.hpp> #include <components/resource/resourcesystem.hpp>
@ -42,6 +45,7 @@
#include "../mwbase/inputmanager.hpp" #include "../mwbase/inputmanager.hpp"
#include "../mwbase/statemanager.hpp" #include "../mwbase/statemanager.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwrender/vismask.hpp" #include "../mwrender/vismask.hpp"
@ -55,8 +59,6 @@
#include "../mwrender/localmap.hpp" #include "../mwrender/localmap.hpp"
#include "../mwsound/soundmanagerimp.hpp"
#include "console.hpp" #include "console.hpp"
#include "journalwindow.hpp" #include "journalwindow.hpp"
#include "journalviewmodel.hpp" #include "journalviewmodel.hpp"
@ -110,7 +112,7 @@ namespace MWGui
WindowManager::WindowManager( WindowManager::WindowManager(
osgViewer::Viewer* viewer, osg::Group* guiRoot, Resource::ResourceSystem* resourceSystem osgViewer::Viewer* viewer, osg::Group* guiRoot, Resource::ResourceSystem* resourceSystem
, const std::string& logpath, const std::string& resourcePath, bool consoleOnlyScripts, , const std::string& logpath, const std::string& resourcePath, bool consoleOnlyScripts,
Translation::Storage& translationDataStorage, ToUTF8::FromType encoding, bool exportFonts, const std::map<std::string, std::string>& fallbackMap) Translation::Storage& translationDataStorage, ToUTF8::FromType encoding, bool exportFonts, const std::map<std::string, std::string>& fallbackMap, const std::string& versionDescription)
: mResourceSystem(resourceSystem) : mResourceSystem(resourceSystem)
, mViewer(viewer) , mViewer(viewer)
, mConsoleOnlyScripts(consoleOnlyScripts) , mConsoleOnlyScripts(consoleOnlyScripts)
@ -184,6 +186,8 @@ namespace MWGui
, mRestAllowed(true) , mRestAllowed(true)
, mFPS(0.0f) , mFPS(0.0f)
, mFallbackMap(fallbackMap) , mFallbackMap(fallbackMap)
, mShowOwned(0)
, mVersionDescription(versionDescription)
{ {
float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); float uiScale = Settings::Manager::getFloat("scaling factor", "GUI");
mGuiPlatform = new osgMyGUI::Platform(viewer, guiRoot, resourceSystem->getTextureManager(), uiScale); mGuiPlatform = new osgMyGUI::Platform(viewer, guiRoot, resourceSystem->getTextureManager(), uiScale);
@ -232,8 +236,9 @@ namespace MWGui
MyGUI::InputManager::getInstance().eventChangeKeyFocus += MyGUI::newDelegate(this, &WindowManager::onKeyFocusChanged); MyGUI::InputManager::getInstance().eventChangeKeyFocus += MyGUI::newDelegate(this, &WindowManager::onKeyFocusChanged);
// Create all cursors in advance
createCursors();
onCursorChange(MyGUI::PointerManager::getInstance().getDefaultPointer()); onCursorChange(MyGUI::PointerManager::getInstance().getDefaultPointer());
mCursorManager->setEnabled(true); mCursorManager->setEnabled(true);
// hide mygui's pointer // hide mygui's pointer
@ -257,6 +262,8 @@ namespace MWGui
MyGUI::ClipboardManager::getInstance().eventClipboardChanged += MyGUI::newDelegate(this, &WindowManager::onClipboardChanged); MyGUI::ClipboardManager::getInstance().eventClipboardChanged += MyGUI::newDelegate(this, &WindowManager::onClipboardChanged);
MyGUI::ClipboardManager::getInstance().eventClipboardRequested += MyGUI::newDelegate(this, &WindowManager::onClipboardRequested); MyGUI::ClipboardManager::getInstance().eventClipboardRequested += MyGUI::newDelegate(this, &WindowManager::onClipboardRequested);
mShowOwned = Settings::Manager::getInt("show owned", "Game");
} }
void WindowManager::initUI() void WindowManager::initUI()
@ -268,7 +275,7 @@ namespace MWGui
mDragAndDrop = new DragAndDrop(); mDragAndDrop = new DragAndDrop();
mRecharge = new Recharge(); mRecharge = new Recharge();
mMenu = new MainMenu(w, h, mResourceSystem->getVFS()); mMenu = new MainMenu(w, h, mResourceSystem->getVFS(), mVersionDescription);
mLocalMapRender = new MWRender::LocalMap(mViewer); mLocalMapRender = new MWRender::LocalMap(mViewer);
mMap = new MapWindow(mCustomMarkers, mDragAndDrop, mLocalMapRender); mMap = new MapWindow(mCustomMarkers, mDragAndDrop, mLocalMapRender);
trackWindow(mMap, "map"); trackWindow(mMap, "map");
@ -893,6 +900,9 @@ namespace MWGui
void WindowManager::updateMap() void WindowManager::updateMap()
{ {
if (!mLocalMapRender)
return;
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
osg::Vec3f playerPosition = player.getRefData().getPosition().asVec3(); osg::Vec3f playerPosition = player.getRefData().getPosition().asVec3();
@ -1031,6 +1041,12 @@ namespace MWGui
void WindowManager::setFocusObject(const MWWorld::Ptr& focus) void WindowManager::setFocusObject(const MWWorld::Ptr& focus)
{ {
mToolTips->setFocusObject(focus); mToolTips->setFocusObject(focus);
if(mHud && (mShowOwned == 2 || mShowOwned == 3))
{
bool owned = mToolTips->checkOwned();
mHud->setCrosshairOwned(owned);
}
} }
void WindowManager::setFocusObjectScreenCoords(float min_x, float min_y, float max_x, float max_y) void WindowManager::setFocusObjectScreenCoords(float min_x, float min_y, float max_x, float max_y)
@ -1078,11 +1094,22 @@ namespace MWGui
void WindowManager::onRetrieveTag(const MyGUI::UString& _tag, MyGUI::UString& _result) void WindowManager::onRetrieveTag(const MyGUI::UString& _tag, MyGUI::UString& _result)
{ {
std::string tag(_tag); std::string tag(_tag);
std::string MyGuiPrefix = "setting=";
size_t MyGuiPrefixLength = MyGuiPrefix.length();
std::string tokenToFind = "sCell="; std::string tokenToFind = "sCell=";
size_t tokenLength = tokenToFind.length(); size_t tokenLength = tokenToFind.length();
if (tag.compare(0, tokenLength, tokenToFind) == 0) if(tag.compare(0, MyGuiPrefixLength, MyGuiPrefix) == 0)
{
tag = tag.substr(MyGuiPrefixLength, tag.length());
std::string settingSection = tag.substr(0, tag.find(","));
std::string settingTag = tag.substr(tag.find(",")+1, tag.length());
_result = Settings::Manager::getString(settingTag, settingSection);
}
else if (tag.compare(0, tokenLength, tokenToFind) == 0)
{ {
_result = mTranslationDataStorage.translateCellName(tag.substr(tokenLength)); _result = mTranslationDataStorage.translateCellName(tag.substr(tokenLength));
} }
@ -1175,31 +1202,7 @@ namespace MWGui
void WindowManager::onCursorChange(const std::string &name) void WindowManager::onCursorChange(const std::string &name)
{ {
if(!mCursorManager->cursorChanged(name)) mCursorManager->cursorChanged(name);
return; //the cursor manager doesn't want any more info about this cursor
//See if we can get the information we need out of the cursor resource
ResourceImageSetPointerFix* imgSetPtr = dynamic_cast<ResourceImageSetPointerFix*>(MyGUI::PointerManager::getInstance().getByName(name));
if(imgSetPtr != NULL)
{
MyGUI::ResourceImageSet* imgSet = imgSetPtr->getImageSet();
std::string tex_name = imgSet->getIndexInfo(0,0).texture;
osg::ref_ptr<osg::Texture2D> tex = mResourceSystem->getTextureManager()->getTexture2D(tex_name, osg::Texture::CLAMP, osg::Texture::CLAMP);
tex->setUnRefImageDataAfterApply(false); // FIXME?
//everything looks good, send it to the cursor manager
if(tex.valid())
{
Uint8 size_x = imgSetPtr->getSize().width;
Uint8 size_y = imgSetPtr->getSize().height;
Uint8 hotspot_x = imgSetPtr->getHotSpot().left;
Uint8 hotspot_y = imgSetPtr->getHotSpot().top;
int rotation = imgSetPtr->getRotation();
mCursorManager->receiveCursorInfo(name, rotation, tex->getImage(), size_x, size_y, hotspot_x, hotspot_y);
}
}
} }
void WindowManager::popGuiMode() void WindowManager::popGuiMode()
@ -1666,10 +1669,10 @@ namespace MWGui
writer.endRecord(ESM::REC_ASPL); writer.endRecord(ESM::REC_ASPL);
} }
for (std::vector<ESM::CustomMarker>::const_iterator it = mCustomMarkers.begin(); it != mCustomMarkers.end(); ++it) for (CustomMarkerCollection::ContainerType::const_iterator it = mCustomMarkers.begin(); it != mCustomMarkers.end(); ++it)
{ {
writer.startRecord(ESM::REC_MARK); writer.startRecord(ESM::REC_MARK);
(*it).save(writer); it->second.save(writer);
writer.endRecord(ESM::REC_MARK); writer.endRecord(ESM::REC_MARK);
} }
} }
@ -1957,6 +1960,33 @@ namespace MWGui
return Misc::ResourceHelpers::correctTexturePath(path, mResourceSystem->getVFS()); return Misc::ResourceHelpers::correctTexturePath(path, mResourceSystem->getVFS());
} }
void WindowManager::createCursors()
{
MyGUI::ResourceManager::EnumeratorPtr enumerator = MyGUI::ResourceManager::getInstance().getEnumerator();
while (enumerator.next())
{
MyGUI::IResource* resource = enumerator.current().second;
ResourceImageSetPointerFix* imgSetPointer = dynamic_cast<ResourceImageSetPointerFix*>(resource);
if (!imgSetPointer)
continue;
std::string tex_name = imgSetPointer->getImageSet()->getIndexInfo(0,0).texture;
osg::ref_ptr<osg::Texture2D> tex = mResourceSystem->getTextureManager()->getTexture2D(tex_name, osg::Texture::CLAMP, osg::Texture::CLAMP);
if(tex.valid())
{
//everything looks good, send it to the cursor manager
Uint8 size_x = imgSetPointer->getSize().width;
Uint8 size_y = imgSetPointer->getSize().height;
Uint8 hotspot_x = imgSetPointer->getHotSpot().left;
Uint8 hotspot_y = imgSetPointer->getHotSpot().top;
int rotation = imgSetPointer->getRotation();
mCursorManager->createCursor(imgSetPointer->getResourceName(), rotation, tex->getImage(), size_x, size_y, hotspot_x, hotspot_y);
}
}
}
void WindowManager::createTextures() void WindowManager::createTextures()
{ {
{ {

View file

@ -116,7 +116,7 @@ namespace MWGui
WindowManager(osgViewer::Viewer* viewer, osg::Group* guiRoot, Resource::ResourceSystem* resourceSystem, WindowManager(osgViewer::Viewer* viewer, osg::Group* guiRoot, Resource::ResourceSystem* resourceSystem,
const std::string& logpath, const std::string& cacheDir, bool consoleOnlyScripts, const std::string& logpath, const std::string& cacheDir, bool consoleOnlyScripts,
Translation::Storage& translationDataStorage, ToUTF8::FromType encoding, bool exportFonts, const std::map<std::string,std::string>& fallbackMap); Translation::Storage& translationDataStorage, ToUTF8::FromType encoding, bool exportFonts, const std::map<std::string,std::string>& fallbackMap, const std::string& versionDescription);
virtual ~WindowManager(); virtual ~WindowManager();
void initUI(); void initUI();
@ -486,11 +486,16 @@ namespace MWGui
float mFPS; float mFPS;
std::map<std::string, std::string> mFallbackMap; std::map<std::string, std::string> mFallbackMap;
int mShowOwned;
std::string mVersionDescription;
/** /**
* Called when MyGUI tries to retrieve a tag's value. Tags must be denoted in #{tag} notation and will be replaced upon setting a user visible text/property. * Called when MyGUI tries to retrieve a tag's value. Tags must be denoted in #{tag} notation and will be replaced upon setting a user visible text/property.
* Supported syntax: * Supported syntax:
* #{GMSTName}: retrieves String value of the GMST called GMSTName * #{GMSTName}: retrieves String value of the GMST called GMSTName
* #{setting=CATEGORY_NAME,SETTING_NAME}: retrieves String value of SETTING_NAME under category CATEGORY_NAME from settings.cfg
* #{sCell=CellID}: retrieves translated name of the given CellID (used only by some Morrowind localisations, in others cell ID is == cell name) * #{sCell=CellID}: retrieves translated name of the given CellID (used only by some Morrowind localisations, in others cell ID is == cell name)
* #{fontcolour=FontColourName}: retrieves the value of the fallback setting "FontColor_color_<FontColourName>" from openmw.cfg, * #{fontcolour=FontColourName}: retrieves the value of the fallback setting "FontColor_color_<FontColourName>" from openmw.cfg,
* in the format "r g b a", float values in range 0-1. Useful for "Colour" and "TextColour" properties in skins. * in the format "r g b a", float values in range 0-1. Useful for "Colour" and "TextColour" properties in skins.
@ -511,6 +516,7 @@ namespace MWGui
void onClipboardRequested(const std::string& _type, std::string& _data); void onClipboardRequested(const std::string& _type, std::string& _data);
void createTextures(); void createTextures();
void createCursors();
void setMenuTransparency(float value); void setMenuTransparency(float value);
}; };
} }

View file

@ -4,12 +4,13 @@
#include <osg/PositionAttitudeTransform> #include <osg/PositionAttitudeTransform>
#include <components/esm/esmreader.hpp>
#include <components/esm/esmwriter.hpp>
#include <components/esm/loadnpc.hpp> #include <components/esm/loadnpc.hpp>
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/inventorystore.hpp" #include "../mwworld/inventorystore.hpp"
#include "../mwworld/manualref.hpp"
#include "../mwworld/actionequip.hpp" #include "../mwworld/actionequip.hpp"
#include "../mwworld/player.hpp" #include "../mwworld/player.hpp"
@ -19,7 +20,7 @@
#include "../mwbase/soundmanager.hpp" #include "../mwbase/soundmanager.hpp"
#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/mechanicsmanager.hpp"
#include "../mwrender/animation.hpp" #include "../mwmechanics/spellcasting.hpp"
#include "npcstats.hpp" #include "npcstats.hpp"
#include "creaturestats.hpp" #include "creaturestats.hpp"
@ -66,44 +67,6 @@ void adjustBoundItem (const std::string& item, bool bound, const MWWorld::Ptr& a
} }
} }
bool disintegrateSlot (MWWorld::Ptr ptr, int slot, float disintegrate)
{
if (ptr.getClass().hasInventoryStore(ptr))
{
MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr);
MWWorld::ContainerStoreIterator item =
inv.getSlot(slot);
if (item != inv.end())
{
if (!item->getClass().hasItemHealth(*item))
return false;
int charge = item->getClass().getItemHealth(*item);
if (charge == 0)
return false;
// FIXME: charge should be a float, not int so that damage < 1 per frame can be applied.
// This was also a bug in the original engine.
charge -=
std::min(static_cast<int>(disintegrate),
charge);
item->getCellRef().setCharge(charge);
if (charge == 0)
{
// Will unequip the broken item and try to find a replacement
if (ptr != MWBase::Environment::get().getWorld()->getPlayerPtr())
inv.autoEquip(ptr);
else
inv.unequipItem(*item, ptr);
}
return true;
}
}
return false;
}
class CheckActorCommanded : public MWMechanics::EffectSourceVisitor class CheckActorCommanded : public MWMechanics::EffectSourceVisitor
{ {
MWWorld::Ptr mActor; MWWorld::Ptr mActor;
@ -515,8 +478,12 @@ namespace MWMechanics
bool wasDead = creatureStats.isDead(); bool wasDead = creatureStats.isDead();
// FIXME: effect ticks should go into separate functions so they can be used with either // tickable effects (i.e. effects having a lasting impact after expiry)
// magnitude (instant effect) or magnitude*duration // these effects can be applied as "instant" (handled in spellcasting.cpp) or with a duration, handled here
for (MagicEffects::Collection::const_iterator it = effects.begin(); it != effects.end(); ++it)
{
effectTick(creatureStats, ptr, it->first, it->second.getMagnitude() * duration);
}
// attributes // attributes
for(int i = 0;i < ESM::Attribute::Length;++i) for(int i = 0;i < ESM::Attribute::Length;++i)
@ -526,9 +493,6 @@ namespace MWMechanics
effects.get(EffectKey(ESM::MagicEffect::DrainAttribute, i)).getMagnitude() - effects.get(EffectKey(ESM::MagicEffect::DrainAttribute, i)).getMagnitude() -
effects.get(EffectKey(ESM::MagicEffect::AbsorbAttribute, i)).getMagnitude())); effects.get(EffectKey(ESM::MagicEffect::AbsorbAttribute, i)).getMagnitude()));
stat.damage(effects.get(EffectKey(ESM::MagicEffect::DamageAttribute, i)).getMagnitude() * duration);
stat.restore(effects.get(EffectKey(ESM::MagicEffect::RestoreAttribute, i)).getMagnitude() * duration);
creatureStats.setAttribute(i, stat); creatureStats.setAttribute(i, stat);
} }
@ -558,12 +522,6 @@ namespace MWMechanics
// Fatigue can be decreased below zero meaning the actor will be knocked out // Fatigue can be decreased below zero meaning the actor will be knocked out
i == 2); i == 2);
float currentDiff = creatureStats.getMagicEffects().get(ESM::MagicEffect::RestoreHealth+i).getMagnitude()
- creatureStats.getMagicEffects().get(ESM::MagicEffect::DamageHealth+i).getMagnitude()
- creatureStats.getMagicEffects().get(ESM::MagicEffect::AbsorbHealth+i).getMagnitude();
stat.setCurrent(stat.getCurrent() + currentDiff * duration, i == 2);
creatureStats.setDynamic(i, stat); creatureStats.setDynamic(i, stat);
} }
@ -591,90 +549,11 @@ namespace MWMechanics
creatureStats.setAiSetting(CreatureStats::AI_Flee, stat); creatureStats.setAiSetting(CreatureStats::AI_Flee, stat);
} }
// Apply disintegration (reduces item health)
float disintegrateWeapon = effects.get(ESM::MagicEffect::DisintegrateWeapon).getMagnitude();
if (disintegrateWeapon > 0)
disintegrateSlot(ptr, MWWorld::InventoryStore::Slot_CarriedRight, disintegrateWeapon*duration);
float disintegrateArmor = effects.get(ESM::MagicEffect::DisintegrateArmor).getMagnitude();
if (disintegrateArmor > 0)
{
// According to UESP
int priorities[] = {
MWWorld::InventoryStore::Slot_CarriedLeft,
MWWorld::InventoryStore::Slot_Cuirass,
MWWorld::InventoryStore::Slot_LeftPauldron,
MWWorld::InventoryStore::Slot_RightPauldron,
MWWorld::InventoryStore::Slot_LeftGauntlet,
MWWorld::InventoryStore::Slot_RightGauntlet,
MWWorld::InventoryStore::Slot_Helmet,
MWWorld::InventoryStore::Slot_Greaves,
MWWorld::InventoryStore::Slot_Boots
};
for (unsigned int i=0; i<sizeof(priorities)/sizeof(int); ++i)
{
if (disintegrateSlot(ptr, priorities[i], disintegrateArmor*duration))
break;
}
}
bool receivedMagicDamage = false;
if (creatureStats.getMagicEffects().get(ESM::MagicEffect::DamageHealth).getMagnitude() > 0.0f
|| creatureStats.getMagicEffects().get(ESM::MagicEffect::AbsorbHealth).getMagnitude() > 0.0f)
receivedMagicDamage = true;
// Apply damage ticks
int damageEffects[] = {
ESM::MagicEffect::FireDamage, ESM::MagicEffect::ShockDamage, ESM::MagicEffect::FrostDamage, ESM::MagicEffect::Poison,
ESM::MagicEffect::SunDamage
};
DynamicStat<float> health = creatureStats.getHealth();
for (unsigned int i=0; i<sizeof(damageEffects)/sizeof(int); ++i)
{
float magnitude = creatureStats.getMagicEffects().get(damageEffects[i]).getMagnitude();
if (damageEffects[i] == ESM::MagicEffect::SunDamage)
{
// isInCell shouldn't be needed, but updateActor called during game start
if (!ptr.isInCell() || !ptr.getCell()->isExterior())
continue;
float time = MWBase::Environment::get().getWorld()->getTimeStamp().getHour();
float timeDiff = std::min(7.f, std::max(0.f, std::abs(time - 13)));
float damageScale = 1.f - timeDiff / 7.f;
// When cloudy, the sun damage effect is halved
static float fMagicSunBlockedMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
"fMagicSunBlockedMult")->getFloat();
int weather = MWBase::Environment::get().getWorld()->getCurrentWeather();
if (weather > 1)
damageScale *= fMagicSunBlockedMult;
health.setCurrent(health.getCurrent() - magnitude * duration * damageScale);
if (magnitude * damageScale > 0.0f)
receivedMagicDamage = true;
}
else
{
health.setCurrent(health.getCurrent() - magnitude * duration);
if (magnitude > 0.0f)
receivedMagicDamage = true;
}
}
if (receivedMagicDamage && ptr == MWBase::Environment::get().getWorld()->getPlayerPtr())
MWBase::Environment::get().getWindowManager()->activateHitOverlay(false);
creatureStats.setHealth(health);
if (!wasDead && creatureStats.isDead()) if (!wasDead && creatureStats.isDead())
{ {
// 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; bool killedByPlayer = false;
bool murderedByPlayer = false;
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
for (ActiveSpells::TIterator it = spells.begin(); it != spells.end(); ++it) for (ActiveSpells::TIterator it = spells.begin(); it != spells.end(); ++it)
{ {
@ -684,33 +563,29 @@ namespace MWMechanics
{ {
int effectId = effectIt->mEffectId; int effectId = effectIt->mEffectId;
bool isDamageEffect = false; bool isDamageEffect = false;
int damageEffects[] = {
ESM::MagicEffect::FireDamage, ESM::MagicEffect::ShockDamage, ESM::MagicEffect::FrostDamage, ESM::MagicEffect::Poison,
ESM::MagicEffect::SunDamage, ESM::MagicEffect::DamageHealth, ESM::MagicEffect::AbsorbHealth
};
for (unsigned int i=0; i<sizeof(damageEffects)/sizeof(int); ++i) for (unsigned int i=0; i<sizeof(damageEffects)/sizeof(int); ++i)
{ {
if (damageEffects[i] == effectId) if (damageEffects[i] == effectId)
isDamageEffect = true; isDamageEffect = true;
} }
if (effectId == ESM::MagicEffect::DamageHealth || effectId == ESM::MagicEffect::AbsorbHealth)
isDamageEffect = true;
MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spell.mCasterActorId); MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spell.mCasterActorId);
if (isDamageEffect && caster == player) if (isDamageEffect && caster == player)
{
killedByPlayer = true; killedByPlayer = true;
// 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,
// for bystanders it is not possible to tell who attacked first, anyway.
if (ptr.getClass().isNpc() && ptr.getClass().getNpcStats(ptr).getCrimeId() != -1
&& ptr != player)
murderedByPlayer = true;
}
} }
} }
if (murderedByPlayer) if (killedByPlayer)
MWBase::Environment::get().getMechanicsManager()->commitCrime(player, ptr, MWBase::MechanicsManager::OT_Murder); {
if (killedByPlayer && player.getClass().getNpcStats(player).isWerewolf()) MWBase::Environment::get().getMechanicsManager()->actorKilled(ptr, player);
player.getClass().getNpcStats(player).addWerewolfKill(); 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?
@ -794,9 +669,6 @@ namespace MWMechanics
skill.setModifier(static_cast<int>(effects.get(EffectKey(ESM::MagicEffect::FortifySkill, i)).getMagnitude() - skill.setModifier(static_cast<int>(effects.get(EffectKey(ESM::MagicEffect::FortifySkill, i)).getMagnitude() -
effects.get(EffectKey(ESM::MagicEffect::DrainSkill, i)).getMagnitude() - effects.get(EffectKey(ESM::MagicEffect::DrainSkill, i)).getMagnitude() -
effects.get(EffectKey(ESM::MagicEffect::AbsorbSkill, i)).getMagnitude())); effects.get(EffectKey(ESM::MagicEffect::AbsorbSkill, i)).getMagnitude()));
skill.damage(effects.get(EffectKey(ESM::MagicEffect::DamageSkill, i)).getMagnitude() * duration);
skill.restore(effects.get(EffectKey(ESM::MagicEffect::RestoreSkill, i)).getMagnitude() * duration);
} }
} }
@ -1197,18 +1069,18 @@ namespace MWMechanics
killDeadActors(); killDeadActors();
// check if we still have any player enemies to switch music // check if we still have any player enemies to switch music
static bool isBattleMusic = false; static int currentMusic = 0;
if (isBattleMusic && hostilesCount == 0 && !(player.getClass().getCreatureStats(player).isDead() && if (currentMusic != 1 && hostilesCount == 0 && !(player.getClass().getCreatureStats(player).isDead() &&
MWBase::Environment::get().getSoundManager()->isMusicPlaying())) MWBase::Environment::get().getSoundManager()->isMusicPlaying()))
{ {
MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore")); MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore"));
isBattleMusic = false; currentMusic = 1;
} }
else if (!isBattleMusic && hostilesCount > 0) else if (currentMusic != 2 && hostilesCount > 0)
{ {
MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Battle")); MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Battle"));
isBattleMusic = true; currentMusic = 2;
} }
static float sneakTimer = 0.f; // times update of sneak icon static float sneakTimer = 0.f; // times update of sneak icon

View file

@ -36,13 +36,13 @@ namespace
float getZAngleToDir(const osg::Vec3f& dir) float getZAngleToDir(const osg::Vec3f& dir)
{ {
return osg::RadiansToDegrees(std::atan2(dir.x(), dir.y())); return std::atan2(dir.x(), dir.y());
} }
float getXAngleToDir(const osg::Vec3f& dir, float dirLen = 0.0f) float getXAngleToDir(const osg::Vec3f& dir, float dirLen = 0.0f)
{ {
float len = (dirLen > 0.0f)? dirLen : dir.length(); float len = (dirLen > 0.0f)? dirLen : dir.length();
return osg::RadiansToDegrees(-std::asin(dir.z() / len)); return -std::asin(dir.z() / len);
} }
@ -221,12 +221,12 @@ namespace MWMechanics
if(movement.mRotation[2] != 0) if(movement.mRotation[2] != 0)
{ {
if(zTurn(actor, osg::DegreesToRadians(movement.mRotation[2]))) movement.mRotation[2] = 0; if(zTurn(actor, movement.mRotation[2])) movement.mRotation[2] = 0;
} }
if(movement.mRotation[0] != 0) if(movement.mRotation[0] != 0)
{ {
if(smoothTurn(actor, osg::DegreesToRadians(movement.mRotation[0]), 0)) movement.mRotation[0] = 0; if(smoothTurn(actor, movement.mRotation[0], 0)) movement.mRotation[0] = 0;
} }
bool& attack = storage.mAttack; bool& attack = storage.mAttack;

View file

@ -104,7 +104,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Po
actor.getClass().getMovementSettings(actor).mPosition[0] = 1; actor.getClass().getMovementSettings(actor).mPosition[0] = 1;
actor.getClass().getMovementSettings(actor).mPosition[1] = 1; actor.getClass().getMovementSettings(actor).mPosition[1] = 1;
// change the angle a bit, too // change the angle a bit, too
zTurn(actor, osg::DegreesToRadians(mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1]))); zTurn(actor, mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1]));
} }
} }
else { //Not stuck, so reset things else { //Not stuck, so reset things
@ -117,7 +117,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Po
actor.getClass().getMovementSettings(actor).mPosition[1] = 1; //Just run forward the rest of the time actor.getClass().getMovementSettings(actor).mPosition[1] = 1; //Just run forward the rest of the time
} }
zTurn(actor, osg::DegreesToRadians(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); zTurn(actor, mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]));
return false; return false;
} }

View file

@ -184,6 +184,11 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac
const ESM::Position &targetPos = target.getRefData().getPosition(); const ESM::Position &targetPos = target.getRefData().getPosition();
float distTo = (targetPos.asVec3() - vActorPos).length(); float distTo = (targetPos.asVec3() - vActorPos).length();
// Small threshold for changing target
if (it == mPackages.begin())
distTo = std::max(0.f, distTo - 50.f);
if (distTo < nearestDist) if (distTo < nearestDist)
{ {
nearestDist = distTo; nearestDist = distTo;

View file

@ -1,5 +1,7 @@
#include "aiwander.hpp" #include "aiwander.hpp"
#include <cfloat>
#include <components/misc/rng.hpp> #include <components/misc/rng.hpp>
#include <components/esm/aisequence.hpp> #include <components/esm/aisequence.hpp>
@ -28,6 +30,12 @@ namespace MWMechanics
static const int GREETING_SHOULD_START = 4; //how many reaction intervals should pass before NPC can greet player static const int GREETING_SHOULD_START = 4; //how many reaction intervals should pass before NPC can greet player
static const int GREETING_SHOULD_END = 10; static const int GREETING_SHOULD_END = 10;
// to prevent overcrowding
static const int DESTINATION_TOLERANCE = 64;
// distance must be long enough that NPC will need to move to get there.
static const int MINIMUM_WANDER_DISTANCE = DESTINATION_TOLERANCE * 2;
const std::string AiWander::sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1] = const std::string AiWander::sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1] =
{ {
std::string("idle2"), std::string("idle2"),
@ -43,10 +51,10 @@ namespace MWMechanics
/// \brief This class holds the variables AiWander needs which are deleted if the package becomes inactive. /// \brief This class holds the variables AiWander needs which are deleted if the package becomes inactive.
struct AiWanderStorage : AiTemporaryBase struct AiWanderStorage : AiTemporaryBase
{ {
// the z rotation angle (degrees) we want to reach // the z rotation angle to reach
// used every frame when mRotate is true // when mTurnActorGivingGreetingToFacePlayer is true
float mTargetAngleRadians; float mTargetAngleRadians;
bool mRotate; bool mTurnActorGivingGreetingToFacePlayer;
float mReaction; // update some actions infrequently float mReaction; // update some actions infrequently
@ -56,27 +64,21 @@ namespace MWMechanics
const MWWorld::CellStore* mCell; // for detecting cell change const MWWorld::CellStore* mCell; // for detecting cell change
// AiWander states // AiWander states
bool mChooseAction; AiWander::WanderState mState;
bool mIdleNow;
bool mMoveNow;
bool mWalking;
unsigned short mPlayedIdle; unsigned short mIdleAnimation;
PathFinder mPathFinder; PathFinder mPathFinder;
AiWanderStorage(): AiWanderStorage():
mTargetAngleRadians(0), mTargetAngleRadians(0),
mRotate(false), mTurnActorGivingGreetingToFacePlayer(false),
mReaction(0), mReaction(0),
mSaidGreeting(AiWander::Greet_None), mSaidGreeting(AiWander::Greet_None),
mGreetingTimer(0), mGreetingTimer(0),
mCell(NULL), mCell(NULL),
mChooseAction(true), mState(AiWander::Wander_ChooseAction),
mIdleNow(false), mIdleAnimation(0)
mMoveNow(false),
mWalking(false),
mPlayedIdle(0)
{}; {};
}; };
@ -109,7 +111,7 @@ namespace MWMechanics
mStartTime = MWBase::Environment::get().getWorld()->getTimeStamp(); mStartTime = MWBase::Environment::get().getWorld()->getTimeStamp();
mStoredAvailableNodes = false; mPopulateAvailableNodes = true;
} }
@ -183,7 +185,7 @@ namespace MWMechanics
if(!currentCell || cellChange) if(!currentCell || cellChange)
{ {
currentCell = actor.getCell(); currentCell = actor.getCell();
mStoredAvailableNodes = false; // prob. not needed since mDistance = 0 mPopulateAvailableNodes = true;
} }
cStats.setDrawState(DrawState_Nothing); cStats.setDrawState(DrawState_Nothing);
@ -192,156 +194,81 @@ namespace MWMechanics
ESM::Position pos = actor.getRefData().getPosition(); ESM::Position pos = actor.getRefData().getPosition();
bool& idleNow = storage.mIdleNow; WanderState& wanderState = storage.mState;
bool& moveNow = storage.mMoveNow;
bool& walking = storage.mWalking;
// Check if an idle actor is too close to a door - if so start walking // Check if an idle actor is too close to a door - if so start walking
mDoorCheckDuration += duration; mDoorCheckDuration += duration;
if(mDoorCheckDuration >= DOOR_CHECK_INTERVAL) if(mDoorCheckDuration >= DOOR_CHECK_INTERVAL)
{ {
mDoorCheckDuration = 0; // restart timer mDoorCheckDuration = 0; // restart timer
if(mDistance && // actor is not intended to be stationary if(mDistance && // actor is not intended to be stationary
idleNow && // but is in idle (wanderState == Wander_IdleNow) && // but is in idle
!walking && // FIXME: some actors are idle while walking
proximityToDoor(actor, MIN_DIST_TO_DOOR_SQUARED*1.6f*1.6f)) // NOTE: checks interior cells only proximityToDoor(actor, MIN_DIST_TO_DOOR_SQUARED*1.6f*1.6f)) // NOTE: checks interior cells only
{ {
idleNow = false; wanderState = Wander_MoveNow;
moveNow = true;
mTrimCurrentNode = false; // just in case mTrimCurrentNode = false; // just in case
} }
} }
// Are we there yet? // Are we there yet?
bool& chooseAction = storage.mChooseAction; if ((wanderState == Wander_Walking) &&
if(walking && storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DESTINATION_TOLERANCE))
storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], 64.f))
{ {
stopWalking(actor, storage); stopWalking(actor, storage);
moveNow = false; wanderState = Wander_ChooseAction;
walking = false;
chooseAction = true;
mHasReturnPosition = false; mHasReturnPosition = false;
} }
if(walking) // have not yet reached the destination if (wanderState == Wander_Walking) // have not yet reached the destination
{ {
// turn towards the next point in mPath // turn towards the next point in mPath
zTurn(actor, osg::DegreesToRadians(storage.mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); zTurn(actor, storage.mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]));
actor.getClass().getMovementSettings(actor).mPosition[1] = 1; actor.getClass().getMovementSettings(actor).mPosition[1] = 1;
// Returns true if evasive action needs to be taken evadeObstacles(actor, storage, duration);
if(mObstacleCheck.check(actor, duration))
{
// first check if we're walking into a door
if(proximityToDoor(actor)) // NOTE: checks interior cells only
{
// remove allowed points then select another random destination
mTrimCurrentNode = true;
trimAllowedNodes(mAllowedNodes, storage.mPathFinder);
mObstacleCheck.clear();
storage.mPathFinder.clearPath();
walking = false;
moveNow = true;
}
else // probably walking into another NPC
{
// TODO: diagonal should have same animation as walk forward
// but doesn't seem to do that?
actor.getClass().getMovementSettings(actor).mPosition[0] = 1;
actor.getClass().getMovementSettings(actor).mPosition[1] = 0.1f;
// change the angle a bit, too
zTurn(actor, osg::DegreesToRadians(storage.mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1])));
}
mStuckCount++; // TODO: maybe no longer needed
}
//#if 0
// TODO: maybe no longer needed
if(mStuckCount >= COUNT_BEFORE_RESET) // something has gone wrong, reset
{
//std::cout << "Reset \""<< cls.getName(actor) << "\"" << std::endl;
mObstacleCheck.clear();
stopWalking(actor, storage);
moveNow = false;
walking = false;
chooseAction = true;
}
//#endif
} }
bool& rotate = storage.mTurnActorGivingGreetingToFacePlayer;
float& targetAngleRadians = storage.mTargetAngleRadians;
bool& rotate = storage.mRotate;
if (rotate) if (rotate)
{ {
// Reduce the turning animation glitch by using a *HUGE* value of // Reduce the turning animation glitch by using a *HUGE* value of
// epsilon... TODO: a proper fix might be in either the physics or the // epsilon... TODO: a proper fix might be in either the physics or the
// animation subsystem // animation subsystem
if (zTurn(actor, targetAngleRadians, osg::DegreesToRadians(5.f))) if (zTurn(actor, storage.mTargetAngleRadians, osg::DegreesToRadians(5.f)))
rotate = false; rotate = false;
} }
// Check if idle animation finished // Check if idle animation finished
short unsigned& playedIdle = storage.mPlayedIdle; short unsigned& idleAnimation = storage.mIdleAnimation;
GreetingState& greetingState = storage.mSaidGreeting; GreetingState& greetingState = storage.mSaidGreeting;
if(idleNow && !checkIdle(actor, playedIdle) && (greetingState == Greet_Done || greetingState == Greet_None)) if ((wanderState == Wander_IdleNow) &&
!checkIdle(actor, idleAnimation) && (greetingState == Greet_Done || greetingState == Greet_None))
{ {
playedIdle = 0; wanderState = Wander_ChooseAction;
idleNow = false;
chooseAction = true;
} }
MWBase::World *world = MWBase::Environment::get().getWorld(); MWBase::World *world = MWBase::Environment::get().getWorld();
if(chooseAction) if (wanderState == Wander_ChooseAction)
{ {
playedIdle = 0; idleAnimation = getRandomIdle();
getRandomIdle(playedIdle); // NOTE: sets mPlayedIdle with a random selection
if(!playedIdle && mDistance) if(!idleAnimation && mDistance)
{ {
chooseAction = false; wanderState = Wander_MoveNow;
moveNow = true;
} }
else else
{ {
// Play idle animation and recreate vanilla (broken?) behavior of resetting start time of AIWander: // Play idle animation and recreate vanilla (broken?) behavior of resetting start time of AIWander:
MWWorld::TimeStamp currentTime = world->getTimeStamp(); MWWorld::TimeStamp currentTime = world->getTimeStamp();
mStartTime = currentTime; mStartTime = currentTime;
playIdle(actor, playedIdle); playIdle(actor, idleAnimation);
chooseAction = false; wanderState = Wander_IdleNow;
idleNow = true;
} }
} }
// Play idle voiced dialogue entries randomly playIdleDialogueRandomly(actor);
int hello = cStats.getAiSetting(CreatureStats::AI_Hello).getModified();
if (hello > 0 && !MWBase::Environment::get().getWorld()->isSwimming(actor)
&& MWBase::Environment::get().getSoundManager()->sayDone(actor))
{
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
static float fVoiceIdleOdds = MWBase::Environment::get().getWorld()->getStore()
.get<ESM::GameSetting>().find("fVoiceIdleOdds")->getFloat();
float roll = Misc::Rng::rollProbability() * 10000.0f;
// In vanilla MW the chance was FPS dependent, and did not allow proper changing of fVoiceIdleOdds
// due to the roll being an integer.
// Our implementation does not have these issues, so needs to be recalibrated. We chose to
// use the chance MW would have when run at 60 FPS with the default value of the GMST for calibration.
float x = fVoiceIdleOdds * 0.6f * (MWBase::Environment::get().getFrameDuration() / 0.1f);
// Only say Idle voices when player is in LOS
// A bit counterintuitive, likely vanilla did this to reduce the appearance of
// voices going through walls?
if (roll < x && (player.getRefData().getPosition().asVec3() - pos.asVec3()).length2()
< 3000*3000 // maybe should be fAudioVoiceDefaultMaxDistance*fAudioMaxDistanceMult instead
&& MWBase::Environment::get().getWorld()->getLOS(player, actor))
MWBase::Environment::get().getDialogueManager()->say(actor, "idle");
}
float& lastReaction = storage.mReaction; float& lastReaction = storage.mReaction;
lastReaction += duration; lastReaction += duration;
@ -358,17 +285,8 @@ namespace MWMechanics
{ {
// End package if duration is complete or mid-night hits: // End package if duration is complete or mid-night hits:
MWWorld::TimeStamp currentTime = world->getTimeStamp(); MWWorld::TimeStamp currentTime = world->getTimeStamp();
if(currentTime.getHour() >= mStartTime.getHour() + mDuration) if((currentTime.getHour() >= mStartTime.getHour() + mDuration) ||
{ (int(currentTime.getHour()) == 0 && currentTime.getDay() != mStartTime.getDay()))
if(!mRepeat)
{
stopWalking(actor, storage);
return true;
}
else
mStartTime = currentTime;
}
else if(int(currentTime.getHour()) == 0 && currentTime.getDay() != mStartTime.getDay())
{ {
if(!mRepeat) if(!mRepeat)
{ {
@ -381,7 +299,7 @@ namespace MWMechanics
} }
// Initialization to discover & store allowed node points for this actor. // Initialization to discover & store allowed node points for this actor.
if(!mStoredAvailableNodes) if (mPopulateAvailableNodes)
{ {
getAllowedNodes(actor, currentCell->getCell()); getAllowedNodes(actor, currentCell->getCell());
} }
@ -399,9 +317,6 @@ namespace MWMechanics
mHasReturnPosition = false; mHasReturnPosition = false;
if (mDistance == 0 && mHasReturnPosition && (pos.asVec3() - mReturnPosition).length2() > 20*20) if (mDistance == 0 && mHasReturnPosition && (pos.asVec3() - mReturnPosition).length2() > 20*20)
{ {
chooseAction = false;
idleNow = false;
if (!storage.mPathFinder.isPathConstructed()) if (!storage.mPathFinder.isPathConstructed())
{ {
ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(mReturnPosition)); ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(mReturnPosition));
@ -410,139 +325,215 @@ namespace MWMechanics
ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos));
// don't take shortcuts for wandering // don't take shortcuts for wandering
storage.mPathFinder.buildPath(start, dest, actor.getCell(), false); storage.mPathFinder.buildSyncedPath(start, dest, actor.getCell(), false);
if(storage.mPathFinder.isPathConstructed()) if(storage.mPathFinder.isPathConstructed())
{ {
moveNow = false; wanderState = Wander_Walking;
walking = true;
} }
} }
} }
// Allow interrupting a walking actor to trigger a greeting // Allow interrupting a walking actor to trigger a greeting
if(idleNow || walking) if ((wanderState == Wander_IdleNow) || (wanderState == Wander_Walking))
{ {
// Play a random voice greeting if the player gets too close playGreetingIfPlayerGetsTooClose(actor, storage);
int hello = cStats.getAiSetting(CreatureStats::AI_Hello).getModified();
float helloDistance = static_cast<float>(hello);
static int iGreetDistanceMultiplier =MWBase::Environment::get().getWorld()->getStore()
.get<ESM::GameSetting>().find("iGreetDistanceMultiplier")->getInt();
helloDistance *= iGreetDistanceMultiplier;
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
osg::Vec3f playerPos(player.getRefData().getPosition().asVec3());
osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
float playerDistSqr = (playerPos - actorPos).length2();
int& greetingTimer = storage.mGreetingTimer;
if (greetingState == Greet_None)
{
if ((playerDistSqr <= helloDistance*helloDistance) &&
!player.getClass().getCreatureStats(player).isDead() && MWBase::Environment::get().getWorld()->getLOS(player, actor)
&& MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, actor))
greetingTimer++;
if (greetingTimer >= GREETING_SHOULD_START)
{
greetingState = Greet_InProgress;
MWBase::Environment::get().getDialogueManager()->say(actor, "hello");
greetingTimer = 0;
}
}
if(greetingState == Greet_InProgress)
{
greetingTimer++;
if(walking)
{
stopWalking(actor, storage);
moveNow = false;
walking = false;
mObstacleCheck.clear();
idleNow = true;
getRandomIdle(playedIdle);
}
if(!rotate)
{
osg::Vec3f dir = playerPos - actorPos;
float faceAngleRadians = std::atan2(dir.x(), dir.y());
targetAngleRadians = faceAngleRadians;
rotate = true;
}
if (greetingTimer >= GREETING_SHOULD_END)
{
greetingState = Greet_Done;
greetingTimer = 0;
}
}
if (greetingState == MWMechanics::AiWander::Greet_Done)
{
float resetDist = 2*helloDistance;
if (playerDistSqr >= resetDist*resetDist)
greetingState = Greet_None;
}
} }
if(moveNow && mDistance) if ((wanderState == Wander_MoveNow) && mDistance)
{ {
// Construct a new path if there isn't one // Construct a new path if there isn't one
if(!storage.mPathFinder.isPathConstructed()) if(!storage.mPathFinder.isPathConstructed())
{ {
assert(mAllowedNodes.size()); if (mAllowedNodes.size())
unsigned int randNode = Misc::Rng::rollDice(mAllowedNodes.size());
// NOTE: initially constructed with local (i.e. cell) co-ordinates
// convert dest to use world co-ordinates
ESM::Pathgrid::Point dest(mAllowedNodes[randNode]);
if (currentCell->getCell()->isExterior())
{ {
dest.mX += currentCell->getCell()->mData.mX * ESM::Land::REAL_SIZE; setPathToAnAllowedNode(actor, storage, pos);
dest.mY += currentCell->getCell()->mData.mY * ESM::Land::REAL_SIZE;
} }
// actor position is already in world co-ordinates
ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos));
// don't take shortcuts for wandering
storage.mPathFinder.buildPath(start, dest, actor.getCell(), false);
if(storage.mPathFinder.isPathConstructed())
{
// buildPath inserts dest in case it is not a pathgraph point
// index which is a duplicate for AiWander. However below code
// does not work since getPath() returns a copy of path not a
// reference
//if(storage.mPathFinder.getPathSize() > 1)
//storage.mPathFinder.getPath().pop_back();
// Remove this node as an option and add back the previously used node (stops NPC from picking the same node):
ESM::Pathgrid::Point temp = mAllowedNodes[randNode];
mAllowedNodes.erase(mAllowedNodes.begin() + randNode);
// check if mCurrentNode was taken out of mAllowedNodes
if(mTrimCurrentNode && mAllowedNodes.size() > 1)
mTrimCurrentNode = false;
else
mAllowedNodes.push_back(mCurrentNode);
mCurrentNode = temp;
moveNow = false;
walking = true;
}
// Choose a different node and delete this one from possible nodes because it is uncreachable:
else
mAllowedNodes.erase(mAllowedNodes.begin() + randNode);
} }
} }
return false; // AiWander package not yet completed return false; // AiWander package not yet completed
} }
void AiWander::evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage, float duration)
{
if (mObstacleCheck.check(actor, duration))
{
// first check if we're walking into a door
if (proximityToDoor(actor)) // NOTE: checks interior cells only
{
// remove allowed points then select another random destination
mTrimCurrentNode = true;
trimAllowedNodes(mAllowedNodes, storage.mPathFinder);
mObstacleCheck.clear();
storage.mPathFinder.clearPath();
storage.mState = Wander_MoveNow;
}
else // probably walking into another NPC
{
// TODO: diagonal should have same animation as walk forward
// but doesn't seem to do that?
actor.getClass().getMovementSettings(actor).mPosition[0] = 1;
actor.getClass().getMovementSettings(actor).mPosition[1] = 0.1f;
// change the angle a bit, too
const ESM::Position& pos = actor.getRefData().getPosition();
zTurn(actor, storage.mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1]));
}
mStuckCount++; // TODO: maybe no longer needed
}
//#if 0
// TODO: maybe no longer needed
if (mStuckCount >= COUNT_BEFORE_RESET) // something has gone wrong, reset
{
//std::cout << "Reset \""<< cls.getName(actor) << "\"" << std::endl;
mObstacleCheck.clear();
stopWalking(actor, storage);
storage.mState = Wander_ChooseAction;
mStuckCount = 0;
}
//#endif
}
void AiWander::playIdleDialogueRandomly(const MWWorld::Ptr& actor)
{
int hello = actor.getClass().getCreatureStats(actor).getAiSetting(CreatureStats::AI_Hello).getModified();
if (hello > 0 && !MWBase::Environment::get().getWorld()->isSwimming(actor)
&& MWBase::Environment::get().getSoundManager()->sayDone(actor))
{
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
static float fVoiceIdleOdds = MWBase::Environment::get().getWorld()->getStore()
.get<ESM::GameSetting>().find("fVoiceIdleOdds")->getFloat();
float roll = Misc::Rng::rollProbability() * 10000.0f;
// In vanilla MW the chance was FPS dependent, and did not allow proper changing of fVoiceIdleOdds
// due to the roll being an integer.
// Our implementation does not have these issues, so needs to be recalibrated. We chose to
// use the chance MW would have when run at 60 FPS with the default value of the GMST for calibration.
float x = fVoiceIdleOdds * 0.6f * (MWBase::Environment::get().getFrameDuration() / 0.1f);
// Only say Idle voices when player is in LOS
// A bit counterintuitive, likely vanilla did this to reduce the appearance of
// voices going through walls?
const ESM::Position& pos = actor.getRefData().getPosition();
if (roll < x && (player.getRefData().getPosition().asVec3() - pos.asVec3()).length2()
< 3000 * 3000 // maybe should be fAudioVoiceDefaultMaxDistance*fAudioMaxDistanceMult instead
&& MWBase::Environment::get().getWorld()->getLOS(player, actor))
MWBase::Environment::get().getDialogueManager()->say(actor, "idle");
}
}
void AiWander::playGreetingIfPlayerGetsTooClose(const MWWorld::Ptr& actor, AiWanderStorage& storage)
{
// Play a random voice greeting if the player gets too close
int hello = actor.getClass().getCreatureStats(actor).getAiSetting(CreatureStats::AI_Hello).getModified();
float helloDistance = static_cast<float>(hello);
static int iGreetDistanceMultiplier = MWBase::Environment::get().getWorld()->getStore()
.get<ESM::GameSetting>().find("iGreetDistanceMultiplier")->getInt();
helloDistance *= iGreetDistanceMultiplier;
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
osg::Vec3f playerPos(player.getRefData().getPosition().asVec3());
osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
float playerDistSqr = (playerPos - actorPos).length2();
int& greetingTimer = storage.mGreetingTimer;
GreetingState& greetingState = storage.mSaidGreeting;
if (greetingState == Greet_None)
{
if ((playerDistSqr <= helloDistance*helloDistance) &&
!player.getClass().getCreatureStats(player).isDead() && MWBase::Environment::get().getWorld()->getLOS(player, actor)
&& MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, actor))
greetingTimer++;
if (greetingTimer >= GREETING_SHOULD_START)
{
greetingState = Greet_InProgress;
MWBase::Environment::get().getDialogueManager()->say(actor, "hello");
greetingTimer = 0;
}
}
if (greetingState == Greet_InProgress)
{
greetingTimer++;
if (storage.mState == Wander_Walking)
{
stopWalking(actor, storage);
mObstacleCheck.clear();
storage.mState = Wander_IdleNow;
}
turnActorToFacePlayer(actorPos, playerPos, storage);
if (greetingTimer >= GREETING_SHOULD_END)
{
greetingState = Greet_Done;
greetingTimer = 0;
}
}
if (greetingState == MWMechanics::AiWander::Greet_Done)
{
float resetDist = 2 * helloDistance;
if (playerDistSqr >= resetDist*resetDist)
greetingState = Greet_None;
}
}
void AiWander::turnActorToFacePlayer(const osg::Vec3f& actorPosition, const osg::Vec3f& playerPosition, AiWanderStorage& storage)
{
osg::Vec3f dir = playerPosition - actorPosition;
float faceAngleRadians = std::atan2(dir.x(), dir.y());
storage.mTargetAngleRadians = faceAngleRadians;
storage.mTurnActorGivingGreetingToFacePlayer = true;
}
void AiWander::setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos)
{
unsigned int randNode = Misc::Rng::rollDice(mAllowedNodes.size());
ESM::Pathgrid::Point dest(mAllowedNodes[randNode]);
ToWorldCoordinates(dest, storage.mCell->getCell());
// actor position is already in world co-ordinates
ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(actorPos));
// don't take shortcuts for wandering
storage.mPathFinder.buildSyncedPath(start, dest, actor.getCell(), false);
if (storage.mPathFinder.isPathConstructed())
{
// Remove this node as an option and add back the previously used node (stops NPC from picking the same node):
ESM::Pathgrid::Point temp = mAllowedNodes[randNode];
mAllowedNodes.erase(mAllowedNodes.begin() + randNode);
// check if mCurrentNode was taken out of mAllowedNodes
if (mTrimCurrentNode && mAllowedNodes.size() > 1)
mTrimCurrentNode = false;
else
mAllowedNodes.push_back(mCurrentNode);
mCurrentNode = temp;
storage.mState = Wander_Walking;
}
// Choose a different node and delete this one from possible nodes because it is uncreachable:
else
mAllowedNodes.erase(mAllowedNodes.begin() + randNode);
}
void AiWander::ToWorldCoordinates(ESM::Pathgrid::Point& point, const ESM::Cell * cell)
{
if (cell->isExterior())
{
point.mX += cell->mData.mX * ESM::Land::REAL_SIZE;
point.mY += cell->mData.mY * ESM::Land::REAL_SIZE;
}
}
void AiWander::trimAllowedNodes(std::vector<ESM::Pathgrid::Point>& nodes, void AiWander::trimAllowedNodes(std::vector<ESM::Pathgrid::Point>& nodes,
const PathFinder& pathfinder) const PathFinder& pathfinder)
{ {
@ -611,9 +602,10 @@ namespace MWMechanics
} }
} }
void AiWander::getRandomIdle(short unsigned& playedIdle) short unsigned AiWander::getRandomIdle()
{ {
unsigned short idleRoll = 0; unsigned short idleRoll = 0;
short unsigned selectedAnimation = 0;
for(unsigned int counter = 0; counter < mIdle.size(); counter++) for(unsigned int counter = 0; counter < mIdle.size(); counter++)
{ {
@ -624,10 +616,11 @@ namespace MWMechanics
unsigned short randSelect = (int)(Misc::Rng::rollProbability() * int(100 / fIdleChanceMultiplier)); unsigned short randSelect = (int)(Misc::Rng::rollProbability() * int(100 / fIdleChanceMultiplier));
if(randSelect < idleChance && randSelect > idleRoll) if(randSelect < idleChance && randSelect > idleRoll)
{ {
playedIdle = counter+2; selectedAnimation = counter + GroupIndex_MinIdle;
idleRoll = randSelect; idleRoll = randSelect;
} }
} }
return selectedAnimation;
} }
void AiWander::fastForward(const MWWorld::Ptr& actor, AiState &state) void AiWander::fastForward(const MWWorld::Ptr& actor, AiState &state)
@ -635,7 +628,7 @@ namespace MWMechanics
if (mDistance == 0) if (mDistance == 0)
return; return;
if (!mStoredAvailableNodes) if (mPopulateAvailableNodes)
getAllowedNodes(actor, actor.getCell()->getCell()); getAllowedNodes(actor, actor.getCell()->getCell());
if (mAllowedNodes.empty()) if (mAllowedNodes.empty())
@ -646,22 +639,21 @@ namespace MWMechanics
int index = Misc::Rng::rollDice(mAllowedNodes.size()); int index = Misc::Rng::rollDice(mAllowedNodes.size());
ESM::Pathgrid::Point dest = mAllowedNodes[index]; ESM::Pathgrid::Point dest = mAllowedNodes[index];
// apply a slight offset to prevent overcrowding dest.mX += OffsetToPreventOvercrowding();
dest.mX += static_cast<int>(Misc::Rng::rollProbability() * 128 - 64); dest.mY += OffsetToPreventOvercrowding();
dest.mY += static_cast<int>(Misc::Rng::rollProbability() * 128 - 64); ToWorldCoordinates(dest, actor.getCell()->getCell());
if (actor.getCell()->isExterior())
{
dest.mX += actor.getCell()->getCell()->mData.mX * ESM::Land::REAL_SIZE;
dest.mY += actor.getCell()->getCell()->mData.mY * ESM::Land::REAL_SIZE;
}
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);
// may have changed cell // may have changed cell
mStoredAvailableNodes = false; mPopulateAvailableNodes = true;
}
int AiWander::OffsetToPreventOvercrowding()
{
return static_cast<int>(DESTINATION_TOLERANCE * (Misc::Rng::rollProbability() * 2.0f - 1.0f));
} }
void AiWander::getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell) void AiWander::getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell)
@ -690,70 +682,86 @@ namespace MWMechanics
// ... pathgrids don't usually include water, so swimmers ignore them // ... pathgrids don't usually include water, so swimmers ignore them
if (mDistance && !actor.getClass().isPureWaterCreature(actor)) if (mDistance && !actor.getClass().isPureWaterCreature(actor))
{ {
float cellXOffset = 0; // get NPC's position in local (i.e. cell) co-ordinates
float cellYOffset = 0; osg::Vec3f npcPos(mInitialActorPosition);
if(cell->isExterior()) if(cell->isExterior())
{ {
cellXOffset = static_cast<float>(cell->mData.mX * ESM::Land::REAL_SIZE); npcPos[0] = npcPos[0] - static_cast<float>(cell->mData.mX * ESM::Land::REAL_SIZE);
cellYOffset = static_cast<float>(cell->mData.mY * ESM::Land::REAL_SIZE); npcPos[1] = npcPos[1] - static_cast<float>(cell->mData.mY * ESM::Land::REAL_SIZE);
} }
// convert npcPos to local (i.e. cell) co-ordinates
osg::Vec3f npcPos(mInitialActorPosition);
npcPos[0] = npcPos[0] - cellXOffset;
npcPos[1] = npcPos[1] - cellYOffset;
// mAllowedNodes for this actor with pathgrid point indexes based on mDistance // mAllowedNodes for this actor with pathgrid point indexes based on mDistance
// NOTE: mPoints and mAllowedNodes are in local co-ordinates // NOTE: mPoints and mAllowedNodes are in local co-ordinates
int pointIndex = 0;
for(unsigned int counter = 0; counter < pathgrid->mPoints.size(); counter++) for(unsigned int counter = 0; counter < pathgrid->mPoints.size(); counter++)
{ {
osg::Vec3f nodePos(PathFinder::MakeOsgVec3(pathgrid->mPoints[counter])); osg::Vec3f nodePos(PathFinder::MakeOsgVec3(pathgrid->mPoints[counter]));
if((npcPos - nodePos).length2() <= mDistance * mDistance) if((npcPos - nodePos).length2() <= mDistance * mDistance)
{
mAllowedNodes.push_back(pathgrid->mPoints[counter]); mAllowedNodes.push_back(pathgrid->mPoints[counter]);
pointIndex = counter;
}
}
if (mAllowedNodes.size() == 1)
{
AddNonPathGridAllowedPoints(npcPos, pathgrid, pointIndex);
} }
if(!mAllowedNodes.empty()) if(!mAllowedNodes.empty())
{ {
osg::Vec3f firstNodePos(PathFinder::MakeOsgVec3(mAllowedNodes[0])); SetCurrentNodeToClosestAllowedNode(npcPos);
float closestNode = (npcPos - firstNodePos).length2();
unsigned int index = 0;
for(unsigned int counterThree = 1; counterThree < mAllowedNodes.size(); counterThree++)
{
osg::Vec3f nodePos(PathFinder::MakeOsgVec3(mAllowedNodes[counterThree]));
float tempDist = (npcPos - nodePos).length2();
if(tempDist < closestNode)
index = counterThree;
}
mCurrentNode = mAllowedNodes[index];
mAllowedNodes.erase(mAllowedNodes.begin() + index);
} }
}
// In vanilla Morrowind, sometimes distance is too small to include at least two points,
// in which case, we will take the two closest points regardless of the wander distance mPopulateAvailableNodes = false;
// This is a backup option, as std::sort is potentially O(n^2) in time. }
if (mAllowedNodes.empty())
// When only one path grid point in wander distance,
// additional points for NPC to wander to are:
// 1. NPC's initial location
// 2. Partway along the path between the point and its connected points.
void AiWander::AddNonPathGridAllowedPoints(osg::Vec3f npcPos, const ESM::Pathgrid * pathGrid, int pointIndex)
{
mAllowedNodes.push_back(PathFinder::MakePathgridPoint(npcPos));
for (std::vector<ESM::Pathgrid::Edge>::const_iterator it = pathGrid->mEdges.begin(); it != pathGrid->mEdges.end(); ++it)
{
if (it->mV0 == pointIndex)
{ {
// Start with list of PathGrid nodes, sorted by distance from actor AddPointBetweenPathGridPoints(pathGrid->mPoints[it->mV0], pathGrid->mPoints[it->mV1]);
std::vector<PathDistance> nodeDistances;
for (unsigned int counter = 0; counter < pathgrid->mPoints.size(); counter++)
{
float distance = (npcPos - PathFinder::MakeOsgVec3(pathgrid->mPoints[counter])).length2();
nodeDistances.push_back(std::make_pair(distance, &pathgrid->mPoints.at(counter)));
}
std::sort(nodeDistances.begin(), nodeDistances.end(), sortByDistance);
// make closest node the current node
mCurrentNode = *nodeDistances[0].second;
// give Actor a 2nd node to walk to
mAllowedNodes.push_back(*nodeDistances[1].second);
} }
mStoredAvailableNodes = true; // set only if successful in finding allowed nodes
} }
} }
bool AiWander::sortByDistance(const PathDistance& left, const PathDistance& right) void AiWander::AddPointBetweenPathGridPoints(const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end)
{ {
return left.first < right.first; osg::Vec3f vectorStart = PathFinder::MakeOsgVec3(start);
osg::Vec3f delta = PathFinder::MakeOsgVec3(end) - vectorStart;
float length = delta.length();
delta.normalize();
int distance = std::max(mDistance / 2, MINIMUM_WANDER_DISTANCE);
// must not travel longer than distance between waypoints or NPC goes past waypoint
distance = std::min(distance, static_cast<int>(length));
delta *= distance;
mAllowedNodes.push_back(PathFinder::MakePathgridPoint(vectorStart + delta));
}
void AiWander::SetCurrentNodeToClosestAllowedNode(osg::Vec3f npcPos)
{
float distanceToClosestNode = FLT_MAX;
unsigned int index = 0;
for (unsigned int counterThree = 0; counterThree < mAllowedNodes.size(); counterThree++)
{
osg::Vec3f nodePos(PathFinder::MakeOsgVec3(mAllowedNodes[counterThree]));
float tempDist = (npcPos - nodePos).length2();
if (tempDist < distanceToClosestNode)
{
index = counterThree;
distanceToClosestNode = tempDist;
}
}
mCurrentNode = mAllowedNodes[index];
mAllowedNodes.erase(mAllowedNodes.begin() + index);
} }
void AiWander::writeState(ESM::AiSequence::AiSequence &sequence) const void AiWander::writeState(ESM::AiSequence::AiSequence &sequence) const

View file

@ -63,6 +63,13 @@ namespace MWMechanics
Greet_InProgress, Greet_InProgress,
Greet_Done Greet_Done
}; };
enum WanderState {
Wander_ChooseAction,
Wander_IdleNow,
Wander_MoveNow,
Wander_Walking
};
private: private:
// NOTE: mDistance and mDuration must be set already // NOTE: mDistance and mDuration must be set already
void init(); void init();
@ -70,7 +77,12 @@ namespace MWMechanics
void stopWalking(const MWWorld::Ptr& actor, AiWanderStorage& storage); void stopWalking(const MWWorld::Ptr& actor, AiWanderStorage& storage);
void playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); void playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect);
bool checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); bool checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect);
void getRandomIdle(unsigned short& playedIdle); short unsigned getRandomIdle();
void setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos);
void playGreetingIfPlayerGetsTooClose(const MWWorld::Ptr& actor, AiWanderStorage& storage);
void evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage, float duration);
void playIdleDialogueRandomly(const MWWorld::Ptr& actor);
void turnActorToFacePlayer(const osg::Vec3f& actorPosition, const osg::Vec3f& playerPosition, AiWanderStorage& storage);
int mDistance; // how far the actor can wander from the spawn point int mDistance; // how far the actor can wander from the spawn point
int mDuration; int mDuration;
@ -88,8 +100,8 @@ namespace MWMechanics
// if false triggers calculating allowed nodes based on mDistance // do we need to calculate allowed nodes based on mDistance
bool mStoredAvailableNodes; bool mPopulateAvailableNodes;
@ -118,15 +130,19 @@ namespace MWMechanics
GroupIndex_MaxIdle = 9 GroupIndex_MaxIdle = 9
}; };
/// convert point from local (i.e. cell) to world co-ordinates
void ToWorldCoordinates(ESM::Pathgrid::Point& point, const ESM::Cell * cell);
void SetCurrentNodeToClosestAllowedNode(osg::Vec3f npcPos);
void AddNonPathGridAllowedPoints(osg::Vec3f npcPos, const ESM::Pathgrid * pathGrid, int pointIndex);
void AddPointBetweenPathGridPoints(const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end);
/// lookup table for converting idleSelect value to groupName /// lookup table for converting idleSelect value to groupName
static const std::string sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1]; static const std::string sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1];
/// record distances of pathgrid point nodes to actor static int OffsetToPreventOvercrowding();
/// first value is distance between actor and node, second value is PathGrid node
typedef std::pair<float, const ESM::Pathgrid::Point*> PathDistance;
/// used to sort array of PathDistance objects into ascending order
static bool sortByDistance(const PathDistance& left, const PathDistance& right);
}; };

View file

@ -43,6 +43,7 @@
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/inventorystore.hpp" #include "../mwworld/inventorystore.hpp"
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include "../mwworld/player.hpp"
namespace namespace
{ {
@ -236,7 +237,7 @@ std::string CharacterController::chooseRandomGroup (const std::string& prefix, i
return prefix + toString(roll); return prefix + toString(roll);
} }
void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterState movement, bool force) void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force)
{ {
// hit recoils/knockdown animations handling // hit recoils/knockdown animations handling
if(mPtr.getClass().isActor()) if(mPtr.getClass().isActor())
@ -251,26 +252,28 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
{ {
mHitState = CharState_KnockOut; mHitState = CharState_KnockOut;
mCurrentHit = "knockout"; mCurrentHit = "knockout";
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::Group_All, false, 1, "start", "stop", 0.0f, ~0ul); mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul);
mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(true); mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(true);
} }
else if(knockdown) else if(knockdown)
{ {
mHitState = CharState_KnockDown; mHitState = CharState_KnockDown;
mCurrentHit = "knockdown"; mCurrentHit = "knockdown";
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::Group_All, true, 1, "start", "stop", 0.0f, 0); mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0);
} }
else if (recovery) else if (recovery)
{ {
mHitState = CharState_Hit; mHitState = CharState_Hit;
mCurrentHit = chooseRandomGroup("hit"); mCurrentHit = chooseRandomGroup("hit");
mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::Group_All, true, 1, "start", "stop", 0.0f, 0); mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0);
} }
else if (block) else if (block)
{ {
mHitState = CharState_Block; mHitState = CharState_Block;
mCurrentHit = "shield"; mCurrentHit = "shield";
mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::Group_All, true, 1, "block start", "block stop", 0.0f, 0); MWRender::Animation::AnimPriority priorityBlock (Priority_Hit);
priorityBlock.mPriority[MWRender::Animation::BoneGroup_LeftArm] = Priority_Block;
mAnimation->play(mCurrentHit, priorityBlock, MWRender::Animation::BlendMask_All, true, 1, "block start", "block stop", 0.0f, 0);
} }
// Cancel upper body animations // Cancel upper body animations
@ -303,7 +306,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
{ {
mHitState = CharState_KnockDown; mHitState = CharState_KnockDown;
mAnimation->disable(mCurrentHit); mAnimation->disable(mCurrentHit);
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::Group_All, true, 1, "loop stop", "stop", 0.0f, 0); mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "loop stop", "stop", 0.0f, 0);
} }
} }
@ -311,40 +314,41 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
if (!mPtr.getClass().isBipedal(mPtr)) if (!mPtr.getClass().isBipedal(mPtr))
weap = sWeaponTypeListEnd; weap = sWeaponTypeListEnd;
if(force && mJumpState != JumpState_None) if(force || jump != mJumpState)
{ {
std::string jump; bool startAtLoop = (jump == mJumpState);
MWRender::Animation::Group jumpgroup = MWRender::Animation::Group_All; mJumpState = jump;
std::string jumpAnimName;
MWRender::Animation::BlendMask jumpmask = MWRender::Animation::BlendMask_All;
if(mJumpState != JumpState_None) if(mJumpState != JumpState_None)
{ {
jump = "jump"; jumpAnimName = "jump";
if(weap != sWeaponTypeListEnd) if(weap != sWeaponTypeListEnd)
{ {
jump += weap->shortgroup; jumpAnimName += weap->shortgroup;
if(!mAnimation->hasAnimation(jump)) if(!mAnimation->hasAnimation(jumpAnimName))
{ {
jumpgroup = MWRender::Animation::Group_LowerBody; jumpmask = MWRender::Animation::BlendMask_LowerBody;
jump = "jump"; jumpAnimName = "jump";
} }
} }
} }
if(mJumpState == JumpState_InAir) if(mJumpState == JumpState_InAir)
{ {
int mode = ((jump == mCurrentJump) ? 2 : 1);
mAnimation->disable(mCurrentJump); mAnimation->disable(mCurrentJump);
mCurrentJump = jump; mCurrentJump = jumpAnimName;
if (mAnimation->hasAnimation("jump")) if (mAnimation->hasAnimation("jump"))
mAnimation->play(mCurrentJump, Priority_Jump, jumpgroup, false, mAnimation->play(mCurrentJump, Priority_Jump, jumpmask, false,
1.0f, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul); 1.0f, (startAtLoop?"loop start":"start"), "stop", 0.0f, ~0ul);
} }
else else
{ {
mAnimation->disable(mCurrentJump); mAnimation->disable(mCurrentJump);
mCurrentJump.clear(); mCurrentJump.clear();
if (mAnimation->hasAnimation("jump")) if (mAnimation->hasAnimation("jump"))
mAnimation->play(jump, Priority_Jump, jumpgroup, true, mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, true,
1.0f, "loop stop", "stop", 0.0f, 0); 1.0f, "loop stop", "stop", 0.0f, 0);
} }
} }
@ -353,55 +357,55 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
{ {
mMovementState = movement; mMovementState = movement;
std::string movement; std::string movementAnimName;
MWRender::Animation::Group movegroup = MWRender::Animation::Group_All; MWRender::Animation::BlendMask movemask = MWRender::Animation::BlendMask_All;
const StateInfo *movestate = std::find_if(sMovementList, sMovementListEnd, FindCharState(mMovementState)); const StateInfo *movestate = std::find_if(sMovementList, sMovementListEnd, FindCharState(mMovementState));
if(movestate != sMovementListEnd) if(movestate != sMovementListEnd)
{ {
movement = movestate->groupname; movementAnimName = movestate->groupname;
if(weap != sWeaponTypeListEnd && movement.find("swim") == std::string::npos) if(weap != sWeaponTypeListEnd && movementAnimName.find("swim") == std::string::npos)
{ {
movement += weap->shortgroup; movementAnimName += weap->shortgroup;
if(!mAnimation->hasAnimation(movement)) if(!mAnimation->hasAnimation(movementAnimName))
{ {
movegroup = MWRender::Animation::Group_LowerBody; movemask = MWRender::Animation::BlendMask_LowerBody;
movement = movestate->groupname; movementAnimName = movestate->groupname;
} }
} }
if(!mAnimation->hasAnimation(movement)) if(!mAnimation->hasAnimation(movementAnimName))
{ {
std::string::size_type swimpos = movement.find("swim"); std::string::size_type swimpos = movementAnimName.find("swim");
if(swimpos == std::string::npos) if(swimpos == std::string::npos)
{ {
std::string::size_type runpos = movement.find("run"); std::string::size_type runpos = movementAnimName.find("run");
if (runpos != std::string::npos) if (runpos != std::string::npos)
{ {
movement.replace(runpos, runpos+3, "walk"); movementAnimName.replace(runpos, runpos+3, "walk");
if (!mAnimation->hasAnimation(movement)) if (!mAnimation->hasAnimation(movementAnimName))
movement.clear(); movementAnimName.clear();
} }
else else
movement.clear(); movementAnimName.clear();
} }
else else
{ {
movegroup = MWRender::Animation::Group_LowerBody; movemask = MWRender::Animation::BlendMask_LowerBody;
movement.erase(swimpos, 4); movementAnimName.erase(swimpos, 4);
if(!mAnimation->hasAnimation(movement)) if(!mAnimation->hasAnimation(movementAnimName))
movement.clear(); movementAnimName.clear();
} }
} }
} }
/* If we're playing the same animation, restart from the loop start instead of the /* If we're playing the same animation, restart from the loop start instead of the
* beginning. */ * beginning. */
int mode = ((movement == mCurrentMovement) ? 2 : 1); int mode = ((movementAnimName == mCurrentMovement) ? 2 : 1);
mMovementAnimationControlled = true; mMovementAnimationControlled = true;
mAnimation->disable(mCurrentMovement); mAnimation->disable(mCurrentMovement);
mCurrentMovement = movement; mCurrentMovement = movementAnimName;
if(!mCurrentMovement.empty()) if(!mCurrentMovement.empty())
{ {
float vel, speedmult = 1.0f; float vel, speedmult = 1.0f;
@ -447,7 +451,17 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
} }
} }
mAnimation->play(mCurrentMovement, Priority_Movement, movegroup, false, MWRender::Animation::AnimPriority priorityMovement (Priority_Movement);
if ((movement == CharState_TurnLeft || movement == CharState_TurnRight)
&& mPtr == MWBase::Environment::get().getWorld()->getPlayerPtr()
&& MWBase::Environment::get().getWorld()->isFirstPerson())
{
priorityMovement.mPriority[MWRender::Animation::BoneGroup_Torso] = 0;
priorityMovement.mPriority[MWRender::Animation::BoneGroup_LeftArm] = 0;
priorityMovement.mPriority[MWRender::Animation::BoneGroup_RightArm] = 0;
}
mAnimation->play(mCurrentMovement, priorityMovement, movemask, false,
speedmult, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul); speedmult, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul);
} }
} }
@ -456,7 +470,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
// FIXME: if one of the below states is close to their last animation frame (i.e. will be disabled in the coming update), // FIXME: if one of the below states is close to their last animation frame (i.e. will be disabled in the coming update),
// the idle animation should be displayed // the idle animation should be displayed
if ((mUpperBodyState != UpperCharState_Nothing if ((mUpperBodyState != UpperCharState_Nothing
|| mMovementState != CharState_None || (mMovementState != CharState_None && mMovementState != CharState_TurnLeft && mMovementState != CharState_TurnRight)
|| mHitState != CharState_None) || mHitState != CharState_None)
&& !mPtr.getClass().isBipedal(mPtr)) && !mPtr.getClass().isBipedal(mPtr))
idle = CharState_None; idle = CharState_None;
@ -486,7 +500,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
mAnimation->disable(mCurrentIdle); mAnimation->disable(mCurrentIdle);
mCurrentIdle = idle; mCurrentIdle = idle;
if(!mCurrentIdle.empty()) if(!mCurrentIdle.empty())
mAnimation->play(mCurrentIdle, Priority_Default, MWRender::Animation::Group_All, false, mAnimation->play(mCurrentIdle, Priority_Default, MWRender::Animation::BlendMask_All, false,
1.0f, "start", "stop", 0.0f, ~0ul, true); 1.0f, "start", "stop", 0.0f, ~0ul, true);
} }
@ -602,7 +616,7 @@ void CharacterController::playDeath(float startpoint, CharacterState death)
mCurrentJump = ""; mCurrentJump = "";
mMovementAnimationControlled = true; mMovementAnimationControlled = true;
mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::Group_All, mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::BlendMask_All,
false, 1.0f, "start", "stop", startpoint, 0); false, 1.0f, "start", "stop", startpoint, 0);
} }
@ -704,7 +718,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim
if(mDeathState == CharState_None) if(mDeathState == CharState_None)
refreshCurrentAnims(mIdleState, mMovementState, true); refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true);
mAnimation->runAnimation(0.f); mAnimation->runAnimation(0.f);
} }
@ -868,10 +882,10 @@ void CharacterController::updateIdleStormState()
mAnimation->getInfo("idlestorm", &complete); mAnimation->getInfo("idlestorm", &complete);
if (complete == 0) if (complete == 0)
mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::Group_RightArm, false, mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::BlendMask_RightArm, false,
1.0f, "start", "loop start", 0.0f, 0); 1.0f, "start", "loop start", 0.0f, 0);
else if (complete == 1) else if (complete == 1)
mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::Group_RightArm, false, mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::BlendMask_RightArm, false,
1.0f, "loop start", "loop stop", 0.0f, ~0ul); 1.0f, "loop start", "loop stop", 0.0f, ~0ul);
} }
else else
@ -882,7 +896,7 @@ void CharacterController::updateIdleStormState()
{ {
if (mAnimation->getCurrentTime("idlestorm") < mAnimation->getTextKeyTime("idlestorm: loop stop")) if (mAnimation->getCurrentTime("idlestorm") < mAnimation->getTextKeyTime("idlestorm: loop stop"))
{ {
mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::Group_RightArm, true, mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::BlendMask_RightArm, true,
1.0f, "loop stop", "stop", 0.0f, 0); 1.0f, "loop stop", "stop", 0.0f, 0);
} }
} }
@ -989,7 +1003,7 @@ bool CharacterController::updateCreatureState()
if (!mCurrentWeapon.empty()) if (!mCurrentWeapon.empty())
{ {
mAnimation->play(mCurrentWeapon, Priority_Weapon, mAnimation->play(mCurrentWeapon, Priority_Weapon,
MWRender::Animation::Group_All, true, MWRender::Animation::BlendMask_All, true,
1, startKey, stopKey, 1, startKey, stopKey,
0.0f, 0); 0.0f, 0);
mUpperBodyState = UpperCharState_StartToMinAttack; mUpperBodyState = UpperCharState_StartToMinAttack;
@ -1051,6 +1065,9 @@ bool CharacterController::updateWeaponState()
} }
} }
MWRender::Animation::AnimPriority priorityWeapon(Priority_Weapon);
priorityWeapon.mPriority[MWRender::Animation::BoneGroup_LowerBody] = 0;
bool forcestateupdate = false; bool forcestateupdate = false;
if(weaptype != mWeaponType && mHitState != CharState_KnockDown && mHitState != CharState_KnockOut if(weaptype != mWeaponType && mHitState != CharState_KnockDown && mHitState != CharState_KnockOut
&& mHitState != CharState_Hit) && mHitState != CharState_Hit)
@ -1063,8 +1080,8 @@ bool CharacterController::updateWeaponState()
if(weaptype == WeapType_None) if(weaptype == WeapType_None)
{ {
getWeaponGroup(mWeaponType, weapgroup); getWeaponGroup(mWeaponType, weapgroup);
mAnimation->play(weapgroup, Priority_Weapon, mAnimation->play(weapgroup, priorityWeapon,
MWRender::Animation::Group_UpperBody, true, MWRender::Animation::BlendMask_All, true,
1.0f, "unequip start", "unequip stop", 0.0f, 0); 1.0f, "unequip start", "unequip stop", 0.0f, 0);
mUpperBodyState = UpperCharState_UnEquipingWeap; mUpperBodyState = UpperCharState_UnEquipingWeap;
} }
@ -1074,8 +1091,8 @@ bool CharacterController::updateWeaponState()
mAnimation->showWeapons(false); mAnimation->showWeapons(false);
mAnimation->setWeaponGroup(weapgroup); mAnimation->setWeaponGroup(weapgroup);
mAnimation->play(weapgroup, Priority_Weapon, mAnimation->play(weapgroup, priorityWeapon,
MWRender::Animation::Group_UpperBody, true, MWRender::Animation::BlendMask_All, true,
1.0f, "equip start", "equip stop", 0.0f, 0); 1.0f, "equip start", "equip stop", 0.0f, 0);
mUpperBodyState = UpperCharState_EquipingWeap; mUpperBodyState = UpperCharState_EquipingWeap;
@ -1145,7 +1162,7 @@ bool CharacterController::updateWeaponState()
bool animPlaying; bool animPlaying;
if(mAttackingOrSpell) if(mAttackingOrSpell)
{ {
if(mUpperBodyState == UpperCharState_WeapEquiped && mHitState == CharState_None) if(mUpperBodyState == UpperCharState_WeapEquiped && (mHitState == CharState_None || mHitState == CharState_Block))
{ {
MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); MWBase::Environment::get().getWorld()->breakInvisibility(mPtr);
mAttackType.clear(); mAttackType.clear();
@ -1154,6 +1171,10 @@ bool CharacterController::updateWeaponState()
// Unset casting flag, otherwise pressing the mouse button down would // Unset casting flag, otherwise pressing the mouse button down would
// continue casting every frame if there is no animation // continue casting every frame if there is no animation
mAttackingOrSpell = false; mAttackingOrSpell = false;
if (mPtr == MWBase::Environment::get().getWorld()->getPlayerPtr())
{
MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false);
}
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
@ -1191,8 +1212,8 @@ bool CharacterController::updateWeaponState()
case 2: mAttackType = "target"; break; case 2: mAttackType = "target"; break;
} }
mAnimation->play(mCurrentWeapon, Priority_Weapon, mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::Group_UpperBody, true, MWRender::Animation::BlendMask_All, true,
weapSpeed, mAttackType+" start", mAttackType+" stop", weapSpeed, mAttackType+" start", mAttackType+" stop",
0.0f, 0); 0.0f, 0);
mUpperBodyState = UpperCharState_CastingSpell; mUpperBodyState = UpperCharState_CastingSpell;
@ -1223,8 +1244,8 @@ bool CharacterController::updateWeaponState()
else if(item.getTypeName() == typeid(ESM::Probe).name()) else if(item.getTypeName() == typeid(ESM::Probe).name())
Security(mPtr).probeTrap(target, item, resultMessage, resultSound); Security(mPtr).probeTrap(target, item, resultMessage, resultSound);
} }
mAnimation->play(mCurrentWeapon, Priority_Weapon, mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::Group_UpperBody, true, MWRender::Animation::BlendMask_All, true,
1.0f, "start", "stop", 0.0, 0); 1.0f, "start", "stop", 0.0, 0);
mUpperBodyState = UpperCharState_FollowStartToFollowStop; mUpperBodyState = UpperCharState_FollowStartToFollowStop;
@ -1250,8 +1271,8 @@ bool CharacterController::updateWeaponState()
determineAttackType(); determineAttackType();
} }
mAnimation->play(mCurrentWeapon, Priority_Weapon, mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::Group_UpperBody, false, MWRender::Animation::BlendMask_All, false,
weapSpeed, mAttackType+" start", mAttackType+" min attack", weapSpeed, mAttackType+" start", mAttackType+" min attack",
0.0f, 0); 0.0f, 0);
mUpperBodyState = UpperCharState_StartToMinAttack; mUpperBodyState = UpperCharState_StartToMinAttack;
@ -1301,8 +1322,8 @@ bool CharacterController::updateWeaponState()
mAttackStrength = attackStrength; mAttackStrength = attackStrength;
mAnimation->disable(mCurrentWeapon); mAnimation->disable(mCurrentWeapon);
mAnimation->play(mCurrentWeapon, Priority_Weapon, mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::Group_UpperBody, false, MWRender::Animation::BlendMask_All, false,
weapSpeed, mAttackType+" max attack", mAttackType+" min hit", weapSpeed, mAttackType+" max attack", mAttackType+" min hit",
1.0f-complete, 0); 1.0f-complete, 0);
@ -1371,15 +1392,6 @@ bool CharacterController::updateWeaponState()
mAnimation->attachArrow(); mAnimation->attachArrow();
mUpperBodyState = UpperCharState_WeapEquiped; mUpperBodyState = UpperCharState_WeapEquiped;
//don't allow to continue playing hit animation on UpperBody after actor had attacked during it
if(mHitState == CharState_Hit)
{
mAnimation->changeGroups(mCurrentHit, MWRender::Animation::Group_LowerBody);
//commenting out following 2 lines will give a bit different combat dynamics(slower)
mHitState = CharState_None;
mCurrentHit.clear();
mPtr.getClass().getCreatureStats(mPtr).setHitRecovery(false);
}
} }
else if(mUpperBodyState == UpperCharState_UnEquipingWeap) else if(mUpperBodyState == UpperCharState_UnEquipingWeap)
mUpperBodyState = UpperCharState_Nothing; mUpperBodyState = UpperCharState_Nothing;
@ -1397,8 +1409,8 @@ bool CharacterController::updateWeaponState()
case UpperCharState_MinAttackToMaxAttack: case UpperCharState_MinAttackToMaxAttack:
//hack to avoid body pos desync when jumping/sneaking in 'max attack' state //hack to avoid body pos desync when jumping/sneaking in 'max attack' state
if(!mAnimation->isPlaying(mCurrentWeapon)) if(!mAnimation->isPlaying(mCurrentWeapon))
mAnimation->play(mCurrentWeapon, Priority_Weapon, mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::Group_UpperBody, false, MWRender::Animation::BlendMask_All, false,
0, mAttackType+" min attack", mAttackType+" max attack", 0.999f, 0); 0, mAttackType+" min attack", mAttackType+" max attack", 0.999f, 0);
break; break;
case UpperCharState_MaxAttackToMinHit: case UpperCharState_MaxAttackToMinHit:
@ -1440,33 +1452,16 @@ bool CharacterController::updateWeaponState()
{ {
mAnimation->disable(mCurrentWeapon); mAnimation->disable(mCurrentWeapon);
if (mUpperBodyState == UpperCharState_FollowStartToFollowStop) if (mUpperBodyState == UpperCharState_FollowStartToFollowStop)
mAnimation->play(mCurrentWeapon, Priority_Weapon, mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::Group_UpperBody, true, MWRender::Animation::BlendMask_All, true,
weapSpeed, start, stop, 0.0f, 0); weapSpeed, start, stop, 0.0f, 0);
else else
mAnimation->play(mCurrentWeapon, Priority_Weapon, mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::Group_UpperBody, false, MWRender::Animation::BlendMask_All, false,
weapSpeed, start, stop, 0.0f, 0); weapSpeed, start, stop, 0.0f, 0);
} }
} }
//if playing combat animation and lowerbody is not busy switch to whole body animation
if((weaptype != WeapType_None || mUpperBodyState == UpperCharState_UnEquipingWeap) && animPlaying)
{
if( mMovementState != CharState_None ||
mJumpState != JumpState_None ||
mHitState != CharState_None ||
MWBase::Environment::get().getWorld()->isSwimming(mPtr) ||
cls.getCreatureStats(mPtr).getMovementFlag(CreatureStats::Flag_Sneak))
{
mAnimation->changeGroups(mCurrentWeapon, MWRender::Animation::Group_UpperBody);
}
else
{
mAnimation->changeGroups(mCurrentWeapon, MWRender::Animation::Group_All);
}
}
if (mPtr.getClass().hasInventoryStore(mPtr)) if (mPtr.getClass().hasInventoryStore(mPtr))
{ {
MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
@ -1475,7 +1470,7 @@ bool CharacterController::updateWeaponState()
&& updateCarriedLeftVisible(mWeaponType)) && updateCarriedLeftVisible(mWeaponType))
{ {
mAnimation->play("torch", Priority_Torch, MWRender::Animation::Group_LeftArm, mAnimation->play("torch", Priority_Torch, MWRender::Animation::BlendMask_LeftArm,
false, 1.0f, "start", "stop", 0.0f, (~(size_t)0), true); false, 1.0f, "start", "stop", 0.0f, (~(size_t)0), true);
} }
else if (mAnimation->isPlaying("torch")) else if (mAnimation->isPlaying("torch"))
@ -1505,7 +1500,7 @@ void CharacterController::update(float duration)
mAnimQueue.pop_front(); mAnimQueue.pop_front();
mAnimation->play(mAnimQueue.front().first, Priority_Default, mAnimation->play(mAnimQueue.front().first, Priority_Default,
MWRender::Animation::Group_All, false, MWRender::Animation::BlendMask_All, false,
1.0f, "start", "stop", 0.0f, mAnimQueue.front().second); 1.0f, "start", "stop", 0.0f, mAnimQueue.front().second);
} }
} }
@ -1562,6 +1557,8 @@ void CharacterController::update(float duration)
CharacterState movestate = CharState_None; CharacterState movestate = CharState_None;
CharacterState idlestate = CharState_SpecialIdle; CharacterState idlestate = CharState_SpecialIdle;
JumpingState jumpstate = JumpState_None;
bool forcestateupdate = false; bool forcestateupdate = false;
mHasMovedInXY = std::abs(vec.x())+std::abs(vec.y()) > 0.0f; mHasMovedInXY = std::abs(vec.x())+std::abs(vec.y()) > 0.0f;
@ -1644,7 +1641,7 @@ void CharacterController::update(float duration)
} }
forcestateupdate = (mJumpState != JumpState_InAir); forcestateupdate = (mJumpState != JumpState_InAir);
mJumpState = JumpState_InAir; jumpstate = JumpState_InAir;
static const float fJumpMoveBase = gmst.find("fJumpMoveBase")->getFloat(); static const float fJumpMoveBase = gmst.find("fJumpMoveBase")->getFloat();
static const float fJumpMoveMult = gmst.find("fJumpMoveMult")->getFloat(); static const float fJumpMoveMult = gmst.find("fJumpMoveMult")->getFloat();
@ -1689,7 +1686,7 @@ void CharacterController::update(float duration)
else if(mJumpState == JumpState_InAir) else if(mJumpState == JumpState_InAir)
{ {
forcestateupdate = true; forcestateupdate = true;
mJumpState = JumpState_Landing; jumpstate = JumpState_Landing;
vec.z() = 0.0f; vec.z() = 0.0f;
float height = cls.getCreatureStats(mPtr).land(); float height = cls.getCreatureStats(mPtr).land();
@ -1720,7 +1717,7 @@ void CharacterController::update(float duration)
} }
else else
{ {
mJumpState = JumpState_None; jumpstate = JumpState_None;
vec.z() = 0.0f; vec.z() = 0.0f;
inJump = false; inJump = false;
@ -1786,19 +1783,22 @@ void CharacterController::update(float duration)
mAnimQueue.pop_front(); mAnimQueue.pop_front();
mAnimation->play(mAnimQueue.front().first, Priority_Default, mAnimation->play(mAnimQueue.front().first, Priority_Default,
MWRender::Animation::Group_All, false, MWRender::Animation::BlendMask_All, false,
1.0f, "start", "stop", 0.0f, mAnimQueue.front().second); 1.0f, "start", "stop", 0.0f, mAnimQueue.front().second);
} }
} }
// bipedal means hand-to-hand could be used (which is handled in updateWeaponState). an existing InventoryStore means an actual weapon could be used.
if(cls.isBipedal(mPtr) || cls.hasInventoryStore(mPtr))
forcestateupdate = updateWeaponState() || forcestateupdate;
else
forcestateupdate = updateCreatureState() || forcestateupdate;
if (!mSkipAnim) if (!mSkipAnim)
refreshCurrentAnims(idlestate, movestate, forcestateupdate); {
// bipedal means hand-to-hand could be used (which is handled in updateWeaponState). an existing InventoryStore means an actual weapon could be used.
if(cls.isBipedal(mPtr) || cls.hasInventoryStore(mPtr))
forcestateupdate = updateWeaponState() || forcestateupdate;
else
forcestateupdate = updateCreatureState() || forcestateupdate;
refreshCurrentAnims(idlestate, movestate, jumpstate, forcestateupdate);
}
if (inJump) if (inJump)
mMovementAnimationControlled = false; mMovementAnimationControlled = false;
@ -1894,7 +1894,7 @@ void CharacterController::playGroup(const std::string &groupname, int mode, int
mIdleState = CharState_SpecialIdle; mIdleState = CharState_SpecialIdle;
mAnimation->play(groupname, Priority_Default, mAnimation->play(groupname, Priority_Default,
MWRender::Animation::Group_All, false, 1.0f, MWRender::Animation::BlendMask_All, false, 1.0f,
((mode==2) ? "loop start" : "start"), "stop", 0.0f, count-1); ((mode==2) ? "loop start" : "start"), "stop", 0.0f, count-1);
} }
else if(mode == 0) else if(mode == 0)
@ -1934,7 +1934,7 @@ void CharacterController::forceStateUpdate()
return; return;
clearAnimQueue(); clearAnimQueue();
refreshCurrentAnims(mIdleState, mMovementState, true); refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true);
if(mDeathState != CharState_None) if(mDeathState != CharState_None)
{ {
playRandomDeath(); playRandomDeath();
@ -2052,12 +2052,13 @@ void CharacterController::setAttackingOrSpell(bool attackingOrSpell)
bool CharacterController::readyToPrepareAttack() const bool CharacterController::readyToPrepareAttack() const
{ {
return mHitState == CharState_None && mUpperBodyState <= UpperCharState_WeapEquiped; return (mHitState == CharState_None || mHitState == CharState_Block)
&& mUpperBodyState <= UpperCharState_WeapEquiped;
} }
bool CharacterController::readyToStartAttack() const bool CharacterController::readyToStartAttack() const
{ {
if (mHitState != CharState_None) if (mHitState != CharState_None && mHitState != CharState_Block)
return false; return false;
if (mPtr.getClass().hasInventoryStore(mPtr) || mPtr.getClass().isBipedal(mPtr)) if (mPtr.getClass().hasInventoryStore(mPtr) || mPtr.getClass().isBipedal(mPtr))

View file

@ -32,6 +32,7 @@ enum Priority {
Priority_Movement, Priority_Movement,
Priority_Hit, Priority_Hit,
Priority_Weapon, Priority_Weapon,
Priority_Block,
Priority_Knockdown, Priority_Knockdown,
Priority_Torch, Priority_Torch,
Priority_Storm, Priority_Storm,
@ -185,7 +186,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener
void determineAttackType(); void determineAttackType();
void refreshCurrentAnims(CharacterState idle, CharacterState movement, bool force=false); void refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force=false);
void clearAnimQueue(); void clearAnimQueue();

View file

@ -3,6 +3,8 @@
#include <algorithm> #include <algorithm>
#include <components/esm/creaturestats.hpp> #include <components/esm/creaturestats.hpp>
#include <components/esm/esmreader.hpp>
#include <components/esm/esmwriter.hpp>
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"

View file

@ -6,6 +6,7 @@
#include <components/misc/rng.hpp> #include <components/misc/rng.hpp>
#include <components/esm/esmwriter.hpp>
#include <components/esm/stolenitems.hpp> #include <components/esm/stolenitems.hpp>
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
@ -1329,6 +1330,27 @@ namespace MWMechanics
return true; return true;
} }
void MechanicsManager::actorKilled(const MWWorld::Ptr &victim, const MWWorld::Ptr &attacker)
{
if (attacker.isEmpty() || attacker != MWBase::Environment::get().getWorld()->getPlayerPtr())
return;
if (victim == attacker)
return; // known to happen
if (!victim.getClass().isNpc())
return; // TODO: implement animal rights
const MWMechanics::NpcStats& victimStats = victim.getClass().getNpcStats(victim);
// 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,
// for bystanders it is not possible to tell who attacked first, anyway.
if (victimStats.getCrimeId() != -1)
MWBase::Environment::get().getMechanicsManager()->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)
{ {
if (observer.getClass().getCreatureStats(observer).isDead() || !observer.getRefData().isEnabled()) if (observer.getClass().getCreatureStats(observer).isDead() || !observer.getRefData().isEnabled())

View file

@ -120,6 +120,11 @@ namespace MWMechanics
OffenseType type, int arg=0, bool victimAware=false); OffenseType type, int arg=0, bool victimAware=false);
/// @return false if the attack was considered a "friendly hit" and forgiven /// @return false if the attack was considered a "friendly hit" and forgiven
virtual bool actorAttacked (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker); virtual bool actorAttacked (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker);
/// Notify that actor was killed, add a murder bounty if applicable
/// @note No-op for non-player attackers
virtual void actorKilled (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker);
/// Utility to check if taking this item is illegal and calling commitCrime if so /// Utility to check if taking this item is illegal and calling commitCrime if so
/// @param container The container the item is in; may be empty for an item in the world /// @param container The container the item is in; may be empty for an item in the world
virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, const MWWorld::Ptr& container, virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, const MWWorld::Ptr& container,
@ -175,13 +180,15 @@ namespace MWMechanics
/// 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 std::string& ownerid);
/// @return is \a ptr allowed to take/use \a cellref or is it a crime?
virtual bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::CellRef& cellref, MWWorld::Ptr& victim);
private: private:
void reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, void reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim,
OffenseType type, int arg=0); OffenseType type, int arg=0);
/// @return is \a ptr allowed to take/use \a cellref or is it a crime?
virtual bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::CellRef& cellref, MWWorld::Ptr& victim);
}; };
} }

View file

@ -264,7 +264,7 @@ namespace MWMechanics
float directionX = nextPoint.mX - x; float directionX = nextPoint.mX - x;
float directionY = nextPoint.mY - y; float directionY = nextPoint.mY - y;
return osg::RadiansToDegrees(std::atan2(directionX, directionY)); return std::atan2(directionX, directionY);
} }
bool PathFinder::checkPathCompleted(float x, float y, float tolerance) bool PathFinder::checkPathCompleted(float x, float y, float tolerance)

View file

@ -19,6 +19,8 @@ namespace MWMechanics
public: public:
PathFinder(); PathFinder();
static const int PathTolerance = 32;
static float sgn(float val) static float sgn(float val)
{ {
if(val > 0) if(val > 0)
@ -35,13 +37,10 @@ namespace MWMechanics
void clearPath(); void clearPath();
void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, bool checkPathCompleted(float x, float y, float tolerance = PathTolerance);
const MWWorld::CellStore* cell, bool allowShortcuts = true);
bool checkPathCompleted(float x, float y, float tolerance=32.f);
///< \Returns true if we are within \a tolerance units of the last path point. ///< \Returns true if we are within \a tolerance units of the last path point.
/// In degrees /// In radians
float getZAngleToNext(float x, float y) const; float getZAngleToNext(float x, float y) const;
bool isPathConstructed() const bool isPathConstructed() const
@ -92,6 +91,8 @@ namespace MWMechanics
} }
private: private:
void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint,
const MWWorld::CellStore* cell, bool allowShortcuts = true);
std::list<ESM::Pathgrid::Point> mPath; std::list<ESM::Pathgrid::Point> mPath;

View file

@ -313,27 +313,27 @@ namespace MWMechanics
return path; // for some reason couldn't build a path return path; // for some reason couldn't build a path
// reconstruct path to return, using world co-ordinates // reconstruct path to return, using world co-ordinates
float xCell = 0; int xCell = 0;
float yCell = 0; int yCell = 0;
if (mIsExterior) if (mIsExterior)
{ {
xCell = static_cast<float>(mPathgrid->mData.mX * ESM::Land::REAL_SIZE); xCell = mPathgrid->mData.mX * ESM::Land::REAL_SIZE;
yCell = static_cast<float>(mPathgrid->mData.mY * ESM::Land::REAL_SIZE); yCell = mPathgrid->mData.mY * ESM::Land::REAL_SIZE;
} }
while(graphParent[current] != -1) while(graphParent[current] != -1)
{ {
ESM::Pathgrid::Point pt = mPathgrid->mPoints[current]; ESM::Pathgrid::Point pt = mPathgrid->mPoints[current];
pt.mX += static_cast<int>(xCell); pt.mX += xCell;
pt.mY += static_cast<int>(yCell); pt.mY += yCell;
path.push_front(pt); path.push_front(pt);
current = graphParent[current]; current = graphParent[current];
} }
// add first node to path explicitly // add first node to path explicitly
ESM::Pathgrid::Point pt = mPathgrid->mPoints[start]; ESM::Pathgrid::Point pt = mPathgrid->mPoints[start];
pt.mX += static_cast<int>(xCell); pt.mX += xCell;
pt.mY += static_cast<int>(yCell); pt.mY += yCell;
path.push_front(pt); path.push_front(pt);
return path; return path;
} }

View file

@ -19,6 +19,8 @@
#include "../mwworld/cellstore.hpp" #include "../mwworld/cellstore.hpp"
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwrender/animation.hpp" #include "../mwrender/animation.hpp"
#include "magiceffects.hpp" #include "magiceffects.hpp"
@ -65,57 +67,6 @@ namespace
target.getClass().getCreatureStats(target).setDynamic(attribute, value); target.getClass().getCreatureStats(target).setDynamic(attribute, value);
} }
// TODO: refactor the effect tick functions in Actors so they can be reused here
void applyInstantEffectTick(MWMechanics::EffectKey effect, const MWWorld::Ptr& target, float magnitude)
{
int effectId = effect.mId;
if (effectId == ESM::MagicEffect::DamageHealth)
{
applyDynamicStatsEffect(0, target, magnitude * -1);
}
else if (effectId == ESM::MagicEffect::RestoreHealth)
{
applyDynamicStatsEffect(0, target, magnitude);
}
else if (effectId == ESM::MagicEffect::DamageFatigue)
{
applyDynamicStatsEffect(2, target, magnitude * -1);
}
else if (effectId == ESM::MagicEffect::RestoreFatigue)
{
applyDynamicStatsEffect(2, target, magnitude);
}
else if (effectId == ESM::MagicEffect::DamageMagicka)
{
applyDynamicStatsEffect(1, target, magnitude * -1);
}
else if (effectId == ESM::MagicEffect::RestoreMagicka)
{
applyDynamicStatsEffect(1, target, magnitude);
}
else if (effectId == ESM::MagicEffect::DamageAttribute || effectId == ESM::MagicEffect::RestoreAttribute)
{
int attribute = effect.mArg;
MWMechanics::AttributeValue value = target.getClass().getCreatureStats(target).getAttribute(attribute);
if (effectId == ESM::MagicEffect::DamageAttribute)
value.damage(magnitude);
else
value.restore(magnitude);
target.getClass().getCreatureStats(target).setAttribute(attribute, value);
}
else if (effectId == ESM::MagicEffect::DamageSkill || effectId == ESM::MagicEffect::RestoreSkill)
{
if (target.getTypeName() != typeid(ESM::NPC).name())
return;
int skill = effect.mArg;
MWMechanics::SkillValue& value = target.getClass().getNpcStats(target).getSkill(skill);
if (effectId == ESM::MagicEffect::DamageSkill)
value.damage(magnitude);
else
value.restore(magnitude);
}
}
} }
namespace MWMechanics namespace MWMechanics
@ -530,7 +481,14 @@ namespace MWMechanics
else else
{ {
if (hasDuration && target.getClass().isActor()) if (hasDuration && target.getClass().isActor())
applyInstantEffectTick(EffectKey(*effectIt), target, magnitude); {
bool wasDead = target.getClass().getCreatureStats(target).isDead();
effectTick(target.getClass().getCreatureStats(target), target, EffectKey(*effectIt), magnitude);
bool isDead = target.getClass().getCreatureStats(target).isDead();
if (!wasDead && isDead)
MWBase::Environment::get().getMechanicsManager()->actorKilled(target, caster);
}
else else
applyInstantEffect(target, caster, EffectKey(*effectIt), magnitude); applyInstantEffect(target, caster, EffectKey(*effectIt), magnitude);
} }
@ -960,4 +918,170 @@ namespace MWMechanics
|| (effectId >= ESM::MagicEffect::SummonFabricant || (effectId >= ESM::MagicEffect::SummonFabricant
&& effectId <= ESM::MagicEffect::SummonCreature05)); && effectId <= ESM::MagicEffect::SummonCreature05));
} }
bool disintegrateSlot (MWWorld::Ptr ptr, int slot, float disintegrate)
{
if (ptr.getClass().hasInventoryStore(ptr))
{
MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr);
MWWorld::ContainerStoreIterator item =
inv.getSlot(slot);
if (item != inv.end())
{
if (!item->getClass().hasItemHealth(*item))
return false;
int charge = item->getClass().getItemHealth(*item);
if (charge == 0)
return false;
// FIXME: charge should be a float, not int so that damage < 1 per frame can be applied.
// This was also a bug in the original engine.
charge -=
std::min(static_cast<int>(disintegrate),
charge);
item->getCellRef().setCharge(charge);
if (charge == 0)
{
// Will unequip the broken item and try to find a replacement
if (ptr != MWBase::Environment::get().getWorld()->getPlayerPtr())
inv.autoEquip(ptr);
else
inv.unequipItem(*item, ptr);
}
return true;
}
}
return false;
}
void adjustDynamicStat(CreatureStats& creatureStats, int index, float magnitude)
{
DynamicStat<float> stat = creatureStats.getDynamic(index);
stat.setCurrent(stat.getCurrent() + magnitude, index == 2);
creatureStats.setDynamic(index, stat);
}
void effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const EffectKey &effectKey, float magnitude)
{
if (magnitude == 0.f)
return;
bool receivedMagicDamage = false;
switch (effectKey.mId)
{
case ESM::MagicEffect::DamageAttribute:
{
AttributeValue attr = creatureStats.getAttribute(effectKey.mArg);
attr.damage(magnitude);
creatureStats.setAttribute(effectKey.mArg, attr);
break;
}
case ESM::MagicEffect::RestoreAttribute:
{
AttributeValue attr = creatureStats.getAttribute(effectKey.mArg);
attr.restore(magnitude);
creatureStats.setAttribute(effectKey.mArg, attr);
break;
}
case ESM::MagicEffect::RestoreHealth:
case ESM::MagicEffect::RestoreMagicka:
case ESM::MagicEffect::RestoreFatigue:
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::RestoreHealth, magnitude);
break;
case ESM::MagicEffect::DamageHealth:
case ESM::MagicEffect::DamageMagicka:
case ESM::MagicEffect::DamageFatigue:
receivedMagicDamage = true;
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::DamageHealth, -magnitude);
break;
case ESM::MagicEffect::AbsorbHealth:
case ESM::MagicEffect::AbsorbMagicka:
case ESM::MagicEffect::AbsorbFatigue:
if (magnitude > 0.f)
receivedMagicDamage = true;
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude);
break;
case ESM::MagicEffect::DisintegrateArmor:
{
// According to UESP
int priorities[] = {
MWWorld::InventoryStore::Slot_CarriedLeft,
MWWorld::InventoryStore::Slot_Cuirass,
MWWorld::InventoryStore::Slot_LeftPauldron,
MWWorld::InventoryStore::Slot_RightPauldron,
MWWorld::InventoryStore::Slot_LeftGauntlet,
MWWorld::InventoryStore::Slot_RightGauntlet,
MWWorld::InventoryStore::Slot_Helmet,
MWWorld::InventoryStore::Slot_Greaves,
MWWorld::InventoryStore::Slot_Boots
};
for (unsigned int i=0; i<sizeof(priorities)/sizeof(int); ++i)
{
if (disintegrateSlot(actor, priorities[i], magnitude))
break;
}
break;
}
case ESM::MagicEffect::DisintegrateWeapon:
disintegrateSlot(actor, MWWorld::InventoryStore::Slot_CarriedRight, magnitude);
break;
case ESM::MagicEffect::SunDamage:
{
// isInCell shouldn't be needed, but updateActor called during game start
if (!actor.isInCell() || !actor.getCell()->isExterior())
break;
float time = MWBase::Environment::get().getWorld()->getTimeStamp().getHour();
float timeDiff = std::min(7.f, std::max(0.f, std::abs(time - 13)));
float damageScale = 1.f - timeDiff / 7.f;
// When cloudy, the sun damage effect is halved
static float fMagicSunBlockedMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
"fMagicSunBlockedMult")->getFloat();
int weather = MWBase::Environment::get().getWorld()->getCurrentWeather();
if (weather > 1)
damageScale *= fMagicSunBlockedMult;
adjustDynamicStat(creatureStats, 0, -magnitude * damageScale);
if (magnitude * damageScale > 0.f)
receivedMagicDamage = true;
break;
}
case ESM::MagicEffect::FireDamage:
case ESM::MagicEffect::ShockDamage:
case ESM::MagicEffect::FrostDamage:
case ESM::MagicEffect::Poison:
{
adjustDynamicStat(creatureStats, 0, -magnitude);
receivedMagicDamage = true;
break;
}
case ESM::MagicEffect::DamageSkill:
case ESM::MagicEffect::RestoreSkill:
{
if (!actor.getClass().isNpc())
break;
NpcStats &npcStats = actor.getClass().getNpcStats(actor);
SkillValue& skill = npcStats.getSkill(effectKey.mArg);
if (effectKey.mId == ESM::MagicEffect::RestoreSkill)
skill.restore(magnitude);
else
skill.damage(magnitude);
break;
}
}
if (receivedMagicDamage && actor == MWBase::Environment::get().getWorld()->getPlayerPtr())
MWBase::Environment::get().getWindowManager()->activateHitOverlay(false);
}
} }

View file

@ -17,6 +17,7 @@ namespace MWMechanics
{ {
struct EffectKey; struct EffectKey;
class MagicEffects; class MagicEffects;
class CreatureStats;
ESM::Skill::SkillEnum spellSchoolToSkill(int school); ESM::Skill::SkillEnum spellSchoolToSkill(int school);
@ -60,6 +61,8 @@ namespace MWMechanics
int getEffectiveEnchantmentCastCost (float castCost, const MWWorld::Ptr& actor); int getEffectiveEnchantmentCastCost (float castCost, const MWWorld::Ptr& actor);
void effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const MWMechanics::EffectKey& effectKey, float magnitude);
class CastSpell class CastSpell
{ {
private: private:

View file

@ -5,6 +5,7 @@
#include <components/esm/loadspel.hpp> #include <components/esm/loadspel.hpp>
#include <components/esm/spellstate.hpp> #include <components/esm/spellstate.hpp>
#include <components/misc/rng.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"

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