Update to upstream/master. Resolve merge conflicts

sceneinput
Stanislav Bas 10 years ago
commit 03c2e11961

1
.gitignore vendored

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

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

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

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

@ -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 apt-add-repository ppa:openmw/openmw
echo "yes" | sudo apt-add-repository ppa:boost-latest/ppa
sudo apt-get update -qq
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 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

@ -20,7 +20,7 @@ message(STATUS "Configuring OpenMW...")
set(OPENMW_VERSION_MAJOR 0)
set(OPENMW_VERSION_MINOR 36)
set(OPENMW_VERSION_RELEASE 0)
set(OPENMW_VERSION_RELEASE 1)
set(OPENMW_VERSION_COMMITHASH "")
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(BOOST_STATIC "Link static build of Boost 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)
@ -199,9 +201,67 @@ IF(BOOST_STATIC)
set(Boost_USE_STATIC_LIBS ON)
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})
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)
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")
@ -366,6 +426,7 @@ IF(NOT WIN32 AND NOT APPLE)
# Install global configuration files
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}/resources/version" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
INSTALL(FILES "${OpenMW_BINARY_DIR}/gamecontrollerdb.txt" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
IF(BUILD_OPENCS)
@ -381,6 +442,7 @@ if(WIN32)
FILE(GLOB dll_files "${OpenMW_BINARY_DIR}/Release/*.dll")
INSTALL(FILES ${dll_files} DESTINATION ".")
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}/README.md" DESTINATION "." RENAME "README.txt")
INSTALL(FILES

@ -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)
OpenMW is an attempt at recreating the engine for the popular role-playing game
Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work.
OpenMW is 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.
* 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)
* Website: http://www.openmw.org
* 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:
* 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
---------------

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

@ -166,7 +166,9 @@ namespace ESSImport
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.flags(f);
return;
}
@ -174,7 +176,9 @@ namespace ESSImport
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.flags(f);
return; // TODO: try to recover
}
@ -185,7 +189,9 @@ namespace ESSImport
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.flags(f);
return;
}
@ -193,8 +199,10 @@ namespace ESSImport
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
<< " (2) 0x" << sub2.mFileOffset << std::endl;
std::cout.flags(f);
break; // TODO: try to recover
}
@ -203,6 +211,8 @@ namespace ESSImport
if (blacklist.find(std::make_pair(rec.mName, sub.mName)) != blacklist.end())
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
<< " (2) 0x" << sub2.mFileOffset << std::endl;
@ -235,6 +245,7 @@ namespace ESSImport
std::cout << "\033[0m";
}
std::cout << std::endl;
std::cout.flags(f);
}
}
}
@ -319,7 +330,11 @@ namespace ESSImport
else
{
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.flags(f);
}
esm.skipRecord();
}

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

@ -57,26 +57,6 @@ Launcher::MainDialog::MainDialog(QWidget *parent)
// Remove what's this? button
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();
}
@ -186,11 +166,38 @@ Launcher::FirstRunDialogResult Launcher::MainDialog::showFirstRunDialog()
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()
{
if (!setupGameSettings())
return false;
setVersionLabel();
mLauncherSettings.setContentList(mGameSettings);
if (!setupGraphicsSettings())
@ -309,11 +316,11 @@ bool Launcher::MainDialog::setupGameSettings()
mGameSettings.readUserFile(stream);
}
// Now the rest
// Now the rest - priority: user > local > global
QStringList paths;
paths.append(userPath + QString("openmw.cfg"));
paths.append(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) {
qDebug() << "Loading config file:" << qPrintable(path);

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

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

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

@ -8,6 +8,20 @@ CSMDoc::Message::Message (const CSMWorld::UniversalId& id, const std::string& me
: 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_)
: mDefault (default_)
@ -18,7 +32,7 @@ void CSMDoc::Messages::add (const CSMWorld::UniversalId& id, const std::string&
{
if (severity==Message::Severity_Default)
severity = mDefault;
mMessages.push_back (Message (id, message, hint, severity));
}

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

@ -162,7 +162,7 @@ void CSMSettings::UserSettings::buildSettingModelDefaults()
ritd->setDeclaredValues (values);
}
declareSection ("table-input", "Table Input");
declareSection ("table-input", "ID Tables");
{
QString inPlaceEdit ("Edit in Place");
QString editRecord ("Edit Record");
@ -226,7 +226,13 @@ void CSMSettings::UserSettings::buildSettingModelDefaults()
"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 edit ("Edit");
@ -266,7 +272,7 @@ void CSMSettings::UserSettings::buildSettingModelDefaults()
shiftCtrlDoubleClick->setDefaultValue (none);
shiftCtrlDoubleClick->setToolTip ("Action on shift control double click in report table:<p>" + toolTip);
}
declareSection ("search", "Search & Replace");
{
Setting *before = createSetting (Type_SpinBox, "char-before",
@ -308,7 +314,7 @@ void CSMSettings::UserSettings::buildSettingModelDefaults()
QStringList modes;
modes << "Ignore" << modeNormal << "Strict";
Setting *warnings = createSetting (Type_ComboBox, "warnings",
"Warning Mode");
warnings->setDeclaredValues (modes);
@ -318,7 +324,16 @@ void CSMSettings::UserSettings::buildSettingModelDefaults()
"<li>Normal: Report warning as a warning</li>"
"<li>Strict: Promote warning to an error</li>"
"</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");
formatInt->setDefaultValues (QStringList() << "Dark magenta");
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 "
"list go to the first/last item");
}
{
/******************************************************************
* There are three types of values:

@ -13,7 +13,7 @@ CSMTools::ReportModel::ReportModel (bool fieldColumn, bool severityColumn)
if (severityColumn)
mColumnSeverity = index++;
if (fieldColumn)
mColumnField = index++;
@ -46,7 +46,7 @@ QVariant CSMTools::ReportModel::data (const QModelIndex & index, int role) const
case Column_Type:
return static_cast<int> (mRows.at (index.row()).mId.getType());
case Column_Id:
{
CSMWorld::UniversalId id = mRows.at (index.row()).mId;
@ -56,7 +56,7 @@ QVariant CSMTools::ReportModel::data (const QModelIndex & index, int role) const
return QString ("-");
}
case Column_Hint:
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)
{
switch (mRows.at (index.row()).mSeverity)
{
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 QString::fromUtf8 (
CSMDoc::Message::toString (mRows.at (index.row()).mSeverity).c_str());
}
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)
{
beginInsertRows (QModelIndex(), mRows.size(), mRows.size());
mRows.push_back (message);
endInsertRows();

@ -62,7 +62,7 @@ int CSMWorld::Data::count (RecordBase::State state, const CollectionBase& collec
CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager)
: 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;
@ -537,14 +537,14 @@ CSMWorld::Data::~Data()
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

@ -113,7 +113,7 @@ namespace CSMWorld
std::map<std::string, std::map<ESM::RefNum, std::string> > mRefLoadCache;
int mReaderIndex;
Resource::ResourceSystem mResourceSystem;
boost::shared_ptr<Resource::ResourceSystem> mResourceSystem;
std::vector<boost::shared_ptr<ESM::ESMReader> > mReaders;
@ -138,9 +138,9 @@ namespace CSMWorld
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;

@ -50,6 +50,24 @@ QModelIndex CSMWorld::IdTableProxyModel::getModelIndex (const std::string& id, i
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)
{
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
{
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);
}
@ -68,3 +100,13 @@ void CSMWorld::IdTableProxyModel::refreshFilter()
updateColumnMap();
invalidateFilter();
}
void CSMWorld::IdTableProxyModel::sourceRowsChanged(const QModelIndex &/*parent*/, int /*start*/, int /*end*/)
{
refreshFilter();
}
void CSMWorld::IdTableProxyModel::sourceDataChanged(const QModelIndex &/*topLeft*/, const QModelIndex &/*bottomRight*/)
{
refreshFilter();
}

