Merge branch 'master' into fatigue

0.6.3
Bret Curtis 7 years ago committed by GitHub
commit 3e4dc31e39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

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

@ -1,13 +1,32 @@
0.45.0 0.45.0
------ ------
Bug #1990: Sunrise/sunset not set correct
Bug #2222: Fatigue's effect on selling price is backwards Bug #2222: Fatigue's effect on selling price is backwards
Bug #2326: After a bound item expires the last equipped item of that type is not automatically re-equipped
Bug #2835: Player able to slowly move when overencumbered Bug #2835: Player able to slowly move when overencumbered
Bug #3374: Touch spells not hitting kwama foragers
Bug #3591: Angled hit distance too low
Bug #3629: DB assassin attack never triggers creature spawning
Bug #3876: Landscape texture painting is misaligned
Bug #3897: Have Goodbye give all choices the effects of Goodbye
Bug #3993: Terrain texture blending map is not upscaled
Bug #3997: Almalexia doesn't pace
Bug #4036: Weird behaviour of AI packages if package target has non-unique ID
Bug #4047: OpenMW not reporting its version number in MacOS; OpenMW-CS not doing it fully
Bug #4215: OpenMW shows book text after last <BR> tag
Bug #4221: Characters get stuck in V-shaped terrain Bug #4221: Characters get stuck in V-shaped terrain
Bug #4251: Stationary NPCs do not return to their position after combat
Bug #4293: Faction members are not aware of faction ownerships in barter Bug #4293: Faction members are not aware of faction ownerships in barter
Bug #4327: Missing animations during spell/weapon stance switching Bug #4327: Missing animations during spell/weapon stance switching
Bug #4368: Settings window ok button doesn't have key focus by default
Bug #4419: MRK NiStringExtraData is handled incorrectly
Bug #4426: RotateWorld behavior is incorrect Bug #4426: RotateWorld behavior is incorrect
Bug #4429: [Windows] Error on build INSTALL.vcxproj project (debug) with cmake 3.7.2
Bug #4432: Guards behaviour is incorrect if they do not have AI packages
Bug #4433: Guard behaviour is incorrect with Alarm = 0 Bug #4433: Guard behaviour is incorrect with Alarm = 0
Bug #4443: Goodbye option and dialogue choices are not mutually exclusive Feature #4324: Add CFBundleIdentifier in Info.plist to allow for macOS function key shortcuts
Feature #4345: Add equivalents for the command line commands to Launcher
Feature #4444: Per-group KF-animation files support Feature #4444: Per-group KF-animation files support
0.44.0 0.44.0

