mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-03-24 13:43:43 +00:00
Merge branch 'master' into menuscripts
This commit is contained in:
commit
b988db9bda
@ -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 #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 #7053: Running into objects doesn't trigger GetCollidingPC
|
||||
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 #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 #7770: Sword of the Perithia: Script execution failure
|
||||
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 #3537: Shader-based water ripples
|
||||
Feature #5173: Support for NiFogProperty
|
||||
@ -166,6 +169,7 @@
|
||||
Feature #7546: Start the game on Fredas
|
||||
Feature #7554: Controller binding for tab for menu navigation
|
||||
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 #7618: Show the player character's health in the save details
|
||||
Feature #7625: Add some missing console error outputs
|
||||
@ -174,6 +178,8 @@
|
||||
Feature #7652: Sort inactive post processing shaders list properly
|
||||
Feature #7698: Implement sAbsorb, sDamage, sDrain, sFortify and sRestore
|
||||
Feature #7709: Improve resolution selection in Launcher
|
||||
Feature #7792: Support Timescale Clouds
|
||||
Feature #7795: Support MaxNumberRipples INI setting
|
||||
Feature #7805: Lua Menu context
|
||||
Task #5896: Do not use deprecated MyGUI properties
|
||||
Task #6624: Drop support for saves made prior to 0.45
|
||||
|
@ -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("plain,p",
|
||||
"Print contents of dialogs, books and scripts. "
|
||||
"(skipped by default)"
|
||||
"(skipped by default) "
|
||||
"Only affects dump mode.");
|
||||
addOption("quiet,q", "Suppress all record information. Useful for speed tests.");
|
||||
addOption("loadcells,C", "Browse through contents of all cells.");
|
||||
@ -390,7 +390,7 @@ namespace
|
||||
|
||||
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->print();
|
||||
}
|
||||
|
@ -464,7 +464,8 @@ namespace EsmTool
|
||||
{
|
||||
std::cout << " Name: " << mData.mName << 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;
|
||||
}
|
||||
|
||||
@ -516,7 +517,8 @@ namespace EsmTool
|
||||
std::cout << " Name: " << mData.mName << std::endl;
|
||||
std::cout << " Model: " << mData.mModel << 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::endl;
|
||||
std::cout << " Weight: " << mData.mData.mWeight << std::endl;
|
||||
@ -679,7 +681,8 @@ namespace EsmTool
|
||||
{
|
||||
std::cout << " Name: " << mData.mName << 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 << " Blood Type: " << mData.mBloodType + 1 << std::endl;
|
||||
std::cout << " Original: " << mData.mOriginal << std::endl;
|
||||
@ -747,7 +750,8 @@ namespace EsmTool
|
||||
{
|
||||
std::cout << " Name: " << mData.mName << 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 << " CloseSound: " << mData.mCloseSound << std::endl;
|
||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
||||
@ -1338,28 +1342,26 @@ namespace EsmTool
|
||||
template <>
|
||||
void Record<CellState>::print()
|
||||
{
|
||||
std::cout << " Id:" << std::endl;
|
||||
std::cout << " CellId: " << mData.mCellState.mId << std::endl;
|
||||
std::cout << " Index:" << std::endl;
|
||||
std::cout << " WaterLevel: " << mData.mCellState.mWaterLevel << std::endl;
|
||||
std::cout << " HasFogOfWar: " << mData.mCellState.mHasFogOfWar << std::endl;
|
||||
std::cout << " LastRespawn:" << std::endl;
|
||||
std::cout << " Cell Id: \"" << mData.mCellState.mId.toString() << "\"" << std::endl;
|
||||
std::cout << " Water Level: " << mData.mCellState.mWaterLevel << std::endl;
|
||||
std::cout << " Has Fog Of War: " << mData.mCellState.mHasFogOfWar << std::endl;
|
||||
std::cout << " Last Respawn:" << std::endl;
|
||||
std::cout << " Day:" << mData.mCellState.mLastRespawn.mDay << std::endl;
|
||||
std::cout << " Hour:" << mData.mCellState.mLastRespawn.mHour << std::endl;
|
||||
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 << " MinX: " << mData.mFogState.mBounds.mMinX << std::endl;
|
||||
std::cout << " MinY: " << mData.mFogState.mBounds.mMinY << std::endl;
|
||||
std::cout << " MaxX: " << mData.mFogState.mBounds.mMaxX << std::endl;
|
||||
std::cout << " MaxY: " << mData.mFogState.mBounds.mMaxY << std::endl;
|
||||
std::cout << " Min X: " << mData.mFogState.mBounds.mMinX << std::endl;
|
||||
std::cout << " Min Y: " << mData.mFogState.mBounds.mMinY << std::endl;
|
||||
std::cout << " Max X: " << mData.mFogState.mBounds.mMaxX << std::endl;
|
||||
std::cout << " Max Y: " << mData.mFogState.mBounds.mMaxY << std::endl;
|
||||
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 << " 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 <>
|
||||
std::string Record<ESM::Cell>::getId() const
|
||||
{
|
||||
return mData.mName;
|
||||
return std::string(); // No ID for Cell record
|
||||
}
|
||||
|
||||
template <>
|
||||
@ -1397,9 +1399,7 @@ namespace EsmTool
|
||||
template <>
|
||||
std::string Record<CellState>::getId() const
|
||||
{
|
||||
std::ostringstream stream;
|
||||
stream << mData.mCellState.mId;
|
||||
return stream.str();
|
||||
return std::string(); // No ID for CellState record
|
||||
}
|
||||
|
||||
} // end namespace
|
||||
|
@ -3,7 +3,9 @@
|
||||
|
||||
#include <QDebug>
|
||||
#include <QFileDialog>
|
||||
#include <QList>
|
||||
#include <QMessageBox>
|
||||
#include <QPair>
|
||||
#include <QPushButton>
|
||||
|
||||
#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.directoryDownButton, &QPushButton::released, this, [this]() { this->moveDirectory(1); });
|
||||
connect(ui.directoryRemoveButton, &QPushButton::released, this, [this]() { this->removeDirectory(); });
|
||||
connect(ui.archiveUpButton, &QPushButton::released, this, [this]() { this->moveArchive(-1); });
|
||||
connect(ui.archiveDownButton, &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->moveArchives(1); });
|
||||
connect(
|
||||
ui.directoryListWidget->model(), &QAbstractItemModel::rowsMoved, this, [this]() { this->sortDirectories(); });
|
||||
|
||||
@ -218,6 +220,18 @@ void Launcher::DataFilesPage::buildView()
|
||||
&DataFilesPage::readNavMeshToolStderr);
|
||||
connect(mNavMeshToolInvoker->getProcess(), qOverload<int, QProcess::ExitStatus>(&QProcess::finished), this,
|
||||
&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()
|
||||
@ -707,17 +721,71 @@ void Launcher::DataFilesPage::removeDirectory()
|
||||
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;
|
||||
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);
|
||||
ui.archiveListWidget->setCurrentRow(newRow);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Launcher::DataFilesPage::addArchive(const QString& name, Qt::CheckState selected, int row)
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <components/process/processinvoker.hpp>
|
||||
|
||||
#include <QDir>
|
||||
#include <QMenu>
|
||||
#include <QStringList>
|
||||
#include <QWidget>
|
||||
|
||||
@ -39,6 +40,7 @@ namespace Launcher
|
||||
|
||||
ContentSelectorView::ContentSelector* mSelector;
|
||||
Ui::DataFilesPage ui;
|
||||
QMenu* mArchiveContextMenu;
|
||||
|
||||
public:
|
||||
explicit DataFilesPage(const Files::ConfigurationManager& cfg, Config::GameSettings& gameSettings,
|
||||
@ -72,9 +74,13 @@ namespace Launcher
|
||||
void addSubdirectories(bool append);
|
||||
void sortDirectories();
|
||||
void removeDirectory();
|
||||
void moveArchive(int step);
|
||||
void moveArchives(int step);
|
||||
void moveDirectory(int step);
|
||||
|
||||
void slotShowArchiveContextMenu(const QPoint& pos);
|
||||
void slotCheckMultiSelectedItems();
|
||||
void slotUncheckMultiSelectedItems();
|
||||
|
||||
void on_newProfileAction_triggered();
|
||||
void on_cloneProfileAction_triggered();
|
||||
void on_deleteProfileAction_triggered();
|
||||
@ -120,7 +126,10 @@ namespace Launcher
|
||||
|
||||
void addArchive(const QString& name, Qt::CheckState selected, int row = -1);
|
||||
void addArchivesFromDir(const QString& dir);
|
||||
bool moveArchive(QListWidgetItem* listItem, int step);
|
||||
void buildView();
|
||||
void buildArchiveContextMenu();
|
||||
void setCheckStateForMultiSelectedItems(bool checked);
|
||||
void setProfile(int index, bool savePrevious);
|
||||
void setProfile(const QString& previous, const QString& current, bool savePrevious);
|
||||
void removeProfile(const QString& profile);
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>573</width>
|
||||
<height>384</height>
|
||||
<height>557</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="contextMenuPolicy">
|
||||
@ -29,6 +29,12 @@
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="dataNoteLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html></string>
|
||||
</property>
|
||||
@ -41,14 +47,111 @@
|
||||
<string>Data Directories</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="dirTabLayout">
|
||||
<item row="0" column="0" rowspan="26">
|
||||
<item row="0" column="0">
|
||||
<widget class="QListWidget" name="directoryListWidget">
|
||||
<property name="dragDropMode">
|
||||
<enum>QAbstractItemView::InternalMove</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</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">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
|
||||
@ -61,116 +164,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</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>
|
||||
</widget>
|
||||
<widget class="QWidget" name="archiveTab">
|
||||
@ -178,64 +171,90 @@
|
||||
<string>Archive Files</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="archiveTabLayout">
|
||||
<item row="0" column="0" rowspan="26">
|
||||
<item row="0" column="0">
|
||||
<widget class="QListWidget" name="archiveListWidget">
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::CustomContextMenu</enum>
|
||||
</property>
|
||||
<property name="dragDropMode">
|
||||
<enum>QAbstractItemView::InternalMove</enum>
|
||||
</property>
|
||||
<property name="defaultDropAction">
|
||||
<enum>Qt::CopyAction</enum>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="archiveUpButton">
|
||||
<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 up</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Move Up</string>
|
||||
</property>
|
||||
</widget>
|
||||
<layout class="QVBoxLayout" name="archiveButtons">
|
||||
<item>
|
||||
<widget class="QPushButton" name="archiveUpButton">
|
||||
<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 up</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<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 row="27" column="0" colspan="2">
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QLabel" name="archiveNoteLabel">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</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>
|
||||
</widget>
|
||||
<widget class="QWidget" name="navigationMeshCacheTab">
|
||||
|
@ -116,9 +116,9 @@ void readVFS(std::unique_ptr<VFS::Archive>&& archive, const std::filesystem::pat
|
||||
|
||||
for (const auto& name : vfs.getRecursiveDirectoryIterator(""))
|
||||
{
|
||||
if (isNIF(name))
|
||||
if (isNIF(name.value()))
|
||||
{
|
||||
readNIF(archivePath, name, &vfs, quiet);
|
||||
readNIF(archivePath, name.value(), &vfs, quiet);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -691,15 +691,6 @@ void CSMTools::ReferenceableCheckStage::npcCheck(
|
||||
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)
|
||||
messages.add(id, "Level is non-positive", "", CSMDoc::Message::Severity_Warning);
|
||||
|
@ -18,16 +18,18 @@
|
||||
#include <apps/opencs/model/world/universalid.hpp>
|
||||
|
||||
#include <components/esm3/cellref.hpp>
|
||||
#include <components/esm3/loadbody.hpp>
|
||||
#include <components/esm3/loadfact.hpp>
|
||||
|
||||
CSMTools::ReferenceCheckStage::ReferenceCheckStage(const CSMWorld::RefCollection& references,
|
||||
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)
|
||||
, mObjects(referencables)
|
||||
, mDataSet(referencables.getDataSet())
|
||||
, mCells(cells)
|
||||
, mFactions(factions)
|
||||
, mBodyParts(bodyparts)
|
||||
{
|
||||
mIgnoreBaseRecords = false;
|
||||
}
|
||||
@ -49,9 +51,11 @@ void CSMTools::ReferenceCheckStage::perform(int stage, CSMDoc::Messages& message
|
||||
else
|
||||
{
|
||||
// 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() + "'", "",
|
||||
CSMDoc::Message::Severity_Error);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check if reference charge is valid for it's proper referenced type
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
struct BodyPart;
|
||||
struct Faction;
|
||||
}
|
||||
|
||||
@ -29,7 +30,8 @@ namespace CSMTools
|
||||
{
|
||||
public:
|
||||
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;
|
||||
int setup() override;
|
||||
@ -40,6 +42,7 @@ namespace CSMTools
|
||||
const CSMWorld::RefIdData& mDataSet;
|
||||
const CSMWorld::IdCollection<CSMWorld::Cell>& mCells;
|
||||
const CSMWorld::IdCollection<ESM::Faction>& mFactions;
|
||||
const CSMWorld::IdCollection<ESM::BodyPart>& mBodyParts;
|
||||
bool mIgnoreBaseRecords;
|
||||
};
|
||||
}
|
||||
|
@ -105,8 +105,8 @@ CSMDoc::OperationHolder* CSMTools::Tools::getVerifier()
|
||||
mData.getFactions(), mData.getScripts(), mData.getResources(CSMWorld::UniversalId::Type_Meshes),
|
||||
mData.getResources(CSMWorld::UniversalId::Type_Icons), mData.getBodyParts()));
|
||||
|
||||
mVerifierOperation->appendStage(new ReferenceCheckStage(
|
||||
mData.getReferences(), mData.getReferenceables(), mData.getCells(), mData.getFactions()));
|
||||
mVerifierOperation->appendStage(new ReferenceCheckStage(mData.getReferences(), mData.getReferenceables(),
|
||||
mData.getCells(), mData.getFactions(), mData.getBodyParts()));
|
||||
|
||||
mVerifierOperation->appendStage(new ScriptCheckStage(mDocument));
|
||||
|
||||
|
@ -385,6 +385,26 @@ namespace CSMWorld
|
||||
case 0:
|
||||
{
|
||||
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;
|
||||
}
|
||||
case 1:
|
||||
|
@ -30,18 +30,18 @@ void CSMWorld::Resources::recreate(const VFS::Manager* vfs, const char* const* e
|
||||
|
||||
for (const auto& filepath : vfs->getRecursiveDirectoryIterator(""))
|
||||
{
|
||||
if (filepath.size() < baseSize + 1 || filepath.substr(0, baseSize) != mBaseDirectory
|
||||
|| (filepath[baseSize] != '/' && filepath[baseSize] != '\\'))
|
||||
const std::string_view view = filepath.view();
|
||||
if (view.size() < baseSize + 1 || !view.starts_with(mBaseDirectory) || view[baseSize] != '/')
|
||||
continue;
|
||||
|
||||
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;
|
||||
|
||||
std::string extension = filepath.substr(extensionIndex + 1);
|
||||
std::string_view extension = view.substr(extensionIndex + 1);
|
||||
|
||||
int i = 0;
|
||||
|
||||
@ -53,10 +53,9 @@ void CSMWorld::Resources::recreate(const VFS::Manager* vfs, const char* const* e
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string file = filepath.substr(baseSize + 1);
|
||||
std::string file(view.substr(baseSize + 1));
|
||||
mFiles.push_back(file);
|
||||
std::replace(file.begin(), file.end(), '\\', '/');
|
||||
mIndex.insert(std::make_pair(Misc::StringUtils::lowerCase(file), static_cast<int>(mFiles.size()) - 1));
|
||||
mIndex.emplace(std::move(file), static_cast<int>(mFiles.size()) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,7 +65,7 @@ namespace MWBase
|
||||
virtual void animationTextKey(const MWWorld::Ptr& actor, const std::string& key) = 0;
|
||||
virtual void playAnimation(const MWWorld::Ptr& object, const std::string& groupname,
|
||||
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;
|
||||
virtual void exteriorCreated(MWWorld::CellStore& cell) = 0;
|
||||
virtual void actorDied(const MWWorld::Ptr& actor) = 0;
|
||||
|
@ -171,7 +171,7 @@ namespace MWBase
|
||||
///< Forces an object to refresh its animation state.
|
||||
|
||||
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;
|
||||
///< Run animation for a MW-reference. Calls to this function for references that are currently not
|
||||
/// in the scene should be ignored.
|
||||
@ -180,8 +180,8 @@ namespace MWBase
|
||||
/// \param number How many times the animation should be run
|
||||
/// \param scripted Whether the animation should be treated as a scripted animation.
|
||||
/// \return Success or error
|
||||
virtual bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, float speed,
|
||||
std::string_view startKey, std::string_view stopKey, bool forceLoop)
|
||||
virtual 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)
|
||||
= 0;
|
||||
///< Lua variant of playAnimationGroup. The mode parameter is omitted
|
||||
/// and forced to 0. modes 1 and 2 can be emulated by doing clearAnimationQueue() and
|
||||
|
@ -249,7 +249,7 @@ namespace MWLua
|
||||
// Extended variant of MWScript's PlayGroup and LoopGroup
|
||||
api["playQueued"] = sol::overload(
|
||||
[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);
|
||||
std::string startKey = options.get_or<std::string>("startkey", "start");
|
||||
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) {
|
||||
int loops = options.get_or("loops", 0);
|
||||
uint32_t loops = options.get_or("loops", 0u);
|
||||
MWRender::Animation::AnimPriority priority = getPriorityArgument(options);
|
||||
BlendMask blendMask = options.get_or("blendmask", BlendMask::BlendMask_All);
|
||||
bool autoDisable = options.get_or("autodisable", true);
|
||||
|
@ -417,7 +417,7 @@ namespace MWLua
|
||||
|
||||
void LuaManager::playAnimation(const MWWorld::Ptr& actor, const std::string& groupname,
|
||||
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();
|
||||
options["blendmask"] = blendMask;
|
||||
|
@ -85,7 +85,8 @@ namespace MWLua
|
||||
void animationTextKey(const MWWorld::Ptr& actor, const std::string& key) override;
|
||||
void playAnimation(const MWWorld::Ptr& actor, const std::string& groupname,
|
||||
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
|
||||
{
|
||||
mEngineEvents.addToQueue(EngineEvents::OnNewExterior{ cell });
|
||||
|
@ -215,7 +215,7 @@ namespace MWLua
|
||||
objectT["recordId"] = sol::readonly_property(
|
||||
[](const ObjectT& o) -> std::string { return o.ptr().getCellRef().getRefId().serializeText(); });
|
||||
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())
|
||||
return sol::nullopt;
|
||||
else
|
||||
@ -619,7 +619,7 @@ namespace MWLua
|
||||
inventoryT["countOf"] = [](const InventoryT& inventory, std::string_view recordId) {
|
||||
const MWWorld::Ptr& ptr = inventory.mObj.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>)
|
||||
{
|
||||
@ -637,7 +637,7 @@ namespace MWLua
|
||||
inventoryT["find"] = [](const InventoryT& inventory, std::string_view recordId) -> sol::optional<ObjectT> {
|
||||
const MWWorld::Ptr& ptr = inventory.mObj.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)
|
||||
{
|
||||
if (item.getCellRef().getRefId() == itemId)
|
||||
@ -651,7 +651,7 @@ namespace MWLua
|
||||
inventoryT["findAll"] = [](const InventoryT& inventory, std::string_view recordId) {
|
||||
const MWWorld::Ptr& ptr = inventory.mObj.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>>();
|
||||
for (const MWWorld::Ptr& item : store)
|
||||
{
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include <components/esm3/loadfact.hpp>
|
||||
#include <components/esm3/loadnpc.hpp>
|
||||
#include <components/lua/luastate.hpp>
|
||||
#include <components/misc/resourcehelpers.hpp>
|
||||
|
||||
#include "apps/openmw/mwbase/environment.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; });
|
||||
record["head"]
|
||||
= 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["baseGold"] = sol::readonly_property([](const ESM::NPC& rec) -> int { return rec.mNpdt.mGold; });
|
||||
addActorServicesBindings<ESM::NPC>(record, context);
|
||||
|
@ -174,6 +174,25 @@ namespace MWMechanics
|
||||
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)
|
||||
{
|
||||
if (mIterating)
|
||||
@ -438,21 +457,8 @@ namespace MWMechanics
|
||||
if (store->get<ESM::Enchantment>().search(id) == nullptr)
|
||||
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) {
|
||||
switch (store->find(spell.mId))
|
||||
{
|
||||
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;
|
||||
}
|
||||
return spell.getEnchantment() == id;
|
||||
}) != mSpells.end();
|
||||
}
|
||||
|
||||
|
@ -73,6 +73,7 @@ namespace MWMechanics
|
||||
const std::string& getDisplayName() const { return mDisplayName; }
|
||||
|
||||
ESM::RefNum getItem() const { return mItem; }
|
||||
ESM::RefId getEnchantment() const;
|
||||
|
||||
// Increments worsenings count and sets the next timestamp
|
||||
void worsen();
|
||||
|
@ -2005,7 +2005,7 @@ namespace MWMechanics
|
||||
}
|
||||
|
||||
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);
|
||||
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)
|
||||
{
|
||||
const auto iter = mIndex.find(ptr.mRef);
|
||||
|
@ -112,9 +112,9 @@ namespace MWMechanics
|
||||
|
||||
void forceStateUpdate(const MWWorld::Ptr& ptr) const;
|
||||
|
||||
bool playAnimationGroup(
|
||||
const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false) const;
|
||||
bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, float speed,
|
||||
bool playAnimationGroup(const MWWorld::Ptr& ptr, std::string_view groupName, int mode, uint32_t number,
|
||||
bool scripted = false) const;
|
||||
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);
|
||||
void enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable);
|
||||
void skipAnimation(const MWWorld::Ptr& ptr) const;
|
||||
|
@ -483,7 +483,8 @@ namespace MWMechanics
|
||||
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)
|
||||
@ -521,7 +522,7 @@ namespace MWMechanics
|
||||
mCurrentJump = jumpAnimName;
|
||||
if (mJumpState == JumpState_InAir)
|
||||
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)
|
||||
playBlendedAnimation(jumpAnimName, Priority_Jump, jumpmask, true, 1.0f, "loop stop", "stop", 0.0f, 0);
|
||||
}
|
||||
@ -749,8 +750,8 @@ namespace MWMechanics
|
||||
}
|
||||
}
|
||||
|
||||
playBlendedAnimation(
|
||||
mCurrentMovement, Priority_Movement, movemask, false, 1.f, "start", "stop", startpoint, ~0ul, true);
|
||||
playBlendedAnimation(mCurrentMovement, Priority_Movement, movemask, false, 1.f, "start", "stop", startpoint,
|
||||
std::numeric_limits<uint32_t>::max(), true);
|
||||
}
|
||||
|
||||
void CharacterController::refreshIdleAnims(CharacterState idle, bool force)
|
||||
@ -778,7 +779,7 @@ namespace MWMechanics
|
||||
}
|
||||
|
||||
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
|
||||
// "idle"+weapon or "idle".
|
||||
@ -1192,8 +1193,8 @@ namespace MWMechanics
|
||||
if (!animPlaying)
|
||||
{
|
||||
int mask = MWRender::BlendMask_Torso | MWRender::BlendMask_RightArm;
|
||||
playBlendedAnimation(
|
||||
"idlestorm", Priority_Storm, mask, true, 1.0f, "start", "stop", 0.0f, ~0ul, true);
|
||||
playBlendedAnimation("idlestorm", Priority_Storm, mask, true, 1.0f, "start", "stop", 0.0f,
|
||||
std::numeric_limits<uint32_t>::max(), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1326,7 +1327,7 @@ namespace MWMechanics
|
||||
mAnimation->disable("shield");
|
||||
|
||||
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"))
|
||||
{
|
||||
@ -2515,7 +2516,8 @@ namespace MWMechanics
|
||||
{
|
||||
AnimationQueueEntry entry;
|
||||
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.mStartKey = "start";
|
||||
entry.mStopKey = "stop";
|
||||
@ -2538,7 +2540,7 @@ namespace MWMechanics
|
||||
|
||||
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,
|
||||
float startpoint, size_t loops, bool loopfallback) const
|
||||
float startpoint, uint32_t loops, bool loopfallback) const
|
||||
{
|
||||
if (mLuaAnimations)
|
||||
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);
|
||||
}
|
||||
|
||||
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))
|
||||
return false;
|
||||
@ -2583,9 +2585,8 @@ namespace MWMechanics
|
||||
// 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
|
||||
// exactly x times, while non-actors will loop x+1 instead.
|
||||
if (mPtr.getClass().isActor())
|
||||
if (mPtr.getClass().isActor() && count > 0)
|
||||
count--;
|
||||
count = std::max(count, 0);
|
||||
|
||||
AnimationQueueEntry entry;
|
||||
entry.mGroup = groupname;
|
||||
@ -2620,7 +2621,7 @@ namespace MWMechanics
|
||||
}
|
||||
|
||||
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.
|
||||
// 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;
|
||||
// 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.
|
||||
entry.mLoopCount = std::max(loops, 0);
|
||||
entry.mLoopCount = loops;
|
||||
entry.mStartKey = startKey;
|
||||
entry.mStopKey = stopKey;
|
||||
entry.mLooping = mAnimation->isLoopingAnimation(groupname) || forceLoop;
|
||||
|
@ -134,7 +134,7 @@ namespace MWMechanics
|
||||
struct AnimationQueueEntry
|
||||
{
|
||||
std::string mGroup;
|
||||
size_t mLoopCount;
|
||||
uint32_t mLoopCount;
|
||||
float mTime;
|
||||
bool mLooping;
|
||||
bool mScripted;
|
||||
@ -276,10 +276,10 @@ namespace MWMechanics
|
||||
|
||||
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,
|
||||
size_t loops, bool loopfallback = false) const;
|
||||
bool playGroup(std::string_view groupname, int mode, int count, bool scripted = false);
|
||||
uint32_t loops, bool loopfallback = false) const;
|
||||
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,
|
||||
int loops, bool forceLoop);
|
||||
uint32_t loops, bool forceLoop);
|
||||
void enableLuaAnimations(bool enable);
|
||||
void skipAnim();
|
||||
bool isAnimPlaying(std::string_view groupName) const;
|
||||
|
@ -749,14 +749,14 @@ namespace MWMechanics
|
||||
}
|
||||
|
||||
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())
|
||||
return mActors.playAnimationGroup(ptr, groupName, mode, number, scripted);
|
||||
else
|
||||
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)
|
||||
{
|
||||
if (ptr.getClass().isActor())
|
||||
|
@ -141,9 +141,9 @@ namespace MWMechanics
|
||||
|
||||
/// Attempt to play an animation group
|
||||
/// @return Success or error
|
||||
bool playAnimationGroup(
|
||||
const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false) override;
|
||||
bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, float speed,
|
||||
bool playAnimationGroup(const MWWorld::Ptr& ptr, std::string_view groupName, int mode, uint32_t number,
|
||||
bool scripted = false) override;
|
||||
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;
|
||||
void enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable) override;
|
||||
void skipAnimation(const MWWorld::Ptr& ptr) override;
|
||||
|
@ -99,7 +99,7 @@ namespace MWMechanics
|
||||
}
|
||||
|
||||
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);
|
||||
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,
|
||||
std::string_view startKey, std::string_view stopKey, bool forceLoop)
|
||||
bool Objects::playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, uint32_t loops,
|
||||
float speed, std::string_view startKey, std::string_view stopKey, bool forceLoop)
|
||||
{
|
||||
const auto iter = mIndex.find(ptr.mRef);
|
||||
if (iter != mIndex.end())
|
||||
|
@ -46,8 +46,8 @@ namespace MWMechanics
|
||||
void onClose(const MWWorld::Ptr& ptr);
|
||||
|
||||
bool playAnimationGroup(
|
||||
const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false);
|
||||
bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, float speed,
|
||||
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, uint32_t loops, float speed,
|
||||
std::string_view startKey, std::string_view stopKey, bool forceLoop);
|
||||
void enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable);
|
||||
void skipAnimation(const MWWorld::Ptr& ptr);
|
||||
|
@ -279,7 +279,8 @@ namespace
|
||||
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 ESM::Static* absorbStatic = esmStore.get<ESM::Static>().find(ESM::RefId::stringRefId("VFX_Absorb"));
|
||||
@ -287,15 +288,14 @@ namespace
|
||||
if (animation && !absorbStatic->mModel.empty())
|
||||
animation->addEffect(Misc::ResourceHelpers::correctMeshPath(absorbStatic->mModel),
|
||||
ESM::MagicEffect::indexToName(ESM::MagicEffect::SpellAbsorption), false);
|
||||
const ESM::Spell* spell = esmStore.get<ESM::Spell>().search(spellId);
|
||||
int spellCost = 0;
|
||||
if (spell)
|
||||
if (const ESM::Spell* spell = esmStore.get<ESM::Spell>().search(spellParams.getId()))
|
||||
{
|
||||
spellCost = MWMechanics::calcSpellCost(*spell);
|
||||
}
|
||||
else
|
||||
{
|
||||
const ESM::Enchantment* enchantment = esmStore.get<ESM::Enchantment>().search(spellId);
|
||||
const ESM::Enchantment* enchantment = esmStore.get<ESM::Enchantment>().search(spellParams.getEnchantment());
|
||||
if (enchantment)
|
||||
spellCost = MWMechanics::getEffectiveEnchantmentCastCost(*enchantment, caster);
|
||||
}
|
||||
@ -342,7 +342,7 @@ namespace
|
||||
{
|
||||
if (canAbsorb && Misc::Rng::roll0to99(prng) < activeEffect.mMagnitude)
|
||||
{
|
||||
absorbSpell(spellParams.getId(), caster, target);
|
||||
absorbSpell(spellParams, caster, target);
|
||||
return MWMechanics::MagicApplicationResult::Type::REMOVED;
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include "collisiontype.hpp"
|
||||
#include "constants.hpp"
|
||||
#include "contacttestwrapper.h"
|
||||
#include "object.hpp"
|
||||
#include "physicssystem.hpp"
|
||||
#include "projectile.hpp"
|
||||
#include "projectileconvexcallback.hpp"
|
||||
@ -243,11 +244,20 @@ namespace MWPhysics
|
||||
float hitHeight = tracer.mHitPoint.z() - tracer.mEndPos.z() + actor.mHalfExtentsZ;
|
||||
osg::Vec3f oldPosition = newPosition;
|
||||
bool usedStepLogic = false;
|
||||
if (hitHeight < Constants::sStepSizeUp && !isActor(tracer.mHitObject))
|
||||
if (!isActor(tracer.mHitObject))
|
||||
{
|
||||
// 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);
|
||||
if (hitHeight < Constants::sStepSizeUp)
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
|
@ -23,6 +23,7 @@ namespace MWPhysics
|
||||
, mPosition(ptr.getRefData().getPosition().asVec3())
|
||||
, mRotation(rotation)
|
||||
, mTaskScheduler(scheduler)
|
||||
, mCollidedWith(ScriptedCollisionType_None)
|
||||
{
|
||||
mCollisionObject = BulletHelpers::makeCollisionObject(mShapeInstance->mCollisionShape.get(),
|
||||
Misc::Convert::toBullet(mPosition), Misc::Convert::toBullet(rotation));
|
||||
@ -166,4 +167,20 @@ namespace MWPhysics
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,14 @@ namespace MWPhysics
|
||||
{
|
||||
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
|
||||
{
|
||||
public:
|
||||
@ -38,6 +46,9 @@ namespace MWPhysics
|
||||
/// @brief update object shape
|
||||
/// @return true if shape changed
|
||||
bool animateCollisionShapes();
|
||||
bool collidedWith(ScriptedCollisionType type) const;
|
||||
void addCollision(ScriptedCollisionType type);
|
||||
void resetCollisions();
|
||||
|
||||
private:
|
||||
osg::ref_ptr<Resource::BulletShapeInstance> mShapeInstance;
|
||||
@ -50,6 +61,7 @@ namespace MWPhysics
|
||||
bool mTransformUpdatePending = false;
|
||||
mutable std::mutex mPositionMutex;
|
||||
PhysicsTaskScheduler* mTaskScheduler;
|
||||
char mCollidedWith;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -673,12 +673,13 @@ namespace MWPhysics
|
||||
// Slow fall reduces fall speed by a factor of (effect magnitude / 200)
|
||||
const float slowFall
|
||||
= 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()
|
||||
|| (!godmode && stats.getMagicEffects().getOrDefault(ESM::MagicEffect::Paralyze).getModifier() > 0);
|
||||
|
||||
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 (willSimulate)
|
||||
@ -708,6 +709,8 @@ namespace MWPhysics
|
||||
changed = false;
|
||||
}
|
||||
}
|
||||
for (auto& [_, object] : mObjects)
|
||||
object->resetCollisions();
|
||||
|
||||
#ifndef BT_NO_PROFILE
|
||||
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);
|
||||
return (std::find(collisions.begin(), collisions.end(), actor) != collisions.end());
|
||||
auto found = mObjects.find(object.mRef);
|
||||
if (found != mObjects.end())
|
||||
return found->second->collidedWith(type);
|
||||
return false;
|
||||
}
|
||||
|
||||
void PhysicsSystem::getActorsCollidingWith(const MWWorld::ConstPtr& object, std::vector<MWWorld::Ptr>& out) const
|
||||
@ -890,7 +895,8 @@ namespace MWPhysics
|
||||
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()
|
||||
, mStandingOn(nullptr)
|
||||
, mIsOnGround(actor.getOnGround())
|
||||
@ -917,6 +923,7 @@ namespace MWPhysics
|
||||
, mIsAquatic(actor.getPtr().getClass().isPureWaterCreature(actor.getPtr()))
|
||||
, mWaterCollision(waterCollision)
|
||||
, mSkipCollisionDetection(!actor.getCollisionMode())
|
||||
, mIsPlayer(isPlayer)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -56,6 +56,7 @@ namespace MWPhysics
|
||||
class Actor;
|
||||
class PhysicsTaskScheduler;
|
||||
class Projectile;
|
||||
enum ScriptedCollisionType : char;
|
||||
|
||||
using ActorMap = std::unordered_map<const MWWorld::LiveCellRefBase*, std::shared_ptr<Actor>>;
|
||||
|
||||
@ -79,7 +80,7 @@ namespace MWPhysics
|
||||
|
||||
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 mInertia;
|
||||
const btCollisionObject* mStandingOn;
|
||||
@ -102,6 +103,7 @@ namespace MWPhysics
|
||||
const bool mIsAquatic;
|
||||
const bool mWaterCollision;
|
||||
const bool mSkipCollisionDetection;
|
||||
const bool mIsPlayer;
|
||||
};
|
||||
|
||||
struct ProjectileFrameData
|
||||
@ -258,9 +260,8 @@ namespace MWPhysics
|
||||
/// 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;
|
||||
|
||||
/// Return true if \a actor has collided with \a object in this frame.
|
||||
/// This will detect running into objects, but will not detect climbing stairs, stepping up a small object, etc.
|
||||
bool isActorCollidingWith(const MWWorld::Ptr& actor, const MWWorld::ConstPtr& object) const;
|
||||
/// Return true if an object of the given type has collided with this object
|
||||
bool isObjectCollidingWith(const MWWorld::ConstPtr& object, ScriptedCollisionType type) const;
|
||||
|
||||
/// 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;
|
||||
|
@ -805,7 +805,7 @@ namespace MWRender
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (!mObjectRoot || mAnimSources.empty())
|
||||
|
@ -153,7 +153,7 @@ namespace MWRender
|
||||
|
||||
bool mPlaying;
|
||||
bool mLoopingEnabled;
|
||||
size_t mLoopCount;
|
||||
uint32_t mLoopCount;
|
||||
|
||||
AnimPriority mPriority;
|
||||
int mBlendMask;
|
||||
@ -379,7 +379,7 @@ namespace MWRender
|
||||
* the "start" and "stop" keys for looping?
|
||||
*/
|
||||
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);
|
||||
|
||||
/** Adjust the speed multiplier of an already playing animation.
|
||||
|
@ -463,8 +463,8 @@ namespace MWRender
|
||||
if (torch != inv.end() && torch->getType() == ESM::Light::sRecordId && showCarriedLeft)
|
||||
{
|
||||
if (!mAnimation->getInfo("torch"))
|
||||
mAnimation->play(
|
||||
"torch", 2, BlendMask::BlendMask_LeftArm, false, 1.0f, "start", "stop", 0.0f, ~0ul, true);
|
||||
mAnimation->play("torch", 2, BlendMask::BlendMask_LeftArm, false, 1.0f, "start", "stop", 0.0f,
|
||||
std::numeric_limits<uint32_t>::max(), true);
|
||||
}
|
||||
else if (mAnimation->getInfo("torch"))
|
||||
mAnimation->disable("torch");
|
||||
|
@ -80,6 +80,25 @@ namespace
|
||||
|
||||
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
|
||||
@ -87,6 +106,7 @@ namespace MWRender
|
||||
|
||||
RippleSimulation::RippleSimulation(osg::Group* parent, Resource::ResourceSystem* resourceSystem)
|
||||
: mParent(parent)
|
||||
, mMaxNumberRipples(Fallback::Map::getInt("Water_MaxNumberRipples"))
|
||||
{
|
||||
mParticleSystem = new osgParticle::ParticleSystem;
|
||||
|
||||
@ -159,9 +179,6 @@ namespace MWRender
|
||||
|
||||
currentPos.z() = mParticleNode->getPosition().z();
|
||||
|
||||
if (mParticleSystem->numParticles() - mParticleSystem->numDeadParticles() > 500)
|
||||
continue; // TODO: remove the oldest particle to make room?
|
||||
|
||||
emitRipple(currentPos);
|
||||
}
|
||||
}
|
||||
@ -226,7 +243,19 @@ namespace MWRender
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mMaxNumberRipples == 0)
|
||||
return;
|
||||
|
||||
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);
|
||||
p->setPosition(osg::Vec3f(pos.x(), pos.y(), 0.f));
|
||||
p->setAngle(osg::Vec3f(0, 0, Misc::Rng::rollProbability() * osg::PI * 2 - osg::PI));
|
||||
|
@ -74,6 +74,8 @@ namespace MWRender
|
||||
std::vector<Emitter> mEmitters;
|
||||
|
||||
Ripples* mRipples = nullptr;
|
||||
|
||||
int mMaxNumberRipples;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -240,6 +240,7 @@ namespace MWRender
|
||||
, mIsStorm(false)
|
||||
, mDay(0)
|
||||
, mMonth(0)
|
||||
, mTimescaleClouds(Fallback::Map::getBool("Weather_Timescale_Clouds"))
|
||||
, mCloudAnimationTimer(0.f)
|
||||
, mRainTimer(0.f)
|
||||
, mStormParticleDirection(MWWorld::Weather::defaultDirection())
|
||||
@ -558,8 +559,17 @@ namespace MWRender
|
||||
mParticleNode->setAttitude(quat);
|
||||
}
|
||||
|
||||
const float timeScale = MWBase::Environment::get().getWorld()->getTimeManager()->getGameTimeScale();
|
||||
|
||||
// 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);
|
||||
mCloudUpdater->setTextureCoord(mCloudAnimationTimer);
|
||||
|
||||
@ -575,8 +585,7 @@ namespace MWRender
|
||||
}
|
||||
|
||||
// rotate the stars by 360 degrees every 4 days
|
||||
mAtmosphereNightRoll += MWBase::Environment::get().getWorld()->getTimeManager()->getGameTimeScale() * duration
|
||||
* osg::DegreesToRadians(360.f) / (3600 * 96.f);
|
||||
mAtmosphereNightRoll += timeScale * duration * osg::DegreesToRadians(360.f) / (3600 * 96.f);
|
||||
if (mAtmosphereNightNode->getNodeMask() != 0)
|
||||
mAtmosphereNightNode->setAttitude(osg::Quat(mAtmosphereNightRoll, osg::Vec3f(0, 0, 1)));
|
||||
mPrecipitationOccluder->update();
|
||||
|
@ -165,6 +165,7 @@ namespace MWRender
|
||||
int mDay;
|
||||
int mMonth;
|
||||
|
||||
bool mTimescaleClouds;
|
||||
float mCloudAnimationTimer;
|
||||
|
||||
float mRainTimer;
|
||||
|
@ -56,7 +56,7 @@ namespace MWScript
|
||||
}
|
||||
|
||||
MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(
|
||||
ptr, group, mode, std::numeric_limits<int>::max(), true);
|
||||
ptr, group, mode, std::numeric_limits<uint32_t>::max(), true);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -64,11 +64,12 @@ namespace MWWorld
|
||||
}
|
||||
case ESM::Format::Tes4:
|
||||
{
|
||||
ESM4::Reader readerESM4(std::move(stream), filepath,
|
||||
MWBase::Environment::get().getResourceSystem()->getVFS(), mReaders.getStatelessEncoder());
|
||||
readerESM4.setModIndex(index);
|
||||
readerESM4.updateModIndices(mNameToIndex);
|
||||
mStore.loadESM4(readerESM4);
|
||||
ESM4::Reader reader(std::move(stream), filepath,
|
||||
MWBase::Environment::get().getResourceSystem()->getVFS(),
|
||||
mEncoder != nullptr ? &mEncoder->getStatelessEncoder() : nullptr);
|
||||
reader.setModIndex(index);
|
||||
reader.updateModIndices(mNameToIndex);
|
||||
mStore.loadESM4(reader);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -274,8 +274,6 @@ namespace MWWorld
|
||||
const std::vector<std::string>& groundcoverFiles, ToUTF8::Utf8Encoder* encoder, Loading::Listener* listener)
|
||||
{
|
||||
mContentFiles = contentFiles;
|
||||
if (encoder)
|
||||
mReaders.setStatelessEncoder(encoder->getStatelessEncoder());
|
||||
mESMVersions.resize(mContentFiles.size(), -1);
|
||||
|
||||
loadContentFiles(fileCollections, contentFiles, encoder, listener);
|
||||
@ -2429,15 +2427,12 @@ namespace MWWorld
|
||||
|
||||
bool World::getPlayerCollidingWith(const MWWorld::ConstPtr& object)
|
||||
{
|
||||
MWWorld::Ptr player = getPlayerPtr();
|
||||
return mPhysics->isActorCollidingWith(player, object);
|
||||
return mPhysics->isObjectCollidingWith(object, MWPhysics::ScriptedCollisionType_Player);
|
||||
}
|
||||
|
||||
bool World::getActorCollidingWith(const MWWorld::ConstPtr& object)
|
||||
{
|
||||
std::vector<MWWorld::Ptr> actors;
|
||||
mPhysics->getActorsCollidingWith(object, actors);
|
||||
return !actors.empty();
|
||||
return mPhysics->isObjectCollidingWith(object, MWPhysics::ScriptedCollisionType_Actor);
|
||||
}
|
||||
|
||||
void World::hurtStandingActors(const ConstPtr& object, float healthPerSecond)
|
||||
|
@ -326,6 +326,8 @@ End)mwscript";
|
||||
Addtopic -spells...
|
||||
Addtopic -magicka...
|
||||
|
||||
player->PositionCell, -97274, -94273, 8064, -12,-12
|
||||
|
||||
End)mwscript";
|
||||
|
||||
const std::string sIssue4061 = R"mwscript(Begin 01_Rz_neuvazhay-koryto2
|
||||
@ -763,7 +765,33 @@ End)mwscript";
|
||||
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<PositionCell>(Compiler::Transformation::opcodePositionCellExplicit);
|
||||
TestInterpreterContext context;
|
||||
run(*script, context);
|
||||
EXPECT_TRUE(topics.empty());
|
||||
|
@ -63,9 +63,22 @@ namespace Compiler
|
||||
switch (mPutback)
|
||||
{
|
||||
case Putback_Special:
|
||||
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
case Putback_Integer:
|
||||
|
||||
|
@ -723,8 +723,10 @@ QString ContentSelectorModel::ContentModel::toolTip(const EsmFile* file) const
|
||||
int index = indexFromItem(item(file->filePath())).row();
|
||||
for (const LoadOrderError& error : checkForLoadOrderErrors(file, index))
|
||||
{
|
||||
assert(error.errorCode() != LoadOrderError::ErrorCode::ErrorCode_None);
|
||||
|
||||
text += "<p>";
|
||||
text += error.toolTip();
|
||||
text += mErrorToolTips[error.errorCode() - 1].arg(error.fileName());
|
||||
text += "</p>";
|
||||
}
|
||||
text += ("</b>");
|
||||
|
@ -93,6 +93,10 @@ namespace ContentSelectorModel
|
||||
QIcon mWarningIcon;
|
||||
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:
|
||||
QString mMimeType;
|
||||
QStringList mMimeTypes;
|
||||
|
@ -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);
|
||||
}
|
@ -28,7 +28,6 @@ namespace ContentSelectorModel
|
||||
}
|
||||
inline ErrorCode errorCode() const { return mErrorCode; }
|
||||
inline QString fileName() const { return mFileName; }
|
||||
QString toolTip() const;
|
||||
|
||||
private:
|
||||
ErrorCode mErrorCode;
|
||||
|
@ -9,8 +9,6 @@
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include <components/to_utf8/to_utf8.hpp>
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
class ReadersCache
|
||||
@ -57,23 +55,13 @@ namespace ESM
|
||||
|
||||
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:
|
||||
const std::size_t mCapacity;
|
||||
std::map<std::size_t, std::list<Item>::iterator> mIndex;
|
||||
std::list<Item> mBusyItems;
|
||||
std::list<Item> mFreeItems;
|
||||
std::list<Item> mClosedItems;
|
||||
std::optional<ToUTF8::StatelessUtf8Encoder> mStatelessEncoder;
|
||||
|
||||
inline void closeExtraReaders();
|
||||
|
||||
inline void releaseItem(std::list<Item>::iterator it) noexcept;
|
||||
|
@ -41,6 +41,7 @@
|
||||
#include <components/files/conversion.hpp>
|
||||
#include <components/misc/strings/lower.hpp>
|
||||
#include <components/to_utf8/to_utf8.hpp>
|
||||
#include <components/vfs/manager.hpp>
|
||||
|
||||
#include "grouptype.hpp"
|
||||
|
||||
|
@ -36,13 +36,17 @@
|
||||
|
||||
#include <components/esm/formid.hpp>
|
||||
#include <components/files/istreamptr.hpp>
|
||||
#include <components/vfs/manager.hpp>
|
||||
|
||||
namespace ToUTF8
|
||||
{
|
||||
class StatelessUtf8Encoder;
|
||||
}
|
||||
|
||||
namespace VFS
|
||||
{
|
||||
class Manager;
|
||||
}
|
||||
|
||||
namespace ESM4
|
||||
{
|
||||
#pragma pack(push, 1)
|
||||
|
@ -7,12 +7,12 @@
|
||||
|
||||
static const std::set<std::string_view> allowedKeysInt = { "LightAttenuation_LinearMethod",
|
||||
"LightAttenuation_OutQuadInLin", "LightAttenuation_QuadraticMethod", "LightAttenuation_UseConstant",
|
||||
"LightAttenuation_UseLinear", "LightAttenuation_UseQuadratic", "Water_NearWaterRadius", "Water_NearWaterPoints",
|
||||
"Water_RippleFrameCount", "Water_SurfaceTileCount", "Water_SurfaceFrameCount", "Weather_Clear_Using_Precip",
|
||||
"Weather_Cloudy_Using_Precip", "Weather_Foggy_Using_Precip", "Weather_Overcast_Using_Precip",
|
||||
"Weather_Rain_Using_Precip", "Weather_Thunderstorm_Using_Precip", "Weather_Ashstorm_Using_Precip",
|
||||
"Weather_Blight_Using_Precip", "Weather_Snow_Using_Precip", "Weather_Blizzard_Using_Precip", "Weather_Rain_Ripples",
|
||||
"Weather_Snow_Ripples" };
|
||||
"LightAttenuation_UseLinear", "LightAttenuation_UseQuadratic", "Water_MaxNumberRipples", "Water_NearWaterRadius",
|
||||
"Water_NearWaterPoints", "Water_RippleFrameCount", "Water_SurfaceTileCount", "Water_SurfaceFrameCount",
|
||||
"Weather_Clear_Using_Precip", "Weather_Cloudy_Using_Precip", "Weather_Foggy_Using_Precip",
|
||||
"Weather_Overcast_Using_Precip", "Weather_Rain_Using_Precip", "Weather_Thunderstorm_Using_Precip",
|
||||
"Weather_Ashstorm_Using_Precip", "Weather_Blight_Using_Precip", "Weather_Snow_Using_Precip",
|
||||
"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",
|
||||
"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_Sunrise_Color", "Weather_Thunderstorm_Sun_Sunset_Color",
|
||||
"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_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",
|
||||
@ -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_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",
|
||||
"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_PSWaterReflectUpdate", "Water_RippleAlphas", "Water_RippleScale", "Water_SurfaceTextureSize",
|
||||
"Water_TileTextureDivisor", "Weather_AlphaReduce", "Weather_Ashstorm_Storm_Threshold",
|
||||
|
@ -43,6 +43,7 @@ namespace fx
|
||||
void setSunPos(const osg::Vec4f& pos, bool night)
|
||||
{
|
||||
mData.get<SunPos>() = pos;
|
||||
mData.get<SunPos>().normalize();
|
||||
|
||||
if (night)
|
||||
mData.get<SunPos>().z() *= -1.f;
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "manager.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <components/files/conversion.hpp>
|
||||
@ -37,30 +38,30 @@ namespace VFS
|
||||
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
|
||||
{
|
||||
assert(Path::isNormalized(normalizedName));
|
||||
const auto found = mIndex.find(normalizedName);
|
||||
if (found == mIndex.end())
|
||||
throw std::runtime_error("Resource '" + std::string(normalizedName) + "' not found");
|
||||
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)
|
||||
{
|
||||
if ((*it)->contains(normalized))
|
||||
if ((*it)->contains(name))
|
||||
return (*it)->getDescription();
|
||||
}
|
||||
return {};
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "filemap.hpp"
|
||||
#include "pathutil.hpp"
|
||||
|
||||
namespace VFS
|
||||
{
|
||||
@ -40,19 +41,19 @@ namespace VFS
|
||||
|
||||
/// Does a file with this name exist?
|
||||
/// @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.
|
||||
/// @note Throws an exception if the file can not be found.
|
||||
/// @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).
|
||||
/// @note Throws an exception if the file can not be found.
|
||||
/// @note May be called from any thread once the index has been built.
|
||||
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
|
||||
/// In practice it return all files of the VFS starting with the given path
|
||||
|
@ -15,6 +15,11 @@ namespace VFS::Path
|
||||
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)
|
||||
{
|
||||
std::transform(name.begin(), name.end(), name.begin(), normalize);
|
||||
|
@ -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++()
|
||||
{
|
||||
|
@ -939,6 +939,7 @@
|
||||
-- @field #string name
|
||||
-- @field #string race
|
||||
-- @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 hair Path to the hair body part model
|
||||
-- @field #string head Path to the head body part model
|
||||
|
Loading…
x
Reference in New Issue
Block a user