1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-03-28 19:21:04 +00:00

Merge branch 'master' into menuscripts

This commit is contained in:
uramer 2024-02-01 20:05:01 +01:00
commit b988db9bda
62 changed files with 614 additions and 363 deletions

View File

@ -61,6 +61,7 @@
Bug #7034: Misc items defined in one content file are not treated as keys if another content file uses them as such Bug #7034: Misc items defined in one content file are not treated as keys if another content file uses them as such
Bug #7042: Weapon follow animations that immediately follow the hit animations cause multiple hits Bug #7042: Weapon follow animations that immediately follow the hit animations cause multiple hits
Bug #7044: Changing a class' services does not affect autocalculated NPCs Bug #7044: Changing a class' services does not affect autocalculated NPCs
Bug #7053: Running into objects doesn't trigger GetCollidingPC
Bug #7054: Quests aren't sorted by name Bug #7054: Quests aren't sorted by name
Bug #7064: NPCs don't report crime if the player is casting offensive spells on them while sneaking Bug #7064: NPCs don't report crime if the player is casting offensive spells on them while sneaking
Bug #7077: OpenMW fails to load certain particle effects in .osgt format Bug #7077: OpenMW fails to load certain particle effects in .osgt format
@ -133,6 +134,8 @@
Bug #7765: OpenMW-CS: Touch Record option is broken Bug #7765: OpenMW-CS: Touch Record option is broken
Bug #7770: Sword of the Perithia: Script execution failure Bug #7770: Sword of the Perithia: Script execution failure
Bug #7780: Non-ASCII texture paths in NIF files don't work Bug #7780: Non-ASCII texture paths in NIF files don't work
Bug #7785: OpenMW-CS initialising Skill and Attribute fields to 0 instead of -1 on non-FortifyStat spells
Bug #7796: Absorbed enchantments don't restore magicka
Feature #2566: Handle NAM9 records for manual cell references Feature #2566: Handle NAM9 records for manual cell references
Feature #3537: Shader-based water ripples Feature #3537: Shader-based water ripples
Feature #5173: Support for NiFogProperty Feature #5173: Support for NiFogProperty
@ -166,6 +169,7 @@
Feature #7546: Start the game on Fredas Feature #7546: Start the game on Fredas
Feature #7554: Controller binding for tab for menu navigation Feature #7554: Controller binding for tab for menu navigation
Feature #7568: Uninterruptable scripted music Feature #7568: Uninterruptable scripted music
Feature #7606: Launcher: allow Shift-select in Archives tab
Feature #7608: Make the missing dependencies warning when loading a savegame more helpful Feature #7608: Make the missing dependencies warning when loading a savegame more helpful
Feature #7618: Show the player character's health in the save details Feature #7618: Show the player character's health in the save details
Feature #7625: Add some missing console error outputs Feature #7625: Add some missing console error outputs
@ -174,6 +178,8 @@
Feature #7652: Sort inactive post processing shaders list properly Feature #7652: Sort inactive post processing shaders list properly
Feature #7698: Implement sAbsorb, sDamage, sDrain, sFortify and sRestore Feature #7698: Implement sAbsorb, sDamage, sDrain, sFortify and sRestore
Feature #7709: Improve resolution selection in Launcher Feature #7709: Improve resolution selection in Launcher
Feature #7792: Support Timescale Clouds
Feature #7795: Support MaxNumberRipples INI setting
Feature #7805: Lua Menu context Feature #7805: Lua Menu context
Task #5896: Do not use deprecated MyGUI properties Task #5896: Do not use deprecated MyGUI properties
Task #6624: Drop support for saves made prior to 0.45 Task #6624: Drop support for saves made prior to 0.45

View File

@ -69,7 +69,7 @@ Allowed options)");
addOption("name,n", bpo::value<std::string>(), "Show only the record with this name. Only affects dump mode."); addOption("name,n", bpo::value<std::string>(), "Show only the record with this name. Only affects dump mode.");
addOption("plain,p", addOption("plain,p",
"Print contents of dialogs, books and scripts. " "Print contents of dialogs, books and scripts. "
"(skipped by default)" "(skipped by default) "
"Only affects dump mode."); "Only affects dump mode.");
addOption("quiet,q", "Suppress all record information. Useful for speed tests."); addOption("quiet,q", "Suppress all record information. Useful for speed tests.");
addOption("loadcells,C", "Browse through contents of all cells."); addOption("loadcells,C", "Browse through contents of all cells.");
@ -390,7 +390,7 @@ namespace
if (!quiet && interested) if (!quiet && interested)
{ {
std::cout << "\nRecord: " << n.toStringView() << " '" << record->getId() << "'\n" std::cout << "\nRecord: " << n.toStringView() << " " << record->getId() << "\n"
<< "Record flags: " << recordFlags(record->getFlags()) << '\n'; << "Record flags: " << recordFlags(record->getFlags()) << '\n';
record->print(); record->print();
} }

View File

@ -464,7 +464,8 @@ namespace EsmTool
{ {
std::cout << " Name: " << mData.mName << std::endl; std::cout << " Name: " << mData.mName << std::endl;
std::cout << " Model: " << mData.mModel << std::endl; std::cout << " Model: " << mData.mModel << std::endl;
std::cout << " Script: " << mData.mScript << std::endl; if (!mData.mScript.empty())
std::cout << " Script: " << mData.mScript << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
@ -516,7 +517,8 @@ namespace EsmTool
std::cout << " Name: " << mData.mName << std::endl; std::cout << " Name: " << mData.mName << std::endl;
std::cout << " Model: " << mData.mModel << std::endl; std::cout << " Model: " << mData.mModel << std::endl;
std::cout << " Icon: " << mData.mIcon << std::endl; std::cout << " Icon: " << mData.mIcon << std::endl;
std::cout << " Script: " << mData.mScript << std::endl; if (!mData.mScript.empty())
std::cout << " Script: " << mData.mScript << std::endl;
std::cout << " Type: " << apparatusTypeLabel(mData.mData.mType) << " (" << mData.mData.mType << ")" std::cout << " Type: " << apparatusTypeLabel(mData.mData.mType) << " (" << mData.mData.mType << ")"
<< std::endl; << std::endl;
std::cout << " Weight: " << mData.mData.mWeight << std::endl; std::cout << " Weight: " << mData.mData.mWeight << std::endl;
@ -679,7 +681,8 @@ namespace EsmTool
{ {
std::cout << " Name: " << mData.mName << std::endl; std::cout << " Name: " << mData.mName << std::endl;
std::cout << " Model: " << mData.mModel << std::endl; std::cout << " Model: " << mData.mModel << std::endl;
std::cout << " Script: " << mData.mScript << std::endl; if (!mData.mScript.empty())
std::cout << " Script: " << mData.mScript << std::endl;
std::cout << " Flags: " << creatureFlags((int)mData.mFlags) << std::endl; std::cout << " Flags: " << creatureFlags((int)mData.mFlags) << std::endl;
std::cout << " Blood Type: " << mData.mBloodType + 1 << std::endl; std::cout << " Blood Type: " << mData.mBloodType + 1 << std::endl;
std::cout << " Original: " << mData.mOriginal << std::endl; std::cout << " Original: " << mData.mOriginal << std::endl;
@ -747,7 +750,8 @@ namespace EsmTool
{ {
std::cout << " Name: " << mData.mName << std::endl; std::cout << " Name: " << mData.mName << std::endl;
std::cout << " Model: " << mData.mModel << std::endl; std::cout << " Model: " << mData.mModel << std::endl;
std::cout << " Script: " << mData.mScript << std::endl; if (!mData.mScript.empty())
std::cout << " Script: " << mData.mScript << std::endl;
std::cout << " OpenSound: " << mData.mOpenSound << std::endl; std::cout << " OpenSound: " << mData.mOpenSound << std::endl;
std::cout << " CloseSound: " << mData.mCloseSound << std::endl; std::cout << " CloseSound: " << mData.mCloseSound << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl;
@ -1338,28 +1342,26 @@ namespace EsmTool
template <> template <>
void Record<CellState>::print() void Record<CellState>::print()
{ {
std::cout << " Id:" << std::endl; std::cout << " Cell Id: \"" << mData.mCellState.mId.toString() << "\"" << std::endl;
std::cout << " CellId: " << mData.mCellState.mId << std::endl; std::cout << " Water Level: " << mData.mCellState.mWaterLevel << std::endl;
std::cout << " Index:" << std::endl; std::cout << " Has Fog Of War: " << mData.mCellState.mHasFogOfWar << std::endl;
std::cout << " WaterLevel: " << mData.mCellState.mWaterLevel << std::endl; std::cout << " Last Respawn:" << std::endl;
std::cout << " HasFogOfWar: " << mData.mCellState.mHasFogOfWar << std::endl;
std::cout << " LastRespawn:" << std::endl;
std::cout << " Day:" << mData.mCellState.mLastRespawn.mDay << std::endl; std::cout << " Day:" << mData.mCellState.mLastRespawn.mDay << std::endl;
std::cout << " Hour:" << mData.mCellState.mLastRespawn.mHour << std::endl; std::cout << " Hour:" << mData.mCellState.mLastRespawn.mHour << std::endl;
if (mData.mCellState.mHasFogOfWar) if (mData.mCellState.mHasFogOfWar)
{ {
std::cout << " NorthMarkerAngle: " << mData.mFogState.mNorthMarkerAngle << std::endl; std::cout << " North Marker Angle: " << mData.mFogState.mNorthMarkerAngle << std::endl;
std::cout << " Bounds:" << std::endl; std::cout << " Bounds:" << std::endl;
std::cout << " MinX: " << mData.mFogState.mBounds.mMinX << std::endl; std::cout << " Min X: " << mData.mFogState.mBounds.mMinX << std::endl;
std::cout << " MinY: " << mData.mFogState.mBounds.mMinY << std::endl; std::cout << " Min Y: " << mData.mFogState.mBounds.mMinY << std::endl;
std::cout << " MaxX: " << mData.mFogState.mBounds.mMaxX << std::endl; std::cout << " Max X: " << mData.mFogState.mBounds.mMaxX << std::endl;
std::cout << " MaxY: " << mData.mFogState.mBounds.mMaxY << std::endl; std::cout << " Max Y: " << mData.mFogState.mBounds.mMaxY << std::endl;
for (const ESM::FogTexture& fogTexture : mData.mFogState.mFogTextures) for (const ESM::FogTexture& fogTexture : mData.mFogState.mFogTextures)
{ {
std::cout << " FogTexture:" << std::endl; std::cout << " Fog Texture:" << std::endl;
std::cout << " X: " << fogTexture.mX << std::endl; std::cout << " X: " << fogTexture.mX << std::endl;
std::cout << " Y: " << fogTexture.mY << std::endl; std::cout << " Y: " << fogTexture.mY << std::endl;
std::cout << " ImageData: (" << fogTexture.mImageData.size() << ")" << std::endl; std::cout << " Image Data: (" << fogTexture.mImageData.size() << ")" << std::endl;
} }
} }
} }
@ -1367,7 +1369,7 @@ namespace EsmTool
template <> template <>
std::string Record<ESM::Cell>::getId() const std::string Record<ESM::Cell>::getId() const
{ {
return mData.mName; return std::string(); // No ID for Cell record
} }
template <> template <>
@ -1397,9 +1399,7 @@ namespace EsmTool
template <> template <>
std::string Record<CellState>::getId() const std::string Record<CellState>::getId() const
{ {
std::ostringstream stream; return std::string(); // No ID for CellState record
stream << mData.mCellState.mId;
return stream.str();
} }
} // end namespace } // end namespace

View File

@ -3,7 +3,9 @@
#include <QDebug> #include <QDebug>
#include <QFileDialog> #include <QFileDialog>
#include <QList>
#include <QMessageBox> #include <QMessageBox>
#include <QPair>
#include <QPushButton> #include <QPushButton>
#include <algorithm> #include <algorithm>
@ -162,8 +164,8 @@ Launcher::DataFilesPage::DataFilesPage(const Files::ConfigurationManager& cfg, C
connect(ui.directoryUpButton, &QPushButton::released, this, [this]() { this->moveDirectory(-1); }); connect(ui.directoryUpButton, &QPushButton::released, this, [this]() { this->moveDirectory(-1); });
connect(ui.directoryDownButton, &QPushButton::released, this, [this]() { this->moveDirectory(1); }); connect(ui.directoryDownButton, &QPushButton::released, this, [this]() { this->moveDirectory(1); });
connect(ui.directoryRemoveButton, &QPushButton::released, this, [this]() { this->removeDirectory(); }); connect(ui.directoryRemoveButton, &QPushButton::released, this, [this]() { this->removeDirectory(); });
connect(ui.archiveUpButton, &QPushButton::released, this, [this]() { this->moveArchive(-1); }); connect(ui.archiveUpButton, &QPushButton::released, this, [this]() { this->moveArchives(-1); });
connect(ui.archiveDownButton, &QPushButton::released, this, [this]() { this->moveArchive(1); }); connect(ui.archiveDownButton, &QPushButton::released, this, [this]() { this->moveArchives(1); });
connect( connect(
ui.directoryListWidget->model(), &QAbstractItemModel::rowsMoved, this, [this]() { this->sortDirectories(); }); ui.directoryListWidget->model(), &QAbstractItemModel::rowsMoved, this, [this]() { this->sortDirectories(); });
@ -218,6 +220,18 @@ void Launcher::DataFilesPage::buildView()
&DataFilesPage::readNavMeshToolStderr); &DataFilesPage::readNavMeshToolStderr);
connect(mNavMeshToolInvoker->getProcess(), qOverload<int, QProcess::ExitStatus>(&QProcess::finished), this, connect(mNavMeshToolInvoker->getProcess(), qOverload<int, QProcess::ExitStatus>(&QProcess::finished), this,
&DataFilesPage::navMeshToolFinished); &DataFilesPage::navMeshToolFinished);
buildArchiveContextMenu();
}
void Launcher::DataFilesPage::buildArchiveContextMenu()
{
connect(ui.archiveListWidget, &QListWidget::customContextMenuRequested, this,
&DataFilesPage::slotShowArchiveContextMenu);
mArchiveContextMenu = new QMenu(ui.archiveListWidget);
mArchiveContextMenu->addAction(tr("&Check Selected"), this, SLOT(slotCheckMultiSelectedItems()));
mArchiveContextMenu->addAction(tr("&Uncheck Selected"), this, SLOT(slotUncheckMultiSelectedItems()));
} }
bool Launcher::DataFilesPage::loadSettings() bool Launcher::DataFilesPage::loadSettings()
@ -707,17 +721,71 @@ void Launcher::DataFilesPage::removeDirectory()
refreshDataFilesView(); refreshDataFilesView();
} }
void Launcher::DataFilesPage::moveArchive(int step) void Launcher::DataFilesPage::slotShowArchiveContextMenu(const QPoint& pos)
{ {
int selectedRow = ui.archiveListWidget->currentRow(); QPoint globalPos = ui.archiveListWidget->viewport()->mapToGlobal(pos);
mArchiveContextMenu->exec(globalPos);
}
void Launcher::DataFilesPage::setCheckStateForMultiSelectedItems(bool checked)
{
Qt::CheckState checkState = checked ? Qt::Checked : Qt::Unchecked;
for (QListWidgetItem* selectedItem : ui.archiveListWidget->selectedItems())
{
selectedItem->setCheckState(checkState);
}
}
void Launcher::DataFilesPage::slotUncheckMultiSelectedItems()
{
setCheckStateForMultiSelectedItems(false);
}
void Launcher::DataFilesPage::slotCheckMultiSelectedItems()
{
setCheckStateForMultiSelectedItems(true);
}
void Launcher::DataFilesPage::moveArchives(int step)
{
QList<QListWidgetItem*> selectedItems = ui.archiveListWidget->selectedItems();
QList<QPair<int, QListWidgetItem*>> sortedItems;
for (QListWidgetItem* selectedItem : selectedItems)
{
int selectedRow = ui.archiveListWidget->row(selectedItem);
sortedItems.append(qMakePair(selectedRow, selectedItem));
}
if (step > 0)
{
std::sort(sortedItems.begin(), sortedItems.end(), [](auto a, auto b) { return a.first > b.first; });
}
else
{
std::sort(sortedItems.begin(), sortedItems.end(), [](auto a, auto b) { return a.first < b.first; });
}
for (auto i : sortedItems)
{
if (!moveArchive(i.second, step))
break;
}
}
bool Launcher::DataFilesPage::moveArchive(QListWidgetItem* listItem, int step)
{
int selectedRow = ui.archiveListWidget->row(listItem);
int newRow = selectedRow + step; int newRow = selectedRow + step;
if (selectedRow == -1 || newRow < 0 || newRow > ui.archiveListWidget->count() - 1) if (selectedRow == -1 || newRow < 0 || newRow > ui.archiveListWidget->count() - 1)
return; return false;
const auto* item = ui.archiveListWidget->takeItem(selectedRow); const QListWidgetItem* item = ui.archiveListWidget->takeItem(selectedRow);
addArchive(item->text(), item->checkState(), newRow); addArchive(item->text(), item->checkState(), newRow);
ui.archiveListWidget->setCurrentRow(newRow); ui.archiveListWidget->setCurrentRow(newRow);
return true;
} }
void Launcher::DataFilesPage::addArchive(const QString& name, Qt::CheckState selected, int row) void Launcher::DataFilesPage::addArchive(const QString& name, Qt::CheckState selected, int row)