@ -3,7 +3,7 @@ OpenMW
[![Build Status](https://api.travis-ci.org/OpenMW/openmw.svg)](https://travis-ci.org/OpenMW/openmw) [![Build status](https://ci.appveyor.com/api/projects/status/github/openmw/openmw?svg=true)](https://ci.appveyor.com/project/psi29a/openmw) [![Coverity Scan Build Status](https://scan.coverity.com/projects/3740/badge.svg)](https://scan.coverity.com/projects/3740) [![Build Status](https://api.travis-ci.org/OpenMW/openmw.svg)](https://travis-ci.org/OpenMW/openmw) [![Build status](https://ci.appveyor.com/api/projects/status/github/openmw/openmw?svg=true)](https://ci.appveyor.com/project/psi29a/openmw) [![Coverity Scan Build Status](https://scan.coverity.com/projects/3740/badge.svg)](https://scan.coverity.com/projects/3740)
OpenMW is a recreation of the engine for the popular role-playing game Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work. OpenMW is a open-source game engine that supports playing Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work.
OpenMW also comes with OpenMW-CS, a replacement for Morrowind's TES Construction Set. OpenMW also comes with OpenMW-CS, a replacement for Morrowind's TES Construction Set.

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

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

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

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

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

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

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

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

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

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

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

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

@ -431,11 +431,8 @@ namespace MWDialogue
void DialogueManager::addChoice (const std::string& text, int choice) void DialogueManager::addChoice (const std::string& text, int choice)
{ {
if (!mGoodbye) mIsInChoice = true;
{ mChoices.push_back(std::make_pair(text, choice));
mIsInChoice = true;
mChoices.push_back(std::make_pair(text, choice));
}
} }
const std::vector<std::pair<std::string, int> >& DialogueManager::getChoices() const std::vector<std::pair<std::string, int> >& DialogueManager::getChoices()
@ -450,8 +447,8 @@ namespace MWDialogue
void DialogueManager::goodbye() void DialogueManager::goodbye()
{ {
if (!mIsInChoice) mIsInChoice = false;
mGoodbye = true; mGoodbye = true;
} }
void DialogueManager::persuade(int type, ResponseCallback* callback) void DialogueManager::persuade(int type, ResponseCallback* callback)

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

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

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

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

@ -50,27 +50,36 @@ bool isConscious(const MWWorld::Ptr& ptr)
return !stats.isDead() && !stats.getKnockedDown(); return !stats.isDead() && !stats.getKnockedDown();
} }
void adjustBoundItem (const std::string& item, bool bound, const MWWorld::Ptr& actor) int getBoundItemSlot (const std::string& itemId)
{ {
if (bound) static std::map<std::string, int> boundItemsMap;
if (boundItemsMap.empty())
{ {
if (actor.getClass().getContainerStore(actor).count(item) == 0) std::string boundId = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sMagicBoundBootsID")->getString();
{ boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Boots;
MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor);
MWWorld::Ptr newPtr = *store.MWWorld::ContainerStore::add(item, 1, actor); boundId = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sMagicBoundCuirassID")->getString();
MWWorld::ActionEquip action(newPtr); boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Cuirass;
action.execute(actor);
MWWorld::ConstContainerStoreIterator rightHand = store.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); boundId = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sMagicBoundLeftGauntletID")->getString();
// change draw state only if the item is in player's right hand boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_LeftGauntlet;
if (actor == MWMechanics::getPlayer()
&& rightHand != store.end() && newPtr == *rightHand) boundId = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sMagicBoundRightGauntletID")->getString();
{ boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_RightGauntlet;
MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon);
} boundId = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sMagicBoundHelmID")->getString();
} boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Helmet;
boundId = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sMagicBoundShieldID")->getString();
boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_CarriedLeft;
} }
else
actor.getClass().getInventoryStore(actor).remove(item, 1, actor, true); int slot = MWWorld::InventoryStore::Slot_CarriedRight;
std::map<std::string, int>::iterator it = boundItemsMap.find(itemId);
if (it != boundItemsMap.end())
slot = it->second;
return slot;
} }
class CheckActorCommanded : public MWMechanics::EffectSourceVisitor class CheckActorCommanded : public MWMechanics::EffectSourceVisitor
@ -139,7 +148,6 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float
namespace MWMechanics namespace MWMechanics
{ {
const float aiProcessingDistance = 7168; const float aiProcessingDistance = 7168;
const float sqrAiProcessingDistance = aiProcessingDistance*aiProcessingDistance; const float sqrAiProcessingDistance = aiProcessingDistance*aiProcessingDistance;
@ -227,6 +235,83 @@ namespace MWMechanics
} }
}; };
void Actors::addBoundItem (const std::string& itemId, const MWWorld::Ptr& actor)
{
MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor);
int slot = getBoundItemSlot(itemId);
if (actor.getClass().getContainerStore(actor).count(itemId) != 0)
return;
MWWorld::ContainerStoreIterator prevItem = store.getSlot(slot);
MWWorld::Ptr boundPtr = *store.MWWorld::ContainerStore::add(itemId, 1, actor);
MWWorld::ActionEquip action(boundPtr);
action.execute(actor);
if (actor != MWMechanics::getPlayer())
return;
MWWorld::Ptr newItem = *store.getSlot(slot);
if (newItem.isEmpty() || boundPtr != newItem)
return;
MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer();
// change draw state only if the item is in player's right hand
if (slot == MWWorld::InventoryStore::Slot_CarriedRight)
player.setDrawState(MWMechanics::DrawState_Weapon);
if (prevItem != store.end())
player.setPreviousItem(itemId, prevItem->getCellRef().getRefId());
}
void Actors::removeBoundItem (const std::string& itemId, const MWWorld::Ptr& actor)
{
MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor);
int slot = getBoundItemSlot(itemId);
MWWorld::ContainerStoreIterator currentItem = store.getSlot(slot);
bool wasEquipped = currentItem != store.end() && Misc::StringUtils::ciEqual(currentItem->getCellRef().getRefId(), itemId);
store.remove(itemId, 1, actor, true);
if (actor != MWMechanics::getPlayer())
return;
MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer();
std::string prevItemId = player.getPreviousItem(itemId);
player.erasePreviousItem(itemId);
if (prevItemId.empty())
return;
// Find the item by id
MWWorld::Ptr item;
for (MWWorld::ContainerStoreIterator iter = store.begin(); iter != store.end(); ++iter)
{
if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), prevItemId))
{
if (item.isEmpty() ||
// Prefer the stack with the lowest remaining uses
!item.getClass().hasItemHealth(*iter) ||
iter->getClass().getItemHealth(*iter) < item.getClass().getItemHealth(item))
{
item = *iter;
}
}
}
// we should equip previous item only if expired bound item was equipped.
if (item.isEmpty() || !wasEquipped)
return;
MWWorld::ActionEquip action(item);
action.execute(actor);
}
void Actors::updateActor (const MWWorld::Ptr& ptr, float duration) void Actors::updateActor (const MWWorld::Ptr& ptr, float duration)
{ {
// magic effects // magic effects
@ -756,25 +841,23 @@ namespace MWMechanics
float magnitude = effects.get(it->first).getMagnitude(); float magnitude = effects.get(it->first).getMagnitude();
if (found != (magnitude > 0)) if (found != (magnitude > 0))
{ {
if (magnitude > 0)
creatureStats.mBoundItems.insert(it->first);
else
creatureStats.mBoundItems.erase(it->first);
std::string itemGmst = it->second; std::string itemGmst = it->second;
std::string item = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find( std::string item = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
itemGmst)->getString(); itemGmst)->getString();
magnitude > 0 ? addBoundItem(item, ptr) : removeBoundItem(item, ptr);
if (it->first == ESM::MagicEffect::BoundGloves) if (it->first == ESM::MagicEffect::BoundGloves)
{ {
item = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
"sMagicBoundLeftGauntletID")->getString();
adjustBoundItem(item, magnitude > 0, ptr);
item = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find( item = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
"sMagicBoundRightGauntletID")->getString(); "sMagicBoundRightGauntletID")->getString();
adjustBoundItem(item, magnitude > 0, ptr); magnitude > 0 ? addBoundItem(item, ptr) : removeBoundItem(item, ptr);
} }
else
adjustBoundItem(item, magnitude > 0, ptr);
if (magnitude > 0)
creatureStats.mBoundItems.insert(it->first);
else
creatureStats.mBoundItems.erase(it->first);
} }
} }

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

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

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

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

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

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

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

@ -27,15 +27,36 @@ MWMechanics::AiPackage::~AiPackage() {}
MWMechanics::AiPackage::AiPackage() : MWMechanics::AiPackage::AiPackage() :
mTimer(AI_REACTION_TIME + 1.0f), // to force initial pathbuild mTimer(AI_REACTION_TIME + 1.0f), // to force initial pathbuild
mTargetActorRefId(""),
mTargetActorId(-1),
mRotateOnTheRunChecks(0), mRotateOnTheRunChecks(0),
mIsShortcutting(false), mIsShortcutting(false),
mShortcutProhibited(false), mShortcutFailPos() mShortcutProhibited(false),
mShortcutFailPos()
{ {
} }
MWWorld::Ptr MWMechanics::AiPackage::getTarget() const MWWorld::Ptr MWMechanics::AiPackage::getTarget() const
{ {
return MWWorld::Ptr(); if (mTargetActorId == -2)
return MWWorld::Ptr();
if (mTargetActorId == -1)
{
MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mTargetActorRefId, false);
if (target.isEmpty())
{
mTargetActorId = -2;
return target;
}
else
mTargetActorId = target.getClass().getCreatureStats(target).getActorId();
}
if (mTargetActorId != -1)
return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId);
else
return MWWorld::Ptr();
} }
bool MWMechanics::AiPackage::sideWithTarget() const bool MWMechanics::AiPackage::sideWithTarget() const

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

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

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

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

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

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

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

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

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

