mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-16 18:19:55 +00:00
Merge branch 'master' into fatigue
This commit is contained in:
commit
3e4dc31e39
65 changed files with 1003 additions and 322 deletions
|
@ -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
|
||||||
|
|
21
CHANGELOG.md
21
CHANGELOG.md
|
@ -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>
|
||||||
|
#include <QFileDialog>
|
||||||
|
#include <QCompleter>
|
||||||
|
#include <components/contentselector/view/contentselector.hpp>
|
||||||
|
#include <components/contentselector/model/esmfile.hpp>
|
||||||
|
|
||||||
Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent)
|
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 ¤t);
|
void slotProfileChangedByUser(const QString &previous, const QString ¤t);
|
||||||
void slotProfileRenamed(const QString &previous, const QString ¤t);
|
void slotProfileRenamed(const QString &previous, const QString ¤t);
|
||||||
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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
48
apps/launcher/utils/cellnameloader.cpp
Normal file
48
apps/launcher/utils/cellnameloader.cpp
Normal file
|
@ -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);
|
||||||
|
}
|
41
apps/launcher/utils/cellnameloader.hpp
Normal file
41
apps/launcher/utils/cellnameloader.hpp
Normal file
|
@ -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))
|
||||||
{
|
{
|
||||||
std::string itemGmst = it->second;
|
|
||||||
std::string item = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
|
|
||||||
itemGmst)->getString();
|
|
||||||
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(
|
|
||||||
"sMagicBoundRightGauntletID")->getString();
|
|
||||||
adjustBoundItem(item, magnitude > 0, ptr);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
adjustBoundItem(item, magnitude > 0, ptr);
|
|
||||||
|
|
||||||
if (magnitude > 0)
|
if (magnitude > 0)
|
||||||
creatureStats.mBoundItems.insert(it->first);
|
creatureStats.mBoundItems.insert(it->first);
|
||||||
else
|
else
|
||||||
creatureStats.mBoundItems.erase(it->first);
|
creatureStats.mBoundItems.erase(it->first);
|
||||||
|
|
||||||
|
std::string itemGmst = it->second;
|
||||||
|
std::string item = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
|
||||||
|
itemGmst)->getString();
|
||||||
|
|
||||||
|
magnitude > 0 ? addBoundItem(item, ptr) : removeBoundItem(item, ptr);
|
||||||
|
|
||||||
|
if (it->first == ESM::MagicEffect::BoundGloves)
|
||||||
|
{
|
||||||
|
item = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
|
||||||
|
"sMagicBoundRightGauntletID")->getString();
|
||||||
|
magnitude > 0 ? addBoundItem(item, ptr) : removeBoundItem(item, ptr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <map>
|
|
||||||
#include <list>
|
#include <list>
|
||||||
|
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
|
@ -26,6 +25,9 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
std::map<std::string, int> mDeathCount;
|
std::map<std::string, int> mDeathCount;
|
||||||
|
|
||||||
|
void addBoundItem (const std::string& itemId, const MWWorld::Ptr& actor);
|
||||||
|
void removeBoundItem (const std::string& itemId, const MWWorld::Ptr& actor);
|
||||||
|
|
||||||
void updateNpc(const MWWorld::Ptr &ptr, float duration);
|
void updateNpc(const MWWorld::Ptr &ptr, float duration);
|
||||||
|
|
||||||
void adjustMagicEffects (const MWWorld::Ptr& creature);
|
void adjustMagicEffects (const MWWorld::Ptr& creature);
|
||||||
|
|
|
@ -103,9 +103,10 @@ namespace MWMechanics
|
||||||
bool isFleeing();
|
bool isFleeing();
|
||||||
};
|
};
|
||||||
|
|
||||||
AiCombat::AiCombat(const MWWorld::Ptr& actor) :
|
AiCombat::AiCombat(const MWWorld::Ptr& actor)
|
||||||
mTargetActorId(actor.getClass().getCreatureStats(actor).getActorId())
|
{
|
||||||
{}
|
mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId();
|
||||||
|
}
|
||||||
|
|
||||||
AiCombat::AiCombat(const ESM::AiSequence::AiCombat *combat)
|
AiCombat::AiCombat(const ESM::AiSequence::AiCombat *combat)
|
||||||
{
|
{
|
||||||
|
|
|
@ -54,9 +54,6 @@ namespace MWMechanics
|
||||||
virtual bool shouldCancelPreviousAi() const { return false; }
|
virtual bool shouldCancelPreviousAi() const { return false; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
int mTargetActorId;
|
|
||||||
|
|
||||||
/// Returns true if combat should end
|
/// Returns true if combat should end
|
||||||
bool attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController);
|
bool attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController);
|
||||||
|
|
||||||
|
|
|
@ -22,28 +22,32 @@
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
AiEscort::AiEscort(const std::string &actorId, int duration, float x, float y, float z)
|
AiEscort::AiEscort(const std::string &actorId, int duration, float x, float y, float z)
|
||||||
: mActorId(actorId), mX(x), mY(y), mZ(z), mDuration(duration), mRemainingDuration(static_cast<float>(duration))
|
: mX(x), mY(y), mZ(z), mDuration(duration), mRemainingDuration(static_cast<float>(duration))
|
||||||
, mCellX(std::numeric_limits<int>::max())
|
, mCellX(std::numeric_limits<int>::max())
|
||||||
, mCellY(std::numeric_limits<int>::max())
|
, mCellY(std::numeric_limits<int>::max())
|
||||||
{
|
{
|
||||||
|
mTargetActorRefId = actorId;
|
||||||
mMaxDist = 450;
|
mMaxDist = 450;
|
||||||
}
|
}
|
||||||
|
|
||||||
AiEscort::AiEscort(const std::string &actorId, const std::string &cellId,int duration, float x, float y, float z)
|
AiEscort::AiEscort(const std::string &actorId, const std::string &cellId, int duration, float x, float y, float z)
|
||||||
: mActorId(actorId), mCellId(cellId), mX(x), mY(y), mZ(z), mDuration(duration), mRemainingDuration(static_cast<float>(duration))
|
: mCellId(cellId), mX(x), mY(y), mZ(z), mDuration(duration), mRemainingDuration(static_cast<float>(duration))
|
||||||
, mCellX(std::numeric_limits<int>::max())
|
, mCellX(std::numeric_limits<int>::max())
|
||||||
, mCellY(std::numeric_limits<int>::max())
|
, mCellY(std::numeric_limits<int>::max())
|
||||||
{
|
{
|
||||||
|
mTargetActorRefId = actorId;
|
||||||
mMaxDist = 450;
|
mMaxDist = 450;
|
||||||
}
|
}
|
||||||
|
|
||||||
AiEscort::AiEscort(const ESM::AiSequence::AiEscort *escort)
|
AiEscort::AiEscort(const ESM::AiSequence::AiEscort *escort)
|
||||||
: mActorId(escort->mTargetId), mCellId(escort->mCellId), mX(escort->mData.mX), mY(escort->mData.mY), mZ(escort->mData.mZ)
|
: mCellId(escort->mCellId), mX(escort->mData.mX), mY(escort->mData.mY), mZ(escort->mData.mZ)
|
||||||
, mMaxDist(450)
|
, mMaxDist(450)
|
||||||
, mRemainingDuration(escort->mRemainingDuration)
|
, mRemainingDuration(escort->mRemainingDuration)
|
||||||
, mCellX(std::numeric_limits<int>::max())
|
, mCellX(std::numeric_limits<int>::max())
|
||||||
, mCellY(std::numeric_limits<int>::max())
|
, mCellY(std::numeric_limits<int>::max())
|
||||||
{
|
{
|
||||||
|
mTargetActorRefId = escort->mTargetId;
|
||||||
|
mTargetActorId = escort->mTargetActorId;
|
||||||
// mDuration isn't saved in the save file, so just giving it "1" for now if the package has a duration.
|
// mDuration isn't saved in the save file, so just giving it "1" for now if the package has a duration.
|
||||||
// The exact value of mDuration only matters for repeating packages.
|
// The exact value of mDuration only matters for repeating packages.
|
||||||
if (mRemainingDuration > 0) // Previously mRemainingDuration could be negative even when mDuration was 0. Checking for > 0 should fix old saves.
|
if (mRemainingDuration > 0) // Previously mRemainingDuration could be negative even when mDuration was 0. Checking for > 0 should fix old saves.
|
||||||
|
@ -78,7 +82,7 @@ namespace MWMechanics
|
||||||
actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing);
|
actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing);
|
||||||
actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false);
|
actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false);
|
||||||
|
|
||||||
const MWWorld::Ptr follower = MWBase::Environment::get().getWorld()->getPtr(mActorId, false);
|
const MWWorld::Ptr follower = MWBase::Environment::get().getWorld()->getPtr(mTargetActorRefId, false);
|
||||||
const float* const leaderPos = actor.getRefData().getPosition().pos;
|
const float* const leaderPos = actor.getRefData().getPosition().pos;
|
||||||
const float* const followerPos = follower.getRefData().getPosition().pos;
|
const float* const followerPos = follower.getRefData().getPosition().pos;
|
||||||
double differenceBetween[3];
|
double differenceBetween[3];
|
||||||
|
@ -119,18 +123,14 @@ namespace MWMechanics
|
||||||
return TypeIdEscort;
|
return TypeIdEscort;
|
||||||
}
|
}
|
||||||
|
|
||||||
MWWorld::Ptr AiEscort::getTarget() const
|
|
||||||
{
|
|
||||||
return MWBase::Environment::get().getWorld()->getPtr(mActorId, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AiEscort::writeState(ESM::AiSequence::AiSequence &sequence) const
|
void AiEscort::writeState(ESM::AiSequence::AiSequence &sequence) const
|
||||||
{
|
{
|
||||||
std::unique_ptr<ESM::AiSequence::AiEscort> escort(new ESM::AiSequence::AiEscort());
|
std::unique_ptr<ESM::AiSequence::AiEscort> escort(new ESM::AiSequence::AiEscort());
|
||||||
escort->mData.mX = mX;
|
escort->mData.mX = mX;
|
||||||
escort->mData.mY = mY;
|
escort->mData.mY = mY;
|
||||||
escort->mData.mZ = mZ;
|
escort->mData.mZ = mZ;
|
||||||
escort->mTargetId = mActorId;
|
escort->mTargetId = mTargetActorRefId;
|
||||||
|
escort->mTargetActorId = mTargetActorId;
|
||||||
escort->mRemainingDuration = mRemainingDuration;
|
escort->mRemainingDuration = mRemainingDuration;
|
||||||
escort->mCellId = mCellId;
|
escort->mCellId = mCellId;
|
||||||
|
|
||||||
|
|
|
@ -24,11 +24,11 @@ namespace MWMechanics
|
||||||
/// Implementation of AiEscort
|
/// Implementation of AiEscort
|
||||||
/** The Actor will escort the specified actor to the world position x, y, z until they reach their position, or they run out of time
|
/** The Actor will escort the specified actor to the world position x, y, z until they reach their position, or they run out of time
|
||||||
\implement AiEscort **/
|
\implement AiEscort **/
|
||||||
AiEscort(const std::string &actorId,int duration, float x, float y, float z);
|
AiEscort(const std::string &actorId, int duration, float x, float y, float z);
|
||||||
/// Implementation of AiEscortCell
|
/// Implementation of AiEscortCell
|
||||||
/** The Actor will escort the specified actor to the cell position x, y, z until they reach their position, or they run out of time
|
/** The Actor will escort the specified actor to the cell position x, y, z until they reach their position, or they run out of time
|
||||||
\implement AiEscortCell **/
|
\implement AiEscortCell **/
|
||||||
AiEscort(const std::string &actorId,const std::string &cellId,int duration, float x, float y, float z);
|
AiEscort(const std::string &actorId, const std::string &cellId, int duration, float x, float y, float z);
|
||||||
|
|
||||||
AiEscort(const ESM::AiSequence::AiEscort* escort);
|
AiEscort(const ESM::AiSequence::AiEscort* escort);
|
||||||
|
|
||||||
|
@ -38,7 +38,6 @@ namespace MWMechanics
|
||||||
|
|
||||||
virtual int getTypeId() const;
|
virtual int getTypeId() const;
|
||||||
|
|
||||||
MWWorld::Ptr getTarget() const;
|
|
||||||
virtual bool sideWithTarget() const { return true; }
|
virtual bool sideWithTarget() const { return true; }
|
||||||
|
|
||||||
void writeState(ESM::AiSequence::AiSequence &sequence) const;
|
void writeState(ESM::AiSequence::AiSequence &sequence) const;
|
||||||
|
@ -46,7 +45,6 @@ namespace MWMechanics
|
||||||
void fastForward(const MWWorld::Ptr& actor, AiState& state);
|
void fastForward(const MWWorld::Ptr& actor, AiState& state);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string mActorId;
|
|
||||||
std::string mCellId;
|
std::string mCellId;
|
||||||
float mX;
|
float mX;
|
||||||
float mY;
|
float mY;
|
||||||
|
|
|
@ -35,32 +35,53 @@ struct AiFollowStorage : AiTemporaryBase
|
||||||
|
|
||||||
int AiFollow::mFollowIndexCounter = 0;
|
int AiFollow::mFollowIndexCounter = 0;
|
||||||
|
|
||||||
AiFollow::AiFollow(const std::string &actorId,float duration, float x, float y, float z)
|
AiFollow::AiFollow(const std::string &actorId, float duration, float x, float y, float z)
|
||||||
: mAlwaysFollow(false), mCommanded(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z)
|
: mAlwaysFollow(false), mCommanded(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z)
|
||||||
, mActorRefId(actorId), mActorId(-1), mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++)
|
, mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++)
|
||||||
{
|
{
|
||||||
|
mTargetActorRefId = actorId;
|
||||||
}
|
}
|
||||||
|
|
||||||
AiFollow::AiFollow(const std::string &actorId,const std::string &cellId,float duration, float x, float y, float z)
|
AiFollow::AiFollow(const std::string &actorId, const std::string &cellId, float duration, float x, float y, float z)
|
||||||
: mAlwaysFollow(false), mCommanded(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z)
|
: mAlwaysFollow(false), mCommanded(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z)
|
||||||
, mActorRefId(actorId), mActorId(-1), mCellId(cellId), mActive(false), mFollowIndex(mFollowIndexCounter++)
|
, mCellId(cellId), mActive(false), mFollowIndex(mFollowIndexCounter++)
|
||||||
{
|
{
|
||||||
|
mTargetActorRefId = actorId;
|
||||||
}
|
}
|
||||||
|
|
||||||
AiFollow::AiFollow(const std::string &actorId, bool commanded)
|
AiFollow::AiFollow(const MWWorld::Ptr& actor, float duration, float x, float y, float z)
|
||||||
|
: mAlwaysFollow(false), mCommanded(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z)
|
||||||
|
, mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++)
|
||||||
|
{
|
||||||
|
mTargetActorRefId = actor.getCellRef().getRefId();
|
||||||
|
mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId();
|
||||||
|
}
|
||||||
|
|
||||||
|
AiFollow::AiFollow(const MWWorld::Ptr& actor, const std::string &cellId, float duration, float x, float y, float z)
|
||||||
|
: mAlwaysFollow(false), mCommanded(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z)
|
||||||
|
, mCellId(cellId), mActive(false), mFollowIndex(mFollowIndexCounter++)
|
||||||
|
{
|
||||||
|
mTargetActorRefId = actor.getCellRef().getRefId();
|
||||||
|
mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId();
|
||||||
|
}
|
||||||
|
|
||||||
|
AiFollow::AiFollow(const MWWorld::Ptr& actor, bool commanded)
|
||||||
: mAlwaysFollow(true), mCommanded(commanded), mDuration(0), mRemainingDuration(0), mX(0), mY(0), mZ(0)
|
: mAlwaysFollow(true), mCommanded(commanded), mDuration(0), mRemainingDuration(0), mX(0), mY(0), mZ(0)
|
||||||
, mActorRefId(actorId), mActorId(-1), mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++)
|
, mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++)
|
||||||
{
|
{
|
||||||
|
mTargetActorRefId = actor.getCellRef().getRefId();
|
||||||
|
mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId();
|
||||||
}
|
}
|
||||||
|
|
||||||
AiFollow::AiFollow(const ESM::AiSequence::AiFollow *follow)
|
AiFollow::AiFollow(const ESM::AiSequence::AiFollow *follow)
|
||||||
: mAlwaysFollow(follow->mAlwaysFollow), mCommanded(follow->mCommanded), mRemainingDuration(follow->mRemainingDuration)
|
: mAlwaysFollow(follow->mAlwaysFollow), mCommanded(follow->mCommanded), mRemainingDuration(follow->mRemainingDuration)
|
||||||
, mX(follow->mData.mX), mY(follow->mData.mY), mZ(follow->mData.mZ)
|
, mX(follow->mData.mX), mY(follow->mData.mY), mZ(follow->mData.mZ)
|
||||||
, mActorRefId(follow->mTargetId), mActorId(-1)
|
|
||||||
, mCellId(follow->mCellId), mActive(follow->mActive), mFollowIndex(mFollowIndexCounter++)
|
, mCellId(follow->mCellId), mActive(follow->mActive), mFollowIndex(mFollowIndexCounter++)
|
||||||
{
|
{
|
||||||
// mDuration isn't saved in the save file, so just giving it "1" for now if the package had a duration.
|
mTargetActorRefId = follow->mTargetId;
|
||||||
// The exact value of mDuration only matters for repeating packages.
|
mTargetActorId = follow->mTargetActorId;
|
||||||
|
// mDuration isn't saved in the save file, so just giving it "1" for now if the package had a duration.
|
||||||
|
// The exact value of mDuration only matters for repeating packages.
|
||||||
if (mRemainingDuration > 0) // Previously mRemainingDuration could be negative even when mDuration was 0. Checking for > 0 should fix old saves.
|
if (mRemainingDuration > 0) // Previously mRemainingDuration could be negative even when mDuration was 0. Checking for > 0 should fix old saves.
|
||||||
mDuration = 1;
|
mDuration = 1;
|
||||||
else
|
else
|
||||||
|
@ -204,7 +225,7 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte
|
||||||
|
|
||||||
std::string AiFollow::getFollowedActor()
|
std::string AiFollow::getFollowedActor()
|
||||||
{
|
{
|
||||||
return mActorRefId;
|
return mTargetActorRefId;
|
||||||
}
|
}
|
||||||
|
|
||||||
AiFollow *MWMechanics::AiFollow::clone() const
|
AiFollow *MWMechanics::AiFollow::clone() const
|
||||||
|
@ -228,7 +249,8 @@ void AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) const
|
||||||
follow->mData.mX = mX;
|
follow->mData.mX = mX;
|
||||||
follow->mData.mY = mY;
|
follow->mData.mY = mY;
|
||||||
follow->mData.mZ = mZ;
|
follow->mData.mZ = mZ;
|
||||||
follow->mTargetId = mActorRefId;
|
follow->mTargetId = mTargetActorRefId;
|
||||||
|
follow->mTargetActorId = mTargetActorId;
|
||||||
follow->mRemainingDuration = mRemainingDuration;
|
follow->mRemainingDuration = mRemainingDuration;
|
||||||
follow->mCellId = mCellId;
|
follow->mCellId = mCellId;
|
||||||
follow->mAlwaysFollow = mAlwaysFollow;
|
follow->mAlwaysFollow = mAlwaysFollow;
|
||||||
|
@ -241,29 +263,6 @@ void AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) const
|
||||||
sequence.mPackages.push_back(package);
|
sequence.mPackages.push_back(package);
|
||||||
}
|
}
|
||||||
|
|
||||||
MWWorld::Ptr AiFollow::getTarget() const
|
|
||||||
{
|
|
||||||
if (mActorId == -2)
|
|
||||||
return MWWorld::Ptr();
|
|
||||||
|
|
||||||
if (mActorId == -1)
|
|
||||||
{
|
|
||||||
MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mActorRefId, false);
|
|
||||||
if (target.isEmpty())
|
|
||||||
{
|
|
||||||
mActorId = -2;
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
mActorId = target.getClass().getCreatureStats(target).getActorId();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mActorId != -1)
|
|
||||||
return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mActorId);
|
|
||||||
else
|
|
||||||
return MWWorld::Ptr();
|
|
||||||
}
|
|
||||||
|
|
||||||
int AiFollow::getFollowIndex() const
|
int AiFollow::getFollowIndex() const
|
||||||
{
|
{
|
||||||
return mFollowIndex;
|
return mFollowIndex;
|
||||||
|
|
|
@ -25,16 +25,17 @@ namespace MWMechanics
|
||||||
class AiFollow : public AiPackage
|
class AiFollow : public AiPackage
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
AiFollow(const std::string &actorId, float duration, float x, float y, float z);
|
||||||
|
AiFollow(const std::string &actorId, const std::string &CellId, float duration, float x, float y, float z);
|
||||||
/// Follow Actor for duration or until you arrive at a world position
|
/// Follow Actor for duration or until you arrive at a world position
|
||||||
AiFollow(const std::string &ActorId,float duration, float X, float Y, float Z);
|
AiFollow(const MWWorld::Ptr& actor, float duration, float X, float Y, float Z);
|
||||||
/// Follow Actor for duration or until you arrive at a position in a cell
|
/// Follow Actor for duration or until you arrive at a position in a cell
|
||||||
AiFollow(const std::string &ActorId,const std::string &CellId,float duration, float X, float Y, float Z);
|
AiFollow(const MWWorld::Ptr& actor, const std::string &CellId, float duration, float X, float Y, float Z);
|
||||||
/// Follow Actor indefinitively
|
/// Follow Actor indefinitively
|
||||||
AiFollow(const std::string &ActorId, bool commanded=false);
|
AiFollow(const MWWorld::Ptr& actor, bool commanded=false);
|
||||||
|
|
||||||
AiFollow(const ESM::AiSequence::AiFollow* follow);
|
AiFollow(const ESM::AiSequence::AiFollow* follow);
|
||||||
|
|
||||||
MWWorld::Ptr getTarget() const;
|
|
||||||
virtual bool sideWithTarget() const { return true; }
|
virtual bool sideWithTarget() const { return true; }
|
||||||
virtual bool followTargetThroughDoors() const { return true; }
|
virtual bool followTargetThroughDoors() const { return true; }
|
||||||
virtual bool shouldCancelPreviousAi() const { return !mCommanded; }
|
virtual bool shouldCancelPreviousAi() const { return !mCommanded; }
|
||||||
|
@ -66,8 +67,6 @@ namespace MWMechanics
|
||||||
float mX;
|
float mX;
|
||||||
float mY;
|
float mY;
|
||||||
float mZ;
|
float mZ;
|
||||||
std::string mActorRefId;
|
|
||||||
mutable int mActorId;
|
|
||||||
std::string mCellId;
|
std::string mCellId;
|
||||||
bool mActive; // have we spotted the target?
|
bool mActive; // have we spotted the target?
|
||||||
int mFollowIndex;
|
int mFollowIndex;
|
||||||
|
|
|
@ -27,15 +27,36 @@ MWMechanics::AiPackage::~AiPackage() {}
|
||||||
|
|
||||||
MWMechanics::AiPackage::AiPackage() :
|
MWMechanics::AiPackage::AiPackage() :
|
||||||
mTimer(AI_REACTION_TIME + 1.0f), // to force initial pathbuild
|
mTimer(AI_REACTION_TIME + 1.0f), // to force initial pathbuild
|
||||||
|
mTargetActorRefId(""),
|
||||||
|
mTargetActorId(-1),
|
||||||
mRotateOnTheRunChecks(0),
|
mRotateOnTheRunChecks(0),
|
||||||
mIsShortcutting(false),
|
mIsShortcutting(false),
|
||||||
mShortcutProhibited(false), mShortcutFailPos()
|
mShortcutProhibited(false),
|
||||||
|
mShortcutFailPos()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
MWWorld::Ptr MWMechanics::AiPackage::getTarget() const
|
MWWorld::Ptr MWMechanics::AiPackage::getTarget() const
|
||||||
{
|
{
|
||||||
return MWWorld::Ptr();
|
if (mTargetActorId == -2)
|
||||||
|
return MWWorld::Ptr();
|
||||||
|
|
||||||
|
if (mTargetActorId == -1)
|
||||||
|
{
|
||||||
|
MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mTargetActorRefId, false);
|
||||||
|
if (target.isEmpty())
|
||||||
|
{
|
||||||
|
mTargetActorId = -2;
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
mTargetActorId = target.getClass().getCreatureStats(target).getActorId();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mTargetActorId != -1)
|
||||||
|
return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId);
|
||||||
|
else
|
||||||
|
return MWWorld::Ptr();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MWMechanics::AiPackage::sideWithTarget() const
|
bool MWMechanics::AiPackage::sideWithTarget() const
|
||||||
|
|
|
@ -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,19 +1073,28 @@ 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 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())
|
||||||
|
{
|
||||||
|
osg::Vec3f pos = getActorHeadTransform(ptr).getTrans();
|
||||||
|
|
||||||
|
std::pair<MWWorld::Ptr,osg::Vec3f> result = mPhysics->getHitContact(ptr, pos, rot, distance, targets);
|
||||||
|
if(!result.first.isEmpty())
|
||||||
|
return std::make_pair(result.first, result.second);
|
||||||
|
}
|
||||||
|
|
||||||
osg::Vec3f pos = ptr.getRefData().getPosition().asVec3();
|
osg::Vec3f pos = ptr.getRefData().getPosition().asVec3();
|
||||||
|
|
||||||
if (ptr == getPlayerPtr())
|
// general case, compatible with all types of different creatures
|
||||||
pos = getActorHeadTransform(ptr).getTrans(); // special cased for better aiming with the camera
|
// note: we intentionally do *not* use the collision box offset here, this is required to make
|
||||||
else
|
// some flying creatures work that have their collision box offset in the air
|
||||||
{
|
pos.z() += halfExtents.z();
|
||||||
// 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
|
|
||||||
osg::Vec3f halfExtents = mPhysics->getHalfExtents(ptr);
|
|
||||||
pos.z() += halfExtents.z() * 2 * 0.75;
|
|
||||||
distance += halfExtents.y();
|
|
||||||
}
|
|
||||||
|
|
||||||
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())
|
||||||
|
@ -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);
|
osg::Vec3f hitPosition = actor.getRefData().getPosition().asVec3();
|
||||||
|
|
||||||
MWRender::RenderingManager::RayResult result2 = mRendering->castRay(origin, dest, true, true);
|
// for player we can take faced object first
|
||||||
|
MWWorld::Ptr target;
|
||||||
|
if (actor == MWMechanics::getPlayer())
|
||||||
|
target = getFacedObject();
|
||||||
|
|
||||||
float dist1 = std::numeric_limits<float>::max();
|
// if the faced object can not be activated, do not use it
|
||||||
float dist2 = std::numeric_limits<float>::max();
|
if (!target.isEmpty() && !target.getClass().canBeActivated(target))
|
||||||
|
target = NULL;
|
||||||
|
|
||||||
if (result1.mHit)
|
if (target.isEmpty())
|
||||||
dist1 = (origin - result1.mHitPos).length();
|
|
||||||
if (result2.mHit)
|
|
||||||
dist2 = (origin - result2.mHitPointWorld).length();
|
|
||||||
|
|
||||||
if (result1.mHit)
|
|
||||||
{
|
{
|
||||||
target = result1.mHitObject;
|
// For actor targets, we want to use hit contact with bounding boxes.
|
||||||
hitPosition = result1.mHitPos;
|
// This is to give a slight tolerance for errors, especially with creatures like the Skeleton that would be very hard to aim at otherwise.
|
||||||
if (dist1 > getMaxActivationDistance() && !target.isEmpty() && (target.getClass().isActor() || !target.getClass().canBeActivated(target)))
|
// For object targets, we want the detailed shapes (rendering raycast).
|
||||||
target = NULL;
|
// If we used the bounding boxes for static objects, then we would not be able to target e.g. objects lying on a shelf.
|
||||||
}
|
std::pair<MWWorld::Ptr,osg::Vec3f> result1 = getHitContact(actor, fCombatDistance, targetActors);
|
||||||
else if (result2.mHit)
|
|
||||||
{
|
|
||||||
target = result2.mHitObject;
|
|
||||||
hitPosition = result2.mHitPointWorld;
|
|
||||||
if (dist2 > getMaxActivationDistance() && !target.isEmpty() && !target.getClass().canBeActivated(target))
|
|
||||||
target = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// When targeting an actor that is in combat with an "on touch" spell,
|
// Get the target to use for "on touch" effects, using the facing direction from Head node
|
||||||
// compare against the minimum of activation distance and combat distance.
|
osg::Vec3f origin = getActorHeadTransform(actor).getTrans();
|
||||||
|
|
||||||
if (!target.isEmpty() && target.getClass().isActor() && target.getClass().getCreatureStats (target).getAiSequence().isInCombat())
|
osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1,0,0))
|
||||||
{
|
* osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0,0,-1));
|
||||||
distance = std::min (distance, getStore().get<ESM::GameSetting>().find("fCombatDistance")->getFloat());
|
|
||||||
if (distance < dist1)
|
osg::Vec3f direction = orient * osg::Vec3f(0,1,0);
|
||||||
target = NULL;
|
float distance = getMaxActivationDistance();
|
||||||
|
osg::Vec3f dest = origin + direction * distance;
|
||||||
|
|
||||||
|
MWRender::RenderingManager::RayResult result2 = mRendering->castRay(origin, dest, true, true);
|
||||||
|
|
||||||
|
float dist1 = std::numeric_limits<float>::max();
|
||||||
|
float dist2 = std::numeric_limits<float>::max();
|
||||||
|
|
||||||
|
if (!result1.first.isEmpty() && result1.first.getClass().isActor())
|
||||||
|
dist1 = (origin - result1.second).length();
|
||||||
|
if (result2.mHit)
|
||||||
|
dist2 = (origin - result2.mHitPointWorld).length();
|
||||||
|
|
||||||
|
if (!result1.first.isEmpty() && result1.first.getClass().isActor())
|
||||||
|
{
|
||||||
|
target = result1.first;
|
||||||
|
hitPosition = result1.second;
|
||||||
|
if (dist1 > getMaxActivationDistance())
|
||||||
|
target = NULL;
|
||||||
|
}
|
||||||
|
else if (result2.mHit)
|
||||||
|
{
|
||||||
|
target = result2.mHitObject;
|
||||||
|
hitPosition = result2.mHitPointWorld;
|
||||||
|
if (dist2 > getMaxActivationDistance() && !target.isEmpty() && !target.getClass().canBeActivated(target))
|
||||||
|
target = NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string selectedSpell = stats.getSpells().getSelectedSpell();
|
std::string selectedSpell = stats.getSpells().getSelectedSpell();
|
||||||
|
@ -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><html><head/><body><p>This temporary page contains new settings that will be available in-game in a post-1.0 release of OpenMW.</p></body></html></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…
Reference in a new issue