View File

@ -6,6 +6,7 @@
#include <components/process/processinvoker.hpp> #include <components/process/processinvoker.hpp>
#include <QDir> #include <QDir>
#include <QMenu>
#include <QStringList> #include <QStringList>
#include <QWidget> #include <QWidget>
@ -39,6 +40,7 @@ namespace Launcher
ContentSelectorView::ContentSelector* mSelector; ContentSelectorView::ContentSelector* mSelector;
Ui::DataFilesPage ui; Ui::DataFilesPage ui;
QMenu* mArchiveContextMenu;
public: public:
explicit DataFilesPage(const Files::ConfigurationManager& cfg, Config::GameSettings& gameSettings, explicit DataFilesPage(const Files::ConfigurationManager& cfg, Config::GameSettings& gameSettings,
@ -72,9 +74,13 @@ namespace Launcher
void addSubdirectories(bool append); void addSubdirectories(bool append);
void sortDirectories(); void sortDirectories();
void removeDirectory(); void removeDirectory();
void moveArchive(int step); void moveArchives(int step);
void moveDirectory(int step); void moveDirectory(int step);
void slotShowArchiveContextMenu(const QPoint& pos);
void slotCheckMultiSelectedItems();
void slotUncheckMultiSelectedItems();
void on_newProfileAction_triggered(); void on_newProfileAction_triggered();
void on_cloneProfileAction_triggered(); void on_cloneProfileAction_triggered();
void on_deleteProfileAction_triggered(); void on_deleteProfileAction_triggered();
@ -120,7 +126,10 @@ namespace Launcher
void addArchive(const QString& name, Qt::CheckState selected, int row = -1); void addArchive(const QString& name, Qt::CheckState selected, int row = -1);
void addArchivesFromDir(const QString& dir); void addArchivesFromDir(const QString& dir);
bool moveArchive(QListWidgetItem* listItem, int step);
void buildView(); void buildView();
void buildArchiveContextMenu();
void setCheckStateForMultiSelectedItems(bool checked);
void setProfile(int index, bool savePrevious); void setProfile(int index, bool savePrevious);
void setProfile(const QString& previous, const QString& current, bool savePrevious); void setProfile(const QString& previous, const QString& current, bool savePrevious);
void removeProfile(const QString& profile); void removeProfile(const QString& profile);

View File

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>573</width> <width>573</width>
<height>384</height> <height>557</height>
</rect> </rect>
</property> </property>
<property name="contextMenuPolicy"> <property name="contextMenuPolicy">
@ -29,6 +29,12 @@
</item> </item>
<item row="1" column="0"> <item row="1" column="0">
<widget class="QLabel" name="dataNoteLabel"> <widget class="QLabel" name="dataNoteLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text"> <property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;note: content files that are not part of current Content List are &lt;span style=&quot; font-style:italic;font-weight: bold&quot;&gt;highlighted&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;note: content files that are not part of current Content List are &lt;span style=&quot; font-style:italic;font-weight: bold&quot;&gt;highlighted&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
@ -41,14 +47,111 @@
<string>Data Directories</string> <string>Data Directories</string>
</attribute> </attribute>
<layout class="QGridLayout" name="dirTabLayout"> <layout class="QGridLayout" name="dirTabLayout">
<item row="0" column="0" rowspan="26"> <item row="0" column="0">
<widget class="QListWidget" name="directoryListWidget"> <widget class="QListWidget" name="directoryListWidget">
<property name="dragDropMode"> <property name="dragDropMode">
<enum>QAbstractItemView::InternalMove</enum> <enum>QAbstractItemView::InternalMove</enum>
</property> </property>
</widget> </widget>
</item> </item>
<item row="32" column="0" colspan="2"> <item row="0" column="1">
<layout class="QVBoxLayout" name="directoryButtons">
<item>
<widget class="QPushButton" name="directoryAddSubdirsButton">
<property name="baseSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="toolTip">
<string>Scan directories for likely data directories and append them at the end of the list.</string>
</property>
<property name="text">
<string>Append</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="directoryInsertButton">
<property name="baseSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="toolTip">
<string>Scan directories for likely data directories and insert them above the selected position</string>
</property>
<property name="text">
<string>Insert Above</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="directoryUpButton">
<property name="baseSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="toolTip">
<string>Move selected directory one position up</string>
</property>
<property name="text">
<string>Move Up</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="directoryDownButton">
<property name="baseSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="toolTip">
<string>Move selected directory one position down</string>
</property>
<property name="text">
<string>Move Down</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="directoryRemoveButton">
<property name="baseSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="toolTip">
<string>Remove selected directory</string>
</property>
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
<item>
<spacer name="directoryButtonsSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="1" column="0" colspan="2">
<widget class="QLabel" name="directoryNoteLabel"> <widget class="QLabel" name="directoryNoteLabel">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Minimum"> <sizepolicy hsizetype="Expanding" vsizetype="Minimum">
@ -61,116 +164,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1">
<widget class="QPushButton" name="directoryAddSubdirsButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="toolTip">
<string>Scan directories for likely data directories and append them at the end of the list.</string>
</property>
<property name="text">
<string>Append</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QPushButton" name="directoryInsertButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="toolTip">
<string>Scan directories for likely data directories and insert them above the selected position</string>
</property>
<property name="text">
<string>Insert Above</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QPushButton" name="directoryUpButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="toolTip">
<string>Move selected directory one position up</string>
</property>
<property name="text">
<string>Move Up</string>
</property>
</widget>
</item>
<item row="12" column="1">
<widget class="QPushButton" name="directoryDownButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="toolTip">
<string>Move selected directory one position down</string>
</property>
<property name="text">
<string>Move Down</string>
</property>
</widget>
</item>
<item row="16" column="1">
<widget class="QPushButton" name="directoryRemoveButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="toolTip">
<string>Remove selected directory</string>
</property>
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="archiveTab"> <widget class="QWidget" name="archiveTab">
@ -178,64 +171,90 @@
<string>Archive Files</string> <string>Archive Files</string>
</attribute> </attribute>
<layout class="QGridLayout" name="archiveTabLayout"> <layout class="QGridLayout" name="archiveTabLayout">
<item row="0" column="0" rowspan="26"> <item row="0" column="0">
<widget class="QListWidget" name="archiveListWidget"> <widget class="QListWidget" name="archiveListWidget">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="dragDropMode"> <property name="dragDropMode">
<enum>QAbstractItemView::InternalMove</enum> <enum>QAbstractItemView::InternalMove</enum>
</property> </property>
<property name="defaultDropAction">
<enum>Qt::CopyAction</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="0" column="1">
<widget class="QPushButton" name="archiveUpButton"> <layout class="QVBoxLayout" name="archiveButtons">
<property name="sizePolicy"> <item>
<sizepolicy hsizetype="Minimum" vsizetype="Fixed"> <widget class="QPushButton" name="archiveUpButton">
<horstretch>0</horstretch> <property name="sizePolicy">
<verstretch>33</verstretch> <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
</sizepolicy> <horstretch>0</horstretch>
</property> <verstretch>33</verstretch>
<property name="baseSize"> </sizepolicy>
<size> </property>
<width>0</width> <property name="baseSize">
<height>33</height> <size>
</size> <width>0</width>
</property> <height>33</height>
<property name="toolTip"> </size>
<string>Move selected archive one position up</string> </property>
</property> <property name="toolTip">
<property name="text"> <string>Move selected archive one position up</string>
<string>Move Up</string> </property>
</property> <property name="text">
</widget> <string>Move Up</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="archiveDownButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>33</verstretch>
</sizepolicy>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="toolTip">
<string>Move selected archive one position down</string>
</property>
<property name="text">
<string>Move Down</string>
</property>
</widget>
</item>
<item>
<spacer name="archiveButtonsSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item> </item>
<item row="27" column="0" colspan="2"> <item row="1" column="0" colspan="2">
<widget class="QLabel" name="archiveNoteLabel"> <widget class="QLabel" name="archiveNoteLabel">
<property name="text"> <property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;note: archives that are not part of current Content List are &lt;span style=&quot; font-style:italic;font-weight: bold&quot;&gt;highlighted&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;note: archives that are not part of current Content List are &lt;span style=&quot; font-style:italic;font-weight: bold&quot;&gt;highlighted&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1">
<widget class="QPushButton" name="archiveDownButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>33</verstretch>
</sizepolicy>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="toolTip">
<string>Move selected archive one position down</string>
</property>
<property name="text">
<string>Move Down</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="navigationMeshCacheTab"> <widget class="QWidget" name="navigationMeshCacheTab">

View File

@ -116,9 +116,9 @@ void readVFS(std::unique_ptr<VFS::Archive>&& archive, const std::filesystem::pat
for (const auto& name : vfs.getRecursiveDirectoryIterator("")) for (const auto& name : vfs.getRecursiveDirectoryIterator(""))
{ {
if (isNIF(name)) if (isNIF(name.value()))
{ {
readNIF(archivePath, name, &vfs, quiet); readNIF(archivePath, name.value(), &vfs, quiet);
} }
} }

View File

@ -691,15 +691,6 @@ void CSMTools::ReferenceableCheckStage::npcCheck(
return; return;
} }
} }
else if (npc.mNpdt.mHealth != 0)
{
for (size_t i = 0; i < npc.mNpdt.mAttributes.size(); ++i)
{
if (npc.mNpdt.mAttributes[i] == 0)
messages.add(id, ESM::Attribute::indexToRefId(i).getRefIdString() + " is equal to zero", {},
CSMDoc::Message::Severity_Warning);
}
}
if (level <= 0) if (level <= 0)
messages.add(id, "Level is non-positive", "", CSMDoc::Message::Severity_Warning); messages.add(id, "Level is non-positive", "", CSMDoc::Message::Severity_Warning);