@ -1214,7 +1214,6 @@ bool CharacterController::updateWeaponState()
bool animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); bool animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete);
if (!animPlaying || complete >= 1.0f) if (!animPlaying || complete >= 1.0f)
{ {
mUpperBodyState = UpperCharState_Nothing;
forcestateupdate = true; forcestateupdate = true;
mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype)); mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype));

@ -22,6 +22,7 @@
#include "aicombat.hpp" #include "aicombat.hpp"
#include "aipursue.hpp" #include "aipursue.hpp"
#include "aitravel.hpp"
#include "spellcasting.hpp" #include "spellcasting.hpp"
#include "autocalcspell.hpp" #include "autocalcspell.hpp"
#include "npcstats.hpp" #include "npcstats.hpp"
@ -1588,9 +1589,12 @@ namespace MWMechanics
void MechanicsManager::startCombat(const MWWorld::Ptr &ptr, const MWWorld::Ptr &target) void MechanicsManager::startCombat(const MWWorld::Ptr &ptr, const MWWorld::Ptr &target)
{ {
if (ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat(target)) MWMechanics::AiSequence& aiSequence = ptr.getClass().getCreatureStats(ptr).getAiSequence();
if (aiSequence.isInCombat(target))
return; return;
ptr.getClass().getCreatureStats(ptr).getAiSequence().stack(MWMechanics::AiCombat(target), ptr);
aiSequence.stack(MWMechanics::AiCombat(target), ptr);
if (target == getPlayer()) if (target == getPlayer())
{ {
// if guard starts combat with player, guards pursuing player should do the same // if guard starts combat with player, guards pursuing player should do the same

@ -547,7 +547,7 @@ namespace MWMechanics
|| (effectIt->mEffectID == ESM::MagicEffect::CommandCreature && target.getTypeName() == typeid(ESM::Creature).name())) || (effectIt->mEffectID == ESM::MagicEffect::CommandCreature && target.getTypeName() == typeid(ESM::Creature).name()))
&& !caster.isEmpty() && caster.getClass().isActor() && target != getPlayer() && magnitude >= target.getClass().getCreatureStats(target).getLevel()) && !caster.isEmpty() && caster.getClass().isActor() && target != getPlayer() && magnitude >= target.getClass().getCreatureStats(target).getLevel())
{ {
MWMechanics::AiFollow package(caster.getCellRef().getRefId(), true); MWMechanics::AiFollow package(caster, true);
target.getClass().getCreatureStats(target).getAiSequence().stack(package, target); target.getClass().getCreatureStats(target).getAiSequence().stack(package, target);
} }

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

