1
0
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:
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 #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

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("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();
}

View File

@ -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

View File

@ -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)

View File

@ -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);

View File

@ -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>&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>
@ -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>&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>
</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">

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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

View File

@ -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;
};
}

View File

@ -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));

View File

@ -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:

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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 });

View File

@ -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)
{

View File

@ -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);

View File

@ -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();
}

View File

@ -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();

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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())

View File

@ -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;

View File

@ -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())

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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)
{

View File

@ -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;
}
}

View File

@ -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;
};
}

View File

@ -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)
{
}

View File

@ -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;

View File

@ -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())

View File

@ -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.

View File

@ -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");

View File

@ -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));

View File

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

View File

@ -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();

View File

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

View File

@ -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);
}
};

View File

@ -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;
}
}

View File

@ -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)

View File

@ -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());

View File

@ -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:

View File

@ -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>");

View File

@ -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;

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 QString fileName() const { return mFileName; }
QString toolTip() const;
private:
ErrorCode mErrorCode;

View File

@ -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;

View File

@ -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"

View File

@ -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)

View File

@ -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",

View File

@ -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;

View File

@ -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 {};

View File

@ -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

View File

@ -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);

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++()
{

View File

@ -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