View File

@ -18,16 +18,18 @@
#include <apps/opencs/model/world/universalid.hpp> #include <apps/opencs/model/world/universalid.hpp>
#include <components/esm3/cellref.hpp> #include <components/esm3/cellref.hpp>
#include <components/esm3/loadbody.hpp>
#include <components/esm3/loadfact.hpp> #include <components/esm3/loadfact.hpp>
CSMTools::ReferenceCheckStage::ReferenceCheckStage(const CSMWorld::RefCollection& references, CSMTools::ReferenceCheckStage::ReferenceCheckStage(const CSMWorld::RefCollection& references,
const CSMWorld::RefIdCollection& referencables, const CSMWorld::IdCollection<CSMWorld::Cell>& cells, const CSMWorld::RefIdCollection& referencables, const CSMWorld::IdCollection<CSMWorld::Cell>& cells,
const CSMWorld::IdCollection<ESM::Faction>& factions) const CSMWorld::IdCollection<ESM::Faction>& factions, const CSMWorld::IdCollection<ESM::BodyPart>& bodyparts)
: mReferences(references) : mReferences(references)
, mObjects(referencables) , mObjects(referencables)
, mDataSet(referencables.getDataSet()) , mDataSet(referencables.getDataSet())
, mCells(cells) , mCells(cells)
, mFactions(factions) , mFactions(factions)
, mBodyParts(bodyparts)
{ {
mIgnoreBaseRecords = false; mIgnoreBaseRecords = false;
} }
@ -49,9 +51,11 @@ void CSMTools::ReferenceCheckStage::perform(int stage, CSMDoc::Messages& message
else else
{ {
// Check for non existing referenced object // Check for non existing referenced object
if (mObjects.searchId(cellRef.mRefID) == -1) if (mObjects.searchId(cellRef.mRefID) == -1 && mBodyParts.searchId(cellRef.mRefID) == -1)
{
messages.add(id, "Instance of a non-existent object '" + cellRef.mRefID.getRefIdString() + "'", "", messages.add(id, "Instance of a non-existent object '" + cellRef.mRefID.getRefIdString() + "'", "",
CSMDoc::Message::Severity_Error); CSMDoc::Message::Severity_Error);
}
else else
{ {
// Check if reference charge is valid for it's proper referenced type // Check if reference charge is valid for it's proper referenced type

View File

@ -8,6 +8,7 @@
namespace ESM namespace ESM
{ {
struct BodyPart;
struct Faction; struct Faction;
} }
@ -29,7 +30,8 @@ namespace CSMTools
{ {
public: public:
ReferenceCheckStage(const CSMWorld::RefCollection& references, const CSMWorld::RefIdCollection& referencables, ReferenceCheckStage(const CSMWorld::RefCollection& references, const CSMWorld::RefIdCollection& referencables,
const CSMWorld::IdCollection<CSMWorld::Cell>& cells, const CSMWorld::IdCollection<ESM::Faction>& factions); const CSMWorld::IdCollection<CSMWorld::Cell>& cells, const CSMWorld::IdCollection<ESM::Faction>& factions,
const CSMWorld::IdCollection<ESM::BodyPart>& bodyparts);
void perform(int stage, CSMDoc::Messages& messages) override; void perform(int stage, CSMDoc::Messages& messages) override;
int setup() override; int setup() override;
@ -40,6 +42,7 @@ namespace CSMTools
const CSMWorld::RefIdData& mDataSet; const CSMWorld::RefIdData& mDataSet;
const CSMWorld::IdCollection<CSMWorld::Cell>& mCells; const CSMWorld::IdCollection<CSMWorld::Cell>& mCells;
const CSMWorld::IdCollection<ESM::Faction>& mFactions; const CSMWorld::IdCollection<ESM::Faction>& mFactions;
const CSMWorld::IdCollection<ESM::BodyPart>& mBodyParts;
bool mIgnoreBaseRecords; bool mIgnoreBaseRecords;
}; };
} }

View File

@ -105,8 +105,8 @@ CSMDoc::OperationHolder* CSMTools::Tools::getVerifier()
mData.getFactions(), mData.getScripts(), mData.getResources(CSMWorld::UniversalId::Type_Meshes), mData.getFactions(), mData.getScripts(), mData.getResources(CSMWorld::UniversalId::Type_Meshes),
mData.getResources(CSMWorld::UniversalId::Type_Icons), mData.getBodyParts())); mData.getResources(CSMWorld::UniversalId::Type_Icons), mData.getBodyParts()));
mVerifierOperation->appendStage(new ReferenceCheckStage( mVerifierOperation->appendStage(new ReferenceCheckStage(mData.getReferences(), mData.getReferenceables(),
mData.getReferences(), mData.getReferenceables(), mData.getCells(), mData.getFactions())); mData.getCells(), mData.getFactions(), mData.getBodyParts()));
mVerifierOperation->appendStage(new ScriptCheckStage(mDocument)); mVerifierOperation->appendStage(new ScriptCheckStage(mDocument));

View File

@ -385,6 +385,26 @@ namespace CSMWorld
case 0: case 0:
{ {
effect.mEffectID = static_cast<short>(value.toInt()); effect.mEffectID = static_cast<short>(value.toInt());
switch (effect.mEffectID)
{
case ESM::MagicEffect::DrainSkill:
case ESM::MagicEffect::DamageSkill:
case ESM::MagicEffect::RestoreSkill:
case ESM::MagicEffect::FortifySkill:
case ESM::MagicEffect::AbsorbSkill:
effect.mAttribute = -1;
break;
case ESM::MagicEffect::DrainAttribute:
case ESM::MagicEffect::DamageAttribute:
case ESM::MagicEffect::RestoreAttribute:
case ESM::MagicEffect::FortifyAttribute:
case ESM::MagicEffect::AbsorbAttribute:
effect.mSkill = -1;
break;
default:
effect.mSkill = -1;
effect.mAttribute = -1;
}
break; break;
} }
case 1: case 1:

View File

@ -30,18 +30,18 @@ void CSMWorld::Resources::recreate(const VFS::Manager* vfs, const char* const* e
for (const auto& filepath : vfs->getRecursiveDirectoryIterator("")) for (const auto& filepath : vfs->getRecursiveDirectoryIterator(""))
{ {
if (filepath.size() < baseSize + 1 || filepath.substr(0, baseSize) != mBaseDirectory const std::string_view view = filepath.view();
|| (filepath[baseSize] != '/' && filepath[baseSize] != '\\')) if (view.size() < baseSize + 1 || !view.starts_with(mBaseDirectory) || view[baseSize] != '/')
continue; continue;
if (extensions) if (extensions)
{ {
std::string::size_type extensionIndex = filepath.find_last_of('.'); const auto extensionIndex = view.find_last_of('.');
if (extensionIndex == std::string::npos) if (extensionIndex == std::string_view::npos)
continue; continue;
std::string extension = filepath.substr(extensionIndex + 1); std::string_view extension = view.substr(extensionIndex + 1);
int i = 0; int i = 0;
@ -53,10 +53,9 @@ void CSMWorld::Resources::recreate(const VFS::Manager* vfs, const char* const* e
continue; continue;
} }
std::string file = filepath.substr(baseSize + 1); std::string file(view.substr(baseSize + 1));
mFiles.push_back(file); mFiles.push_back(file);
std::replace(file.begin(), file.end(), '\\', '/'); mIndex.emplace(std::move(file), static_cast<int>(mFiles.size()) - 1);
mIndex.insert(std::make_pair(Misc::StringUtils::lowerCase(file), static_cast<int>(mFiles.size()) - 1));
} }
} }

View File

@ -65,7 +65,7 @@ namespace MWBase
virtual void animationTextKey(const MWWorld::Ptr& actor, const std::string& key) = 0; virtual void animationTextKey(const MWWorld::Ptr& actor, const std::string& key) = 0;
virtual void playAnimation(const MWWorld::Ptr& object, const std::string& groupname, virtual void playAnimation(const MWWorld::Ptr& object, const std::string& groupname,
const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult, const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult,
std::string_view start, std::string_view stop, float startpoint, size_t loops, bool loopfallback) std::string_view start, std::string_view stop, float startpoint, uint32_t loops, bool loopfallback)
= 0; = 0;
virtual void exteriorCreated(MWWorld::CellStore& cell) = 0; virtual void exteriorCreated(MWWorld::CellStore& cell) = 0;
virtual void actorDied(const MWWorld::Ptr& actor) = 0; virtual void actorDied(const MWWorld::Ptr& actor) = 0;

View File

@ -171,7 +171,7 @@ namespace MWBase
///< Forces an object to refresh its animation state. ///< Forces an object to refresh its animation state.
virtual bool playAnimationGroup( virtual bool playAnimationGroup(
const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number = 1, bool scripted = false) const MWWorld::Ptr& ptr, std::string_view groupName, int mode, uint32_t number = 1, bool scripted = false)
= 0; = 0;
///< Run animation for a MW-reference. Calls to this function for references that are currently not ///< Run animation for a MW-reference. Calls to this function for references that are currently not
/// in the scene should be ignored. /// in the scene should be ignored.
@ -180,8 +180,8 @@ namespace MWBase
/// \param number How many times the animation should be run /// \param number How many times the animation should be run
/// \param scripted Whether the animation should be treated as a scripted animation. /// \param scripted Whether the animation should be treated as a scripted animation.
/// \return Success or error /// \return Success or error
virtual bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, float speed, virtual bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, uint32_t loops,
std::string_view startKey, std::string_view stopKey, bool forceLoop) float speed, std::string_view startKey, std::string_view stopKey, bool forceLoop)
= 0; = 0;
///< Lua variant of playAnimationGroup. The mode parameter is omitted ///< Lua variant of playAnimationGroup. The mode parameter is omitted
/// and forced to 0. modes 1 and 2 can be emulated by doing clearAnimationQueue() and /// and forced to 0. modes 1 and 2 can be emulated by doing clearAnimationQueue() and

View File