@ -848,6 +848,16 @@ namespace MWPhysics
const osg::Quat &orient, const osg::Quat &orient,
float queryDistance, std::vector<MWWorld::Ptr> targets) float queryDistance, std::vector<MWWorld::Ptr> targets)
{ {
// First of all, try to hit where you aim to
int hitmask = CollisionType_World | CollisionType_Door | CollisionType_HeightMap | CollisionType_Actor;
RayResult result = castRay(origin, origin + (orient * osg::Vec3f(0.0f, queryDistance, 0.0f)), actor, targets, CollisionType_Actor, hitmask);
if (result.mHit)
{
return std::make_pair(result.mHitObject, result.mHitPos);
}
// Use cone shape as fallback
const MWWorld::Store<ESM::GameSetting> &store = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>(); const MWWorld::Store<ESM::GameSetting> &store = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
btConeShape shape (osg::DegreesToRadians(store.find("fCombatAngleXY")->getFloat()/2.0f), queryDistance); btConeShape shape (osg::DegreesToRadians(store.find("fCombatAngleXY")->getFloat()/2.0f), queryDistance);

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

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

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

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

@ -1073,20 +1073,29 @@ namespace MWWorld
osg::Quat rot = osg::Quat(posdata.rot[0], osg::Vec3f(-1,0,0)) * osg::Quat(posdata.rot[2], osg::Vec3f(0,0,-1)); osg::Quat rot = osg::Quat(posdata.rot[0], osg::Vec3f(-1,0,0)) * osg::Quat(posdata.rot[2], osg::Vec3f(0,0,-1));
osg::Vec3f pos = ptr.getRefData().getPosition().asVec3(); osg::Vec3f halfExtents = mPhysics->getHalfExtents(ptr);
// the origin of hitbox is an actor's front, not center
distance += halfExtents.y();
// special cased for better aiming with the camera
// if we do not hit anything, will use the default approach as fallback
if (ptr == getPlayerPtr()) if (ptr == getPlayerPtr())
pos = getActorHeadTransform(ptr).getTrans(); // special cased for better aiming with the camera
else
{ {
// general case, compatible with all types of different creatures osg::Vec3f pos = getActorHeadTransform(ptr).getTrans();
// note: we intentionally do *not* use the collision box offset here, this is required to make
// some flying creatures work that have their collision box offset in the air std::pair<MWWorld::Ptr,osg::Vec3f> result = mPhysics->getHitContact(ptr, pos, rot, distance, targets);
osg::Vec3f halfExtents = mPhysics->getHalfExtents(ptr); if(!result.first.isEmpty())
pos.z() += halfExtents.z() * 2 * 0.75; return std::make_pair(result.first, result.second);
distance += halfExtents.y();
} }
osg::Vec3f pos = ptr.getRefData().getPosition().asVec3();
// general case, compatible with all types of different creatures
// note: we intentionally do *not* use the collision box offset here, this is required to make
// some flying creatures work that have their collision box offset in the air
pos.z() += halfExtents.z();
std::pair<MWWorld::Ptr,osg::Vec3f> result = mPhysics->getHitContact(ptr, pos, rot, distance, targets); std::pair<MWWorld::Ptr,osg::Vec3f> result = mPhysics->getHitContact(ptr, pos, rot, distance, targets);
if(result.first.isEmpty()) if(result.first.isEmpty())
return std::make_pair(MWWorld::Ptr(), osg::Vec3f()); return std::make_pair(MWWorld::Ptr(), osg::Vec3f());
@ -2752,64 +2761,66 @@ namespace MWWorld
{ {
MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
// Get the target to use for "on touch" effects, using the facing direction from Head node
MWWorld::Ptr target;
float distance = getActivationDistancePlusTelekinesis();
osg::Vec3f hitPosition = actor.getRefData().getPosition().asVec3();
osg::Vec3f origin = getActorHeadTransform(actor).getTrans();
osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1,0,0))
* osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0,0,-1));
osg::Vec3f direction = orient * osg::Vec3f(0,1,0);
osg::Vec3f dest = origin + direction * distance;
// For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result.
std::vector<MWWorld::Ptr> targetActors; std::vector<MWWorld::Ptr> targetActors;
if (!actor.isEmpty() && actor != MWMechanics::getPlayer()) if (!actor.isEmpty() && actor != MWMechanics::getPlayer())
actor.getClass().getCreatureStats(actor).getAiSequence().getCombatTargets(targetActors); actor.getClass().getCreatureStats(actor).getAiSequence().getCombatTargets(targetActors);
// For actor targets, we want to use bounding boxes (physics raycast). const float fCombatDistance = getStore().get<ESM::GameSetting>().find("fCombatDistance")->getFloat();
// This is to give a slight tolerance for errors, especially with creatures like the Skeleton that would be very hard to aim at otherwise.
// For object targets, we want the detailed shapes (rendering raycast).
// If we used the bounding boxes for static objects, then we would not be able to target e.g. objects lying on a shelf.
MWPhysics::PhysicsSystem::RayResult result1 = mPhysics->castRay(origin, dest, actor, targetActors, MWPhysics::CollisionType_Actor);
MWRender::RenderingManager::RayResult result2 = mRendering->castRay(origin, dest, true, true); osg::Vec3f hitPosition = actor.getRefData().getPosition().asVec3();
float dist1 = std::numeric_limits<float>::max(); // for player we can take faced object first
float dist2 = std::numeric_limits<float>::max(); MWWorld::Ptr target;
if (actor == MWMechanics::getPlayer())
target = getFacedObject();
if (result1.mHit) // if the faced object can not be activated, do not use it
dist1 = (origin - result1.mHitPos).length(); if (!target.isEmpty() && !target.getClass().canBeActivated(target))
if (result2.mHit) target = NULL;
dist2 = (origin - result2.mHitPointWorld).length();
if (result1.mHit) if (target.isEmpty())
{
target = result1.mHitObject;
hitPosition = result1.mHitPos;
if (dist1 > getMaxActivationDistance() && !target.isEmpty() && (target.getClass().isActor() || !target.getClass().canBeActivated(target)))
target = NULL;
}
else if (result2.mHit)
{ {
target = result2.mHitObject; // For actor targets, we want to use hit contact with bounding boxes.
hitPosition = result2.mHitPointWorld; // This is to give a slight tolerance for errors, especially with creatures like the Skeleton that would be very hard to aim at otherwise.
if (dist2 > getMaxActivationDistance() && !target.isEmpty() && !target.getClass().canBeActivated(target)) // For object targets, we want the detailed shapes (rendering raycast).
target = NULL; // If we used the bounding boxes for static objects, then we would not be able to target e.g. objects lying on a shelf.
} std::pair<MWWorld::Ptr,osg::Vec3f> result1 = getHitContact(actor, fCombatDistance, targetActors);
// When targeting an actor that is in combat with an "on touch" spell, // Get the target to use for "on touch" effects, using the facing direction from Head node
// compare against the minimum of activation distance and combat distance. osg::Vec3f origin = getActorHeadTransform(actor).getTrans();
if (!target.isEmpty() && target.getClass().isActor() && target.getClass().getCreatureStats (target).getAiSequence().isInCombat()) osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1,0,0))
{ * osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0,0,-1));
distance = std::min (distance, getStore().get<ESM::GameSetting>().find("fCombatDistance")->getFloat());
if (distance < dist1) osg::Vec3f direction = orient * osg::Vec3f(0,1,0);
target = NULL; float distance = getMaxActivationDistance();
osg::Vec3f dest = origin + direction * distance;
MWRender::RenderingManager::RayResult result2 = mRendering->castRay(origin, dest, true, true);
float dist1 = std::numeric_limits<float>::max();
float dist2 = std::numeric_limits<float>::max();
if (!result1.first.isEmpty() && result1.first.getClass().isActor())
dist1 = (origin - result1.second).length();
if (result2.mHit)
dist2 = (origin - result2.mHitPointWorld).length();
if (!result1.first.isEmpty() && result1.first.getClass().isActor())
{
target = result1.first;
hitPosition = result1.second;
if (dist1 > getMaxActivationDistance())
target = NULL;
}
else if (result2.mHit)
{
target = result2.mHitObject;
hitPosition = result2.mHitPointWorld;
if (dist2 > getMaxActivationDistance() && !target.isEmpty() && !target.getClass().canBeActivated(target))
target = NULL;
}
} }
std::string selectedSpell = stats.getSpells().getSelectedSpell(); std::string selectedSpell = stats.getSpells().getSelectedSpell();
@ -3466,7 +3477,7 @@ namespace MWWorld
osg::Vec3f World::aimToTarget(const ConstPtr &actor, const MWWorld::ConstPtr& target) osg::Vec3f World::aimToTarget(const ConstPtr &actor, const MWWorld::ConstPtr& target)
{ {
osg::Vec3f weaponPos = actor.getRefData().getPosition().asVec3(); osg::Vec3f weaponPos = actor.getRefData().getPosition().asVec3();
weaponPos.z() += mPhysics->getHalfExtents(actor).z() * 2 * 0.75; weaponPos.z() += mPhysics->getHalfExtents(actor).z();
osg::Vec3f targetPos = mPhysics->getCollisionObjectPosition(target); osg::Vec3f targetPos = mPhysics->getCollisionObjectPosition(target);
return (targetPos - weaponPos); return (targetPos - weaponPos);
} }
@ -3475,7 +3486,7 @@ namespace MWWorld
{ {
osg::Vec3f weaponPos = actor.getRefData().getPosition().asVec3(); osg::Vec3f weaponPos = actor.getRefData().getPosition().asVec3();
osg::Vec3f halfExtents = mPhysics->getHalfExtents(actor); osg::Vec3f halfExtents = mPhysics->getHalfExtents(actor);
weaponPos.z() += halfExtents.z() * 2 * 0.75; weaponPos.z() += halfExtents.z();
return mPhysics->getHitDistance(weaponPos, target) - halfExtents.y(); return mPhysics->getHitDistance(weaponPos, target) - halfExtents.y();
} }