@ -11,6 +11,8 @@
#include "../filter/node.hpp"
#include "columns.hpp"
namespace CSMWorld
{
class IdTableProxyModel : public QSortFilterProxyModel
@ -20,6 +22,11 @@ namespace CSMWorld
boost::shared_ptr<CSMFilter::Node> mFilter;
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:
void updateColumnMap();
@ -30,6 +37,8 @@ namespace CSMWorld
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 refreshFilter();
@ -39,6 +48,12 @@ namespace CSMWorld
bool lessThan(const QModelIndex &left, const QModelIndex &right) 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);
};
}

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

@ -161,8 +161,19 @@ namespace CSMWorld
template<typename ESXRecordT, typename IdAccessorT>
int NestedIdCollection<ESXRecordT, IdAccessorT>::getNestedColumnsCount(int row, int column) const
{
return getAdapter(Collection<ESXRecordT, IdAccessorT>::getColumn(column)).getColumnsCount(
Collection<ESXRecordT, IdAccessorT>::getRecord(row));
const ColumnBase &nestedColumn = Collection<ESXRecordT, IdAccessorT>::getColumn(column);
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>

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

@ -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();
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,
esmLand->mY);

@ -116,7 +116,7 @@ const CSMWorld::CellRef& CSVRender::Object::getReference() const
CSVRender::Object::Object (CSMWorld::Data& data, osg::Group* parentNode,
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;
parentNode->addChild(mBaseNode);

@ -8,7 +8,7 @@
CSVRender::PreviewWidget::PreviewWidget (CSMWorld::Data& data,
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);

@ -13,6 +13,7 @@
#include <osg/LightModel>
#include <components/resource/scenemanager.hpp>
#include <components/resource/resourcesystem.hpp>
#include "../widget/scenetoolmode.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)
, mSceneManager(sceneManager)
, mResourceSystem(resourceSystem)
, mLighting(NULL)
, mHasDefaultAmbient(false)
{
@ -147,7 +148,7 @@ SceneWidget::SceneWidget(Resource::SceneManager* sceneManager, QWidget *parent,
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
mSceneManager->releaseGLObjects(mView->getCamera()->getGraphicsContext()->getState());
mResourceSystem->getSceneManager()->releaseGLObjects(mView->getCamera()->getGraphicsContext()->getState());
}
void SceneWidget::setLighting(Lighting *lighting)

@ -4,6 +4,8 @@
#include <QWidget>
#include <QTimer>
#include <boost/shared_ptr.hpp>
#include "lightingday.hpp"
#include "lightingnight.hpp"
#include "lightingbright.hpp"
@ -13,7 +15,7 @@
namespace Resource
{
class SceneManager;
class ResourceSystem;
}
namespace osg
@ -57,7 +59,7 @@ namespace CSVRender
{
Q_OBJECT
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();
CSVWidget::SceneToolMode *makeLightingSelector (CSVWidget::SceneToolbar *parent);
@ -73,7 +75,7 @@ namespace CSVRender
void setAmbient(const osg::Vec4f& ambient);
Resource::SceneManager* mSceneManager;
boost::shared_ptr<Resource::ResourceSystem> mResourceSystem;
Lighting* mLighting;

@ -24,7 +24,7 @@
#include "editmode.hpp"
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)
{
setAcceptDrops(true);

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

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

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

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

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

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

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

@ -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);
}
}

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

@ -4,7 +4,8 @@
#include <QStatusBar>
#include <QStackedLayout>
#include <QLabel>
#include <QSplitter>
#include <QTimer>
#include "../../model/doc/document.hpp"
#include "../../model/world/universalid.hpp"
@ -15,45 +16,96 @@
#include "../../model/settings/usersettings.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)
: 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;
layout->setContentsMargins (QMargins (0, 0, 0, 0));
std::vector<std::string> selection (1, id.getId());
mCommandDispatcher.setSelection (selection);
mMain = new QSplitter (this);
mMain->setOrientation (Qt::Vertical);
mLayout.addWidget (mMain, 2);
mBottom = new QWidget(this);
QStackedLayout *bottmLayout = new QStackedLayout(mBottom);
bottmLayout->setContentsMargins (0, 0, 0, 0);
QStatusBar *statusBar = new QStatusBar(mBottom);
mStatus = new QLabel(mBottom);
statusBar->addWidget (mStatus);
bottmLayout->addWidget (statusBar);
mBottom->setLayout (bottmLayout);
mEditor = new ScriptEdit (mDocument, ScriptHighlighter::Mode_General, this);
mMain->addWidget (mEditor);
mMain->setCollapsible (0, false);
layout->addWidget (mBottom, 0);
layout->insertWidget (0, mEditor = new ScriptEdit (mDocument, ScriptHighlighter::Mode_General, this), 2);
mErrors = new ScriptErrorTable (document, this);
mMain->addWidget (mErrors);
QWidget *widget = new QWidget;
widget->setLayout (layout);
QWidget *widget = new QWidget (this);;
widget->setLayout (&mLayout);
setWidget (widget);
mModel = &dynamic_cast<CSMWorld::IdTable&> (
*document.getData().getTableModel (CSMWorld::UniversalId::Type_Scripts));
for (int i=0; i<mModel->columnCount(); ++i)
if (mModel->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display)==
CSMWorld::ColumnBase::Display_ScriptFile)
{
mColumn = i;
break;
}
mColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_ScriptText);
mStateColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Modification);
QString source = mModel->data (mModel->getModelIndex (id.getId(), mColumn)).toString();
if (mColumn==-1)
throw std::logic_error ("Can't find script column");
mEditor->setPlainText (source);
// bottom box and buttons
mBottom = new TableBottomBox (CreatorFactory<GenericCreator>(), document, id, this);
mEditor->setPlainText (mModel->data (mModel->getModelIndex (id.getId(), mColumn)).toString());
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 (mModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)),
@ -64,35 +116,80 @@ CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc:
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)
{
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");
mBottom->setVisible(showLinenum == "true");
}
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::updateStatusBar ()
void CSVWorld::ScriptSubView::setStatusBar (bool show)
{
std::ostringstream stream;
stream << "(" << mEditor->textCursor().blockNumber() + 1 << ", "
<< mEditor->textCursor().columnNumber() + 1 << ")";
mBottom->setStatusBar (show);
}
mStatus->setText (QString::fromUtf8 (stream.str().c_str()));
void CSVWorld::ScriptSubView::updateStatusBar ()
{
mBottom->positionChanged (mEditor->textCursor().blockNumber() + 1,
mEditor->textCursor().columnNumber() + 1);
}
void CSVWorld::ScriptSubView::setEditLock (bool locked)
{
mEditor->setReadOnly (locked);
if (mButtons)
mButtons->setEditLock (locked);
mCommandDispatcher.setEditLock (locked);
}
void CSVWorld::ScriptSubView::useHint (const std::string& hint)
@ -129,8 +226,12 @@ void CSVWorld::ScriptSubView::textChanged()
ScriptEdit::ChangeLock lock (*mEditor);
QString source = mEditor->toPlainText();
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)
@ -142,12 +243,21 @@ void CSVWorld::ScriptSubView::dataChanged (const QModelIndex& topLeft, const QMo
QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn);
if (index.row()>=topLeft.row() && index.row()<=bottomRight.row() &&
index.column()>=topLeft.column() && index.column()<=bottomRight.column())
if (index.row()>=topLeft.row() && index.row()<=bottomRight.row())
{
QTextCursor cursor = mEditor->textCursor();
mEditor->setPlainText (mModel->data (index).toString());
mEditor->setTextCursor (cursor);
if (mStateColumn>=topLeft.column() && mStateColumn<=bottomRight.column())
updateDeletedState();
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();
}
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());
}

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

@ -679,10 +679,6 @@ void CSVWorld::Table::tableSizeUpdate()
}
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()

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

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

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

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

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