@ -249,7 +249,7 @@ namespace MWLua
// Extended variant of MWScript's PlayGroup and LoopGroup // Extended variant of MWScript's PlayGroup and LoopGroup
api["playQueued"] = sol::overload( api["playQueued"] = sol::overload(
[mechanics](const sol::object& object, const std::string& groupname, const sol::table& options) { [mechanics](const sol::object& object, const std::string& groupname, const sol::table& options) {
int numberOfLoops = options.get_or("loops", std::numeric_limits<int>::max()); uint32_t numberOfLoops = options.get_or("loops", std::numeric_limits<uint32_t>::max());
float speed = options.get_or("speed", 1.f); float speed = options.get_or("speed", 1.f);
std::string startKey = options.get_or<std::string>("startkey", "start"); std::string startKey = options.get_or<std::string>("startkey", "start");
std::string stopKey = options.get_or<std::string>("stopkey", "stop"); std::string stopKey = options.get_or<std::string>("stopkey", "stop");
@ -265,7 +265,7 @@ namespace MWLua
}); });
api["playBlended"] = [](const sol::object& object, std::string_view groupname, const sol::table& options) { api["playBlended"] = [](const sol::object& object, std::string_view groupname, const sol::table& options) {
int loops = options.get_or("loops", 0); uint32_t loops = options.get_or("loops", 0u);
MWRender::Animation::AnimPriority priority = getPriorityArgument(options); MWRender::Animation::AnimPriority priority = getPriorityArgument(options);
BlendMask blendMask = options.get_or("blendmask", BlendMask::BlendMask_All); BlendMask blendMask = options.get_or("blendmask", BlendMask::BlendMask_All);
bool autoDisable = options.get_or("autodisable", true); bool autoDisable = options.get_or("autodisable", true);

View File

@ -417,7 +417,7 @@ namespace MWLua
void LuaManager::playAnimation(const MWWorld::Ptr& actor, const std::string& groupname, void LuaManager::playAnimation(const MWWorld::Ptr& actor, const std::string& groupname,
const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult, const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult,
std::string_view start, std::string_view stop, float startpoint, size_t loops, bool loopfallback) std::string_view start, std::string_view stop, float startpoint, uint32_t loops, bool loopfallback)
{ {
sol::table options = mLua.newTable(); sol::table options = mLua.newTable();
options["blendmask"] = blendMask; options["blendmask"] = blendMask;

View File

@ -85,7 +85,8 @@ namespace MWLua
void animationTextKey(const MWWorld::Ptr& actor, const std::string& key) override; void animationTextKey(const MWWorld::Ptr& actor, const std::string& key) override;
void playAnimation(const MWWorld::Ptr& actor, const std::string& groupname, void playAnimation(const MWWorld::Ptr& actor, const std::string& groupname,
const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult, const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult,
std::string_view start, std::string_view stop, float startpoint, size_t loops, bool loopfallback) override; std::string_view start, std::string_view stop, float startpoint, uint32_t loops,
bool loopfallback) override;
void exteriorCreated(MWWorld::CellStore& cell) override void exteriorCreated(MWWorld::CellStore& cell) override
{ {
mEngineEvents.addToQueue(EngineEvents::OnNewExterior{ cell }); mEngineEvents.addToQueue(EngineEvents::OnNewExterior{ cell });

View File

@ -215,7 +215,7 @@ namespace MWLua
objectT["recordId"] = sol::readonly_property( objectT["recordId"] = sol::readonly_property(
[](const ObjectT& o) -> std::string { return o.ptr().getCellRef().getRefId().serializeText(); }); [](const ObjectT& o) -> std::string { return o.ptr().getCellRef().getRefId().serializeText(); });
objectT["globalVariable"] = sol::readonly_property([](const ObjectT& o) -> sol::optional<std::string> { objectT["globalVariable"] = sol::readonly_property([](const ObjectT& o) -> sol::optional<std::string> {
std::string globalVariable = o.ptr().getCellRef().getGlobalVariable(); std::string_view globalVariable = o.ptr().getCellRef().getGlobalVariable();
if (globalVariable.empty()) if (globalVariable.empty())
return sol::nullopt; return sol::nullopt;
else else
@ -619,7 +619,7 @@ namespace MWLua
inventoryT["countOf"] = [](const InventoryT& inventory, std::string_view recordId) { inventoryT["countOf"] = [](const InventoryT& inventory, std::string_view recordId) {
const MWWorld::Ptr& ptr = inventory.mObj.ptr(); const MWWorld::Ptr& ptr = inventory.mObj.ptr();
MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr);
return store.count(ESM::RefId::stringRefId(recordId)); return store.count(ESM::RefId::deserializeText(recordId));
}; };
if constexpr (std::is_same_v<ObjectT, GObject>) if constexpr (std::is_same_v<ObjectT, GObject>)
{ {
@ -637,7 +637,7 @@ namespace MWLua
inventoryT["find"] = [](const InventoryT& inventory, std::string_view recordId) -> sol::optional<ObjectT> { inventoryT["find"] = [](const InventoryT& inventory, std::string_view recordId) -> sol::optional<ObjectT> {
const MWWorld::Ptr& ptr = inventory.mObj.ptr(); const MWWorld::Ptr& ptr = inventory.mObj.ptr();
MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr);
auto itemId = ESM::RefId::stringRefId(recordId); auto itemId = ESM::RefId::deserializeText(recordId);
for (const MWWorld::Ptr& item : store) for (const MWWorld::Ptr& item : store)
{ {
if (item.getCellRef().getRefId() == itemId) if (item.getCellRef().getRefId() == itemId)
@ -651,7 +651,7 @@ namespace MWLua
inventoryT["findAll"] = [](const InventoryT& inventory, std::string_view recordId) { inventoryT["findAll"] = [](const InventoryT& inventory, std::string_view recordId) {
const MWWorld::Ptr& ptr = inventory.mObj.ptr(); const MWWorld::Ptr& ptr = inventory.mObj.ptr();
MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr);
auto itemId = ESM::RefId::stringRefId(recordId); auto itemId = ESM::RefId::deserializeText(recordId);
ObjectIdList list = std::make_shared<std::vector<ObjectId>>(); ObjectIdList list = std::make_shared<std::vector<ObjectId>>();
for (const MWWorld::Ptr& item : store) for (const MWWorld::Ptr& item : store)
{ {

View File

@ -4,6 +4,7 @@
#include <components/esm3/loadfact.hpp> #include <components/esm3/loadfact.hpp>
#include <components/esm3/loadnpc.hpp> #include <components/esm3/loadnpc.hpp>
#include <components/lua/luastate.hpp> #include <components/lua/luastate.hpp>
#include <components/misc/resourcehelpers.hpp>
#include "apps/openmw/mwbase/environment.hpp" #include "apps/openmw/mwbase/environment.hpp"
#include "apps/openmw/mwbase/mechanicsmanager.hpp" #include "apps/openmw/mwbase/mechanicsmanager.hpp"
@ -78,6 +79,8 @@ namespace MWLua
= sol::readonly_property([](const ESM::NPC& rec) -> int { return (int)rec.mNpdt.mDisposition; }); = sol::readonly_property([](const ESM::NPC& rec) -> int { return (int)rec.mNpdt.mDisposition; });
record["head"] record["head"]
= sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mHead.serializeText(); }); = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mHead.serializeText(); });
record["model"] = sol::readonly_property(
[](const ESM::NPC& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); });
record["isMale"] = sol::readonly_property([](const ESM::NPC& rec) -> bool { return rec.isMale(); }); record["isMale"] = sol::readonly_property([](const ESM::NPC& rec) -> bool { return rec.isMale(); });
record["baseGold"] = sol::readonly_property([](const ESM::NPC& rec) -> int { return rec.mNpdt.mGold; }); record["baseGold"] = sol::readonly_property([](const ESM::NPC& rec) -> int { return rec.mNpdt.mGold; });
addActorServicesBindings<ESM::NPC>(record, context); addActorServicesBindings<ESM::NPC>(record, context);

View File

@ -174,6 +174,25 @@ namespace MWMechanics
mWorsenings = -1; mWorsenings = -1;
} }
ESM::RefId ActiveSpells::ActiveSpellParams::getEnchantment() const
{
// Enchantment id is not stored directly. Instead the enchanted item is stored.
const auto& store = MWBase::Environment::get().getESMStore();
switch (store->find(mId))
{
case ESM::REC_ARMO:
return store->get<ESM::Armor>().find(mId)->mEnchant;
case ESM::REC_BOOK:
return store->get<ESM::Book>().find(mId)->mEnchant;
case ESM::REC_CLOT:
return store->get<ESM::Clothing>().find(mId)->mEnchant;
case ESM::REC_WEAP:
return store->get<ESM::Weapon>().find(mId)->mEnchant;
default:
return {};
}
}
void ActiveSpells::update(const MWWorld::Ptr& ptr, float duration) void ActiveSpells::update(const MWWorld::Ptr& ptr, float duration)
{ {
if (mIterating) if (mIterating)
@ -438,21 +457,8 @@ namespace MWMechanics
if (store->get<ESM::Enchantment>().search(id) == nullptr) if (store->get<ESM::Enchantment>().search(id) == nullptr)
return false; return false;
// Enchantment id is not stored directly. Instead the enchanted item is stored.
return std::find_if(mSpells.begin(), mSpells.end(), [&](const auto& spell) { return std::find_if(mSpells.begin(), mSpells.end(), [&](const auto& spell) {
switch (store->find(spell.mId)) return spell.getEnchantment() == id;
{
case ESM::REC_ARMO:
return store->get<ESM::Armor>().find(spell.mId)->mEnchant == id;
case ESM::REC_BOOK:
return store->get<ESM::Book>().find(spell.mId)->mEnchant == id;
case ESM::REC_CLOT:
return store->get<ESM::Clothing>().find(spell.mId)->mEnchant == id;
case ESM::REC_WEAP:
return store->get<ESM::Weapon>().find(spell.mId)->mEnchant == id;
default:
return false;
}
}) != mSpells.end(); }) != mSpells.end();
} }

View File

@ -73,6 +73,7 @@ namespace MWMechanics
const std::string& getDisplayName() const { return mDisplayName; } const std::string& getDisplayName() const { return mDisplayName; }
ESM::RefNum getItem() const { return mItem; } ESM::RefNum getItem() const { return mItem; }
ESM::RefId getEnchantment() const;
// Increments worsenings count and sets the next timestamp // Increments worsenings count and sets the next timestamp
void worsen(); void worsen();

View File

@ -2005,7 +2005,7 @@ namespace MWMechanics
} }
bool Actors::playAnimationGroup( bool Actors::playAnimationGroup(
const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted) const const MWWorld::Ptr& ptr, std::string_view groupName, int mode, uint32_t number, bool scripted) const
{ {
const auto iter = mIndex.find(ptr.mRef); const auto iter = mIndex.find(ptr.mRef);
if (iter != mIndex.end()) if (iter != mIndex.end())
@ -2020,7 +2020,7 @@ namespace MWMechanics
} }
} }
bool Actors::playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, float speed, bool Actors::playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, uint32_t loops, float speed,
std::string_view startKey, std::string_view stopKey, bool forceLoop) std::string_view startKey, std::string_view stopKey, bool forceLoop)
{ {
const auto iter = mIndex.find(ptr.mRef); const auto iter = mIndex.find(ptr.mRef);

View File

@ -112,9 +112,9 @@ namespace MWMechanics
void forceStateUpdate(const MWWorld::Ptr& ptr) const; void forceStateUpdate(const MWWorld::Ptr& ptr) const;
bool playAnimationGroup( bool playAnimationGroup(const MWWorld::Ptr& ptr, std::string_view groupName, int mode, uint32_t number,
const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false) const; bool scripted = false) const;
bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, float speed, bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, uint32_t loops, float speed,
std::string_view startKey, std::string_view stopKey, bool forceLoop); std::string_view startKey, std::string_view stopKey, bool forceLoop);
void enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable); void enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable);
void skipAnimation(const MWWorld::Ptr& ptr) const; void skipAnimation(const MWWorld::Ptr& ptr) const;

View File

@ -483,7 +483,8 @@ namespace MWMechanics
return; return;
} }
playBlendedAnimation(mCurrentHit, priority, MWRender::BlendMask_All, true, 1, startKey, stopKey, 0.0f, ~0ul); playBlendedAnimation(mCurrentHit, priority, MWRender::BlendMask_All, true, 1, startKey, stopKey, 0.0f,
std::numeric_limits<uint32_t>::max());
} }
void CharacterController::refreshJumpAnims(JumpingState jump, bool force) void CharacterController::refreshJumpAnims(JumpingState jump, bool force)
@ -521,7 +522,7 @@ namespace MWMechanics
mCurrentJump = jumpAnimName; mCurrentJump = jumpAnimName;
if (mJumpState == JumpState_InAir) if (mJumpState == JumpState_InAir)
playBlendedAnimation(jumpAnimName, Priority_Jump, jumpmask, false, 1.0f, playBlendedAnimation(jumpAnimName, Priority_Jump, jumpmask, false, 1.0f,
startAtLoop ? "loop start" : "start", "stop", 0.f, ~0ul); startAtLoop ? "loop start" : "start", "stop", 0.f, std::numeric_limits<uint32_t>::max());
else if (mJumpState == JumpState_Landing) else if (mJumpState == JumpState_Landing)
playBlendedAnimation(jumpAnimName, Priority_Jump, jumpmask, true, 1.0f, "loop stop", "stop", 0.0f, 0); playBlendedAnimation(jumpAnimName, Priority_Jump, jumpmask, true, 1.0f, "loop stop", "stop", 0.0f, 0);
} }
@ -749,8 +750,8 @@ namespace MWMechanics
} }
} }
playBlendedAnimation( playBlendedAnimation(mCurrentMovement, Priority_Movement, movemask, false, 1.f, "start", "stop", startpoint,
mCurrentMovement, Priority_Movement, movemask, false, 1.f, "start", "stop", startpoint, ~0ul, true); std::numeric_limits<uint32_t>::max(), true);
} }
void CharacterController::refreshIdleAnims(CharacterState idle, bool force) void CharacterController::refreshIdleAnims(CharacterState idle, bool force)
@ -778,7 +779,7 @@ namespace MWMechanics
} }
MWRender::Animation::AnimPriority priority = getIdlePriority(mIdleState); MWRender::Animation::AnimPriority priority = getIdlePriority(mIdleState);
size_t numLoops = std::numeric_limits<size_t>::max(); size_t numLoops = std::numeric_limits<uint32_t>::max();
// Only play "idleswim" or "idlesneak" if they exist. Otherwise, fallback to // Only play "idleswim" or "idlesneak" if they exist. Otherwise, fallback to
// "idle"+weapon or "idle". // "idle"+weapon or "idle".
@ -1192,8 +1193,8 @@ namespace MWMechanics
if (!animPlaying) if (!animPlaying)
{ {
int mask = MWRender::BlendMask_Torso | MWRender::BlendMask_RightArm; int mask = MWRender::BlendMask_Torso | MWRender::BlendMask_RightArm;
playBlendedAnimation( playBlendedAnimation("idlestorm", Priority_Storm, mask, true, 1.0f, "start", "stop", 0.0f,
"idlestorm", Priority_Storm, mask, true, 1.0f, "start", "stop", 0.0f, ~0ul, true); std::numeric_limits<uint32_t>::max(), true);
} }
else else
{ {
@ -1326,7 +1327,7 @@ namespace MWMechanics
mAnimation->disable("shield"); mAnimation->disable("shield");
playBlendedAnimation("torch", Priority_Torch, MWRender::BlendMask_LeftArm, false, 1.0f, "start", "stop", playBlendedAnimation("torch", Priority_Torch, MWRender::BlendMask_LeftArm, false, 1.0f, "start", "stop",
0.0f, std::numeric_limits<size_t>::max(), true); 0.0f, std::numeric_limits<uint32_t>::max(), true);
} }
else if (mAnimation->isPlaying("torch")) else if (mAnimation->isPlaying("torch"))
{ {
@ -2515,7 +2516,8 @@ namespace MWMechanics
{ {
AnimationQueueEntry entry; AnimationQueueEntry entry;
entry.mGroup = iter->mGroup; entry.mGroup = iter->mGroup;
entry.mLoopCount = iter->mLoopCount; entry.mLoopCount
= static_cast<uint32_t>(std::min<uint64_t>(iter->mLoopCount, std::numeric_limits<uint32_t>::max()));
entry.mLooping = mAnimation->isLoopingAnimation(entry.mGroup); entry.mLooping = mAnimation->isLoopingAnimation(entry.mGroup);
entry.mStartKey = "start"; entry.mStartKey = "start";
entry.mStopKey = "stop"; entry.mStopKey = "stop";
@ -2538,7 +2540,7 @@ namespace MWMechanics
void CharacterController::playBlendedAnimation(const std::string& groupname, const MWRender::AnimPriority& priority, void CharacterController::playBlendedAnimation(const std::string& groupname, const MWRender::AnimPriority& priority,
int blendMask, bool autodisable, float speedmult, std::string_view start, std::string_view stop, int blendMask, bool autodisable, float speedmult, std::string_view start, std::string_view stop,
float startpoint, size_t loops, bool loopfallback) const float startpoint, uint32_t loops, bool loopfallback) const
{ {
if (mLuaAnimations) if (mLuaAnimations)
MWBase::Environment::get().getLuaManager()->playAnimation(mPtr, groupname, priority, blendMask, autodisable, MWBase::Environment::get().getLuaManager()->playAnimation(mPtr, groupname, priority, blendMask, autodisable,
@ -2548,7 +2550,7 @@ namespace MWMechanics
groupname, priority, blendMask, autodisable, speedmult, start, stop, startpoint, loops, loopfallback); groupname, priority, blendMask, autodisable, speedmult, start, stop, startpoint, loops, loopfallback);
} }
bool CharacterController::playGroup(std::string_view groupname, int mode, int count, bool scripted) bool CharacterController::playGroup(std::string_view groupname, int mode, uint32_t count, bool scripted)
{ {
if (!mAnimation || !mAnimation->hasAnimation(groupname)) if (!mAnimation || !mAnimation->hasAnimation(groupname))
return false; return false;
@ -2583,9 +2585,8 @@ namespace MWMechanics
// if played with a count of 0, all objects play exactly once from start to stop. // if played with a count of 0, all objects play exactly once from start to stop.
// But if the count is x > 0, actors and non-actors behave differently. actors will loop // But if the count is x > 0, actors and non-actors behave differently. actors will loop
// exactly x times, while non-actors will loop x+1 instead. // exactly x times, while non-actors will loop x+1 instead.
if (mPtr.getClass().isActor()) if (mPtr.getClass().isActor() && count > 0)
count--; count--;
count = std::max(count, 0);
AnimationQueueEntry entry; AnimationQueueEntry entry;
entry.mGroup = groupname; entry.mGroup = groupname;
@ -2620,7 +2621,7 @@ namespace MWMechanics
} }
bool CharacterController::playGroupLua(std::string_view groupname, float speed, std::string_view startKey, bool CharacterController::playGroupLua(std::string_view groupname, float speed, std::string_view startKey,
std::string_view stopKey, int loops, bool forceLoop) std::string_view stopKey, uint32_t loops, bool forceLoop)
{ {
// Note: In mwscript, "idle" is a special case used to clear the anim queue. // Note: In mwscript, "idle" is a special case used to clear the anim queue.
// In lua we offer an explicit clear method instead so this method does not treat "idle" special. // In lua we offer an explicit clear method instead so this method does not treat "idle" special.
@ -2632,7 +2633,7 @@ namespace MWMechanics
entry.mGroup = groupname; entry.mGroup = groupname;
// Note: MWScript gives one less loop to actors than non-actors. // Note: MWScript gives one less loop to actors than non-actors.
// But this is the Lua version. We don't need to reproduce this weirdness here. // But this is the Lua version. We don't need to reproduce this weirdness here.
entry.mLoopCount = std::max(loops, 0); entry.mLoopCount = loops;
entry.mStartKey = startKey; entry.mStartKey = startKey;
entry.mStopKey = stopKey; entry.mStopKey = stopKey;
entry.mLooping = mAnimation->isLoopingAnimation(groupname) || forceLoop; entry.mLooping = mAnimation->isLoopingAnimation(groupname) || forceLoop;

View File

@ -134,7 +134,7 @@ namespace MWMechanics
struct AnimationQueueEntry struct AnimationQueueEntry
{ {
std::string mGroup; std::string mGroup;
size_t mLoopCount; uint32_t mLoopCount;
float mTime; float mTime;
bool mLooping; bool mLooping;
bool mScripted; bool mScripted;
@ -276,10 +276,10 @@ namespace MWMechanics
void playBlendedAnimation(const std::string& groupname, const MWRender::AnimPriority& priority, int blendMask, void playBlendedAnimation(const std::string& groupname, const MWRender::AnimPriority& priority, int blendMask,
bool autodisable, float speedmult, std::string_view start, std::string_view stop, float startpoint, bool autodisable, float speedmult, std::string_view start, std::string_view stop, float startpoint,
size_t loops, bool loopfallback = false) const; uint32_t loops, bool loopfallback = false) const;
bool playGroup(std::string_view groupname, int mode, int count, bool scripted = false); bool playGroup(std::string_view groupname, int mode, uint32_t count, bool scripted = false);
bool playGroupLua(std::string_view groupname, float speed, std::string_view startKey, std::string_view stopKey, bool playGroupLua(std::string_view groupname, float speed, std::string_view startKey, std::string_view stopKey,
int loops, bool forceLoop); uint32_t loops, bool forceLoop);
void enableLuaAnimations(bool enable); void enableLuaAnimations(bool enable);
void skipAnim(); void skipAnim();
bool isAnimPlaying(std::string_view groupName) const; bool isAnimPlaying(std::string_view groupName) const;

View File

@ -749,14 +749,14 @@ namespace MWMechanics
} }
bool MechanicsManager::playAnimationGroup( bool MechanicsManager::playAnimationGroup(
const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted) const MWWorld::Ptr& ptr, std::string_view groupName, int mode, uint32_t number, bool scripted)
{ {
if (ptr.getClass().isActor()) if (ptr.getClass().isActor())
return mActors.playAnimationGroup(ptr, groupName, mode, number, scripted); return mActors.playAnimationGroup(ptr, groupName, mode, number, scripted);
else else
return mObjects.playAnimationGroup(ptr, groupName, mode, number, scripted); return mObjects.playAnimationGroup(ptr, groupName, mode, number, scripted);
} }
bool MechanicsManager::playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, bool MechanicsManager::playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, uint32_t loops,
float speed, std::string_view startKey, std::string_view stopKey, bool forceLoop) float speed, std::string_view startKey, std::string_view stopKey, bool forceLoop)
{ {
if (ptr.getClass().isActor()) if (ptr.getClass().isActor())

View File

@ -141,9 +141,9 @@ namespace MWMechanics
/// Attempt to play an animation group /// Attempt to play an animation group
/// @return Success or error /// @return Success or error
bool playAnimationGroup( bool playAnimationGroup(const MWWorld::Ptr& ptr, std::string_view groupName, int mode, uint32_t number,
const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false) override; bool scripted = false) override;
bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, float speed, bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, uint32_t loops, float speed,
std::string_view startKey, std::string_view stopKey, bool forceLoop) override; std::string_view startKey, std::string_view stopKey, bool forceLoop) override;
void enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable) override; void enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable) override;
void skipAnimation(const MWWorld::Ptr& ptr) override; void skipAnimation(const MWWorld::Ptr& ptr) override;

View File

@ -99,7 +99,7 @@ namespace MWMechanics
} }
bool Objects::playAnimationGroup( bool Objects::playAnimationGroup(
const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted) const MWWorld::Ptr& ptr, std::string_view groupName, int mode, uint32_t number, bool scripted)
{ {
const auto iter = mIndex.find(ptr.mRef); const auto iter = mIndex.find(ptr.mRef);
if (iter != mIndex.end()) if (iter != mIndex.end())
@ -114,8 +114,8 @@ namespace MWMechanics
} }
} }
bool Objects::playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, float speed, bool Objects::playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, uint32_t loops,
std::string_view startKey, std::string_view stopKey, bool forceLoop) float speed, std::string_view startKey, std::string_view stopKey, bool forceLoop)
{ {
const auto iter = mIndex.find(ptr.mRef); const auto iter = mIndex.find(ptr.mRef);
if (iter != mIndex.end()) if (iter != mIndex.end())

View File

@ -46,8 +46,8 @@ namespace MWMechanics
void onClose(const MWWorld::Ptr& ptr); void onClose(const MWWorld::Ptr& ptr);
bool playAnimationGroup( bool playAnimationGroup(
const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false); const MWWorld::Ptr& ptr, std::string_view groupName, int mode, uint32_t number, bool scripted = false);
bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, float speed, bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, uint32_t loops, float speed,
std::string_view startKey, std::string_view stopKey, bool forceLoop); std::string_view startKey, std::string_view stopKey, bool forceLoop);
void enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable); void enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable);
void skipAnimation(const MWWorld::Ptr& ptr); void skipAnimation(const MWWorld::Ptr& ptr);