@ -172,7 +172,7 @@ macro (get_generator_is_multi_config VALUE)
if (CMAKE_VERSION VERSION_GREATER 3.9 OR CMAKE_VERSION VERSION_EQUAL 3.9) if (CMAKE_VERSION VERSION_GREATER 3.9 OR CMAKE_VERSION VERSION_EQUAL 3.9)
get_cmake_property(${VALUE} GENERATOR_IS_MULTI_CONFIG) get_cmake_property(${VALUE} GENERATOR_IS_MULTI_CONFIG)
else (CMAKE_VERSION VERSION_GREATER 3.9 OR CMAKE_VERSION VERSION_EQUAL 3.9) else (CMAKE_VERSION VERSION_GREATER 3.9 OR CMAKE_VERSION VERSION_EQUAL 3.9)
list(LENGTH "${CMAKE_CONFIGURATION_TYPES}" ${VALUE}) list(LENGTH CMAKE_CONFIGURATION_TYPES ${VALUE})
endif (CMAKE_VERSION VERSION_GREATER 3.9 OR CMAKE_VERSION VERSION_EQUAL 3.9) endif (CMAKE_VERSION VERSION_GREATER 3.9 OR CMAKE_VERSION VERSION_EQUAL 3.9)
endif (DEFINED generator_is_multi_config_var) endif (DEFINED generator_is_multi_config_var)
endmacro (get_generator_is_multi_config) endmacro (get_generator_is_multi_config)

@ -119,6 +119,11 @@ namespace Compiler
return false; return false;
} }
} }
else if (code==Scanner::S_comma && (mState==NameState || mState==EndNameState))
{
// ignoring comma (for now)
return true;
}
return Parser::parseSpecial (code, loc, scanner); return Parser::parseSpecial (code, loc, scanner);
} }

@ -512,7 +512,7 @@ namespace Compiler
return true; return true;
} }
if (code==Scanner::S_member && mState==PotentialExplicitState) if (code==Scanner::S_member && mState==PotentialExplicitState && mAllowExpression)
{ {
mState = MemberState; mState = MemberState;
parseExpression (scanner, loc); parseExpression (scanner, loc);

@ -239,4 +239,4 @@ void ContentSelectorView::ContentSelector::slotUncheckMultiSelectedItems()
void ContentSelectorView::ContentSelector::slotCheckMultiSelectedItems() void ContentSelectorView::ContentSelector::slotCheckMultiSelectedItems()
{ {
setCheckStateForMultiSelectedItems(true); setCheckStateForMultiSelectedItems(true);
} }

@ -15,7 +15,6 @@ namespace ContentSelectorView
Q_OBJECT Q_OBJECT
QMenu *mContextMenu; QMenu *mContextMenu;
QStringList mFilePaths;
protected: protected:
@ -61,6 +60,7 @@ namespace ContentSelectorView
void signalCurrentGamefileIndexChanged (int); void signalCurrentGamefileIndexChanged (int);
void signalAddonDataChanged (const QModelIndex& topleft, const QModelIndex& bottomright); void signalAddonDataChanged (const QModelIndex& topleft, const QModelIndex& bottomright);
void signalSelectedFilesChanged(QStringList selectedFiles);
private slots: private slots:

@ -35,17 +35,20 @@ namespace AiSequence
void AiTravel::load(ESMReader &esm) void AiTravel::load(ESMReader &esm)
{ {
esm.getHNT (mData, "DATA"); esm.getHNT (mData, "DATA");
esm.getHNOT (mHidden, "HIDD");
} }
void AiTravel::save(ESMWriter &esm) const void AiTravel::save(ESMWriter &esm) const
{ {
esm.writeHNT ("DATA", mData); esm.writeHNT ("DATA", mData);
esm.writeHNT ("HIDD", mHidden);
} }
void AiEscort::load(ESMReader &esm) void AiEscort::load(ESMReader &esm)
{ {
esm.getHNT (mData, "DATA"); esm.getHNT (mData, "DATA");
mTargetId = esm.getHNString("TARG"); mTargetId = esm.getHNString("TARG");
esm.getHNOT (mTargetActorId, "TAID");
esm.getHNT (mRemainingDuration, "DURA"); esm.getHNT (mRemainingDuration, "DURA");
mCellId = esm.getHNOString ("CELL"); mCellId = esm.getHNOString ("CELL");
} }
@ -54,6 +57,7 @@ namespace AiSequence
{ {
esm.writeHNT ("DATA", mData); esm.writeHNT ("DATA", mData);
esm.writeHNString ("TARG", mTargetId); esm.writeHNString ("TARG", mTargetId);
esm.writeHNT ("TAID", mTargetActorId);
esm.writeHNT ("DURA", mRemainingDuration); esm.writeHNT ("DURA", mRemainingDuration);
if (!mCellId.empty()) if (!mCellId.empty())
esm.writeHNString ("CELL", mCellId); esm.writeHNString ("CELL", mCellId);
@ -63,6 +67,7 @@ namespace AiSequence
{ {
esm.getHNT (mData, "DATA"); esm.getHNT (mData, "DATA");
mTargetId = esm.getHNString("TARG"); mTargetId = esm.getHNString("TARG");
esm.getHNOT (mTargetActorId, "TAID");
esm.getHNT (mRemainingDuration, "DURA"); esm.getHNT (mRemainingDuration, "DURA");
mCellId = esm.getHNOString ("CELL"); mCellId = esm.getHNOString ("CELL");
esm.getHNT (mAlwaysFollow, "ALWY"); esm.getHNT (mAlwaysFollow, "ALWY");
@ -76,6 +81,7 @@ namespace AiSequence
{ {
esm.writeHNT ("DATA", mData); esm.writeHNT ("DATA", mData);
esm.writeHNString("TARG", mTargetId); esm.writeHNString("TARG", mTargetId);
esm.writeHNT ("TAID", mTargetActorId);
esm.writeHNT ("DURA", mRemainingDuration); esm.writeHNT ("DURA", mRemainingDuration);
if (!mCellId.empty()) if (!mCellId.empty())
esm.writeHNString ("CELL", mCellId); esm.writeHNString ("CELL", mCellId);
@ -154,6 +160,8 @@ namespace AiSequence
break; break;
} }
} }
esm.writeHNT ("LAST", mLastAiPackage);
} }
void AiSequence::load(ESMReader &esm) void AiSequence::load(ESMReader &esm)
@ -221,6 +229,8 @@ namespace AiSequence
return; return;
} }
} }
esm.getHNOT (mLastAiPackage, "LAST");
} }
} }
} }