@ -23,6 +23,7 @@ namespace MWWorld
{
class Ptr;
class CellStore;
class CellRef;
}
namespace Loading
@ -128,6 +129,11 @@ namespace MWBase
OffenseType type, int arg=0, bool victimAware=false) = 0;
/// @return false if the attack was considered a "friendly hit" and forgiven
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
/// @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,
@ -211,6 +217,8 @@ namespace MWBase
/// Has the player stolen this item from the given owner?
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;
};
}

@ -25,20 +25,16 @@
#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
{
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
{
return ptr.get<ESM::Miscellaneous>()->mBase->mId;

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

@ -751,12 +751,7 @@ namespace MWClass
attacker.getClass().getNpcStats(attacker).addWerewolfKill();
}
// 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.
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);
MWBase::Environment::get().getMechanicsManager()->actorKilled(ptr, attacker);
}
}

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

@ -203,6 +203,8 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c
return false; // script does not have a variable of this 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();

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

@ -132,134 +132,141 @@ namespace MWGui
void CharacterCreation::spawnDialog(const char id)
{
switch (id)
try
{
case GM_Name:
MWBase::Environment::get().getWindowManager()->removeDialog(mNameDialog);
mNameDialog = 0;
mNameDialog = new TextInputDialog();
mNameDialog->setTextLabel(MWBase::Environment::get().getWindowManager()->getGameSettingString("sName", "Name"));
mNameDialog->setTextInput(mPlayerName);
mNameDialog->setNextButtonShow(mCreationStage >= CSE_NameChosen);
mNameDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onNameDialogDone);
mNameDialog->setVisible(true);
break;
switch (id)
{
case GM_Name:
MWBase::Environment::get().getWindowManager()->removeDialog(mNameDialog);
mNameDialog = 0;
mNameDialog = new TextInputDialog();
mNameDialog->setTextLabel(MWBase::Environment::get().getWindowManager()->getGameSettingString("sName", "Name"));
mNameDialog->setTextInput(mPlayerName);
mNameDialog->setNextButtonShow(mCreationStage >= CSE_NameChosen);
mNameDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onNameDialogDone);
mNameDialog->setVisible(true);
break;
case GM_Race:
MWBase::Environment::get().getWindowManager()->removeDialog(mRaceDialog);
mRaceDialog = 0;
mRaceDialog = new RaceDialog(mViewer, mResourceSystem);
mRaceDialog->setNextButtonShow(mCreationStage >= CSE_RaceChosen);
mRaceDialog->setRaceId(mPlayerRaceId);
mRaceDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onRaceDialogDone);
mRaceDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onRaceDialogBack);
mRaceDialog->setVisible(true);
if (mCreationStage < CSE_NameChosen)
mCreationStage = CSE_NameChosen;
break;
case GM_Race:
MWBase::Environment::get().getWindowManager()->removeDialog(mRaceDialog);
mRaceDialog = 0;
mRaceDialog = new RaceDialog(mViewer, mResourceSystem);
mRaceDialog->setNextButtonShow(mCreationStage >= CSE_RaceChosen);
mRaceDialog->setRaceId(mPlayerRaceId);
mRaceDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onRaceDialogDone);
mRaceDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onRaceDialogBack);
mRaceDialog->setVisible(true);
if (mCreationStage < CSE_NameChosen)
mCreationStage = CSE_NameChosen;
break;
case GM_Class:
MWBase::Environment::get().getWindowManager()->removeDialog(mClassChoiceDialog);
mClassChoiceDialog = 0;
mClassChoiceDialog = new ClassChoiceDialog();
mClassChoiceDialog->eventButtonSelected += MyGUI::newDelegate(this, &CharacterCreation::onClassChoice);
mClassChoiceDialog->setVisible(true);
if (mCreationStage < CSE_RaceChosen)
mCreationStage = CSE_RaceChosen;
break;
case GM_Class:
MWBase::Environment::get().getWindowManager()->removeDialog(mClassChoiceDialog);
mClassChoiceDialog = 0;
mClassChoiceDialog = new ClassChoiceDialog();
mClassChoiceDialog->eventButtonSelected += MyGUI::newDelegate(this, &CharacterCreation::onClassChoice);
mClassChoiceDialog->setVisible(true);
if (mCreationStage < CSE_RaceChosen)
mCreationStage = CSE_RaceChosen;
break;
case GM_ClassPick:
MWBase::Environment::get().getWindowManager()->removeDialog(mPickClassDialog);
mPickClassDialog = 0;
mPickClassDialog = new PickClassDialog();
mPickClassDialog->setNextButtonShow(mCreationStage >= CSE_ClassChosen);
mPickClassDialog->setClassId(mPlayerClass.mName);
mPickClassDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onPickClassDialogDone);
mPickClassDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onPickClassDialogBack);
mPickClassDialog->setVisible(true);
if (mCreationStage < CSE_RaceChosen)
mCreationStage = CSE_RaceChosen;
break;
case GM_ClassPick:
MWBase::Environment::get().getWindowManager()->removeDialog(mPickClassDialog);
mPickClassDialog = 0;
mPickClassDialog = new PickClassDialog();
mPickClassDialog->setNextButtonShow(mCreationStage >= CSE_ClassChosen);
mPickClassDialog->setClassId(mPlayerClass.mName);
mPickClassDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onPickClassDialogDone);
mPickClassDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onPickClassDialogBack);
mPickClassDialog->setVisible(true);
if (mCreationStage < CSE_RaceChosen)
mCreationStage = CSE_RaceChosen;
break;
case GM_Birth:
MWBase::Environment::get().getWindowManager()->removeDialog(mBirthSignDialog);
mBirthSignDialog = 0;
mBirthSignDialog = new BirthDialog();
mBirthSignDialog->setNextButtonShow(mCreationStage >= CSE_BirthSignChosen);
mBirthSignDialog->setBirthId(mPlayerBirthSignId);
mBirthSignDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onBirthSignDialogDone);
mBirthSignDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onBirthSignDialogBack);
mBirthSignDialog->setVisible(true);
if (mCreationStage < CSE_ClassChosen)
mCreationStage = CSE_ClassChosen;
break;
case GM_Birth:
MWBase::Environment::get().getWindowManager()->removeDialog(mBirthSignDialog);
mBirthSignDialog = 0;
mBirthSignDialog = new BirthDialog();
mBirthSignDialog->setNextButtonShow(mCreationStage >= CSE_BirthSignChosen);
mBirthSignDialog->setBirthId(mPlayerBirthSignId);
mBirthSignDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onBirthSignDialogDone);
mBirthSignDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onBirthSignDialogBack);
mBirthSignDialog->setVisible(true);
if (mCreationStage < CSE_ClassChosen)
mCreationStage = CSE_ClassChosen;
break;
case GM_ClassCreate:
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);
case GM_ClassCreate:
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);
{
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() );
}
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);
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);
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->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;
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;
}
}

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

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

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

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

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

