diff --git a/AUTHORS.md b/AUTHORS.md index 17f11730d..b13953824 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -37,6 +37,7 @@ Programmers Britt Mathis (galdor557) Capostrophic cc9cii + Cédric Mocquillon Chris Boyce (slothlife) Chris Robinson (KittyCat) Cory F. Cohen (cfcohen) @@ -62,6 +63,7 @@ Programmers Evgeniy Mineev (sandstranger) Federico Guerra (FedeWar) Fil Krynicki (filkry) + Florian Weber (Florianjw) Gašper Sedej gugus/gus Hallfaer Tuilinn diff --git a/CHANGELOG.md b/CHANGELOG.md index d1ec6e58e..f4bf94678 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,32 @@ 0.45.0 ------ + + Bug #1990: Sunrise/sunset not set correct Bug #2222: Fatigue's effect on selling price is backwards + Bug #2326: After a bound item expires the last equipped item of that type is not automatically re-equipped Bug #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
tag 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 #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 #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 #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 0.44.0 diff --git a/README.md b/README.md index cc600fdbe..7d92879e2 100644 --- a/README.md +++ b/README.md @@ -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) -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. diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index 99e7b4daa..ec2e963d1 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -8,6 +8,7 @@ set(LAUNCHER settingspage.cpp advancedpage.cpp + utils/cellnameloader.cpp utils/profilescombobox.cpp utils/textinputdialog.cpp utils/lineedit.cpp @@ -24,6 +25,7 @@ set(LAUNCHER_HEADER settingspage.hpp advancedpage.hpp + utils/cellnameloader.hpp utils/profilescombobox.hpp utils/textinputdialog.hpp utils/lineedit.hpp @@ -39,6 +41,7 @@ set(LAUNCHER_HEADER_MOC settingspage.hpp advancedpage.hpp + utils/cellnameloader.hpp utils/textinputdialog.hpp utils/profilescombobox.hpp utils/lineedit.hpp diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index 9ae83419d..2b2d7b448 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -1,10 +1,18 @@ #include "advancedpage.hpp" -#include - -Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent) +#include +#include +#include +#include +#include +#include + +Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg, + Config::GameSettings &gameSettings, + Settings::Manager &engineSettings, QWidget *parent) : QWidget(parent) , mCfgMgr(cfg) + , mGameSettings(gameSettings) , mEngineSettings(engineSettings) { setObjectName ("AdvancedPage"); @@ -13,8 +21,54 @@ Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg, Settings: 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() { + // 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 loadSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "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 // 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 saveSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "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(); if (cValue != mEngineSettings.getBool(setting, group)) mEngineSettings.setBool(setting, group, cValue); +} + +void Launcher::AdvancedPage::slotLoadedCellsChanged(QStringList cellNames) +{ + loadCellsForAutocomplete(cellNames); } \ No newline at end of file diff --git a/apps/launcher/advancedpage.hpp b/apps/launcher/advancedpage.hpp index a8361c98e..59de3d319 100644 --- a/apps/launcher/advancedpage.hpp +++ b/apps/launcher/advancedpage.hpp @@ -8,6 +8,7 @@ #include namespace Files { struct ConfigurationManager; } +namespace Config { class GameSettings; } namespace Launcher { @@ -16,15 +17,29 @@ namespace Launcher Q_OBJECT 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(); void saveSettings(); + public slots: + void slotLoadedCellsChanged(QStringList cellNames); + + private slots: + void on_skipMenuCheckBox_stateChanged(int state); + void on_runScriptAfterStartupBrowseButton_clicked(); + private: Files::ConfigurationManager &mCfgMgr; + Config::GameSettings &mGameSettings; 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 saveSettingBool(QCheckBox *checkbox, const std::string& setting, const std::string& group); }; diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 0b0f8c75e..7b703a924 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -7,7 +7,10 @@ #include #include #include +#include +#include +#include #include #include @@ -16,6 +19,7 @@ #include #include +#include #include "utils/textinputdialog.hpp" #include "utils/profilescombobox.hpp" @@ -40,6 +44,13 @@ Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config: buildView(); 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() @@ -142,6 +153,17 @@ void Launcher::DataFilesPage::saveSettings(const QString &profile) 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) { mLauncherSettings.removeContentList(profile); @@ -308,3 +330,31 @@ bool Launcher::DataFilesPage::showDeleteMessageBox (const QString &text) 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 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); +} \ No newline at end of file diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index d25d20fc9..2cbace38e 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -7,6 +7,7 @@ #include #include +#include class QSortFilterProxyModel; class QAbstractItemModel; @@ -41,8 +42,15 @@ namespace Launcher void saveSettings(const QString &profile = ""); bool loadSettings(); + /** + * Returns the file paths of all selected content files + * @return the file paths of all selected content files + */ + QStringList selectedFilePaths(); + signals: void signalProfileChanged (int index); + void signalLoadedCellsChanged(QStringList selectedFiles); public slots: void slotProfileChanged (int index); @@ -52,6 +60,7 @@ namespace Launcher void slotProfileChangedByUser(const QString &previous, const QString ¤t); void slotProfileRenamed(const QString &previous, const QString ¤t); void slotProfileDeleted(const QString &item); + void slotAddonDataChanged (); void updateOkButton(const QString &text); @@ -72,7 +81,7 @@ namespace Launcher Config::LauncherSettings &mLauncherSettings; QString mPreviousProfile; - + QStringList previousSelectedFiles; QString mDataLocal; void setPluginsCheckstates(Qt::CheckState state); @@ -87,6 +96,7 @@ namespace Launcher void addProfile (const QString &profile, bool setAsCurrent); void checkForDefaultProfile(); void populateFileViews(const QString& contentModelName); + void reloadCells(QStringList selectedFiles); class PathIterator { diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 1a210ccc5..27fa2d903 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -119,7 +119,7 @@ void Launcher::MainDialog::createPages() mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this); mGraphicsPage = new GraphicsPage(mCfgMgr, mEngineSettings, 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 mPlayPage->setProfilesModel(mDataFilesPage->profilesModel()); @@ -139,6 +139,8 @@ void Launcher::MainDialog::createPages() connect(mPlayPage, SIGNAL(signalProfileChanged(int)), mDataFilesPage, SLOT(slotProfileChanged(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); } diff --git a/apps/launcher/utils/cellnameloader.cpp b/apps/launcher/utils/cellnameloader.cpp new file mode 100644 index 000000000..6d1ed2f49 --- /dev/null +++ b/apps/launcher/utils/cellnameloader.cpp @@ -0,0 +1,48 @@ +#include "cellnameloader.hpp" + +#include +#include + +QSet CellNameLoader::getCellNames(QStringList &contentPaths) +{ + QSet 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); +} \ No newline at end of file diff --git a/apps/launcher/utils/cellnameloader.hpp b/apps/launcher/utils/cellnameloader.hpp new file mode 100644 index 000000000..c58d09226 --- /dev/null +++ b/apps/launcher/utils/cellnameloader.hpp @@ -0,0 +1,41 @@ +#ifndef OPENMW_CELLNAMELOADER_H +#define OPENMW_CELLNAMELOADER_H + +#include +#include +#include + +#include + +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 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 diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp index d609a6253..df37afe60 100644 --- a/apps/opencs/model/world/columnbase.hpp +++ b/apps/opencs/model/world/columnbase.hpp @@ -88,6 +88,7 @@ namespace CSMWorld Display_UnsignedInteger8, Display_Integer, Display_Float, + Display_Double, Display_Var, Display_GmstVarType, Display_GlobalVarType, diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index f1025acb9..4ad447b0a 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -1371,7 +1371,7 @@ namespace CSMWorld RotColumn (ESM::Position ESXRecordT::* position, int index, bool door) : Column ( (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& record) const { diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 60a513cb6..6716c7240 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -184,11 +184,11 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_PosZ, CSMWorld::ColumnBase::Display_Float)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_RotX, CSMWorld::ColumnBase::Display_Float)); + new RefIdColumn (Columns::ColumnId_RotX, CSMWorld::ColumnBase::Display_Double)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_RotY, CSMWorld::ColumnBase::Display_Float)); + new RefIdColumn (Columns::ColumnId_RotY, CSMWorld::ColumnBase::Display_Double)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_RotZ, CSMWorld::ColumnBase::Display_Float)); + new RefIdColumn (Columns::ColumnId_RotZ, CSMWorld::ColumnBase::Display_Double)); // Nested table mColumns.push_back(RefIdColumn (Columns::ColumnId_AiPackageList, diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp index efba1ea82..eab37e1bf 100644 --- a/apps/opencs/view/world/util.cpp +++ b/apps/opencs/view/world/util.cpp @@ -233,6 +233,15 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO 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: { QPlainTextEdit *edit = new QPlainTextEdit(parent); diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index da6e80e79..fb492ff3b 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -431,11 +431,8 @@ namespace MWDialogue 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 >& DialogueManager::getChoices() @@ -450,8 +447,8 @@ namespace MWDialogue void DialogueManager::goodbye() { - if (!mIsInChoice) - mGoodbye = true; + mIsInChoice = false; + mGoodbye = true; } void DialogueManager::persuade(int type, ResponseCallback* callback) diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 14bbe81ef..450799f29 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -635,6 +635,11 @@ namespace MWGui void DialogueWindow::onChoiceActivated(int id) { + if (mGoodbye) + { + onGoodbyeActivated(); + return; + } MWBase::Environment::get().getDialogueManager()->questionAnswered(id, mCallback.get()); updateTopics(); } diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp index a9319048e..16568e2f3 100644 --- a/apps/openmw/mwgui/formatting.cpp +++ b/apps/openmw/mwgui/formatting.cpp @@ -31,6 +31,14 @@ namespace MWGui boost::algorithm::replace_all(mText, "\r", ""); + // vanilla game does not show any text after last
tag. + const std::string lowerText = Misc::StringUtils::lowerCase(mText); + int index = lowerText.rfind("
"); + if (index == -1) + mText = ""; + else + mText = mText.substr(0, index+4); + registerTag("br", Event_BrTag); registerTag("p", Event_PTag); registerTag("img", Event_ImgTag); diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 9bf6e4385..677ddefb3 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -562,8 +562,9 @@ namespace MWGui void SettingsWindow::onOpen() { - updateControlsBox (); + updateControlsBox(); resetScrollbars(); + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mOkButton); } void SettingsWindow::onWindowResize(MyGUI::Window *_sender) diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index 61febf315..52575e25c 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -310,8 +310,10 @@ namespace MWGui void WaitDialog::wakeUp () { mSleeping = false; - mTimeAdvancer.stop(); - stopWaiting(); + if (mInterruptAt != -1) + onWaitingInterrupted(); + else + stopWaiting(); } } diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 7990373a7..de394c446 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -50,27 +50,36 @@ bool isConscious(const MWWorld::Ptr& ptr) 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 boundItemsMap; + if (boundItemsMap.empty()) { - if (actor.getClass().getContainerStore(actor).count(item) == 0) - { - MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); - MWWorld::Ptr newPtr = *store.MWWorld::ContainerStore::add(item, 1, actor); - MWWorld::ActionEquip action(newPtr); - action.execute(actor); - MWWorld::ConstContainerStoreIterator rightHand = store.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - // change draw state only if the item is in player's right hand - if (actor == MWMechanics::getPlayer() - && rightHand != store.end() && newPtr == *rightHand) - { - MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon); - } - } + std::string boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundBootsID")->getString(); + boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Boots; + + boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundCuirassID")->getString(); + boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Cuirass; + + boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundLeftGauntletID")->getString(); + boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_LeftGauntlet; + + boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundRightGauntletID")->getString(); + boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_RightGauntlet; + + boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundHelmID")->getString(); + boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Helmet; + + boundId = MWBase::Environment::get().getWorld()->getStore().get().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::iterator it = boundItemsMap.find(itemId); + if (it != boundItemsMap.end()) + slot = it->second; + + return slot; } class CheckActorCommanded : public MWMechanics::EffectSourceVisitor @@ -139,7 +148,6 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float namespace MWMechanics { - const float aiProcessingDistance = 7168; 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) { // magic effects @@ -756,25 +841,23 @@ namespace MWMechanics float magnitude = effects.get(it->first).getMagnitude(); if (found != (magnitude > 0)) { + if (magnitude > 0) + creatureStats.mBoundItems.insert(it->first); + else + creatureStats.mBoundItems.erase(it->first); + std::string itemGmst = it->second; std::string item = MWBase::Environment::get().getWorld()->getStore().get().find( itemGmst)->getString(); + + magnitude > 0 ? addBoundItem(item, ptr) : removeBoundItem(item, ptr); + if (it->first == ESM::MagicEffect::BoundGloves) { - item = MWBase::Environment::get().getWorld()->getStore().get().find( - "sMagicBoundLeftGauntletID")->getString(); - adjustBoundItem(item, magnitude > 0, ptr); item = MWBase::Environment::get().getWorld()->getStore().get().find( "sMagicBoundRightGauntletID")->getString(); - adjustBoundItem(item, magnitude > 0, ptr); + magnitude > 0 ? addBoundItem(item, ptr) : removeBoundItem(item, ptr); } - else - adjustBoundItem(item, magnitude > 0, ptr); - - if (magnitude > 0) - creatureStats.mBoundItems.insert(it->first); - else - creatureStats.mBoundItems.erase(it->first); } } diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 15f2d3dc8..0de1f4d6c 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include "../mwbase/world.hpp" @@ -26,6 +25,9 @@ namespace MWMechanics { std::map 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 adjustMagicEffects (const MWWorld::Ptr& creature); diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 83ebc67d9..06248fa10 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -103,9 +103,10 @@ namespace MWMechanics bool isFleeing(); }; - AiCombat::AiCombat(const MWWorld::Ptr& actor) : - mTargetActorId(actor.getClass().getCreatureStats(actor).getActorId()) - {} + AiCombat::AiCombat(const MWWorld::Ptr& actor) + { + mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId(); + } AiCombat::AiCombat(const ESM::AiSequence::AiCombat *combat) { diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index e7d74248b..6e1f0c623 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -54,9 +54,6 @@ namespace MWMechanics virtual bool shouldCancelPreviousAi() const { return false; } private: - - int mTargetActorId; - /// Returns true if combat should end bool attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController); diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index 103ef32e1..a86d13d75 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -22,28 +22,32 @@ namespace MWMechanics { 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(duration)) + : mX(x), mY(y), mZ(z), mDuration(duration), mRemainingDuration(static_cast(duration)) , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::max()) { + mTargetActorRefId = actorId; mMaxDist = 450; } - 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(duration)) + AiEscort::AiEscort(const std::string &actorId, const std::string &cellId, int duration, float x, float y, float z) + : mCellId(cellId), mX(x), mY(y), mZ(z), mDuration(duration), mRemainingDuration(static_cast(duration)) , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::max()) { + mTargetActorRefId = actorId; mMaxDist = 450; } 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) , mRemainingDuration(escort->mRemainingDuration) , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::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. // 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. @@ -78,7 +82,7 @@ namespace MWMechanics actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); 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 followerPos = follower.getRefData().getPosition().pos; double differenceBetween[3]; @@ -119,18 +123,14 @@ namespace MWMechanics return TypeIdEscort; } - MWWorld::Ptr AiEscort::getTarget() const - { - return MWBase::Environment::get().getWorld()->getPtr(mActorId, false); - } - void AiEscort::writeState(ESM::AiSequence::AiSequence &sequence) const { std::unique_ptr escort(new ESM::AiSequence::AiEscort()); escort->mData.mX = mX; escort->mData.mY = mY; escort->mData.mZ = mZ; - escort->mTargetId = mActorId; + escort->mTargetId = mTargetActorRefId; + escort->mTargetActorId = mTargetActorId; escort->mRemainingDuration = mRemainingDuration; escort->mCellId = mCellId; diff --git a/apps/openmw/mwmechanics/aiescort.hpp b/apps/openmw/mwmechanics/aiescort.hpp index 719582271..82dba960e 100644 --- a/apps/openmw/mwmechanics/aiescort.hpp +++ b/apps/openmw/mwmechanics/aiescort.hpp @@ -24,11 +24,11 @@ namespace MWMechanics /// 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 \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 /** 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 **/ - 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); @@ -38,7 +38,6 @@ namespace MWMechanics virtual int getTypeId() const; - MWWorld::Ptr getTarget() const; virtual bool sideWithTarget() const { return true; } void writeState(ESM::AiSequence::AiSequence &sequence) const; @@ -46,7 +45,6 @@ namespace MWMechanics void fastForward(const MWWorld::Ptr& actor, AiState& state); private: - std::string mActorId; std::string mCellId; float mX; float mY; diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index fd5f9c7fe..13de01f9a 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -35,32 +35,53 @@ struct AiFollowStorage : AiTemporaryBase 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) -, 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) -, 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) -, 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) : mAlwaysFollow(follow->mAlwaysFollow), mCommanded(follow->mCommanded), mRemainingDuration(follow->mRemainingDuration) , 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++) { -// 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. + mTargetActorRefId = follow->mTargetId; + 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. mDuration = 1; else @@ -204,7 +225,7 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte std::string AiFollow::getFollowedActor() { - return mActorRefId; + return mTargetActorRefId; } AiFollow *MWMechanics::AiFollow::clone() const @@ -228,7 +249,8 @@ void AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) const follow->mData.mX = mX; follow->mData.mY = mY; follow->mData.mZ = mZ; - follow->mTargetId = mActorRefId; + follow->mTargetId = mTargetActorRefId; + follow->mTargetActorId = mTargetActorId; follow->mRemainingDuration = mRemainingDuration; follow->mCellId = mCellId; follow->mAlwaysFollow = mAlwaysFollow; @@ -241,29 +263,6 @@ void AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) const 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 { return mFollowIndex; diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index 051a4a2ce..f0d43c9a7 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -25,16 +25,17 @@ namespace MWMechanics class AiFollow : public AiPackage { 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 - 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 - 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 - AiFollow(const std::string &ActorId, bool commanded=false); + AiFollow(const MWWorld::Ptr& actor, bool commanded=false); AiFollow(const ESM::AiSequence::AiFollow* follow); - MWWorld::Ptr getTarget() const; virtual bool sideWithTarget() const { return true; } virtual bool followTargetThroughDoors() const { return true; } virtual bool shouldCancelPreviousAi() const { return !mCommanded; } @@ -66,8 +67,6 @@ namespace MWMechanics float mX; float mY; float mZ; - std::string mActorRefId; - mutable int mActorId; std::string mCellId; bool mActive; // have we spotted the target? int mFollowIndex; diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 38f641b94..6a0f5b013 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -27,15 +27,36 @@ MWMechanics::AiPackage::~AiPackage() {} MWMechanics::AiPackage::AiPackage() : mTimer(AI_REACTION_TIME + 1.0f), // to force initial pathbuild + mTargetActorRefId(""), + mTargetActorId(-1), mRotateOnTheRunChecks(0), mIsShortcutting(false), - mShortcutProhibited(false), mShortcutFailPos() + mShortcutProhibited(false), + mShortcutFailPos() { } 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 diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 06b4adf61..7e8f905ad 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -48,7 +48,8 @@ namespace MWMechanics TypeIdPursue = 6, TypeIdAvoidDoor = 7, TypeIdFace = 8, - TypeIdBreathe = 9 + TypeIdBreathe = 9, + TypeIdInternalTravel = 10 }; ///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) 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) virtual bool sideWithTarget() const; @@ -128,6 +132,9 @@ namespace MWMechanics float mTimer; + std::string mTargetActorRefId; + mutable int mTargetActorId; + osg::Vec3f mLastActorPos; short mRotateOnTheRunChecks; // attempts to check rotation to the pathpoint on the run possibility diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index fd8b5752a..f46594655 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -15,13 +15,13 @@ namespace MWMechanics { 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) - : mTargetActorId(pursue->mTargetActorId) { + mTargetActorId = pursue->mTargetActorId; } AiPursue *MWMechanics::AiPursue::clone() const diff --git a/apps/openmw/mwmechanics/aipursue.hpp b/apps/openmw/mwmechanics/aipursue.hpp index cb93e9636..455b0a2fd 100644 --- a/apps/openmw/mwmechanics/aipursue.hpp +++ b/apps/openmw/mwmechanics/aipursue.hpp @@ -40,10 +40,6 @@ namespace MWMechanics virtual bool canCancel() const { return false; } virtual bool shouldCancelPreviousAi() const { return false; } - - private: - - int mTargetActorId; // The actor to pursue }; } #endif diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 2a2d598f5..85afbc453 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -184,7 +184,8 @@ bool isActualAiPackage(int packageTypeId) && packageTypeId != AiPackage::TypeIdPursue && packageTypeId != AiPackage::TypeIdAvoidDoor && 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) @@ -298,7 +299,7 @@ void AiSequence::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()) 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())) 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 - if (package.shouldCancelPreviousAi()) + if (cancelOther && package.shouldCancelPreviousAi()) { for(std::list::iterator it = mPackages.begin(); it != mPackages.end();) { @@ -392,6 +418,8 @@ void AiSequence::writeState(ESM::AiSequence::AiSequence &sequence) const { (*iter)->writeState(sequence); } + + sequence.mLastAiPackage = mLastAiPackage; } void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence) @@ -403,7 +431,7 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence) int count = 0; for (std::vector::const_iterator it = sequence.mPackages.begin(); it != sequence.mPackages.end(); ++it) - { + { if (isActualAiPackage(it->mType)) count++; } @@ -462,6 +490,8 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence) mPackages.push_back(package.release()); } + + mLastAiPackage = sequence.mLastAiPackage; } void AiSequence::fastForward(const MWWorld::Ptr& actor, AiState& state) diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 41d204ee2..d725409de 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -115,7 +115,7 @@ namespace MWMechanics ///< Add \a package to the front of the sequence /** Suspends current package @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. /** If there is no active package, it will throw an exception **/ diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index 36b96101f..72e6ced19 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -28,15 +28,14 @@ bool isWithinMaxRange(const osg::Vec3f& pos1, const osg::Vec3f& pos2) namespace MWMechanics { - AiTravel::AiTravel(float x, float y, float z) - : mX(x),mY(y),mZ(z) + AiTravel::AiTravel(float x, float y, float z, bool hidden) + : mX(x),mY(y),mZ(z),mHidden(hidden) { } 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 @@ -64,7 +63,7 @@ namespace MWMechanics int AiTravel::getTypeId() const { - return TypeIdTravel; + return mHidden ? TypeIdInternalTravel : TypeIdTravel; } void AiTravel::fastForward(const MWWorld::Ptr& actor, AiState& state) @@ -83,6 +82,7 @@ namespace MWMechanics travel->mData.mX = mX; travel->mData.mY = mY; travel->mData.mZ = mZ; + travel->mHidden = mHidden; ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Travel; diff --git a/apps/openmw/mwmechanics/aitravel.hpp b/apps/openmw/mwmechanics/aitravel.hpp index 8c75bded1..c297771d0 100644 --- a/apps/openmw/mwmechanics/aitravel.hpp +++ b/apps/openmw/mwmechanics/aitravel.hpp @@ -20,7 +20,7 @@ namespace MWMechanics { public: /// 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); /// Simulates the passing of time @@ -38,6 +38,8 @@ namespace MWMechanics float mX; float mY; float mZ; + + bool mHidden; }; } diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 8199170dc..ee680159e 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -59,7 +59,7 @@ namespace MWMechanics float mTargetAngleRadians; bool mTurnActorGivingGreetingToFacePlayer; float mReaction; // update some actions infrequently - + AiWander::GreetingState mSaidGreeting; int mGreetingTimer; @@ -70,7 +70,7 @@ namespace MWMechanics bool mIsWanderingManually; bool mCanWanderAlongPathGrid; - + unsigned short mIdleAnimation; std::vector mBadIdles; // Idle animations that when called cause errors @@ -86,7 +86,7 @@ namespace MWMechanics float mDoorCheckDuration; int mStuckCount; - + AiWanderStorage(): mTargetAngleRadians(0), mTurnActorGivingGreetingToFacePlayer(false), @@ -111,7 +111,7 @@ namespace MWMechanics mIsWanderingManually = isManualWander; } }; - + AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): 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)) @@ -223,7 +223,7 @@ namespace MWMechanics if (mPathFinder.isPathConstructed()) storage.setState(Wander_Walking); } - + doPerFrameActionsForState(actor, duration, storage, pos); playIdleDialogueRandomly(actor); @@ -298,13 +298,6 @@ namespace MWMechanics if(mDistance && cellChange) 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 WanderState& wanderState = storage.mState; if ((wanderState == Wander_IdleNow) || (wanderState == Wander_Walking)) @@ -321,7 +314,7 @@ namespace MWMechanics { setPathToAnAllowedNode(actor, storage, pos); } - } + } } else if (storage.mIsWanderingManually && mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DESTINATION_TOLERANCE)) { completeManualWalking(actor, storage); } @@ -330,10 +323,19 @@ namespace MWMechanics } 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) { @@ -342,35 +344,14 @@ namespace MWMechanics // End package if duration is complete if (mRemainingDuration <= 0) { - stopWalking(actor, storage); - return true; + stopWalking(actor, storage); + return true; } } // if get here, not yet completed 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. */ @@ -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) { // Is there no destination or are we there yet? @@ -873,7 +854,7 @@ namespace MWMechanics state.moveIn(new AiWanderStorage()); - MWBase::Environment::get().getWorld()->moveObject(actor, static_cast(dest.mX), + MWBase::Environment::get().getWorld()->moveObject(actor, static_cast(dest.mX), static_cast(dest.mY), static_cast(dest.mZ)); actor.getClass().adjustPosition(actor, false); } @@ -914,7 +895,7 @@ namespace MWMechanics // get NPC's position in local (i.e. cell) coordinates osg::Vec3f npcPos(mInitialActorPosition); CoordinateConverter(cell).toLocal(npcPos); - + // Find closest pathgrid point int closestPointIndex = PathFinder::GetClosestPoint(pathgrid, npcPos); @@ -945,7 +926,7 @@ namespace MWMechanics 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: // 1. NPC's initial location // 2. Partway along the path between the point and its connected points. @@ -969,7 +950,7 @@ namespace MWMechanics delta.normalize(); int distance = std::max(mDistance / 2, MINIMUM_WANDER_DISTANCE); - + // must not travel longer than distance between waypoints or NPC goes past waypoint distance = std::min(distance, static_cast(length)); delta *= distance; @@ -1041,4 +1022,3 @@ namespace MWMechanics init(); } } - diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 6266a7708..d96d93165 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -47,9 +47,11 @@ namespace MWMechanics virtual void writeState(ESM::AiSequence::AiSequence &sequence) const; virtual void fastForward(const MWWorld::Ptr& actor, AiState& state); - + bool getRepeat() const; - + + osg::Vec3f getDestination(const MWWorld::Ptr& actor) const; + enum GreetingState { Greet_None, Greet_InProgress, @@ -85,7 +87,6 @@ namespace MWMechanics bool reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos, float duration); 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); bool destinationIsAtWater(const MWWorld::Ptr &actor, const osg::Vec3f& destination); bool destinationThroughGround(const osg::Vec3f& startPoint, const osg::Vec3f& destination); diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index a0aad8b1b..07e5fa7d6 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1214,7 +1214,6 @@ bool CharacterController::updateWeaponState() bool animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); if (!animPlaying || complete >= 1.0f) { - mUpperBodyState = UpperCharState_Nothing; forcestateupdate = true; mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype)); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 0a3f8cad6..80b9ace86 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -22,6 +22,7 @@ #include "aicombat.hpp" #include "aipursue.hpp" +#include "aitravel.hpp" #include "spellcasting.hpp" #include "autocalcspell.hpp" #include "npcstats.hpp" @@ -1588,9 +1589,12 @@ namespace MWMechanics 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; - ptr.getClass().getCreatureStats(ptr).getAiSequence().stack(MWMechanics::AiCombat(target), ptr); + + aiSequence.stack(MWMechanics::AiCombat(target), ptr); if (target == getPlayer()) { // if guard starts combat with player, guards pursuing player should do the same diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index d71b4c8ba..f6d92726d 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -547,7 +547,7 @@ namespace MWMechanics || (effectIt->mEffectID == ESM::MagicEffect::CommandCreature && target.getTypeName() == typeid(ESM::Creature).name())) && !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); } diff --git a/apps/openmw/mwmechanics/summoning.cpp b/apps/openmw/mwmechanics/summoning.cpp index fad7bc96d..71c49f9df 100644 --- a/apps/openmw/mwmechanics/summoning.cpp +++ b/apps/openmw/mwmechanics/summoning.cpp @@ -59,7 +59,7 @@ namespace MWMechanics MWMechanics::CreatureStats& summonedCreatureStats = ref.getPtr().getClass().getCreatureStats(ref.getPtr()); // 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()); creatureActorId = summonedCreatureStats.getActorId(); diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 77e836789..e2fa7be54 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -848,6 +848,16 @@ namespace MWPhysics const osg::Quat &orient, float queryDistance, std::vector 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 &store = MWBase::Environment::get().getWorld()->getStore().get(); btConeShape shape (osg::DegreesToRadians(store.find("fCombatAngleXY")->getFloat()/2.0f), queryDistance); diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 34c5f713d..5439447fd 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -287,6 +287,7 @@ namespace MWWorld mAttackingOrSpell = false; mCurrentCrimeId = -1; mPaidCrimeId = -1; + mPreviousItems.clear(); mLastKnownExteriorPosition = osg::Vec3f(0,0,0); for (int i=0; i + #include "../mwworld/refdata.hpp" #include "../mwworld/livecellref.hpp" @@ -46,6 +48,9 @@ namespace MWWorld int mCurrentCrimeId; // the id assigned witnesses int mPaidCrimeId; // the last id paid off (0 bounty) + typedef std::map PreviousItems; // previous equipped items, needed for bound spells + PreviousItems mPreviousItems; + // Saved stats prior to becoming a werewolf MWMechanics::SkillValue mSaveSkills[ESM::Skill::Length]; MWMechanics::AttributeValue mSaveAttributes[ESM::Attribute::Length]; @@ -120,6 +125,10 @@ namespace MWWorld int getNewCrimeId(); // get new id for witnesses void recordCrimeId(); // record the paid crime id when bounty is 0 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 diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 155e4340a..d12e633be 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" @@ -43,49 +42,67 @@ namespace } template -T TimeOfDayInterpolator::getValue(const float gameHour, const TimeOfDaySettings& timeSettings) const +T TimeOfDayInterpolator::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 - if (gameHour <= timeSettings.mNightEnd || gameHour >= timeSettings.mNightStart + 1) + if (gameHour < timeSettings.mNightEnd - preSunriseTime || gameHour > timeSettings.mNightStart + postSunsetTime) return mNightValue; // 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 - float advance = timeSettings.mSunriseTime - gameHour; - float factor = advance / 0.5f; + float advance = middle - gameHour; + float factor = 0.f; + if (duration > 0) + factor = advance / duration * 2; return lerp(mSunriseValue, mNightValue, factor); } else { // fade out - float advance = gameHour - timeSettings.mSunriseTime; - float factor = advance / 3.f; + float advance = gameHour - middle; + float factor = 1.f; + if (duration > 0) + factor = advance / duration * 2; return lerp(mSunriseValue, mDayValue, factor); } } // day - else if (gameHour >= timeSettings.mDayStart + 1 && gameHour <= timeSettings.mDayEnd - 1) + else if (gameHour > timeSettings.mDayStart + postSunriseTime && gameHour < timeSettings.mDayEnd - preSunsetTime) return mDayValue; // 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 - float advance = (timeSettings.mDayEnd + 1) - gameHour; - float factor = (advance / 2); + float advance = middle - gameHour; + float factor = 0.f; + if (duration > 0) + factor = advance / duration * 2; return lerp(mSunsetValue, mDayValue, factor); } else { // fade out - float advance = gameHour - (timeSettings.mDayEnd + 1); - float factor = advance / 2.f; + float advance = gameHour - middle; + float factor = 1.f; + if (duration > 0) + factor = advance / duration * 2; return lerp(mSunsetValue, mNightValue, factor); } } @@ -539,10 +556,28 @@ WeatherManager::WeatherManager(MWRender::RenderingManager& rendering, const Fall , mPlayingSoundID() { mTimeSettings.mNightStart = mSunsetTime + mSunsetDuration; - mTimeSettings.mNightEnd = mSunriseTime - 0.5f; + mTimeSettings.mNightEnd = mSunriseTime; mTimeSettings.mDayStart = mSunriseTime + mSunriseDuration; 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); // 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; double theta; - if ( !is_night ) { + if ( !is_night ) + { theta = static_cast(osg::PI) * (adjustedHour - mSunriseTime) / dayDuration; - } else { - theta = static_cast(osg::PI) * (1.f - (adjustedHour - adjustedNightStart) / nightDuration); + } + else + { + theta = static_cast(osg::PI) + static_cast(osg::PI) * (adjustedHour - adjustedNightStart) / nightDuration; } osg::Vec3f final( @@ -711,7 +749,7 @@ void WeatherManager::update(float duration, bool paused, const TimeStamp& time, 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; if (time.getHour() < mSunriseTime || time.getHour() > mSunsetTime) @@ -784,7 +822,7 @@ unsigned int WeatherManager::getWeatherID() 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; } @@ -1060,20 +1098,25 @@ inline void WeatherManager::calculateResult(const int weatherID, const float gam mResult.mParticleEffect = current.mParticleEffect; 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.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); 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 @@ -1087,15 +1130,17 @@ inline void WeatherManager::calculateResult(const int weatherID, const float gam else 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; 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 mResult.mSunDiscColor.a() = 1; diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index 4be0d3e20..cf6868356 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -7,6 +7,8 @@ #include +#include + #include "../mwbase/soundmanager.hpp" #include "../mwrender/sky.hpp" @@ -38,6 +40,13 @@ namespace MWWorld { class TimeStamp; + struct WeatherSetting + { + float mPreSunriseTime; + float mPostSunriseTime; + float mPreSunsetTime; + float mPostSunsetTime; + }; struct TimeOfDaySettings { @@ -45,7 +54,37 @@ namespace MWWorld float mNightEnd; float mDayStart; float mDayEnd; - float mSunriseTime; + + std::map mSunriseTransitions; + + float mStarsPostSunsetStart; + float mStarsPreSunriseFinish; + float mStarsFadingDuration; + + WeatherSetting getSetting(const std::string& type) const + { + std::map::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. @@ -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: T mSunriseValue, mDayValue, mSunsetValue, mNightValue; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 13970ee13..cbd5b9340 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1073,20 +1073,29 @@ namespace MWWorld osg::Quat rot = osg::Quat(posdata.rot[0], osg::Vec3f(-1,0,0)) * osg::Quat(posdata.rot[2], osg::Vec3f(0,0,-1)); - osg::Vec3f pos = ptr.getRefData().getPosition().asVec3(); + osg::Vec3f halfExtents = mPhysics->getHalfExtents(ptr); + + // the origin of hitbox is an actor's front, not center + distance += halfExtents.y(); + // special cased for better aiming with the camera + // if we do not hit anything, will use the default approach as fallback if (ptr == getPlayerPtr()) - pos = getActorHeadTransform(ptr).getTrans(); // special cased for better aiming with the camera - else { - // 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(); + osg::Vec3f pos = getActorHeadTransform(ptr).getTrans(); + + std::pair 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(); + + // general case, compatible with all types of different creatures + // note: we intentionally do *not* use the collision box offset here, this is required to make + // some flying creatures work that have their collision box offset in the air + pos.z() += halfExtents.z(); + std::pair result = mPhysics->getHitContact(ptr, pos, rot, distance, targets); if(result.first.isEmpty()) return std::make_pair(MWWorld::Ptr(), osg::Vec3f()); @@ -2752,64 +2761,66 @@ namespace MWWorld { 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. std::vector targetActors; if (!actor.isEmpty() && actor != MWMechanics::getPlayer()) actor.getClass().getCreatureStats(actor).getAiSequence().getCombatTargets(targetActors); - // For actor targets, we want to use bounding boxes (physics raycast). - // 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); + const float fCombatDistance = getStore().get().find("fCombatDistance")->getFloat(); - MWRender::RenderingManager::RayResult result2 = mRendering->castRay(origin, dest, true, true); + osg::Vec3f hitPosition = actor.getRefData().getPosition().asVec3(); - float dist1 = std::numeric_limits::max(); - float dist2 = std::numeric_limits::max(); + // for player we can take faced object first + MWWorld::Ptr target; + if (actor == MWMechanics::getPlayer()) + target = getFacedObject(); - if (result1.mHit) - dist1 = (origin - result1.mHitPos).length(); - if (result2.mHit) - dist2 = (origin - result2.mHitPointWorld).length(); + // if the faced object can not be activated, do not use it + if (!target.isEmpty() && !target.getClass().canBeActivated(target)) + target = NULL; - if (result1.mHit) - { - target = result1.mHitObject; - hitPosition = result1.mHitPos; - if (dist1 > getMaxActivationDistance() && !target.isEmpty() && (target.getClass().isActor() || !target.getClass().canBeActivated(target))) - target = NULL; - } - else if (result2.mHit) + if (target.isEmpty()) { - target = result2.mHitObject; - hitPosition = result2.mHitPointWorld; - if (dist2 > getMaxActivationDistance() && !target.isEmpty() && !target.getClass().canBeActivated(target)) - target = NULL; - } + // For actor targets, we want to use hit contact with bounding boxes. + // 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. + std::pair result1 = getHitContact(actor, fCombatDistance, targetActors); - // When targeting an actor that is in combat with an "on touch" spell, - // compare against the minimum of activation distance and combat distance. + // Get the target to use for "on touch" effects, using the facing direction from Head node + osg::Vec3f origin = getActorHeadTransform(actor).getTrans(); - if (!target.isEmpty() && target.getClass().isActor() && target.getClass().getCreatureStats (target).getAiSequence().isInCombat()) - { - distance = std::min (distance, getStore().get().find("fCombatDistance")->getFloat()); - if (distance < dist1) - target = NULL; + 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); + float distance = getMaxActivationDistance(); + osg::Vec3f dest = origin + direction * distance; + + MWRender::RenderingManager::RayResult result2 = mRendering->castRay(origin, dest, true, true); + + float dist1 = std::numeric_limits::max(); + float dist2 = std::numeric_limits::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(); @@ -3466,7 +3477,7 @@ namespace MWWorld osg::Vec3f World::aimToTarget(const ConstPtr &actor, const MWWorld::ConstPtr& target) { 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); return (targetPos - weaponPos); } @@ -3475,7 +3486,7 @@ namespace MWWorld { osg::Vec3f weaponPos = actor.getRefData().getPosition().asVec3(); osg::Vec3f halfExtents = mPhysics->getHalfExtents(actor); - weaponPos.z() += halfExtents.z() * 2 * 0.75; + weaponPos.z() += halfExtents.z(); return mPhysics->getHitDistance(weaponPos, target) - halfExtents.y(); } diff --git a/cmake/OpenMWMacros.cmake b/cmake/OpenMWMacros.cmake index fe2837a09..2fa86094f 100644 --- a/cmake/OpenMWMacros.cmake +++ b/cmake/OpenMWMacros.cmake @@ -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) get_cmake_property(${VALUE} GENERATOR_IS_MULTI_CONFIG) 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 (DEFINED generator_is_multi_config_var) endmacro (get_generator_is_multi_config) diff --git a/components/compiler/fileparser.cpp b/components/compiler/fileparser.cpp index 52a9a63f1..c9e205b8a 100644 --- a/components/compiler/fileparser.cpp +++ b/components/compiler/fileparser.cpp @@ -119,6 +119,11 @@ namespace Compiler return false; } } + else if (code==Scanner::S_comma && (mState==NameState || mState==EndNameState)) + { + // ignoring comma (for now) + return true; + } return Parser::parseSpecial (code, loc, scanner); } diff --git a/components/compiler/lineparser.cpp b/components/compiler/lineparser.cpp index c7f82a3d0..2d551348d 100644 --- a/components/compiler/lineparser.cpp +++ b/components/compiler/lineparser.cpp @@ -512,7 +512,7 @@ namespace Compiler return true; } - if (code==Scanner::S_member && mState==PotentialExplicitState) + if (code==Scanner::S_member && mState==PotentialExplicitState && mAllowExpression) { mState = MemberState; parseExpression (scanner, loc); diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index f4da7e6ad..5e16064f4 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -239,4 +239,4 @@ void ContentSelectorView::ContentSelector::slotUncheckMultiSelectedItems() void ContentSelectorView::ContentSelector::slotCheckMultiSelectedItems() { setCheckStateForMultiSelectedItems(true); -} +} \ No newline at end of file diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index 9f775d597..323f926ed 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -15,7 +15,6 @@ namespace ContentSelectorView Q_OBJECT QMenu *mContextMenu; - QStringList mFilePaths; protected: @@ -61,6 +60,7 @@ namespace ContentSelectorView void signalCurrentGamefileIndexChanged (int); void signalAddonDataChanged (const QModelIndex& topleft, const QModelIndex& bottomright); + void signalSelectedFilesChanged(QStringList selectedFiles); private slots: diff --git a/components/esm/aisequence.cpp b/components/esm/aisequence.cpp index c39ef8269..335863c32 100644 --- a/components/esm/aisequence.cpp +++ b/components/esm/aisequence.cpp @@ -35,17 +35,20 @@ namespace AiSequence void AiTravel::load(ESMReader &esm) { esm.getHNT (mData, "DATA"); + esm.getHNOT (mHidden, "HIDD"); } void AiTravel::save(ESMWriter &esm) const { esm.writeHNT ("DATA", mData); + esm.writeHNT ("HIDD", mHidden); } void AiEscort::load(ESMReader &esm) { esm.getHNT (mData, "DATA"); mTargetId = esm.getHNString("TARG"); + esm.getHNOT (mTargetActorId, "TAID"); esm.getHNT (mRemainingDuration, "DURA"); mCellId = esm.getHNOString ("CELL"); } @@ -54,6 +57,7 @@ namespace AiSequence { esm.writeHNT ("DATA", mData); esm.writeHNString ("TARG", mTargetId); + esm.writeHNT ("TAID", mTargetActorId); esm.writeHNT ("DURA", mRemainingDuration); if (!mCellId.empty()) esm.writeHNString ("CELL", mCellId); @@ -63,6 +67,7 @@ namespace AiSequence { esm.getHNT (mData, "DATA"); mTargetId = esm.getHNString("TARG"); + esm.getHNOT (mTargetActorId, "TAID"); esm.getHNT (mRemainingDuration, "DURA"); mCellId = esm.getHNOString ("CELL"); esm.getHNT (mAlwaysFollow, "ALWY"); @@ -76,6 +81,7 @@ namespace AiSequence { esm.writeHNT ("DATA", mData); esm.writeHNString("TARG", mTargetId); + esm.writeHNT ("TAID", mTargetActorId); esm.writeHNT ("DURA", mRemainingDuration); if (!mCellId.empty()) esm.writeHNString ("CELL", mCellId); @@ -154,6 +160,8 @@ namespace AiSequence break; } } + + esm.writeHNT ("LAST", mLastAiPackage); } void AiSequence::load(ESMReader &esm) @@ -221,6 +229,8 @@ namespace AiSequence return; } } + + esm.getHNOT (mLastAiPackage, "LAST"); } } } diff --git a/components/esm/aisequence.hpp b/components/esm/aisequence.hpp index 52446d38f..d8c20185f 100644 --- a/components/esm/aisequence.hpp +++ b/components/esm/aisequence.hpp @@ -80,6 +80,7 @@ namespace ESM struct AiTravel : AiPackage { AiTravelData mData; + bool mHidden; void load(ESMReader &esm); void save(ESMWriter &esm) const; @@ -89,6 +90,7 @@ namespace ESM { AiEscortData mData; + int mTargetActorId; std::string mTargetId; std::string mCellId; float mRemainingDuration; @@ -101,6 +103,7 @@ namespace ESM { AiEscortData mData; + int mTargetActorId; std::string mTargetId; std::string mCellId; float mRemainingDuration; @@ -147,10 +150,14 @@ namespace ESM struct AiSequence { - AiSequence() {} + AiSequence() + { + mLastAiPackage = -1; + } ~AiSequence(); std::vector mPackages; + int mLastAiPackage; void load (ESMReader &esm); void save (ESMWriter &esm) const; diff --git a/components/esm/player.cpp b/components/esm/player.cpp index 9ec53240a..7dad34dfb 100644 --- a/components/esm/player.cpp +++ b/components/esm/player.cpp @@ -31,6 +31,18 @@ void ESM::Player::load (ESMReader &esm) mPaidCrimeId = -1; 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()) { for (int i=0; ifirst); + esm.writeHNString ("PREV", it->second); + } + for (int i=0; i mSaveAttributes[ESM::Attribute::Length]; StatState mSaveSkills[ESM::Skill::Length]; + typedef std::map PreviousItems; // previous equipped items, needed for bound spells + PreviousItems mPreviousItems; + void load (ESMReader &esm); void save (ESMWriter &esm) const; }; diff --git a/components/esm/savedgame.cpp b/components/esm/savedgame.cpp index 3220f496e..ea9fef4fb 100644 --- a/components/esm/savedgame.cpp +++ b/components/esm/savedgame.cpp @@ -5,7 +5,7 @@ #include "defs.hpp" unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; -int ESM::SavedGame::sCurrentFormat = 3; +int ESM::SavedGame::sCurrentFormat = 5; void ESM::SavedGame::load (ESMReader &esm) { diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index dc144d119..dadc64f57 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -430,13 +430,16 @@ namespace ESMTerrain // Second iteration - create and fill in the blend maps 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 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(); for (int y=0; y(std::floor((layerIndex - 1) / 4.f)) : layerIndex - 1); int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0; - if (blendIndex == i) - pData[(blendmapSize - y - 1)*blendmapSize*channels + x*channels + channel] = 255; - else - pData[(blendmapSize - y - 1)*blendmapSize*channels + x*channels + channel] = 0; + int alpha = (blendIndex == i) ? 255 : 0; + + int realY = (blendmapSize - y - 1)*imageScaleFactor; + 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); } } diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index ddfa02a09..49f591b47 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -104,13 +104,13 @@ namespace Nif void NiLookAtController::read(NIFStream *nif) { Controller::read(nif); - data.read(nif); + target.read(nif); } void NiLookAtController::post(NIFFile *nif) { Controller::post(nif); - data.post(nif); + target.post(nif); } void NiPathController::read(NIFStream *nif) diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index ce8bff041..f22d10622 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -102,7 +102,7 @@ public: class NiLookAtController : public Controller { public: - NiKeyframeDataPtr data; + NodePtr target; void read(NIFStream *nif); void post(NIFFile *nif); diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 0add92f29..aada21fce 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -261,7 +261,7 @@ namespace NifOsg osg::ref_ptr textkeys (new TextKeyMapHolder); - osg::ref_ptr created = handleNode(nifNode, NULL, imageManager, std::vector(), 0, false, false, &textkeys->mTextKeys); + osg::ref_ptr created = handleNode(nifNode, NULL, imageManager, std::vector(), 0, false, false, false, &textkeys->mTextKeys); if (nif->getUseSkinning()) { @@ -463,7 +463,7 @@ namespace NifOsg } osg::ref_ptr handleNode(const Nif::Node* nifNode, osg::Group* parentNode, Resource::ImageManager* imageManager, - std::vector boundTextures, int animflags, bool skipMeshes, bool isAnimated, TextKeyMap* textKeys, osg::Node* rootNode=NULL) + std::vector 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")) return NULL; @@ -510,7 +510,7 @@ namespace NifOsg if(sd->string == "MRK" && !Loader::getShowMarkers()) { // Marker objects. These meshes are only visible in the editor. - skipMeshes = true; + hasMarkers = true; } } } @@ -542,7 +542,7 @@ namespace NifOsg 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); } @@ -554,13 +554,18 @@ namespace NifOsg if (nifNode->recType == Nif::RC_NiTriShape && !skipMeshes) { const Nif::NiTriShape* triShape = static_cast(nifNode); - if (triShape->skin.empty()) - handleTriShape(triShape, node, composite, boundTextures, animflags); - else - handleSkinnedTriShape(triShape, node, composite, boundTextures, animflags); + const std::string nodeName = Misc::StringUtils::lowerCase(triShape->name); + static const std::string pattern = "tri editormarker"; + if (!hasMarkers || nodeName.compare(0, pattern.size(), pattern) != 0) + { + if (triShape->skin.empty()) + handleTriShape(triShape, node, composite, boundTextures, animflags); + else + handleSkinnedTriShape(triShape, node, composite, boundTextures, animflags); - if (!nifNode->controller.empty()) - handleMeshControllers(nifNode, node, composite, boundTextures, animflags); + if (!nifNode->controller.empty()) + handleMeshControllers(nifNode, node, composite, boundTextures, animflags); + } } 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) { 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); } } diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 640f2932b..56ace0e5a 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -25,6 +25,9 @@ namespace Terrain matrix.preMultTranslate(osg::Vec3f(0.5f, 0.5f, 0.f)); matrix.preMultScale(osg::Vec3f(scale, scale, 1.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); diff --git a/files/mac/openmw-Info.plist.in b/files/mac/openmw-Info.plist.in index b4a8af480..20dc36afa 100644 --- a/files/mac/openmw-Info.plist.in +++ b/files/mac/openmw-Info.plist.in @@ -8,6 +8,8 @@ English CFBundleExecutable openmw-launcher + CFBundleIdentifier + org.openmw.openmw CFBundleInfoDictionaryVersion 6.0 CFBundleLongVersionString @@ -18,6 +20,8 @@ APPL CFBundleSignature ???? + CFBundleShortVersionString + ${OPENMW_VERSION} CFBundleVersion ${OPENMW_VERSION} CSResourcesFileMapped diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index f436b4db3..7a01ce41d 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -11,13 +11,6 @@ - - - - <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> - - - @@ -27,9 +20,9 @@ 0 - -187 + 0 630 - 510 + 746 @@ -276,6 +269,94 @@ + + + + Testing + + + + + + These settings are intended for testing mods and will cause issues if used for normal gameplay. + + + true + + + + + + + Qt::Horizontal + + + + + + + Skip menu and generate default character + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + Start default character at + + + + + + + default cell + + + + + + + + + Run script after startup: + + + + + + + + + + + + Browse… + + + + + + + +