@ -80,6 +80,7 @@ namespace ESM
struct AiTravel : AiPackage struct AiTravel : AiPackage
{ {
AiTravelData mData; AiTravelData mData;
bool mHidden;
void load(ESMReader &esm); void load(ESMReader &esm);
void save(ESMWriter &esm) const; void save(ESMWriter &esm) const;
@ -89,6 +90,7 @@ namespace ESM
{ {
AiEscortData mData; AiEscortData mData;
int mTargetActorId;
std::string mTargetId; std::string mTargetId;
std::string mCellId; std::string mCellId;
float mRemainingDuration; float mRemainingDuration;
@ -101,6 +103,7 @@ namespace ESM
{ {
AiEscortData mData; AiEscortData mData;
int mTargetActorId;
std::string mTargetId; std::string mTargetId;
std::string mCellId; std::string mCellId;
float mRemainingDuration; float mRemainingDuration;
@ -147,10 +150,14 @@ namespace ESM
struct AiSequence struct AiSequence
{ {
AiSequence() {} AiSequence()
{
mLastAiPackage = -1;
}
~AiSequence(); ~AiSequence();
std::vector<AiPackageContainer> mPackages; std::vector<AiPackageContainer> mPackages;
int mLastAiPackage;
void load (ESMReader &esm); void load (ESMReader &esm);
void save (ESMWriter &esm) const; void save (ESMWriter &esm) const;

@ -31,6 +31,18 @@ void ESM::Player::load (ESMReader &esm)
mPaidCrimeId = -1; mPaidCrimeId = -1;
esm.getHNOT (mPaidCrimeId, "PAYD"); esm.getHNOT (mPaidCrimeId, "PAYD");
bool checkPrevItems = true;
while (checkPrevItems)
{
std::string boundItemId = esm.getHNOString("BOUN");
std::string prevItemId = esm.getHNOString("PREV");
if (!boundItemId.empty())
mPreviousItems[boundItemId] = prevItemId;
else
checkPrevItems = false;
}
if (esm.hasMoreSubs()) if (esm.hasMoreSubs())
{ {
for (int i=0; i<ESM::Attribute::Length; ++i) for (int i=0; i<ESM::Attribute::Length; ++i)
@ -62,6 +74,12 @@ void ESM::Player::save (ESMWriter &esm) const
esm.writeHNT ("CURD", mCurrentCrimeId); esm.writeHNT ("CURD", mCurrentCrimeId);
esm.writeHNT ("PAYD", mPaidCrimeId); esm.writeHNT ("PAYD", mPaidCrimeId);
for (PreviousItems::const_iterator it=mPreviousItems.begin(); it != mPreviousItems.end(); ++it)
{
esm.writeHNString ("BOUN", it->first);
esm.writeHNString ("PREV", it->second);
}
for (int i=0; i<ESM::Attribute::Length; ++i) for (int i=0; i<ESM::Attribute::Length; ++i)
mSaveAttributes[i].save(esm); mSaveAttributes[i].save(esm);
for (int i=0; i<ESM::Skill::Length; ++i) for (int i=0; i<ESM::Skill::Length; ++i)

@ -34,6 +34,9 @@ namespace ESM
StatState<int> mSaveAttributes[ESM::Attribute::Length]; StatState<int> mSaveAttributes[ESM::Attribute::Length];
StatState<int> mSaveSkills[ESM::Skill::Length]; StatState<int> mSaveSkills[ESM::Skill::Length];
typedef std::map<std::string, std::string> PreviousItems; // previous equipped items, needed for bound spells
PreviousItems mPreviousItems;
void load (ESMReader &esm); void load (ESMReader &esm);
void save (ESMWriter &esm) const; void save (ESMWriter &esm) const;
}; };

@ -5,7 +5,7 @@
#include "defs.hpp" #include "defs.hpp"
unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE;
int ESM::SavedGame::sCurrentFormat = 3; int ESM::SavedGame::sCurrentFormat = 5;
void ESM::SavedGame::load (ESMReader &esm) void ESM::SavedGame::load (ESMReader &esm)
{ {

@ -430,13 +430,16 @@ namespace ESMTerrain
// Second iteration - create and fill in the blend maps // Second iteration - create and fill in the blend maps
const int blendmapSize = (realTextureSize-1) * chunkSize + 1; const int blendmapSize = (realTextureSize-1) * chunkSize + 1;
// We need to upscale the blendmap 2x with nearest neighbor sampling to look like Vanilla
const int imageScaleFactor = 2;
const int blendmapImageSize = blendmapSize * imageScaleFactor;
for (int i=0; i<numBlendmaps; ++i) for (int i=0; i<numBlendmaps; ++i)
{ {
GLenum format = pack ? GL_RGBA : GL_ALPHA; GLenum format = pack ? GL_RGBA : GL_ALPHA;
osg::ref_ptr<osg::Image> image (new osg::Image); osg::ref_ptr<osg::Image> image (new osg::Image);
image->allocateImage(blendmapSize, blendmapSize, 1, format, GL_UNSIGNED_BYTE); image->allocateImage(blendmapImageSize, blendmapImageSize, 1, format, GL_UNSIGNED_BYTE);
unsigned char* pData = image->data(); unsigned char* pData = image->data();
for (int y=0; y<blendmapSize; ++y) for (int y=0; y<blendmapSize; ++y)
@ -449,13 +452,17 @@ namespace ESMTerrain
int blendIndex = (pack ? static_cast<int>(std::floor((layerIndex - 1) / 4.f)) : layerIndex - 1); int blendIndex = (pack ? static_cast<int>(std::floor((layerIndex - 1) / 4.f)) : layerIndex - 1);
int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0; int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0;
if (blendIndex == i) int alpha = (blendIndex == i) ? 255 : 0;
pData[(blendmapSize - y - 1)*blendmapSize*channels + x*channels + channel] = 255;
else int realY = (blendmapSize - y - 1)*imageScaleFactor;
pData[(blendmapSize - y - 1)*blendmapSize*channels + x*channels + channel] = 0; int realX = x*imageScaleFactor;
pData[((realY+0)*blendmapImageSize + realX + 0)*channels + channel] = alpha;
pData[((realY+1)*blendmapImageSize + realX + 0)*channels + channel] = alpha;
pData[((realY+0)*blendmapImageSize + realX + 1)*channels + channel] = alpha;
pData[((realY+1)*blendmapImageSize + realX + 1)*channels + channel] = alpha;
} }
} }
blendmaps.push_back(image); blendmaps.push_back(image);
} }
} }

@ -104,13 +104,13 @@ namespace Nif
void NiLookAtController::read(NIFStream *nif) void NiLookAtController::read(NIFStream *nif)
{ {
Controller::read(nif); Controller::read(nif);
data.read(nif); target.read(nif);
} }
void NiLookAtController::post(NIFFile *nif) void NiLookAtController::post(NIFFile *nif)
{ {
Controller::post(nif); Controller::post(nif);
data.post(nif); target.post(nif);
} }
void NiPathController::read(NIFStream *nif) void NiPathController::read(NIFStream *nif)

@ -102,7 +102,7 @@ public:
class NiLookAtController : public Controller class NiLookAtController : public Controller
{ {
public: public:
NiKeyframeDataPtr data; NodePtr target;
void read(NIFStream *nif); void read(NIFStream *nif);
void post(NIFFile *nif); void post(NIFFile *nif);

@ -261,7 +261,7 @@ namespace NifOsg
osg::ref_ptr<TextKeyMapHolder> textkeys (new TextKeyMapHolder); osg::ref_ptr<TextKeyMapHolder> textkeys (new TextKeyMapHolder);
osg::ref_ptr<osg::Node> created = handleNode(nifNode, NULL, imageManager, std::vector<int>(), 0, false, false, &textkeys->mTextKeys); osg::ref_ptr<osg::Node> created = handleNode(nifNode, NULL, imageManager, std::vector<int>(), 0, false, false, false, &textkeys->mTextKeys);
if (nif->getUseSkinning()) if (nif->getUseSkinning())
{ {
@ -463,7 +463,7 @@ namespace NifOsg
} }
osg::ref_ptr<osg::Node> handleNode(const Nif::Node* nifNode, osg::Group* parentNode, Resource::ImageManager* imageManager, osg::ref_ptr<osg::Node> handleNode(const Nif::Node* nifNode, osg::Group* parentNode, Resource::ImageManager* imageManager,
std::vector<int> boundTextures, int animflags, bool skipMeshes, bool isAnimated, TextKeyMap* textKeys, osg::Node* rootNode=NULL) std::vector<int> boundTextures, int animflags, bool skipMeshes, bool hasMarkers, bool isAnimated, TextKeyMap* textKeys, osg::Node* rootNode=NULL)
{ {
if (rootNode != NULL && Misc::StringUtils::ciEqual(nifNode->name, "Bounding Box")) if (rootNode != NULL && Misc::StringUtils::ciEqual(nifNode->name, "Bounding Box"))
return NULL; return NULL;
@ -510,7 +510,7 @@ namespace NifOsg
if(sd->string == "MRK" && !Loader::getShowMarkers()) if(sd->string == "MRK" && !Loader::getShowMarkers())
{ {
// Marker objects. These meshes are only visible in the editor. // Marker objects. These meshes are only visible in the editor.
skipMeshes = true; hasMarkers = true;
} }
} }
} }
@ -542,7 +542,7 @@ namespace NifOsg
node->setNodeMask(0x1); node->setNodeMask(0x1);
} }
if (skipMeshes && isAnimated) // make sure the empty node is not optimized away so the physicssystem can find it. if ((skipMeshes || hasMarkers) && isAnimated) // make sure the empty node is not optimized away so the physicssystem can find it.
{ {
node->setDataVariance(osg::Object::DYNAMIC); node->setDataVariance(osg::Object::DYNAMIC);
} }
@ -554,13 +554,18 @@ namespace NifOsg
if (nifNode->recType == Nif::RC_NiTriShape && !skipMeshes) if (nifNode->recType == Nif::RC_NiTriShape && !skipMeshes)
{ {
const Nif::NiTriShape* triShape = static_cast<const Nif::NiTriShape*>(nifNode); const Nif::NiTriShape* triShape = static_cast<const Nif::NiTriShape*>(nifNode);
if (triShape->skin.empty()) const std::string nodeName = Misc::StringUtils::lowerCase(triShape->name);
handleTriShape(triShape, node, composite, boundTextures, animflags); static const std::string pattern = "tri editormarker";
else if (!hasMarkers || nodeName.compare(0, pattern.size(), pattern) != 0)
handleSkinnedTriShape(triShape, node, composite, boundTextures, animflags); {
if (triShape->skin.empty())
handleTriShape(triShape, node, composite, boundTextures, animflags);
else
handleSkinnedTriShape(triShape, node, composite, boundTextures, animflags);
if (!nifNode->controller.empty()) if (!nifNode->controller.empty())
handleMeshControllers(nifNode, node, composite, boundTextures, animflags); handleMeshControllers(nifNode, node, composite, boundTextures, animflags);
}
} }
if(nifNode->recType == Nif::RC_NiAutoNormalParticles || nifNode->recType == Nif::RC_NiRotatingParticles) if(nifNode->recType == Nif::RC_NiAutoNormalParticles || nifNode->recType == Nif::RC_NiRotatingParticles)
@ -598,7 +603,7 @@ namespace NifOsg
for(size_t i = 0;i < children.length();++i) for(size_t i = 0;i < children.length();++i)
{ {
if(!children[i].empty()) if(!children[i].empty())
handleNode(children[i].getPtr(), node, imageManager, boundTextures, animflags, skipMeshes, isAnimated, textKeys, rootNode); handleNode(children[i].getPtr(), node, imageManager, boundTextures, animflags, skipMeshes, hasMarkers, isAnimated, textKeys, rootNode);
} }
} }