@ -10,7 +10,6 @@
#include <components/settings/settings.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/world.hpp"
@ -518,7 +517,19 @@ namespace MWGui
{
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)
{
mHealth->setVisible(visible);

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

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

@ -10,6 +10,8 @@
#include <osg/Texture2D>
#include <components/misc/stringops.hpp>
#include <components/myguiplatform/myguitexture.hpp>
#include <components/settings/settings.hpp>
@ -19,12 +21,12 @@
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/scriptmanager.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/action.hpp"
#include "../mwscript/interpretercontext.hpp"
#include "../mwbase/scriptmanager.hpp"
#include "../mwrender/characterpreview.hpp"
#include "itemview.hpp"
@ -432,6 +434,20 @@ namespace MWGui
{
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 (!script.empty()
// 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);
action->execute (MWBase::Environment::get().getWorld()->getPlayerPtr());
action->execute (player);
mSkippedToEquip = MWWorld::Ptr();
}

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

@ -4,8 +4,6 @@
#include <MyGUI_Gui.h>
#include <MyGUI_RenderManager.h>
#include <components/version/version.hpp>
#include <components/widgets/imagebutton.hpp>
#include <components/settings/settings.hpp>
#include <components/vfs/manager.hpp>
@ -14,12 +12,8 @@
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/journal.hpp"
#include "../mwbase/dialoguemanager.hpp"
#include "../mwbase/statemanager.hpp"
#include "../mwstate/character.hpp"
#include "savegamedialog.hpp"
#include "confirmationdialog.hpp"
#include "backgroundimage.hpp"
@ -28,7 +22,7 @@
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")
, mWidth (w), mHeight (h)
, mVFS(vfs), mButtonBox(0)
@ -38,20 +32,7 @@ namespace MWGui
, mSaveGameDialog(NULL)
{
getWidget(mVersionText, "VersionText");
std::stringstream sstream;
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);
mVersionText->setCaption(versionDescription);
mHasAnimatedMenu = mVFS->exists("video/menu_background.bik");

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

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

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

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

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

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

@ -233,6 +233,7 @@ namespace MWGui
if (mResolutionList->findItemIndexWith(str) == MyGUI::ITEM_NONE)
mResolutionList->addItem(str);
}
highlightCurrentResolution();
std::string tf = Settings::Manager::getString("texture filtering", "General");
mTextureFilteringButton->setCaption(textureFilteringToStr(tf));
@ -299,8 +300,28 @@ namespace MWGui
}
void SettingsWindow::onResolutionCancel()
{
highlightCurrentResolution();
}
void SettingsWindow::highlightCurrentResolution()
{
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)

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

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

@ -38,6 +38,7 @@ namespace MWGui
, mLastMouseY(0)
, mEnabled(true)
, mFullHelp(false)
, mShowOwned(0)
{
getWidget(mDynamicToolTipBox, "DynamicToolTipBox");
@ -55,6 +56,8 @@ namespace MWGui
{
mMainWidget->getChildAt(i)->setVisible(false);
}
mShowOwned = Settings::Manager::getInt("show owned", "Game");
}
void ToolTips::setEnabled(bool enabled)
@ -346,10 +349,41 @@ namespace MWGui
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)
{
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 image = info.icon;

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

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

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

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

@ -22,6 +22,9 @@
#include <components/sdlutil/sdlcursormanager.hpp>
#include <components/esm/esmreader.hpp>
#include <components/esm/esmwriter.hpp>
#include <components/fontloader/fontloader.hpp>
#include <components/resource/resourcesystem.hpp>
@ -42,6 +45,7 @@
#include "../mwbase/inputmanager.hpp"
#include "../mwbase/statemanager.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwrender/vismask.hpp"
@ -55,8 +59,6 @@
#include "../mwrender/localmap.hpp"
#include "../mwsound/soundmanagerimp.hpp"
#include "console.hpp"
#include "journalwindow.hpp"
#include "journalviewmodel.hpp"
@ -110,7 +112,7 @@ namespace MWGui
WindowManager::WindowManager(
osgViewer::Viewer* viewer, osg::Group* guiRoot, Resource::ResourceSystem* resourceSystem
, 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)
, mViewer(viewer)
, mConsoleOnlyScripts(consoleOnlyScripts)
@ -184,6 +186,8 @@ namespace MWGui
, mRestAllowed(true)
, mFPS(0.0f)
, mFallbackMap(fallbackMap)
, mShowOwned(0)
, mVersionDescription(versionDescription)
{
float uiScale = Settings::Manager::getFloat("scaling factor", "GUI");
mGuiPlatform = new osgMyGUI::Platform(viewer, guiRoot, resourceSystem->getTextureManager(), uiScale);
@ -232,8 +236,9 @@ namespace MWGui
MyGUI::InputManager::getInstance().eventChangeKeyFocus += MyGUI::newDelegate(this, &WindowManager::onKeyFocusChanged);
// Create all cursors in advance
createCursors();
onCursorChange(MyGUI::PointerManager::getInstance().getDefaultPointer());
mCursorManager->setEnabled(true);
// hide mygui's pointer
@ -257,6 +262,8 @@ namespace MWGui
MyGUI::ClipboardManager::getInstance().eventClipboardChanged += MyGUI::newDelegate(this, &WindowManager::onClipboardChanged);
MyGUI::ClipboardManager::getInstance().eventClipboardRequested += MyGUI::newDelegate(this, &WindowManager::onClipboardRequested);
mShowOwned = Settings::Manager::getInt("show owned", "Game");
}
void WindowManager::initUI()
@ -268,7 +275,7 @@ namespace MWGui
mDragAndDrop = new DragAndDrop();
mRecharge = new Recharge();
mMenu = new MainMenu(w, h, mResourceSystem->getVFS());
mMenu = new MainMenu(w, h, mResourceSystem->getVFS(), mVersionDescription);
mLocalMapRender = new MWRender::LocalMap(mViewer);
mMap = new MapWindow(mCustomMarkers, mDragAndDrop, mLocalMapRender);
trackWindow(mMap, "map");
@ -893,6 +900,9 @@ namespace MWGui
void WindowManager::updateMap()
{
if (!mLocalMapRender)
return;
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
osg::Vec3f playerPosition = player.getRefData().getPosition().asVec3();
@ -1031,6 +1041,12 @@ namespace MWGui
void WindowManager::setFocusObject(const MWWorld::Ptr& 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)
@ -1078,11 +1094,22 @@ namespace MWGui
void WindowManager::onRetrieveTag(const MyGUI::UString& _tag, MyGUI::UString& _result)
{
std::string tag(_tag);
std::string MyGuiPrefix = "setting=";
size_t MyGuiPrefixLength = MyGuiPrefix.length();
std::string tokenToFind = "sCell=";
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));
}
@ -1175,31 +1202,7 @@ namespace MWGui
void WindowManager::onCursorChange(const std::string &name)
{
if(!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);
}
}
mCursorManager->cursorChanged(name);
}
void WindowManager::popGuiMode()
@ -1666,10 +1669,10 @@ namespace MWGui
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);
(*it).save(writer);
it->second.save(writer);
writer.endRecord(ESM::REC_MARK);
}
}
@ -1957,6 +1960,33 @@ namespace MWGui
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()
{
{

@ -116,7 +116,7 @@ namespace MWGui
WindowManager(osgViewer::Viewer* viewer, osg::Group* guiRoot, Resource::ResourceSystem* resourceSystem,
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();
void initUI();
@ -486,11 +486,16 @@ namespace MWGui
float mFPS;
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.
* Supported syntax:
* #{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)
* #{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.
@ -511,6 +516,7 @@ namespace MWGui
void onClipboardRequested(const std::string& _type, std::string& _data);
void createTextures();
void createCursors();
void setMenuTransparency(float value);
};
}

@ -4,12 +4,13 @@
#include <osg/PositionAttitudeTransform>
#include <components/esm/esmreader.hpp>
#include <components/esm/esmwriter.hpp>
#include <components/esm/loadnpc.hpp>
#include "../mwworld/esmstore.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwworld/manualref.hpp"
#include "../mwworld/actionequip.hpp"
#include "../mwworld/player.hpp"
@ -19,7 +20,7 @@
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwrender/animation.hpp"
#include "../mwmechanics/spellcasting.hpp"
#include "npcstats.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
{
MWWorld::Ptr mActor;
@ -515,8 +478,12 @@ namespace MWMechanics
bool wasDead = creatureStats.isDead();
// FIXME: effect ticks should go into separate functions so they can be used with either
// magnitude (instant effect) or magnitude*duration
// tickable effects (i.e. effects having a lasting impact after expiry)
// 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
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::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);
}
@ -558,12 +522,6 @@ namespace MWMechanics
// Fatigue can be decreased below zero meaning the actor will be knocked out
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);
}
@ -591,90 +549,11 @@ namespace MWMechanics
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())
{
// The actor was killed by a magic effect. Figure out if the player was responsible for it.
const ActiveSpells& spells = creatureStats.getActiveSpells();
bool killedByPlayer = false;
bool murderedByPlayer = false;
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
for (ActiveSpells::TIterator it = spells.begin(); it != spells.end(); ++it)
{
@ -684,33 +563,29 @@ namespace MWMechanics
{
int effectId = effectIt->mEffectId;
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)
{
if (damageEffects[i] == effectId)
isDamageEffect = true;
}
if (effectId == ESM::MagicEffect::DamageHealth || effectId == ESM::MagicEffect::AbsorbHealth)
isDamageEffect = true;
MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spell.mCasterActorId);
if (isDamageEffect && caster == player)
{
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)
MWBase::Environment::get().getMechanicsManager()->commitCrime(player, ptr, MWBase::MechanicsManager::OT_Murder);
if (killedByPlayer && player.getClass().getNpcStats(player).isWerewolf())
player.getClass().getNpcStats(player).addWerewolfKill();
if (killedByPlayer)
{
MWBase::Environment::get().getMechanicsManager()->actorKilled(ptr, player);
if (player.getClass().getNpcStats(player).isWerewolf())
player.getClass().getNpcStats(player).addWerewolfKill();
}
}
// TODO: dirty flag for magic effects to avoid some unnecessary work below?
@ -794,9 +669,6 @@ namespace MWMechanics
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::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();
// 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()->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"));
isBattleMusic = true;
currentMusic = 2;
}
static float sneakTimer = 0.f; // times update of sneak icon