View File

@ -279,7 +279,8 @@ namespace
return false; return false;
} }
void absorbSpell(const ESM::RefId& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target) void absorbSpell(const MWMechanics::ActiveSpells::ActiveSpellParams& spellParams, const MWWorld::Ptr& caster,
const MWWorld::Ptr& target)
{ {
const auto& esmStore = *MWBase::Environment::get().getESMStore(); const auto& esmStore = *MWBase::Environment::get().getESMStore();
const ESM::Static* absorbStatic = esmStore.get<ESM::Static>().find(ESM::RefId::stringRefId("VFX_Absorb")); const ESM::Static* absorbStatic = esmStore.get<ESM::Static>().find(ESM::RefId::stringRefId("VFX_Absorb"));
@ -287,15 +288,14 @@ namespace
if (animation && !absorbStatic->mModel.empty()) if (animation && !absorbStatic->mModel.empty())
animation->addEffect(Misc::ResourceHelpers::correctMeshPath(absorbStatic->mModel), animation->addEffect(Misc::ResourceHelpers::correctMeshPath(absorbStatic->mModel),
ESM::MagicEffect::indexToName(ESM::MagicEffect::SpellAbsorption), false); ESM::MagicEffect::indexToName(ESM::MagicEffect::SpellAbsorption), false);
const ESM::Spell* spell = esmStore.get<ESM::Spell>().search(spellId);
int spellCost = 0; int spellCost = 0;
if (spell) if (const ESM::Spell* spell = esmStore.get<ESM::Spell>().search(spellParams.getId()))
{ {
spellCost = MWMechanics::calcSpellCost(*spell); spellCost = MWMechanics::calcSpellCost(*spell);
} }
else else
{ {
const ESM::Enchantment* enchantment = esmStore.get<ESM::Enchantment>().search(spellId); const ESM::Enchantment* enchantment = esmStore.get<ESM::Enchantment>().search(spellParams.getEnchantment());
if (enchantment) if (enchantment)
spellCost = MWMechanics::getEffectiveEnchantmentCastCost(*enchantment, caster); spellCost = MWMechanics::getEffectiveEnchantmentCastCost(*enchantment, caster);
} }
@ -342,7 +342,7 @@ namespace
{ {
if (canAbsorb && Misc::Rng::roll0to99(prng) < activeEffect.mMagnitude) if (canAbsorb && Misc::Rng::roll0to99(prng) < activeEffect.mMagnitude)
{ {
absorbSpell(spellParams.getId(), caster, target); absorbSpell(spellParams, caster, target);
return MWMechanics::MagicApplicationResult::Type::REMOVED; return MWMechanics::MagicApplicationResult::Type::REMOVED;
} }
} }

View File

@ -15,6 +15,7 @@
#include "collisiontype.hpp" #include "collisiontype.hpp"
#include "constants.hpp" #include "constants.hpp"
#include "contacttestwrapper.h" #include "contacttestwrapper.h"
#include "object.hpp"
#include "physicssystem.hpp" #include "physicssystem.hpp"
#include "projectile.hpp" #include "projectile.hpp"
#include "projectileconvexcallback.hpp" #include "projectileconvexcallback.hpp"
@ -243,11 +244,20 @@ namespace MWPhysics
float hitHeight = tracer.mHitPoint.z() - tracer.mEndPos.z() + actor.mHalfExtentsZ; float hitHeight = tracer.mHitPoint.z() - tracer.mEndPos.z() + actor.mHalfExtentsZ;
osg::Vec3f oldPosition = newPosition; osg::Vec3f oldPosition = newPosition;
bool usedStepLogic = false; bool usedStepLogic = false;
if (hitHeight < Constants::sStepSizeUp && !isActor(tracer.mHitObject)) if (!isActor(tracer.mHitObject))
{ {
// Try to step up onto it. if (hitHeight < Constants::sStepSizeUp)
// NOTE: this modifies newPosition and velocity on its own if successful {
usedStepLogic = stepper.step(newPosition, velocity, remainingTime, seenGround, iterations == 0); // Try to step up onto it.
// NOTE: this modifies newPosition and velocity on its own if successful
usedStepLogic = stepper.step(newPosition, velocity, remainingTime, seenGround, iterations == 0);
}
auto* ptrHolder = static_cast<PtrHolder*>(tracer.mHitObject->getUserPointer());
if (Object* hitObject = dynamic_cast<Object*>(ptrHolder))
{
hitObject->addCollision(
actor.mIsPlayer ? ScriptedCollisionType_Player : ScriptedCollisionType_Actor);
}
} }
if (usedStepLogic) if (usedStepLogic)
{ {

View File

@ -23,6 +23,7 @@ namespace MWPhysics
, mPosition(ptr.getRefData().getPosition().asVec3()) , mPosition(ptr.getRefData().getPosition().asVec3())
, mRotation(rotation) , mRotation(rotation)
, mTaskScheduler(scheduler) , mTaskScheduler(scheduler)
, mCollidedWith(ScriptedCollisionType_None)
{ {
mCollisionObject = BulletHelpers::makeCollisionObject(mShapeInstance->mCollisionShape.get(), mCollisionObject = BulletHelpers::makeCollisionObject(mShapeInstance->mCollisionShape.get(),
Misc::Convert::toBullet(mPosition), Misc::Convert::toBullet(rotation)); Misc::Convert::toBullet(mPosition), Misc::Convert::toBullet(rotation));
@ -166,4 +167,20 @@ namespace MWPhysics
} }
return result; return result;
} }
bool Object::collidedWith(ScriptedCollisionType type) const
{
return mCollidedWith & type;
}
void Object::addCollision(ScriptedCollisionType type)
{
std::unique_lock<std::mutex> lock(mPositionMutex);
mCollidedWith |= type;
}
void Object::resetCollisions()
{
mCollidedWith = ScriptedCollisionType_None;
}
} }