@ -25,6 +25,9 @@ namespace Terrain
matrix.preMultTranslate(osg::Vec3f(0.5f, 0.5f, 0.f)); matrix.preMultTranslate(osg::Vec3f(0.5f, 0.5f, 0.f));
matrix.preMultScale(osg::Vec3f(scale, scale, 1.f)); matrix.preMultScale(osg::Vec3f(scale, scale, 1.f));
matrix.preMultTranslate(osg::Vec3f(-0.5f, -0.5f, 0.f)); matrix.preMultTranslate(osg::Vec3f(-0.5f, -0.5f, 0.f));
// We need to nudge the blendmap to look like vanilla.
// This causes visible seams unless the blendmap's resolution is doubled, but Vanilla also doubles the blendmap, apparently.
matrix.preMultTranslate(osg::Vec3f(1.0f/blendmapScale/4.0f, 1.0f/blendmapScale/4.0f, 0.f));
texMat = new osg::TexMat(matrix); texMat = new osg::TexMat(matrix);

@ -8,6 +8,8 @@
<string>English</string> <string>English</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>openmw-launcher</string> <string>openmw-launcher</string>
<key>CFBundleIdentifier</key>
<string>org.openmw.openmw</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>CFBundleLongVersionString</key> <key>CFBundleLongVersionString</key>
@ -18,6 +20,8 @@
<string>APPL</string> <string>APPL</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleShortVersionString</key>
<string>${OPENMW_VERSION}</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>${OPENMW_VERSION}</string> <string>${OPENMW_VERSION}</string>
<key>CSResourcesFileMapped</key> <key>CSResourcesFileMapped</key>