@ -36,13 +36,13 @@ namespace
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 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(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(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;

@ -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[1] = 1;
// 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
@ -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
}
zTurn(actor, osg::DegreesToRadians(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])));
zTurn(actor, mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]));
return false;
}

@ -184,6 +184,11 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac
const ESM::Position &targetPos = target.getRefData().getPosition();
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)
{
nearestDist = distTo;

@ -1,5 +1,7 @@
#include "aiwander.hpp"
#include <cfloat>
#include <components/misc/rng.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_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] =
{
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.
struct AiWanderStorage : AiTemporaryBase
{
// the z rotation angle (degrees) we want to reach
// used every frame when mRotate is true
// the z rotation angle to reach
// when mTurnActorGivingGreetingToFacePlayer is true
float mTargetAngleRadians;
bool mRotate;
bool mTurnActorGivingGreetingToFacePlayer;
float mReaction; // update some actions infrequently
@ -56,27 +64,21 @@ namespace MWMechanics
const MWWorld::CellStore* mCell; // for detecting cell change
// AiWander states
bool mChooseAction;
bool mIdleNow;
bool mMoveNow;
bool mWalking;
AiWander::WanderState mState;
unsigned short mPlayedIdle;
unsigned short mIdleAnimation;
PathFinder mPathFinder;
AiWanderStorage():
mTargetAngleRadians(0),
mRotate(false),
mTurnActorGivingGreetingToFacePlayer(false),
mReaction(0),
mSaidGreeting(AiWander::Greet_None),
mGreetingTimer(0),
mCell(NULL),
mChooseAction(true),
mIdleNow(false),
mMoveNow(false),
mWalking(false),
mPlayedIdle(0)
mState(AiWander::Wander_ChooseAction),
mIdleAnimation(0)
{};
};
@ -109,7 +111,7 @@ namespace MWMechanics
mStartTime = MWBase::Environment::get().getWorld()->getTimeStamp();
mStoredAvailableNodes = false;
mPopulateAvailableNodes = true;
}
@ -183,7 +185,7 @@ namespace MWMechanics
if(!currentCell || cellChange)
{
currentCell = actor.getCell();
mStoredAvailableNodes = false; // prob. not needed since mDistance = 0
mPopulateAvailableNodes = true;
}
cStats.setDrawState(DrawState_Nothing);
@ -192,156 +194,81 @@ namespace MWMechanics
ESM::Position pos = actor.getRefData().getPosition();
bool& idleNow = storage.mIdleNow;
bool& moveNow = storage.mMoveNow;
bool& walking = storage.mWalking;
WanderState& wanderState = storage.mState;
// Check if an idle actor is too close to a door - if so start walking
mDoorCheckDuration += duration;
if(mDoorCheckDuration >= DOOR_CHECK_INTERVAL)
{
mDoorCheckDuration = 0; // restart timer
if(mDistance && // actor is not intended to be stationary
idleNow && // but is in idle
!walking && // FIXME: some actors are idle while walking
(wanderState == Wander_IdleNow) && // but is in idle
proximityToDoor(actor, MIN_DIST_TO_DOOR_SQUARED*1.6f*1.6f)) // NOTE: checks interior cells only
{
idleNow = false;
moveNow = true;
wanderState = Wander_MoveNow;
mTrimCurrentNode = false; // just in case
}
}
// Are we there yet?
bool& chooseAction = storage.mChooseAction;
if(walking &&
storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], 64.f))
if ((wanderState == Wander_Walking) &&
storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DESTINATION_TOLERANCE))
{
stopWalking(actor, storage);
moveNow = false;
walking = false;
chooseAction = true;
wanderState = Wander_ChooseAction;
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
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;
// Returns true if evasive action needs to be taken
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
evadeObstacles(actor, storage, duration);
}
float& targetAngleRadians = storage.mTargetAngleRadians;
bool& rotate = storage.mRotate;
bool& rotate = storage.mTurnActorGivingGreetingToFacePlayer;
if (rotate)
{
// Reduce the turning animation glitch by using a *HUGE* value of
// epsilon... TODO: a proper fix might be in either the physics or the
// animation subsystem
if (zTurn(actor, targetAngleRadians, osg::DegreesToRadians(5.f)))
if (zTurn(actor, storage.mTargetAngleRadians, osg::DegreesToRadians(5.f)))
rotate = false;
}
// Check if idle animation finished
short unsigned& playedIdle = storage.mPlayedIdle;
short unsigned& idleAnimation = storage.mIdleAnimation;
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;
idleNow = false;
chooseAction = true;
wanderState = Wander_ChooseAction;
}
MWBase::World *world = MWBase::Environment::get().getWorld();
if(chooseAction)
if (wanderState == Wander_ChooseAction)
{
playedIdle = 0;
getRandomIdle(playedIdle); // NOTE: sets mPlayedIdle with a random selection
idleAnimation = getRandomIdle();
if(!playedIdle && mDistance)
if(!idleAnimation && mDistance)
{
chooseAction = false;
moveNow = true;
wanderState = Wander_MoveNow;
}
else
{
// Play idle animation and recreate vanilla (broken?) behavior of resetting start time of AIWander:
MWWorld::TimeStamp currentTime = world->getTimeStamp();
mStartTime = currentTime;
playIdle(actor, playedIdle);
chooseAction = false;
idleNow = true;
playIdle(actor, idleAnimation);
wanderState = Wander_IdleNow;
}
}
// Play idle voiced dialogue entries randomly
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");
}
playIdleDialogueRandomly(actor);
float& lastReaction = storage.mReaction;
lastReaction += duration;
@ -358,17 +285,8 @@ namespace MWMechanics
{
// End package if duration is complete or mid-night hits:
MWWorld::TimeStamp currentTime = world->getTimeStamp();
if(currentTime.getHour() >= mStartTime.getHour() + mDuration)
{
if(!mRepeat)
{
stopWalking(actor, storage);
return true;
}
else
mStartTime = currentTime;
}
else if(int(currentTime.getHour()) == 0 && currentTime.getDay() != mStartTime.getDay())
if((currentTime.getHour() >= mStartTime.getHour() + mDuration) ||
(int(currentTime.getHour()) == 0 && currentTime.getDay() != mStartTime.getDay()))
{
if(!mRepeat)
{
@ -381,7 +299,7 @@ namespace MWMechanics
}
// Initialization to discover & store allowed node points for this actor.
if(!mStoredAvailableNodes)
if (mPopulateAvailableNodes)
{
getAllowedNodes(actor, currentCell->getCell());
}
@ -399,9 +317,6 @@ namespace MWMechanics
mHasReturnPosition = false;
if (mDistance == 0 && mHasReturnPosition && (pos.asVec3() - mReturnPosition).length2() > 20*20)
{
chooseAction = false;
idleNow = false;
if (!storage.mPathFinder.isPathConstructed())
{
ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(mReturnPosition));
@ -410,137 +325,213 @@ namespace MWMechanics
ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos));
// 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())
{
moveNow = false;
walking = true;
wanderState = Wander_Walking;
}
}
}
// 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
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();
playGreetingIfPlayerGetsTooClose(actor, storage);
}
int& greetingTimer = storage.mGreetingTimer;
if (greetingState == Greet_None)
if ((wanderState == Wander_MoveNow) && mDistance)
{
// Construct a new path if there isn't one
if(!storage.mPathFinder.isPathConstructed())
{
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)
if (mAllowedNodes.size())
{
greetingState = Greet_InProgress;
MWBase::Environment::get().getDialogueManager()->say(actor, "hello");
greetingTimer = 0;
setPathToAnAllowedNode(actor, storage, pos);
}
}
}
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;
}
if(greetingState == Greet_InProgress)
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(walking)
{
stopWalking(actor, storage);
moveNow = false;
walking = false;
mObstacleCheck.clear();
idleNow = true;
getRandomIdle(playedIdle);
}
if(!rotate)
{
osg::Vec3f dir = playerPos - actorPos;
if (greetingTimer >= GREETING_SHOULD_START)
{
greetingState = Greet_InProgress;
MWBase::Environment::get().getDialogueManager()->say(actor, "hello");
greetingTimer = 0;
}
}
float faceAngleRadians = std::atan2(dir.x(), dir.y());
targetAngleRadians = faceAngleRadians;
rotate = true;
}
if (greetingTimer >= GREETING_SHOULD_END)
{
greetingState = Greet_Done;
greetingTimer = 0;
}
if (greetingState == Greet_InProgress)
{
greetingTimer++;
if (storage.mState == Wander_Walking)
{
stopWalking(actor, storage);
mObstacleCheck.clear();
storage.mState = Wander_IdleNow;
}
if (greetingState == MWMechanics::AiWander::Greet_Done)
turnActorToFacePlayer(actorPos, playerPos, storage);
if (greetingTimer >= GREETING_SHOULD_END)
{
float resetDist = 2*helloDistance;
if (playerDistSqr >= resetDist*resetDist)
greetingState = Greet_None;
greetingState = Greet_Done;
greetingTimer = 0;
}
}
if(moveNow && mDistance)
if (greetingState == MWMechanics::AiWander::Greet_Done)
{
// Construct a new path if there isn't one
if(!storage.mPathFinder.isPathConstructed())
{
assert(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;
dest.mY += currentCell->getCell()->mData.mY * ESM::Land::REAL_SIZE;
}
float resetDist = 2 * helloDistance;
if (playerDistSqr >= resetDist*resetDist)
greetingState = Greet_None;
}
}
// actor position is already in world co-ordinates
ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos));
void AiWander::turnActorToFacePlayer(const osg::Vec3f& actorPosition, const osg::Vec3f& playerPosition, AiWanderStorage& storage)
{
osg::Vec3f dir = playerPosition - actorPosition;
// don't take shortcuts for wandering
storage.mPathFinder.buildPath(start, dest, actor.getCell(), false);
float faceAngleRadians = std::atan2(dir.x(), dir.y());
storage.mTargetAngleRadians = faceAngleRadians;
storage.mTurnActorGivingGreetingToFacePlayer = true;
}
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);
}
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);
}
return false; // AiWander package not yet completed
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,
@ -611,9 +602,10 @@ namespace MWMechanics
}
}
void AiWander::getRandomIdle(short unsigned& playedIdle)
short unsigned AiWander::getRandomIdle()
{
unsigned short idleRoll = 0;
short unsigned selectedAnimation = 0;
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));
if(randSelect < idleChance && randSelect > idleRoll)
{
playedIdle = counter+2;
selectedAnimation = counter + GroupIndex_MinIdle;
idleRoll = randSelect;
}
}
return selectedAnimation;
}
void AiWander::fastForward(const MWWorld::Ptr& actor, AiState &state)
@ -635,7 +628,7 @@ namespace MWMechanics
if (mDistance == 0)
return;
if (!mStoredAvailableNodes)
if (mPopulateAvailableNodes)
getAllowedNodes(actor, actor.getCell()->getCell());
if (mAllowedNodes.empty())
@ -646,22 +639,21 @@ namespace MWMechanics
int index = Misc::Rng::rollDice(mAllowedNodes.size());
ESM::Pathgrid::Point dest = mAllowedNodes[index];
// apply a slight offset to prevent overcrowding
dest.mX += static_cast<int>(Misc::Rng::rollProbability() * 128 - 64);
dest.mY += static_cast<int>(Misc::Rng::rollProbability() * 128 - 64);
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;
}
dest.mX += OffsetToPreventOvercrowding();
dest.mY += OffsetToPreventOvercrowding();
ToWorldCoordinates(dest, actor.getCell()->getCell());
MWBase::Environment::get().getWorld()->moveObject(actor, static_cast<float>(dest.mX),
static_cast<float>(dest.mY), static_cast<float>(dest.mZ));
actor.getClass().adjustPosition(actor, false);
// 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)
@ -690,70 +682,86 @@ namespace MWMechanics
// ... pathgrids don't usually include water, so swimmers ignore them
if (mDistance && !actor.getClass().isPureWaterCreature(actor))
{
float cellXOffset = 0;
float cellYOffset = 0;
// get NPC's position in local (i.e. cell) co-ordinates
osg::Vec3f npcPos(mInitialActorPosition);
if(cell->isExterior())
{
cellXOffset = static_cast<float>(cell->mData.mX * ESM::Land::REAL_SIZE);
cellYOffset = static_cast<float>(cell->mData.mY * ESM::Land::REAL_SIZE);
npcPos[0] = npcPos[0] - static_cast<float>(cell->mData.mX * 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
// NOTE: mPoints and mAllowedNodes are in local co-ordinates
int pointIndex = 0;
for(unsigned int counter = 0; counter < pathgrid->mPoints.size(); counter++)
{
osg::Vec3f nodePos(PathFinder::MakeOsgVec3(pathgrid->mPoints[counter]));
if((npcPos - nodePos).length2() <= mDistance * mDistance)
{
mAllowedNodes.push_back(pathgrid->mPoints[counter]);
pointIndex = counter;
}
}
if(!mAllowedNodes.empty())
if (mAllowedNodes.size() == 1)
{
osg::Vec3f firstNodePos(PathFinder::MakeOsgVec3(mAllowedNodes[0]));
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);
AddNonPathGridAllowedPoints(npcPos, pathgrid, pointIndex);
}
// 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
// This is a backup option, as std::sort is potentially O(n^2) in time.
if (mAllowedNodes.empty())
if(!mAllowedNodes.empty())
{
// Start with list of PathGrid nodes, sorted by distance from actor
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);
SetCurrentNodeToClosestAllowedNode(npcPos);
}
}
// make closest node the current node
mCurrentNode = *nodeDistances[0].second;
mPopulateAvailableNodes = false;
}
// give Actor a 2nd node to walk to
mAllowedNodes.push_back(*nodeDistances[1].second);
// 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)
{
AddPointBetweenPathGridPoints(pathGrid->mPoints[it->mV0], pathGrid->mPoints[it->mV1]);
}
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