View File

@ -18,6 +18,14 @@ namespace MWPhysics
{ {
class PhysicsTaskScheduler; class PhysicsTaskScheduler;
enum ScriptedCollisionType : char
{
ScriptedCollisionType_None = 0,
ScriptedCollisionType_Actor = 1,
// Note that this isn't 3, colliding with a player doesn't count as colliding with an actor
ScriptedCollisionType_Player = 2
};
class Object final : public PtrHolder class Object final : public PtrHolder
{ {
public: public:
@ -38,6 +46,9 @@ namespace MWPhysics
/// @brief update object shape /// @brief update object shape
/// @return true if shape changed /// @return true if shape changed
bool animateCollisionShapes(); bool animateCollisionShapes();
bool collidedWith(ScriptedCollisionType type) const;
void addCollision(ScriptedCollisionType type);
void resetCollisions();
private: private:
osg::ref_ptr<Resource::BulletShapeInstance> mShapeInstance; osg::ref_ptr<Resource::BulletShapeInstance> mShapeInstance;
@ -50,6 +61,7 @@ namespace MWPhysics
bool mTransformUpdatePending = false; bool mTransformUpdatePending = false;
mutable std::mutex mPositionMutex; mutable std::mutex mPositionMutex;
PhysicsTaskScheduler* mTaskScheduler; PhysicsTaskScheduler* mTaskScheduler;
char mCollidedWith;
}; };
} }

View File

@ -673,12 +673,13 @@ namespace MWPhysics
// Slow fall reduces fall speed by a factor of (effect magnitude / 200) // Slow fall reduces fall speed by a factor of (effect magnitude / 200)
const float slowFall const float slowFall
= 1.f - std::clamp(effects.getOrDefault(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f, 0.f, 1.f); = 1.f - std::clamp(effects.getOrDefault(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f, 0.f, 1.f);
const bool godmode = ptr == world->getPlayerConstPtr() && world->getGodModeState(); const bool isPlayer = ptr == world->getPlayerConstPtr();
const bool godmode = isPlayer && world->getGodModeState();
const bool inert = stats.isDead() const bool inert = stats.isDead()
|| (!godmode && stats.getMagicEffects().getOrDefault(ESM::MagicEffect::Paralyze).getModifier() > 0); || (!godmode && stats.getMagicEffects().getOrDefault(ESM::MagicEffect::Paralyze).getModifier() > 0);
simulations.emplace_back(ActorSimulation{ simulations.emplace_back(ActorSimulation{
physicActor, ActorFrameData{ *physicActor, inert, waterCollision, slowFall, waterlevel } }); physicActor, ActorFrameData{ *physicActor, inert, waterCollision, slowFall, waterlevel, isPlayer } });
// if the simulation will run, a jump request will be fulfilled. Update mechanics accordingly. // if the simulation will run, a jump request will be fulfilled. Update mechanics accordingly.
if (willSimulate) if (willSimulate)
@ -708,6 +709,8 @@ namespace MWPhysics
changed = false; changed = false;
} }
} }
for (auto& [_, object] : mObjects)
object->resetCollisions();
#ifndef BT_NO_PROFILE #ifndef BT_NO_PROFILE
CProfileManager::Reset(); CProfileManager::Reset();
@ -782,10 +785,12 @@ namespace MWPhysics
} }
} }
bool PhysicsSystem::isActorCollidingWith(const MWWorld::Ptr& actor, const MWWorld::ConstPtr& object) const bool PhysicsSystem::isObjectCollidingWith(const MWWorld::ConstPtr& object, ScriptedCollisionType type) const
{ {
std::vector<MWWorld::Ptr> collisions = getCollisions(object, CollisionType_World, CollisionType_Actor); auto found = mObjects.find(object.mRef);
return (std::find(collisions.begin(), collisions.end(), actor) != collisions.end()); if (found != mObjects.end())
return found->second->collidedWith(type);
return false;
} }
void PhysicsSystem::getActorsCollidingWith(const MWWorld::ConstPtr& object, std::vector<MWWorld::Ptr>& out) const void PhysicsSystem::getActorsCollidingWith(const MWWorld::ConstPtr& object, std::vector<MWWorld::Ptr>& out) const
@ -890,7 +895,8 @@ namespace MWPhysics
mDebugDrawer->addCollision(position, normal); mDebugDrawer->addCollision(position, normal);
} }
ActorFrameData::ActorFrameData(Actor& actor, bool inert, bool waterCollision, float slowFall, float waterlevel) ActorFrameData::ActorFrameData(
Actor& actor, bool inert, bool waterCollision, float slowFall, float waterlevel, bool isPlayer)
: mPosition() : mPosition()
, mStandingOn(nullptr) , mStandingOn(nullptr)
, mIsOnGround(actor.getOnGround()) , mIsOnGround(actor.getOnGround())
@ -917,6 +923,7 @@ namespace MWPhysics
, mIsAquatic(actor.getPtr().getClass().isPureWaterCreature(actor.getPtr())) , mIsAquatic(actor.getPtr().getClass().isPureWaterCreature(actor.getPtr()))
, mWaterCollision(waterCollision) , mWaterCollision(waterCollision)
, mSkipCollisionDetection(!actor.getCollisionMode()) , mSkipCollisionDetection(!actor.getCollisionMode())
, mIsPlayer(isPlayer)
{ {
} }

View File

@ -56,6 +56,7 @@ namespace MWPhysics
class Actor; class Actor;
class PhysicsTaskScheduler; class PhysicsTaskScheduler;
class Projectile; class Projectile;
enum ScriptedCollisionType : char;
using ActorMap = std::unordered_map<const MWWorld::LiveCellRefBase*, std::shared_ptr<Actor>>; using ActorMap = std::unordered_map<const MWWorld::LiveCellRefBase*, std::shared_ptr<Actor>>;
@ -79,7 +80,7 @@ namespace MWPhysics
struct ActorFrameData struct ActorFrameData
{ {
ActorFrameData(Actor& actor, bool inert, bool waterCollision, float slowFall, float waterlevel); ActorFrameData(Actor& actor, bool inert, bool waterCollision, float slowFall, float waterlevel, bool isPlayer);
osg::Vec3f mPosition; osg::Vec3f mPosition;
osg::Vec3f mInertia; osg::Vec3f mInertia;
const btCollisionObject* mStandingOn; const btCollisionObject* mStandingOn;
@ -102,6 +103,7 @@ namespace MWPhysics
const bool mIsAquatic; const bool mIsAquatic;
const bool mWaterCollision; const bool mWaterCollision;
const bool mSkipCollisionDetection; const bool mSkipCollisionDetection;
const bool mIsPlayer;
}; };
struct ProjectileFrameData struct ProjectileFrameData
@ -258,9 +260,8 @@ namespace MWPhysics
/// Get the handle of all actors standing on \a object in this frame. /// Get the handle of all actors standing on \a object in this frame.
void getActorsStandingOn(const MWWorld::ConstPtr& object, std::vector<MWWorld::Ptr>& out) const; void getActorsStandingOn(const MWWorld::ConstPtr& object, std::vector<MWWorld::Ptr>& out) const;
/// Return true if \a actor has collided with \a object in this frame. /// Return true if an object of the given type has collided with this object
/// This will detect running into objects, but will not detect climbing stairs, stepping up a small object, etc. bool isObjectCollidingWith(const MWWorld::ConstPtr& object, ScriptedCollisionType type) const;
bool isActorCollidingWith(const MWWorld::Ptr& actor, const MWWorld::ConstPtr& object) const;
/// Get the handle of all actors colliding with \a object in this frame. /// Get the handle of all actors colliding with \a object in this frame.
void getActorsCollidingWith(const MWWorld::ConstPtr& object, std::vector<MWWorld::Ptr>& out) const; void getActorsCollidingWith(const MWWorld::ConstPtr& object, std::vector<MWWorld::Ptr>& out) const;

View File