@ -11,13 +11,6 @@
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="pageVerticalLayout"> <layout class="QVBoxLayout" name="pageVerticalLayout">
<item>
<widget class="QLabel" name="pageDescriptionLabel">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This temporary page contains new settings that will be available in-game in a post-1.0 release of OpenMW.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QScrollArea" name="scrollArea"> <widget class="QScrollArea" name="scrollArea">
<property name="widgetResizable"> <property name="widgetResizable">
@ -27,9 +20,9 @@
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>0</x> <x>0</x>
<y>-187</y> <y>0</y>
<width>630</width> <width>630</width>
<height>510</height> <height>746</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="scrollAreaVerticalLayout"> <layout class="QVBoxLayout" name="scrollAreaVerticalLayout">
@ -276,6 +269,94 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<widget class="QGroupBox" name="testingGroup">
<property name="title">
<string>Testing</string>
</property>
<layout class="QVBoxLayout" name="testingGroupVerticalLayout">
<item>
<widget class="QLabel" name="testingLabel">
<property name="text">
<string>These settings are intended for testing mods and will cause issues if used for normal gameplay.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="Line" name="testingLine">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="skipMenuCheckBox">
<property name="text">
<string>Skip menu and generate default character</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="startDefaultCharacterAtHorizontalLayout">
<item>
<spacer name="startDefaultCharacterAtHorizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="startDefaultCharacterAtLabel">
<property name="text">
<string>Start default character at</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="startDefaultCharacterAtField">
<property name="placeholderText">
<string>default cell</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Run script after startup:</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="runScriptAfterStartupHorizontalLayout">
<item>
<widget class="QLineEdit" name="runScriptAfterStartupField"/>
</item>
<item>
<widget class="QPushButton" name="runScriptAfterStartupBrowseButton">
<property name="text">
<string>Browse…</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item> <item>
<widget class="QGroupBox" name="otherGroup"> <widget class="QGroupBox" name="otherGroup">
<property name="title"> <property name="title">

Loading…
Cancel
Save