@ -63,6 +63,13 @@ namespace MWMechanics
Greet_InProgress,
Greet_Done
};
enum WanderState {
Wander_ChooseAction,
Wander_IdleNow,
Wander_MoveNow,
Wander_Walking
};
private:
// NOTE: mDistance and mDuration must be set already
void init();
@ -70,7 +77,12 @@ namespace MWMechanics
void stopWalking(const MWWorld::Ptr& actor, AiWanderStorage& storage);
void playIdle(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 mDuration;
@ -88,8 +100,8 @@ namespace MWMechanics
// if false triggers calculating allowed nodes based on mDistance
bool mStoredAvailableNodes;
// do we need to calculate allowed nodes based on mDistance
bool mPopulateAvailableNodes;
@ -118,15 +130,19 @@ namespace MWMechanics
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
static const std::string sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1];
/// record distances of pathgrid point nodes to actor
/// 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);
static int OffsetToPreventOvercrowding();
};

@ -43,6 +43,7 @@
#include "../mwworld/class.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwworld/player.hpp"
namespace
{
@ -236,7 +237,7 @@ std::string CharacterController::chooseRandomGroup (const std::string& prefix, i
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
if(mPtr.getClass().isActor())
@ -251,26 +252,28 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
{
mHitState = CharState_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);
}
else if(knockdown)
{
mHitState = CharState_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)
{
mHitState = CharState_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)
{
mHitState = CharState_Block;
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
@ -303,7 +306,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
{
mHitState = CharState_KnockDown;
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))
weap = sWeaponTypeListEnd;
if(force && mJumpState != JumpState_None)
if(force || jump != mJumpState)
{
std::string jump;
MWRender::Animation::Group jumpgroup = MWRender::Animation::Group_All;
bool startAtLoop = (jump == mJumpState);
mJumpState = jump;
std::string jumpAnimName;
MWRender::Animation::BlendMask jumpmask = MWRender::Animation::BlendMask_All;
if(mJumpState != JumpState_None)
{
jump = "jump";
jumpAnimName = "jump";
if(weap != sWeaponTypeListEnd)
{
jump += weap->shortgroup;
if(!mAnimation->hasAnimation(jump))
jumpAnimName += weap->shortgroup;
if(!mAnimation->hasAnimation(jumpAnimName))
{
jumpgroup = MWRender::Animation::Group_LowerBody;
jump = "jump";
jumpmask = MWRender::Animation::BlendMask_LowerBody;
jumpAnimName = "jump";
}
}
}
if(mJumpState == JumpState_InAir)
{
int mode = ((jump == mCurrentJump) ? 2 : 1);
mAnimation->disable(mCurrentJump);
mCurrentJump = jump;
mCurrentJump = jumpAnimName;
if (mAnimation->hasAnimation("jump"))
mAnimation->play(mCurrentJump, Priority_Jump, jumpgroup, false,
1.0f, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul);
mAnimation->play(mCurrentJump, Priority_Jump, jumpmask, false,
1.0f, (startAtLoop?"loop start":"start"), "stop", 0.0f, ~0ul);
}
else
{
mAnimation->disable(mCurrentJump);
mCurrentJump.clear();
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);
}
}
@ -353,55 +357,55 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
{
mMovementState = movement;
std::string movement;
MWRender::Animation::Group movegroup = MWRender::Animation::Group_All;
std::string movementAnimName;
MWRender::Animation::BlendMask movemask = MWRender::Animation::BlendMask_All;
const StateInfo *movestate = std::find_if(sMovementList, sMovementListEnd, FindCharState(mMovementState));
if(movestate != sMovementListEnd)
{
movement = movestate->groupname;
if(weap != sWeaponTypeListEnd && movement.find("swim") == std::string::npos)
movementAnimName = movestate->groupname;
if(weap != sWeaponTypeListEnd && movementAnimName.find("swim") == std::string::npos)
{
movement += weap->shortgroup;
if(!mAnimation->hasAnimation(movement))
movementAnimName += weap->shortgroup;
if(!mAnimation->hasAnimation(movementAnimName))
{
movegroup = MWRender::Animation::Group_LowerBody;
movement = movestate->groupname;
movemask = MWRender::Animation::BlendMask_LowerBody;
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)
{
std::string::size_type runpos = movement.find("run");
std::string::size_type runpos = movementAnimName.find("run");
if (runpos != std::string::npos)
{
movement.replace(runpos, runpos+3, "walk");
if (!mAnimation->hasAnimation(movement))
movement.clear();
movementAnimName.replace(runpos, runpos+3, "walk");
if (!mAnimation->hasAnimation(movementAnimName))
movementAnimName.clear();
}
else
movement.clear();
movementAnimName.clear();
}
else
{
movegroup = MWRender::Animation::Group_LowerBody;
movement.erase(swimpos, 4);
if(!mAnimation->hasAnimation(movement))
movement.clear();
movemask = MWRender::Animation::BlendMask_LowerBody;
movementAnimName.erase(swimpos, 4);
if(!mAnimation->hasAnimation(movementAnimName))
movementAnimName.clear();
}
}
}
/* If we're playing the same animation, restart from the loop start instead of the
* beginning. */
int mode = ((movement == mCurrentMovement) ? 2 : 1);
int mode = ((movementAnimName == mCurrentMovement) ? 2 : 1);
mMovementAnimationControlled = true;
mAnimation->disable(mCurrentMovement);
mCurrentMovement = movement;
mCurrentMovement = movementAnimName;
if(!mCurrentMovement.empty())
{
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);
}
}
@ -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),
// the idle animation should be displayed
if ((mUpperBodyState != UpperCharState_Nothing
|| mMovementState != CharState_None
|| (mMovementState != CharState_None && mMovementState != CharState_TurnLeft && mMovementState != CharState_TurnRight)
|| mHitState != CharState_None)
&& !mPtr.getClass().isBipedal(mPtr))
idle = CharState_None;
@ -486,7 +500,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
mAnimation->disable(mCurrentIdle);
mCurrentIdle = idle;
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);
}
@ -602,7 +616,7 @@ void CharacterController::playDeath(float startpoint, CharacterState death)
mCurrentJump = "";
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);
}
@ -704,7 +718,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim
if(mDeathState == CharState_None)
refreshCurrentAnims(mIdleState, mMovementState, true);
refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true);
mAnimation->runAnimation(0.f);
}
@ -868,10 +882,10 @@ void CharacterController::updateIdleStormState()
mAnimation->getInfo("idlestorm", &complete);
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);
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);
}
else
@ -882,7 +896,7 @@ void CharacterController::updateIdleStormState()
{
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);
}
}
@ -989,7 +1003,7 @@ bool CharacterController::updateCreatureState()
if (!mCurrentWeapon.empty())
{
mAnimation->play(mCurrentWeapon, Priority_Weapon,
MWRender::Animation::Group_All, true,
MWRender::Animation::BlendMask_All, true,
1, startKey, stopKey,
0.0f, 0);
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;
if(weaptype != mWeaponType && mHitState != CharState_KnockDown && mHitState != CharState_KnockOut
&& mHitState != CharState_Hit)
@ -1063,8 +1080,8 @@ bool CharacterController::updateWeaponState()
if(weaptype == WeapType_None)
{
getWeaponGroup(mWeaponType, weapgroup);
mAnimation->play(weapgroup, Priority_Weapon,
MWRender::Animation::Group_UpperBody, true,
mAnimation->play(weapgroup, priorityWeapon,
MWRender::Animation::BlendMask_All, true,
1.0f, "unequip start", "unequip stop", 0.0f, 0);
mUpperBodyState = UpperCharState_UnEquipingWeap;
}
@ -1074,8 +1091,8 @@ bool CharacterController::updateWeaponState()
mAnimation->showWeapons(false);
mAnimation->setWeaponGroup(weapgroup);
mAnimation->play(weapgroup, Priority_Weapon,
MWRender::Animation::Group_UpperBody, true,
mAnimation->play(weapgroup, priorityWeapon,
MWRender::Animation::BlendMask_All, true,
1.0f, "equip start", "equip stop", 0.0f, 0);
mUpperBodyState = UpperCharState_EquipingWeap;
@ -1145,7 +1162,7 @@ bool CharacterController::updateWeaponState()
bool animPlaying;
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);
mAttackType.clear();
@ -1154,6 +1171,10 @@ bool CharacterController::updateWeaponState()
// Unset casting flag, otherwise pressing the mouse button down would
// continue casting every frame if there is no animation
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();
@ -1191,8 +1212,8 @@ bool CharacterController::updateWeaponState()
case 2: mAttackType = "target"; break;
}
mAnimation->play(mCurrentWeapon, Priority_Weapon,
MWRender::Animation::Group_UpperBody, true,
mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::BlendMask_All, true,
weapSpeed, mAttackType+" start", mAttackType+" stop",
0.0f, 0);
mUpperBodyState = UpperCharState_CastingSpell;
@ -1223,8 +1244,8 @@ bool CharacterController::updateWeaponState()
else if(item.getTypeName() == typeid(ESM::Probe).name())
Security(mPtr).probeTrap(target, item, resultMessage, resultSound);
}
mAnimation->play(mCurrentWeapon, Priority_Weapon,
MWRender::Animation::Group_UpperBody, true,
mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::BlendMask_All, true,
1.0f, "start", "stop", 0.0, 0);
mUpperBodyState = UpperCharState_FollowStartToFollowStop;
@ -1250,8 +1271,8 @@ bool CharacterController::updateWeaponState()
determineAttackType();
}
mAnimation->play(mCurrentWeapon, Priority_Weapon,
MWRender::Animation::Group_UpperBody, false,
mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::BlendMask_All, false,
weapSpeed, mAttackType+" start", mAttackType+" min attack",
0.0f, 0);
mUpperBodyState = UpperCharState_StartToMinAttack;
@ -1301,8 +1322,8 @@ bool CharacterController::updateWeaponState()
mAttackStrength = attackStrength;
mAnimation->disable(mCurrentWeapon);
mAnimation->play(mCurrentWeapon, Priority_Weapon,
MWRender::Animation::Group_UpperBody, false,
mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::BlendMask_All, false,
weapSpeed, mAttackType+" max attack", mAttackType+" min hit",
1.0f-complete, 0);
@ -1371,15 +1392,6 @@ bool CharacterController::updateWeaponState()
mAnimation->attachArrow();
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)
mUpperBodyState = UpperCharState_Nothing;
@ -1397,8 +1409,8 @@ bool CharacterController::updateWeaponState()
case UpperCharState_MinAttackToMaxAttack:
//hack to avoid body pos desync when jumping/sneaking in 'max attack' state
if(!mAnimation->isPlaying(mCurrentWeapon))
mAnimation->play(mCurrentWeapon, Priority_Weapon,
MWRender::Animation::Group_UpperBody, false,
mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::BlendMask_All, false,
0, mAttackType+" min attack", mAttackType+" max attack", 0.999f, 0);
break;
case UpperCharState_MaxAttackToMinHit:
@ -1440,33 +1452,16 @@ bool CharacterController::updateWeaponState()
{
mAnimation->disable(mCurrentWeapon);
if (mUpperBodyState == UpperCharState_FollowStartToFollowStop)
mAnimation->play(mCurrentWeapon, Priority_Weapon,
MWRender::Animation::Group_UpperBody, true,
mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::BlendMask_All, true,
weapSpeed, start, stop, 0.0f, 0);
else
mAnimation->play(mCurrentWeapon, Priority_Weapon,
MWRender::Animation::Group_UpperBody, false,
mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::BlendMask_All, false,
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))
{
MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
@ -1475,7 +1470,7 @@ bool CharacterController::updateWeaponState()
&& 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);
}
else if (mAnimation->isPlaying("torch"))
@ -1505,7 +1500,7 @@ void CharacterController::update(float duration)
mAnimQueue.pop_front();
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);
}
}
@ -1562,6 +1557,8 @@ void CharacterController::update(float duration)
CharacterState movestate = CharState_None;
CharacterState idlestate = CharState_SpecialIdle;
JumpingState jumpstate = JumpState_None;
bool forcestateupdate = false;
mHasMovedInXY = std::abs(vec.x())+std::abs(vec.y()) > 0.0f;
@ -1644,7 +1641,7 @@ void CharacterController::update(float duration)
}
forcestateupdate = (mJumpState != JumpState_InAir);
mJumpState = JumpState_InAir;
jumpstate = JumpState_InAir;
static const float fJumpMoveBase = gmst.find("fJumpMoveBase")->getFloat();
static const float fJumpMoveMult = gmst.find("fJumpMoveMult")->getFloat();
@ -1689,7 +1686,7 @@ void CharacterController::update(float duration)
else if(mJumpState == JumpState_InAir)
{
forcestateupdate = true;
mJumpState = JumpState_Landing;
jumpstate = JumpState_Landing;
vec.z() = 0.0f;
float height = cls.getCreatureStats(mPtr).land();
@ -1720,7 +1717,7 @@ void CharacterController::update(float duration)
}
else
{
mJumpState = JumpState_None;
jumpstate = JumpState_None;
vec.z() = 0.0f;
inJump = false;
@ -1786,19 +1783,22 @@ void CharacterController::update(float duration)
mAnimQueue.pop_front();
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);
}
}
// 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)
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)
mMovementAnimationControlled = false;
@ -1894,7 +1894,7 @@ void CharacterController::playGroup(const std::string &groupname, int mode, int
mIdleState = CharState_SpecialIdle;
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);
}
else if(mode == 0)
@ -1934,7 +1934,7 @@ void CharacterController::forceStateUpdate()
return;
clearAnimQueue();
refreshCurrentAnims(mIdleState, mMovementState, true);
refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true);
if(mDeathState != CharState_None)
{
playRandomDeath();
@ -2052,12 +2052,13 @@ void CharacterController::setAttackingOrSpell(bool attackingOrSpell)
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
{
if (mHitState != CharState_None)
if (mHitState != CharState_None && mHitState != CharState_Block)
return false;
if (mPtr.getClass().hasInventoryStore(mPtr) || mPtr.getClass().isBipedal(mPtr))

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

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

@ -6,6 +6,7 @@
#include <components/misc/rng.hpp>
#include <components/esm/esmwriter.hpp>
#include <components/esm/stolenitems.hpp>
#include "../mwworld/esmstore.hpp"
@ -1329,6 +1330,27 @@ namespace MWMechanics
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)
{
if (observer.getClass().getCreatureStats(observer).isDead() || !observer.getRefData().isEnabled())

@ -120,6 +120,11 @@ namespace MWMechanics
OffenseType type, int arg=0, bool victimAware=false);
/// @return false if the attack was considered a "friendly hit" and forgiven
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
/// @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,
@ -175,13 +180,15 @@ namespace MWMechanics
/// Has the player stolen this item from the given owner?
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:
void reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim,
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);
};
}

@ -264,7 +264,7 @@ namespace MWMechanics
float directionX = nextPoint.mX - x;
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)

@ -19,6 +19,8 @@ namespace MWMechanics
public:
PathFinder();
static const int PathTolerance = 32;
static float sgn(float val)
{
if(val > 0)
@ -35,13 +37,10 @@ namespace MWMechanics
void clearPath();
void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint,
const MWWorld::CellStore* cell, bool allowShortcuts = true);
bool checkPathCompleted(float x, float y, float tolerance=32.f);
bool checkPathCompleted(float x, float y, float tolerance = PathTolerance);
///< \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;
bool isPathConstructed() const
@ -92,6 +91,8 @@ namespace MWMechanics
}
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;

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

@ -19,6 +19,8 @@
#include "../mwworld/cellstore.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwrender/animation.hpp"
#include "magiceffects.hpp"
@ -65,57 +67,6 @@ namespace
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
@ -530,7 +481,14 @@ namespace MWMechanics
else
{
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
applyInstantEffect(target, caster, EffectKey(*effectIt), magnitude);
}
@ -960,4 +918,170 @@ namespace MWMechanics
|| (effectId >= ESM::MagicEffect::SummonFabricant
&& 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);
}
}

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

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

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

Loading…
Cancel
Save