@ -805,7 +805,7 @@ namespace MWRender
} }
void Animation::play(std::string_view groupname, const AnimPriority& priority, int blendMask, bool autodisable, void Animation::play(std::string_view groupname, const AnimPriority& priority, int blendMask, bool autodisable,
float speedmult, std::string_view start, std::string_view stop, float startpoint, size_t loops, float speedmult, std::string_view start, std::string_view stop, float startpoint, uint32_t loops,
bool loopfallback) bool loopfallback)
{ {
if (!mObjectRoot || mAnimSources.empty()) if (!mObjectRoot || mAnimSources.empty())

View File

@ -153,7 +153,7 @@ namespace MWRender
bool mPlaying; bool mPlaying;
bool mLoopingEnabled; bool mLoopingEnabled;
size_t mLoopCount; uint32_t mLoopCount;
AnimPriority mPriority; AnimPriority mPriority;
int mBlendMask; int mBlendMask;
@ -379,7 +379,7 @@ namespace MWRender
* the "start" and "stop" keys for looping? * the "start" and "stop" keys for looping?
*/ */
void play(std::string_view groupname, const AnimPriority& priority, int blendMask, bool autodisable, void play(std::string_view groupname, const AnimPriority& priority, int blendMask, bool autodisable,
float speedmult, std::string_view start, std::string_view stop, float startpoint, size_t loops, float speedmult, std::string_view start, std::string_view stop, float startpoint, uint32_t loops,
bool loopfallback = false); bool loopfallback = false);
/** Adjust the speed multiplier of an already playing animation. /** Adjust the speed multiplier of an already playing animation.

View File

@ -463,8 +463,8 @@ namespace MWRender
if (torch != inv.end() && torch->getType() == ESM::Light::sRecordId && showCarriedLeft) if (torch != inv.end() && torch->getType() == ESM::Light::sRecordId && showCarriedLeft)
{ {
if (!mAnimation->getInfo("torch")) if (!mAnimation->getInfo("torch"))
mAnimation->play( mAnimation->play("torch", 2, BlendMask::BlendMask_LeftArm, false, 1.0f, "start", "stop", 0.0f,
"torch", 2, BlendMask::BlendMask_LeftArm, false, 1.0f, "start", "stop", 0.0f, ~0ul, true); std::numeric_limits<uint32_t>::max(), true);
} }
else if (mAnimation->getInfo("torch")) else if (mAnimation->getInfo("torch"))
mAnimation->disable("torch"); mAnimation->disable("torch");

View File

@ -80,6 +80,25 @@ namespace
node->setStateSet(stateset); node->setStateSet(stateset);
} }
int findOldestParticleAlive(const osgParticle::ParticleSystem* partsys)
{
int oldest = -1;
float oldestAge = 0.f;
for (int i = 0; i < partsys->numParticles(); ++i)
{
const osgParticle::Particle* particle = partsys->getParticle(i);
if (!particle->isAlive())
continue;
const float age = particle->getAge();
if (oldest == -1 || age > oldestAge)
{
oldest = i;
oldestAge = age;
}
}
return oldest;
}
} }
namespace MWRender namespace MWRender
@ -87,6 +106,7 @@ namespace MWRender
RippleSimulation::RippleSimulation(osg::Group* parent, Resource::ResourceSystem* resourceSystem) RippleSimulation::RippleSimulation(osg::Group* parent, Resource::ResourceSystem* resourceSystem)
: mParent(parent) : mParent(parent)
, mMaxNumberRipples(Fallback::Map::getInt("Water_MaxNumberRipples"))
{ {
mParticleSystem = new osgParticle::ParticleSystem; mParticleSystem = new osgParticle::ParticleSystem;
@ -159,9 +179,6 @@ namespace MWRender
currentPos.z() = mParticleNode->getPosition().z(); currentPos.z() = mParticleNode->getPosition().z();
if (mParticleSystem->numParticles() - mParticleSystem->numDeadParticles() > 500)
continue; // TODO: remove the oldest particle to make room?
emitRipple(currentPos); emitRipple(currentPos);
} }
} }
@ -226,7 +243,19 @@ namespace MWRender
} }
else else
{ {
if (mMaxNumberRipples == 0)
return;
osgParticle::ParticleSystem::ScopedWriteLock lock(*mParticleSystem->getReadWriteMutex()); osgParticle::ParticleSystem::ScopedWriteLock lock(*mParticleSystem->getReadWriteMutex());
if (mParticleSystem->numParticles() - mParticleSystem->numDeadParticles() > mMaxNumberRipples)
{
// osgParticle::ParticleSystem design requires this to be O(N)
// However, the number of particles we'll have to go through is not large
// If the user makes the limit absurd and manages to actually hit it this could be a problem
const int oldest = findOldestParticleAlive(mParticleSystem);
if (oldest != -1)
mParticleSystem->reuseParticle(oldest);
}
osgParticle::Particle* p = mParticleSystem->createParticle(nullptr); osgParticle::Particle* p = mParticleSystem->createParticle(nullptr);
p->setPosition(osg::Vec3f(pos.x(), pos.y(), 0.f)); p->setPosition(osg::Vec3f(pos.x(), pos.y(), 0.f));
p->setAngle(osg::Vec3f(0, 0, Misc::Rng::rollProbability() * osg::PI * 2 - osg::PI)); p->setAngle(osg::Vec3f(0, 0, Misc::Rng::rollProbability() * osg::PI * 2 - osg::PI));

View File

@ -74,6 +74,8 @@ namespace MWRender
std::vector<Emitter> mEmitters; std::vector<Emitter> mEmitters;
Ripples* mRipples = nullptr; Ripples* mRipples = nullptr;
int mMaxNumberRipples;
}; };
} }

View File

@ -240,6 +240,7 @@ namespace MWRender
, mIsStorm(false) , mIsStorm(false)
, mDay(0) , mDay(0)
, mMonth(0) , mMonth(0)
, mTimescaleClouds(Fallback::Map::getBool("Weather_Timescale_Clouds"))
, mCloudAnimationTimer(0.f) , mCloudAnimationTimer(0.f)
, mRainTimer(0.f) , mRainTimer(0.f)
, mStormParticleDirection(MWWorld::Weather::defaultDirection()) , mStormParticleDirection(MWWorld::Weather::defaultDirection())
@ -558,8 +559,17 @@ namespace MWRender
mParticleNode->setAttitude(quat); mParticleNode->setAttitude(quat);
} }
const float timeScale = MWBase::Environment::get().getWorld()->getTimeManager()->getGameTimeScale();
// UV Scroll the clouds // UV Scroll the clouds
mCloudAnimationTimer += duration * mCloudSpeed * 0.003; float cloudDelta = duration * mCloudSpeed / 400.f;
if (mTimescaleClouds)
cloudDelta *= timeScale / 60.f;
mCloudAnimationTimer += cloudDelta;
if (mCloudAnimationTimer >= 4.f)
mCloudAnimationTimer -= 4.f;
mNextCloudUpdater->setTextureCoord(mCloudAnimationTimer); mNextCloudUpdater->setTextureCoord(mCloudAnimationTimer);
mCloudUpdater->setTextureCoord(mCloudAnimationTimer); mCloudUpdater->setTextureCoord(mCloudAnimationTimer);
@ -575,8 +585,7 @@ namespace MWRender
} }
// rotate the stars by 360 degrees every 4 days // rotate the stars by 360 degrees every 4 days
mAtmosphereNightRoll += MWBase::Environment::get().getWorld()->getTimeManager()->getGameTimeScale() * duration mAtmosphereNightRoll += timeScale * duration * osg::DegreesToRadians(360.f) / (3600 * 96.f);
* osg::DegreesToRadians(360.f) / (3600 * 96.f);
if (mAtmosphereNightNode->getNodeMask() != 0) if (mAtmosphereNightNode->getNodeMask() != 0)
mAtmosphereNightNode->setAttitude(osg::Quat(mAtmosphereNightRoll, osg::Vec3f(0, 0, 1))); mAtmosphereNightNode->setAttitude(osg::Quat(mAtmosphereNightRoll, osg::Vec3f(0, 0, 1)));
mPrecipitationOccluder->update(); mPrecipitationOccluder->update();

View File

@ -165,6 +165,7 @@ namespace MWRender
int mDay; int mDay;
int mMonth; int mMonth;
bool mTimescaleClouds;
float mCloudAnimationTimer; float mCloudAnimationTimer;
float mRainTimer; float mRainTimer;

View File

@ -56,7 +56,7 @@ namespace MWScript
} }
MWBase::Environment::get().getMechanicsManager()->playAnimationGroup( MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(
ptr, group, mode, std::numeric_limits<int>::max(), true); ptr, group, mode, std::numeric_limits<uint32_t>::max(), true);
} }
}; };

View File

@ -64,11 +64,12 @@ namespace MWWorld
} }
case ESM::Format::Tes4: case ESM::Format::Tes4:
{ {
ESM4::Reader readerESM4(std::move(stream), filepath, ESM4::Reader reader(std::move(stream), filepath,
MWBase::Environment::get().getResourceSystem()->getVFS(), mReaders.getStatelessEncoder()); MWBase::Environment::get().getResourceSystem()->getVFS(),
readerESM4.setModIndex(index); mEncoder != nullptr ? &mEncoder->getStatelessEncoder() : nullptr);
readerESM4.updateModIndices(mNameToIndex); reader.setModIndex(index);
mStore.loadESM4(readerESM4); reader.updateModIndices(mNameToIndex);
mStore.loadESM4(reader);
break; break;
} }
} }

View File

@ -274,8 +274,6 @@ namespace MWWorld
const std::vector<std::string>& groundcoverFiles, ToUTF8::Utf8Encoder* encoder, Loading::Listener* listener) const std::vector<std::string>& groundcoverFiles, ToUTF8::Utf8Encoder* encoder, Loading::Listener* listener)
{ {
mContentFiles = contentFiles; mContentFiles = contentFiles;
if (encoder)
mReaders.setStatelessEncoder(encoder->getStatelessEncoder());
mESMVersions.resize(mContentFiles.size(), -1); mESMVersions.resize(mContentFiles.size(), -1);
loadContentFiles(fileCollections, contentFiles, encoder, listener); loadContentFiles(fileCollections, contentFiles, encoder, listener);
@ -2429,15 +2427,12 @@ namespace MWWorld
bool World::getPlayerCollidingWith(const MWWorld::ConstPtr& object) bool World::getPlayerCollidingWith(const MWWorld::ConstPtr& object)
{ {
MWWorld::Ptr player = getPlayerPtr(); return mPhysics->isObjectCollidingWith(object, MWPhysics::ScriptedCollisionType_Player);
return mPhysics->isActorCollidingWith(player, object);
} }
bool World::getActorCollidingWith(const MWWorld::ConstPtr& object) bool World::getActorCollidingWith(const MWWorld::ConstPtr& object)
{ {
std::vector<MWWorld::Ptr> actors; return mPhysics->isObjectCollidingWith(object, MWPhysics::ScriptedCollisionType_Actor);
mPhysics->getActorsCollidingWith(object, actors);
return !actors.empty();
} }
void World::hurtStandingActors(const ConstPtr& object, float healthPerSecond) void World::hurtStandingActors(const ConstPtr& object, float healthPerSecond)

View File

@ -326,6 +326,8 @@ End)mwscript";
Addtopic -spells... Addtopic -spells...
Addtopic -magicka... Addtopic -magicka...
player->PositionCell, -97274, -94273, 8064, -12,-12
End)mwscript"; End)mwscript";
const std::string sIssue4061 = R"mwscript(Begin 01_Rz_neuvazhay-koryto2 const std::string sIssue4061 = R"mwscript(Begin 01_Rz_neuvazhay-koryto2
@ -763,7 +765,33 @@ End)mwscript";
mTopics.erase(mTopics.begin()); mTopics.erase(mTopics.begin());
} }
}; };
class PositionCell : public Interpreter::Opcode0
{
public:
void execute(Interpreter::Runtime& runtime)
{
std::string_view target = runtime.getStringLiteral(runtime[0].mInteger);
runtime.pop();
Interpreter::Type_Float x = runtime[0].mFloat;
runtime.pop();
Interpreter::Type_Float y = runtime[0].mFloat;
runtime.pop();
Interpreter::Type_Float z = runtime[0].mFloat;
runtime.pop();
Interpreter::Type_Float zRot = runtime[0].mFloat;
runtime.pop();
std::string_view cellID = runtime.getStringLiteral(runtime[0].mInteger);
runtime.pop();
EXPECT_EQ(target, "player");
EXPECT_EQ(x, -97274);
EXPECT_EQ(y, -94273);
EXPECT_EQ(z, 8064);
EXPECT_EQ(zRot, -12);
EXPECT_EQ(cellID, "-12");
}
};
installOpcode<AddTopic>(Compiler::Dialogue::opcodeAddTopic, topics); installOpcode<AddTopic>(Compiler::Dialogue::opcodeAddTopic, topics);
installOpcode<PositionCell>(Compiler::Transformation::opcodePositionCellExplicit);
TestInterpreterContext context; TestInterpreterContext context;
run(*script, context); run(*script, context);
EXPECT_TRUE(topics.empty()); EXPECT_TRUE(topics.empty());

View File

@ -63,9 +63,22 @@ namespace Compiler
switch (mPutback) switch (mPutback)
{ {
case Putback_Special: case Putback_Special:
{
mPutback = Putback_None; mPutback = Putback_None;
// Replicate behaviour from scanSpecial so putting something back doesn't change the way it's handled
if (mExpectName && (mPutbackCode == S_member || mPutbackCode == S_minus))
{
mExpectName = false;
bool cont = false;
bool tolerant = mTolerantNames;
mTolerantNames = true;
MultiChar c{ mPutbackCode == S_member ? '.' : '-' };
scanName(c, parser, cont);
mTolerantNames = tolerant;
return cont;
}
return parser.parseSpecial(mPutbackCode, mPutbackLoc, *this); return parser.parseSpecial(mPutbackCode, mPutbackLoc, *this);
}
case Putback_Integer: case Putback_Integer:

View File

@ -723,8 +723,10 @@ QString ContentSelectorModel::ContentModel::toolTip(const EsmFile* file) const
int index = indexFromItem(item(file->filePath())).row(); int index = indexFromItem(item(file->filePath())).row();
for (const LoadOrderError& error : checkForLoadOrderErrors(file, index)) for (const LoadOrderError& error : checkForLoadOrderErrors(file, index))
{ {
assert(error.errorCode() != LoadOrderError::ErrorCode::ErrorCode_None);
text += "<p>"; text += "<p>";
text += error.toolTip(); text += mErrorToolTips[error.errorCode() - 1].arg(error.fileName());
text += "</p>"; text += "</p>";
} }
text += ("</b>"); text += ("</b>");

View File

@ -93,6 +93,10 @@ namespace ContentSelectorModel
QIcon mWarningIcon; QIcon mWarningIcon;
bool mShowOMWScripts; bool mShowOMWScripts;
QString mErrorToolTips[ContentSelectorModel::LoadOrderError::ErrorCode_LoadOrder]
= { tr("Unable to find dependent file: %1"), tr("Dependent file needs to be active: %1"),
tr("This file needs to load after %1") };
public: public:
QString mMimeType; QString mMimeType;
QStringList mMimeTypes; QStringList mMimeTypes;

View File

@ -1,12 +0,0 @@
#include "loadordererror.hpp"
#include <cassert>
QString ContentSelectorModel::LoadOrderError::sErrorToolTips[ErrorCode_LoadOrder]
= { QString("Unable to find dependent file: %1"), QString("Dependent file needs to be active: %1"),
QString("This file needs to load after %1") };
QString ContentSelectorModel::LoadOrderError::toolTip() const
{
assert(mErrorCode);
return sErrorToolTips[mErrorCode - 1].arg(mFileName);
}

View File

@ -28,7 +28,6 @@ namespace ContentSelectorModel
} }
inline ErrorCode errorCode() const { return mErrorCode; } inline ErrorCode errorCode() const { return mErrorCode; }
inline QString fileName() const { return mFileName; } inline QString fileName() const { return mFileName; }
QString toolTip() const;
private: private:
ErrorCode mErrorCode; ErrorCode mErrorCode;

View File

@ -9,8 +9,6 @@
#include <optional> #include <optional>
#include <string> #include <string>
#include <components/to_utf8/to_utf8.hpp>
namespace ESM namespace ESM
{ {
class ReadersCache class ReadersCache
@ -57,23 +55,13 @@ namespace ESM
BusyItem get(std::size_t index); BusyItem get(std::size_t index);
void setStatelessEncoder(const ToUTF8::StatelessUtf8Encoder& statelessEncoderPtr)
{
mStatelessEncoder.emplace(statelessEncoderPtr);
}
const ToUTF8::StatelessUtf8Encoder* getStatelessEncoder()
{
return mStatelessEncoder.has_value() ? &mStatelessEncoder.value() : nullptr;
}
private: private:
const std::size_t mCapacity; const std::size_t mCapacity;
std::map<std::size_t, std::list<Item>::iterator> mIndex; std::map<std::size_t, std::list<Item>::iterator> mIndex;
std::list<Item> mBusyItems; std::list<Item> mBusyItems;
std::list<Item> mFreeItems; std::list<Item> mFreeItems;
std::list<Item> mClosedItems; std::list<Item> mClosedItems;
std::optional<ToUTF8::StatelessUtf8Encoder> mStatelessEncoder;
inline void closeExtraReaders(); inline void closeExtraReaders();
inline void releaseItem(std::list<Item>::iterator it) noexcept; inline void releaseItem(std::list<Item>::iterator it) noexcept;

View File

@ -41,6 +41,7 @@
#include <components/files/conversion.hpp> #include <components/files/conversion.hpp>
#include <components/misc/strings/lower.hpp> #include <components/misc/strings/lower.hpp>
#include <components/to_utf8/to_utf8.hpp> #include <components/to_utf8/to_utf8.hpp>
#include <components/vfs/manager.hpp>
#include "grouptype.hpp" #include "grouptype.hpp"

View File

@ -36,13 +36,17 @@
#include <components/esm/formid.hpp> #include <components/esm/formid.hpp>
#include <components/files/istreamptr.hpp> #include <components/files/istreamptr.hpp>
#include <components/vfs/manager.hpp>
namespace ToUTF8 namespace ToUTF8
{ {
class StatelessUtf8Encoder; class StatelessUtf8Encoder;
} }
namespace VFS
{
class Manager;
}
namespace ESM4 namespace ESM4
{ {
#pragma pack(push, 1) #pragma pack(push, 1)

View File

@ -7,12 +7,12 @@
static const std::set<std::string_view> allowedKeysInt = { "LightAttenuation_LinearMethod", static const std::set<std::string_view> allowedKeysInt = { "LightAttenuation_LinearMethod",
"LightAttenuation_OutQuadInLin", "LightAttenuation_QuadraticMethod", "LightAttenuation_UseConstant", "LightAttenuation_OutQuadInLin", "LightAttenuation_QuadraticMethod", "LightAttenuation_UseConstant",
"LightAttenuation_UseLinear", "LightAttenuation_UseQuadratic", "Water_NearWaterRadius", "Water_NearWaterPoints", "LightAttenuation_UseLinear", "LightAttenuation_UseQuadratic", "Water_MaxNumberRipples", "Water_NearWaterRadius",
"Water_RippleFrameCount", "Water_SurfaceTileCount", "Water_SurfaceFrameCount", "Weather_Clear_Using_Precip", "Water_NearWaterPoints", "Water_RippleFrameCount", "Water_SurfaceTileCount", "Water_SurfaceFrameCount",
"Weather_Cloudy_Using_Precip", "Weather_Foggy_Using_Precip", "Weather_Overcast_Using_Precip", "Weather_Clear_Using_Precip", "Weather_Cloudy_Using_Precip", "Weather_Foggy_Using_Precip",
"Weather_Rain_Using_Precip", "Weather_Thunderstorm_Using_Precip", "Weather_Ashstorm_Using_Precip", "Weather_Overcast_Using_Precip", "Weather_Rain_Using_Precip", "Weather_Thunderstorm_Using_Precip",
"Weather_Blight_Using_Precip", "Weather_Snow_Using_Precip", "Weather_Blizzard_Using_Precip", "Weather_Rain_Ripples", "Weather_Ashstorm_Using_Precip", "Weather_Blight_Using_Precip", "Weather_Snow_Using_Precip",
"Weather_Snow_Ripples" }; "Weather_Blizzard_Using_Precip", "Weather_Rain_Ripples", "Weather_Snow_Ripples", "Weather_Timescale_Clouds" };
static const std::set<std::string_view> allowedKeysFloat = { "General_Werewolf_FOV", "Inventory_DirectionalAmbientB", static const std::set<std::string_view> allowedKeysFloat = { "General_Werewolf_FOV", "Inventory_DirectionalAmbientB",
"Inventory_DirectionalAmbientG", "Inventory_DirectionalAmbientR", "Inventory_DirectionalDiffuseB", "Inventory_DirectionalAmbientG", "Inventory_DirectionalAmbientR", "Inventory_DirectionalDiffuseB",
@ -186,7 +186,7 @@ static const std::set<std::string_view> allowedKeysNonNumeric = { "Blood_Model_0
"Weather_Thunderstorm_Sun_Disc_Sunset_Color", "Weather_Thunderstorm_Sun_Night_Color", "Weather_Thunderstorm_Sun_Disc_Sunset_Color", "Weather_Thunderstorm_Sun_Night_Color",
"Weather_Thunderstorm_Sun_Sunrise_Color", "Weather_Thunderstorm_Sun_Sunset_Color", "Weather_Thunderstorm_Sun_Sunrise_Color", "Weather_Thunderstorm_Sun_Sunset_Color",
"Weather_Thunderstorm_Thunder_Sound_ID_0", "Weather_Thunderstorm_Thunder_Sound_ID_1", "Weather_Thunderstorm_Thunder_Sound_ID_0", "Weather_Thunderstorm_Thunder_Sound_ID_1",
"Weather_Thunderstorm_Thunder_Sound_ID_2", "Weather_Thunderstorm_Thunder_Sound_ID_3", "Weather_Timescale_Clouds", "Weather_Thunderstorm_Thunder_Sound_ID_2", "Weather_Thunderstorm_Thunder_Sound_ID_3",
"Weather_Clear_Thunder_Sound_ID_0", "Weather_Clear_Thunder_Sound_ID_1", "Weather_Clear_Thunder_Sound_ID_2", "Weather_Clear_Thunder_Sound_ID_0", "Weather_Clear_Thunder_Sound_ID_1", "Weather_Clear_Thunder_Sound_ID_2",
"Weather_Clear_Thunder_Sound_ID_3", "Weather_Cloudy_Thunder_Sound_ID_0", "Weather_Cloudy_Thunder_Sound_ID_1", "Weather_Clear_Thunder_Sound_ID_3", "Weather_Cloudy_Thunder_Sound_ID_0", "Weather_Cloudy_Thunder_Sound_ID_1",
"Weather_Cloudy_Thunder_Sound_ID_2", "Weather_Cloudy_Thunder_Sound_ID_3", "Weather_Foggy_Thunder_Sound_ID_0", "Weather_Cloudy_Thunder_Sound_ID_2", "Weather_Cloudy_Thunder_Sound_ID_3", "Weather_Foggy_Thunder_Sound_ID_0",
@ -218,7 +218,7 @@ static const std::set<std::string_view> allowedKeysUnused = { "Inventory_Uniform
"Map_Travel_Boat_Blue", "Map_Travel_Boat_Green", "Map_Travel_Boat_Red", "Map_Travel_Magic_Blue", "Map_Travel_Boat_Blue", "Map_Travel_Boat_Green", "Map_Travel_Boat_Red", "Map_Travel_Magic_Blue",
"Map_Travel_Magic_Green", "Map_Travel_Magic_Red", "Map_Travel_Siltstrider_Blue", "Map_Travel_Siltstrider_Green", "Map_Travel_Magic_Green", "Map_Travel_Magic_Red", "Map_Travel_Siltstrider_Blue", "Map_Travel_Siltstrider_Green",
"Map_Travel_Siltstrider_Red", "Movies_Game_Logo", "PixelWater_Resolution", "PixelWater_SurfaceFPS", "Map_Travel_Siltstrider_Red", "Movies_Game_Logo", "PixelWater_Resolution", "PixelWater_SurfaceFPS",
"PixelWater_TileCount", "Movies_Loading", "Movies_Options_Menu", "Movies_Project_Logo", "Water_MaxNumberRipples", "PixelWater_TileCount", "Movies_Loading", "Movies_Options_Menu", "Movies_Project_Logo",
"Water_NearWaterUnderwaterFreq", "Water_NearWaterUnderwaterVolume", "Water_PSWaterReflectTerrain", "Water_NearWaterUnderwaterFreq", "Water_NearWaterUnderwaterVolume", "Water_PSWaterReflectTerrain",
"Water_PSWaterReflectUpdate", "Water_RippleAlphas", "Water_RippleScale", "Water_SurfaceTextureSize", "Water_PSWaterReflectUpdate", "Water_RippleAlphas", "Water_RippleScale", "Water_SurfaceTextureSize",
"Water_TileTextureDivisor", "Weather_AlphaReduce", "Weather_Ashstorm_Storm_Threshold", "Water_TileTextureDivisor", "Weather_AlphaReduce", "Weather_Ashstorm_Storm_Threshold",

View File

@ -43,6 +43,7 @@ namespace fx
void setSunPos(const osg::Vec4f& pos, bool night) void setSunPos(const osg::Vec4f& pos, bool night)
{ {
mData.get<SunPos>() = pos; mData.get<SunPos>() = pos;
mData.get<SunPos>().normalize();
if (night) if (night)
mData.get<SunPos>().z() *= -1.f; mData.get<SunPos>().z() *= -1.f;

View File

@ -1,6 +1,7 @@
#include "manager.hpp" #include "manager.hpp"
#include <algorithm> #include <algorithm>
#include <cassert>
#include <stdexcept> #include <stdexcept>
#include <components/files/conversion.hpp> #include <components/files/conversion.hpp>
@ -37,30 +38,30 @@ namespace VFS
archive->listResources(mIndex); archive->listResources(mIndex);
} }
Files::IStreamPtr Manager::get(std::string_view name) const Files::IStreamPtr Manager::get(const Path::Normalized& name) const
{ {
return getNormalized(Path::normalizeFilename(name)); return getNormalized(name);
} }
Files::IStreamPtr Manager::getNormalized(std::string_view normalizedName) const Files::IStreamPtr Manager::getNormalized(std::string_view normalizedName) const
{ {
assert(Path::isNormalized(normalizedName));
const auto found = mIndex.find(normalizedName); const auto found = mIndex.find(normalizedName);
if (found == mIndex.end()) if (found == mIndex.end())
throw std::runtime_error("Resource '" + std::string(normalizedName) + "' not found"); throw std::runtime_error("Resource '" + std::string(normalizedName) + "' not found");
return found->second->open(); return found->second->open();
} }
bool Manager::exists(std::string_view name) const bool Manager::exists(const Path::Normalized& name) const
{ {
return mIndex.find(Path::normalizeFilename(name)) != mIndex.end(); return mIndex.find(name) != mIndex.end();
} }
std::string Manager::getArchive(std::string_view name) const std::string Manager::getArchive(const Path::Normalized& name) const
{ {
std::string normalized = Path::normalizeFilename(name);
for (auto it = mArchives.rbegin(); it != mArchives.rend(); ++it) for (auto it = mArchives.rbegin(); it != mArchives.rend(); ++it)
{ {
if ((*it)->contains(normalized)) if ((*it)->contains(name))
return (*it)->getDescription(); return (*it)->getDescription();
} }
return {}; return {};

View File

@ -10,6 +10,7 @@
#include <vector> #include <vector>
#include "filemap.hpp" #include "filemap.hpp"
#include "pathutil.hpp"
namespace VFS namespace VFS
{ {
@ -40,19 +41,19 @@ namespace VFS
/// Does a file with this name exist? /// Does a file with this name exist?
/// @note May be called from any thread once the index has been built. /// @note May be called from any thread once the index has been built.
bool exists(std::string_view name) const; bool exists(const Path::Normalized& name) const;
/// Retrieve a file by name. /// Retrieve a file by name.
/// @note Throws an exception if the file can not be found. /// @note Throws an exception if the file can not be found.
/// @note May be called from any thread once the index has been built. /// @note May be called from any thread once the index has been built.
Files::IStreamPtr get(std::string_view name) const; Files::IStreamPtr get(const Path::Normalized& name) const;
/// Retrieve a file by name (name is already normalized). /// Retrieve a file by name (name is already normalized).
/// @note Throws an exception if the file can not be found. /// @note Throws an exception if the file can not be found.
/// @note May be called from any thread once the index has been built. /// @note May be called from any thread once the index has been built.
Files::IStreamPtr getNormalized(std::string_view normalizedName) const; Files::IStreamPtr getNormalized(std::string_view normalizedName) const;
std::string getArchive(std::string_view name) const; std::string getArchive(const Path::Normalized& name) const;
/// Recursively iterate over the elements of the given path /// Recursively iterate over the elements of the given path
/// In practice it return all files of the VFS starting with the given path /// In practice it return all files of the VFS starting with the given path

View File

@ -15,6 +15,11 @@ namespace VFS::Path
return c == '\\' ? '/' : Misc::StringUtils::toLower(c); return c == '\\' ? '/' : Misc::StringUtils::toLower(c);
} }
inline constexpr bool isNormalized(std::string_view name)
{
return std::all_of(name.begin(), name.end(), [](char v) { return v == normalize(v); });
}
inline void normalizeFilenameInPlace(std::string& name) inline void normalizeFilenameInPlace(std::string& name)
{ {
std::transform(name.begin(), name.end(), name.begin(), normalize); std::transform(name.begin(), name.end(), name.begin(), normalize);

View File

@ -16,9 +16,9 @@ namespace VFS
{ {
} }
const std::string& operator*() const { return mIt->first.value(); } const Path::Normalized& operator*() const { return mIt->first; }
const std::string* operator->() const { return &mIt->first.value(); } const Path::Normalized* operator->() const { return &mIt->first; }
RecursiveDirectoryIterator& operator++() RecursiveDirectoryIterator& operator++()
{ {

View File

@ -939,6 +939,7 @@
-- @field #string name -- @field #string name
-- @field #string race -- @field #string race
-- @field #string class Name of the NPC's class (e. g. Acrobat) -- @field #string class Name of the NPC's class (e. g. Acrobat)
-- @field #string model Path to the model associated with this NPC, used for animations.
-- @field #string mwscript MWScript that is attached to this NPC -- @field #string mwscript MWScript that is attached to this NPC
-- @field #string hair Path to the hair body part model -- @field #string hair Path to the hair body part model
-- @field #string head Path to the head body part model -- @field #string head Path to the head body part model