1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-06-28 05:11:34 +00:00

Merge branch 'master' of gitlab.com:openmw/openmw into lua_controller_cursor

This commit is contained in:
Zackhasacat 2024-01-24 15:50:46 -06:00
commit 3ef2f71062
154 changed files with 2658 additions and 1683 deletions

View file

@ -16,8 +16,10 @@
Bug #4754: Stack of ammunition cannot be equipped partially Bug #4754: Stack of ammunition cannot be equipped partially
Bug #4816: GetWeaponDrawn returns 1 before weapon is attached Bug #4816: GetWeaponDrawn returns 1 before weapon is attached
Bug #4822: Non-weapon equipment and body parts can't inherit time from parent animation Bug #4822: Non-weapon equipment and body parts can't inherit time from parent animation
Bug #4898: Odd/Incorrect lighting on meshes
Bug #5057: Weapon swing sound plays at same pitch whether it hits or misses Bug #5057: Weapon swing sound plays at same pitch whether it hits or misses
Bug #5062: Root bone rotations for NPC animation don't work the same as for creature animation Bug #5062: Root bone rotations for NPC animation don't work the same as for creature animation
Bug #5065: Actors with scripted animation still try to wander and turn around without moving
Bug #5066: Quirks with starting and stopping scripted animations Bug #5066: Quirks with starting and stopping scripted animations
Bug #5129: Stuttering animation on Centurion Archer Bug #5129: Stuttering animation on Centurion Archer
Bug #5280: Unskinned shapes in skinned equipment are rendered in the wrong place Bug #5280: Unskinned shapes in skinned equipment are rendered in the wrong place
@ -128,7 +130,9 @@
Bug #7742: Governing attribute training limit should use the modified attribute Bug #7742: Governing attribute training limit should use the modified attribute
Bug #7758: Water walking is not taken into account to compute path cost on the water Bug #7758: Water walking is not taken into account to compute path cost on the water
Bug #7761: Rain and ambient loop sounds are mutually exclusive Bug #7761: Rain and ambient loop sounds are mutually exclusive
Bug #7765: OpenMW-CS: Touch Record option is broken
Bug #7770: Sword of the Perithia: Script execution failure Bug #7770: Sword of the Perithia: Script execution failure
Bug #7780: Non-ASCII texture paths in NIF files don't work
Feature #2566: Handle NAM9 records for manual cell references Feature #2566: Handle NAM9 records for manual cell references
Feature #3537: Shader-based water ripples Feature #3537: Shader-based water ripples
Feature #5173: Support for NiFogProperty Feature #5173: Support for NiFogProperty

View file

@ -174,7 +174,7 @@ namespace
constexpr double expiryDelay = 0; constexpr double expiryDelay = 0;
Resource::ImageManager imageManager(&vfs, expiryDelay); Resource::ImageManager imageManager(&vfs, expiryDelay);
Resource::NifFileManager nifFileManager(&vfs); Resource::NifFileManager nifFileManager(&vfs, &encoder.getStatelessEncoder());
Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, expiryDelay); Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, expiryDelay);
Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay); Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay);

View file

@ -85,7 +85,7 @@ namespace ESSImport
Misc::StringUtils::lowerCaseInPlace(group); Misc::StringUtils::lowerCaseInPlace(group);
ESM::AnimationState::ScriptedAnimation scriptedAnim; ESM::AnimationState::ScriptedAnimation scriptedAnim;
scriptedAnim.mGroup = group; scriptedAnim.mGroup = std::move(group);
scriptedAnim.mTime = anis.mTime; scriptedAnim.mTime = anis.mTime;
scriptedAnim.mAbsolute = true; scriptedAnim.mAbsolute = true;
// Neither loop count nor queueing seems to be supported by the ess format. // Neither loop count nor queueing seems to be supported by the ess format.

View file

@ -306,12 +306,12 @@ namespace ESSImport
mMarkers.push_back(marker); mMarkers.push_back(marker);
} }
newcell.mRefs = cellrefs; newcell.mRefs = std::move(cellrefs);
if (cell.isExterior()) if (cell.isExterior())
mExtCells[std::make_pair(cell.mData.mX, cell.mData.mY)] = newcell; mExtCells[std::make_pair(cell.mData.mX, cell.mData.mY)] = std::move(newcell);
else else
mIntCells[cell.mName] = newcell; mIntCells[cell.mName] = std::move(newcell);
} }
void ConvertCell::writeCell(const Cell& cell, ESM::ESMWriter& esm) void ConvertCell::writeCell(const Cell& cell, ESM::ESMWriter& esm)

View file

@ -34,7 +34,6 @@ Launcher::GraphicsPage::GraphicsPage(QWidget* parent)
connect(standardRadioButton, &QRadioButton::toggled, this, &GraphicsPage::slotStandardToggled); connect(standardRadioButton, &QRadioButton::toggled, this, &GraphicsPage::slotStandardToggled);
connect(screenComboBox, qOverload<int>(&QComboBox::currentIndexChanged), this, &GraphicsPage::screenChanged); connect(screenComboBox, qOverload<int>(&QComboBox::currentIndexChanged), this, &GraphicsPage::screenChanged);
connect(framerateLimitCheckBox, &QCheckBox::toggled, this, &GraphicsPage::slotFramerateLimitToggled); connect(framerateLimitCheckBox, &QCheckBox::toggled, this, &GraphicsPage::slotFramerateLimitToggled);
connect(shadowDistanceCheckBox, &QCheckBox::toggled, this, &GraphicsPage::slotShadowDistLimitToggled);
} }
bool Launcher::GraphicsPage::setupSDL() bool Launcher::GraphicsPage::setupSDL()
@ -126,58 +125,6 @@ bool Launcher::GraphicsPage::loadSettings()
framerateLimitSpinBox->setValue(fpsLimit); framerateLimitSpinBox->setValue(fpsLimit);
} }
// Lighting
int lightingMethod = 1;
switch (Settings::shaders().mLightingMethod)
{
case SceneUtil::LightingMethod::FFP:
lightingMethod = 0;
break;
case SceneUtil::LightingMethod::PerObjectUniform:
lightingMethod = 1;
break;
case SceneUtil::LightingMethod::SingleUBO:
lightingMethod = 2;
break;
}
lightingMethodComboBox->setCurrentIndex(lightingMethod);
// Shadows
if (Settings::shadows().mActorShadows)
actorShadowsCheckBox->setCheckState(Qt::Checked);
if (Settings::shadows().mPlayerShadows)
playerShadowsCheckBox->setCheckState(Qt::Checked);
if (Settings::shadows().mTerrainShadows)
terrainShadowsCheckBox->setCheckState(Qt::Checked);
if (Settings::shadows().mObjectShadows)
objectShadowsCheckBox->setCheckState(Qt::Checked);
if (Settings::shadows().mEnableIndoorShadows)
indoorShadowsCheckBox->setCheckState(Qt::Checked);
const auto& boundMethod = Settings::shadows().mComputeSceneBounds.get();
if (boundMethod == "bounds")
shadowComputeSceneBoundsComboBox->setCurrentIndex(0);
else if (boundMethod == "primitives")
shadowComputeSceneBoundsComboBox->setCurrentIndex(1);
else
shadowComputeSceneBoundsComboBox->setCurrentIndex(2);
const int shadowDistLimit = Settings::shadows().mMaximumShadowMapDistance;
if (shadowDistLimit > 0)
{
shadowDistanceCheckBox->setCheckState(Qt::Checked);
shadowDistanceSpinBox->setValue(shadowDistLimit);
}
const float shadowFadeStart = Settings::shadows().mShadowFadeStart;
if (shadowFadeStart != 0)
fadeStartSpinBox->setValue(shadowFadeStart);
const int shadowRes = Settings::shadows().mShadowMapResolution;
int shadowResIndex = shadowResolutionComboBox->findText(QString::number(shadowRes));
if (shadowResIndex != -1)
shadowResolutionComboBox->setCurrentIndex(shadowResIndex);
return true; return true;
} }
@ -220,53 +167,6 @@ void Launcher::GraphicsPage::saveSettings()
{ {
Settings::video().mFramerateLimit.set(0); Settings::video().mFramerateLimit.set(0);
} }
// Lighting
static constexpr std::array<SceneUtil::LightingMethod, 3> lightingMethodMap = {
SceneUtil::LightingMethod::FFP,
SceneUtil::LightingMethod::PerObjectUniform,
SceneUtil::LightingMethod::SingleUBO,
};
Settings::shaders().mLightingMethod.set(lightingMethodMap[lightingMethodComboBox->currentIndex()]);
// Shadows
const int cShadowDist = shadowDistanceCheckBox->checkState() != Qt::Unchecked ? shadowDistanceSpinBox->value() : 0;
Settings::shadows().mMaximumShadowMapDistance.set(cShadowDist);
const float cFadeStart = fadeStartSpinBox->value();
if (cShadowDist > 0)
Settings::shadows().mShadowFadeStart.set(cFadeStart);
const bool cActorShadows = actorShadowsCheckBox->checkState() != Qt::Unchecked;
const bool cObjectShadows = objectShadowsCheckBox->checkState() != Qt::Unchecked;
const bool cTerrainShadows = terrainShadowsCheckBox->checkState() != Qt::Unchecked;
const bool cPlayerShadows = playerShadowsCheckBox->checkState() != Qt::Unchecked;
if (cActorShadows || cObjectShadows || cTerrainShadows || cPlayerShadows)
{
Settings::shadows().mEnableShadows.set(true);
Settings::shadows().mActorShadows.set(cActorShadows);
Settings::shadows().mPlayerShadows.set(cPlayerShadows);
Settings::shadows().mObjectShadows.set(cObjectShadows);
Settings::shadows().mTerrainShadows.set(cTerrainShadows);
}
else
{
Settings::shadows().mEnableShadows.set(false);
Settings::shadows().mActorShadows.set(false);
Settings::shadows().mPlayerShadows.set(false);
Settings::shadows().mObjectShadows.set(false);
Settings::shadows().mTerrainShadows.set(false);
}
Settings::shadows().mEnableIndoorShadows.set(indoorShadowsCheckBox->checkState() != Qt::Unchecked);
Settings::shadows().mShadowMapResolution.set(shadowResolutionComboBox->currentText().toInt());
auto index = shadowComputeSceneBoundsComboBox->currentIndex();
if (index == 0)
Settings::shadows().mComputeSceneBounds.set("bounds");
else if (index == 1)
Settings::shadows().mComputeSceneBounds.set("primitives");
else
Settings::shadows().mComputeSceneBounds.set("none");
} }
QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen) QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen)
@ -377,9 +277,3 @@ void Launcher::GraphicsPage::slotFramerateLimitToggled(bool checked)
{ {
framerateLimitSpinBox->setEnabled(checked); framerateLimitSpinBox->setEnabled(checked);
} }
void Launcher::GraphicsPage::slotShadowDistLimitToggled(bool checked)
{
shadowDistanceSpinBox->setEnabled(checked);
fadeStartSpinBox->setEnabled(checked);
}

View file

@ -31,7 +31,6 @@ namespace Launcher
void slotFullScreenChanged(int state); void slotFullScreenChanged(int state);
void slotStandardToggled(bool checked); void slotStandardToggled(bool checked);
void slotFramerateLimitToggled(bool checked); void slotFramerateLimitToggled(bool checked);
void slotShadowDistLimitToggled(bool checked);
private: private:
QVector<QStringList> mResolutionsPerScreen; QVector<QStringList> mResolutionsPerScreen;

View file

@ -212,6 +212,65 @@ bool Launcher::SettingsPage::loadSettings()
loadSettingBool(Settings::fog().mExponentialFog, *exponentialFogCheckBox); loadSettingBool(Settings::fog().mExponentialFog, *exponentialFogCheckBox);
loadSettingBool(Settings::fog().mSkyBlending, *skyBlendingCheckBox); loadSettingBool(Settings::fog().mSkyBlending, *skyBlendingCheckBox);
skyBlendingStartComboBox->setValue(Settings::fog().mSkyBlendingStart); skyBlendingStartComboBox->setValue(Settings::fog().mSkyBlendingStart);
loadSettingBool(Settings::shadows().mActorShadows, *actorShadowsCheckBox);
loadSettingBool(Settings::shadows().mPlayerShadows, *playerShadowsCheckBox);
loadSettingBool(Settings::shadows().mTerrainShadows, *terrainShadowsCheckBox);
loadSettingBool(Settings::shadows().mObjectShadows, *objectShadowsCheckBox);
loadSettingBool(Settings::shadows().mEnableIndoorShadows, *indoorShadowsCheckBox);
const auto& boundMethod = Settings::shadows().mComputeSceneBounds.get();
if (boundMethod == "bounds")
shadowComputeSceneBoundsComboBox->setCurrentIndex(0);
else if (boundMethod == "primitives")
shadowComputeSceneBoundsComboBox->setCurrentIndex(1);
else
shadowComputeSceneBoundsComboBox->setCurrentIndex(2);
const int shadowDistLimit = Settings::shadows().mMaximumShadowMapDistance;
if (shadowDistLimit > 0)
{
shadowDistanceCheckBox->setCheckState(Qt::Checked);
shadowDistanceSpinBox->setValue(shadowDistLimit);
shadowDistanceSpinBox->setEnabled(true);
fadeStartSpinBox->setEnabled(true);
}
const float shadowFadeStart = Settings::shadows().mShadowFadeStart;
if (shadowFadeStart != 0)
fadeStartSpinBox->setValue(shadowFadeStart);
const int shadowRes = Settings::shadows().mShadowMapResolution;
int shadowResIndex = shadowResolutionComboBox->findText(QString::number(shadowRes));
if (shadowResIndex != -1)
shadowResolutionComboBox->setCurrentIndex(shadowResIndex);
connect(shadowDistanceCheckBox, &QCheckBox::toggled, this, &SettingsPage::slotShadowDistLimitToggled);
lightsMaxLightsSpinBox->setValue(Settings::shaders().mMaxLights);
lightsMaximumDistanceSpinBox->setValue(Settings::shaders().mMaximumLightDistance);
lightFadeMultiplierSpinBox->setValue(Settings::shaders().mLightFadeStart);
lightsBoundingSphereMultiplierSpinBox->setValue(Settings::shaders().mLightBoundsMultiplier);
lightsMinimumInteriorBrightnessSpinBox->setValue(Settings::shaders().mMinimumInteriorBrightness);
connect(lightingMethodComboBox, qOverload<int>(&QComboBox::currentIndexChanged), this,
&SettingsPage::slotLightTypeCurrentIndexChanged);
int lightingMethod = 1;
switch (Settings::shaders().mLightingMethod)
{
case SceneUtil::LightingMethod::FFP:
lightingMethod = 0;
break;
case SceneUtil::LightingMethod::PerObjectUniform:
lightingMethod = 1;
break;
case SceneUtil::LightingMethod::SingleUBO:
lightingMethod = 2;
break;
}
lightingMethodComboBox->setCurrentIndex(lightingMethod);
slotLightTypeCurrentIndexChanged(lightingMethod);
} }
// Audio // Audio
@ -359,6 +418,58 @@ void Launcher::SettingsPage::saveSettings()
saveSettingBool(*exponentialFogCheckBox, Settings::fog().mExponentialFog); saveSettingBool(*exponentialFogCheckBox, Settings::fog().mExponentialFog);
saveSettingBool(*skyBlendingCheckBox, Settings::fog().mSkyBlending); saveSettingBool(*skyBlendingCheckBox, Settings::fog().mSkyBlending);
Settings::fog().mSkyBlendingStart.set(skyBlendingStartComboBox->value()); Settings::fog().mSkyBlendingStart.set(skyBlendingStartComboBox->value());
static constexpr std::array<SceneUtil::LightingMethod, 3> lightingMethodMap = {
SceneUtil::LightingMethod::FFP,
SceneUtil::LightingMethod::PerObjectUniform,
SceneUtil::LightingMethod::SingleUBO,
};
Settings::shaders().mLightingMethod.set(lightingMethodMap[lightingMethodComboBox->currentIndex()]);
const int cShadowDist
= shadowDistanceCheckBox->checkState() != Qt::Unchecked ? shadowDistanceSpinBox->value() : 0;
Settings::shadows().mMaximumShadowMapDistance.set(cShadowDist);
const float cFadeStart = fadeStartSpinBox->value();
if (cShadowDist > 0)
Settings::shadows().mShadowFadeStart.set(cFadeStart);
const bool cActorShadows = actorShadowsCheckBox->checkState() != Qt::Unchecked;
const bool cObjectShadows = objectShadowsCheckBox->checkState() != Qt::Unchecked;
const bool cTerrainShadows = terrainShadowsCheckBox->checkState() != Qt::Unchecked;
const bool cPlayerShadows = playerShadowsCheckBox->checkState() != Qt::Unchecked;
if (cActorShadows || cObjectShadows || cTerrainShadows || cPlayerShadows)
{
Settings::shadows().mEnableShadows.set(true);
Settings::shadows().mActorShadows.set(cActorShadows);
Settings::shadows().mPlayerShadows.set(cPlayerShadows);
Settings::shadows().mObjectShadows.set(cObjectShadows);
Settings::shadows().mTerrainShadows.set(cTerrainShadows);
}
else
{
Settings::shadows().mEnableShadows.set(false);
Settings::shadows().mActorShadows.set(false);
Settings::shadows().mPlayerShadows.set(false);
Settings::shadows().mObjectShadows.set(false);
Settings::shadows().mTerrainShadows.set(false);
}
Settings::shadows().mEnableIndoorShadows.set(indoorShadowsCheckBox->checkState() != Qt::Unchecked);
Settings::shadows().mShadowMapResolution.set(shadowResolutionComboBox->currentText().toInt());
auto index = shadowComputeSceneBoundsComboBox->currentIndex();
if (index == 0)
Settings::shadows().mComputeSceneBounds.set("bounds");
else if (index == 1)
Settings::shadows().mComputeSceneBounds.set("primitives");
else
Settings::shadows().mComputeSceneBounds.set("none");
Settings::shaders().mMaxLights.set(lightsMaxLightsSpinBox->value());
Settings::shaders().mMaximumLightDistance.set(lightsMaximumDistanceSpinBox->value());
Settings::shaders().mLightFadeStart.set(lightFadeMultiplierSpinBox->value());
Settings::shaders().mLightBoundsMultiplier.set(lightsBoundingSphereMultiplierSpinBox->value());
Settings::shaders().mMinimumInteriorBrightness.set(lightsMinimumInteriorBrightnessSpinBox->value());
} }
// Audio // Audio
@ -461,3 +572,17 @@ void Launcher::SettingsPage::slotSkyBlendingToggled(bool checked)
skyBlendingStartComboBox->setEnabled(checked); skyBlendingStartComboBox->setEnabled(checked);
skyBlendingStartLabel->setEnabled(checked); skyBlendingStartLabel->setEnabled(checked);
} }
void Launcher::SettingsPage::slotShadowDistLimitToggled(bool checked)
{
shadowDistanceSpinBox->setEnabled(checked);
fadeStartSpinBox->setEnabled(checked);
}
void Launcher::SettingsPage::slotLightTypeCurrentIndexChanged(int index)
{
lightsMaximumDistanceSpinBox->setEnabled(index != 0);
lightsMaxLightsSpinBox->setEnabled(index != 0);
lightsBoundingSphereMultiplierSpinBox->setEnabled(index != 0);
lightsMinimumInteriorBrightnessSpinBox->setEnabled(index != 0);
}

View file

@ -32,6 +32,8 @@ namespace Launcher
void slotAnimSourcesToggled(bool checked); void slotAnimSourcesToggled(bool checked);
void slotPostProcessToggled(bool checked); void slotPostProcessToggled(bool checked);
void slotSkyBlendingToggled(bool checked); void slotSkyBlendingToggled(bool checked);
void slotShadowDistLimitToggled(bool checked);
void slotLightTypeCurrentIndexChanged(int index);
private: private:
Config::GameSettings& mGameSettings; Config::GameSettings& mGameSettings;

View file

@ -11,459 +11,229 @@
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item alignment="Qt::AlignTop"> <item>
<widget class="QTabWidget" name="DisplayTabWidget"> <widget class="QGroupBox" name="groupBox">
<property name="currentIndex"> <layout class="QHBoxLayout" name="horizontalLayout">
<number>0</number> <item>
</property> <layout class="QGridLayout" name="gridLayout_4" columnstretch="1,1">
<widget class="QWidget" name="DisplayWrapper"> <item row="5" column="0">
<attribute name="title"> <widget class="QLabel" name="screenLabel">
<string>Display</string> <property name="text">
</attribute> <string>Screen</string>
<layout class="QHBoxLayout" name="horizontalLayout_2"> </property>
<item> </widget>
<layout class="QGridLayout" name="gridLayout_4" columnstretch="1,1"> </item>
<item row="5" column="0"> <item row="3" column="0">
<widget class="QLabel" name="screenLabel"> <widget class="QLabel" name="windowModeLabel">
<property name="text">
<string>Window mode</string>
</property>
</widget>
</item>
<item row="6" column="1">
<layout class="QGridLayout" name="resolutionLayout">
<item row="1" column="2">
<layout class="QHBoxLayout" name="customResolutionLayout" stretch="1,0,1">
<item>
<widget class="QSpinBox" name="customWidthSpinBox">
<property name="minimum">
<number>800</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="multiplyLabel">
<property name="text">
<string> × </string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="customHeightSpinBox">
<property name="minimum">
<number>600</number>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="1">
<widget class="QRadioButton" name="customRadioButton">
<property name="text">
<string>Custom:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QRadioButton" name="standardRadioButton">
<property name="text">
<string>Standard:</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QComboBox" name="resolutionComboBox"/>
</item>
</layout>
</item>
<item row="4" column="1">
<widget class="QComboBox" name="antiAliasingComboBox">
<item>
<property name="text"> <property name="text">
<string>Screen:</string> <string>0</string>
</property> </property>
</widget> </item>
</item> <item>
<item row="4" column="1">
<widget class="QComboBox" name="antiAliasingComboBox">
<item>
<property name="text">
<string>0</string>
</property>
</item>
<item>
<property name="text">
<string>2</string>
</property>
</item>
<item>
<property name="text">
<string>4</string>
</property>
</item>
<item>
<property name="text">
<string>8</string>
</property>
</item>
<item>
<property name="text">
<string>16</string>
</property>
</item>
</widget>
</item>
<item row="5" column="1">
<widget class="QComboBox" name="screenComboBox"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="windowModeLabel">
<property name="text"> <property name="text">
<string>Window Mode:</string> <string>2</string>
</property> </property>
</widget> </item>
</item> <item>
<item row="6" column="0">
<widget class="QLabel" name="resolutionLabel">
<property name="text"> <property name="text">
<string>Resolution:</string> <string>4</string>
</property> </property>
<property name="alignment"> </item>
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> <item>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="vSyncLabel">
<property name="text"> <property name="text">
<string>Vertical Sync:</string> <string>8</string>
</property> </property>
</widget> </item>
</item> <item>
<item row="4" column="0">
<widget class="QLabel" name="antiAliasingLabel">
<property name="text"> <property name="text">
<string>Anti-aliasing:</string> <string>16</string>
</property> </property>
</widget> </item>
</item> </widget>
<item row="6" column="1"> </item>
<layout class="QGridLayout" name="resolutionLayout"> <item row="1" column="0">
<item row="1" column="2"> <widget class="QCheckBox" name="framerateLimitCheckBox">
<layout class="QHBoxLayout" name="customResolutionLayout" stretch="1,0,1"> <property name="text">
<item> <string>Framerate limit</string>
<widget class="QSpinBox" name="customWidthSpinBox"> </property>
<property name="minimum"> </widget>
<number>800</number> </item>
</property> <item row="0" column="0">
</widget> <widget class="QCheckBox" name="windowBorderCheckBox">
</item> <property name="text">
<item> <string>Window border</string>
<widget class="QLabel" name="multiplyLabel"> </property>
<property name="text"> </widget>
<string> × </string> </item>
</property> <item row="5" column="1">
</widget> <widget class="QComboBox" name="screenComboBox"/>
</item> </item>
<item> <item row="2" column="1">
<widget class="QSpinBox" name="customHeightSpinBox"> <widget class="QComboBox" name="vSyncComboBox">
<property name="minimum"> <property name="currentIndex">
<number>600</number> <number>0</number>
</property> </property>
</widget> <item>
</item>
</layout>
</item>
<item row="1" column="1">
<widget class="QRadioButton" name="customRadioButton">
<property name="text">
<string>Custom:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QRadioButton" name="standardRadioButton">
<property name="text">
<string>Standard:</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QComboBox" name="resolutionComboBox"/>
</item>
</layout>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="windowModeComboBox">
<property name="currentIndex">
<number>0</number>
</property>
<item>
<property name="text">
<string>Fullscreen</string>
</property>
</item>
<item>
<property name="text">
<string>Windowed Fullscreen</string>
</property>
</item>
<item>
<property name="text">
<string>Windowed</string>
</property>
</item>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="windowBorderCheckBox">
<property name="text"> <property name="text">
<string>Window Border</string> <string>Disabled</string>
</property> </property>
</widget> </item>
</item> <item>
<item row="2" column="1">
<widget class="QComboBox" name="vSyncComboBox">
<property name="currentIndex">
<number>0</number>
</property>
<item>
<property name="text">
<string>Disabled</string>
</property>
</item>
<item>
<property name="text">
<string>Enabled</string>
</property>
</item>
<item>
<property name="text">
<string>Adaptive</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="framerateLimitCheckBox">
<property name="text"> <property name="text">
<string>Framerate Limit:</string> <string>Enabled</string>
</property> </property>
</widget> </item>
</item> <item>
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="framerateLimitSpinBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="suffix">
<string> FPS</string>
</property>
<property name="decimals">
<number>1</number>
</property>
<property name="minimum">
<double>1.000000000000000</double>
</property>
<property name="maximum">
<double>1000.000000000000000</double>
</property>
<property name="singleStep">
<double>15.000000000000000</double>
</property>
<property name="value">
<double>300.000000000000000</double>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="LightingWrapper">
<attribute name="title">
<string>Lighting</string>
</attribute>
<layout class="QVBoxLayout" name="lightingLayout">
<item>
<layout class="QHBoxLayout" name="lightingMethodLayout">
<item>
<widget class="QLabel" name="lightingMethodLabel">
<property name="text"> <property name="text">
<string>Lighting Method:</string> <string>Adaptive</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="lightingMethodComboBox">
<item>
<property name="text">
<string>legacy</string>
</property>
</item>
<item>
<property name="text">
<string>shaders compatibility</string>
</property>
</item>
<item>
<property name="text">
<string>shaders</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<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>
</widget>
<widget class="QWidget" name="ShadowWrapper">
<attribute name="title">
<string>Shadows</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QGridLayout" name="shadowsLayout" columnstretch="0,0">
<item row="0" column="0">
<widget class="QCheckBox" name="playerShadowsCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable shadows exclusively for the player character. May have a very minor performance impact.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
</item>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="windowModeComboBox">
<property name="currentIndex">
<number>0</number>
</property>
<item>
<property name="text"> <property name="text">
<string>Enable Player Shadows</string> <string>Fullscreen</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="actorShadowsCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable shadows for NPCs and creatures besides the player character. May have a minor performance impact.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
</item>
<item>
<property name="text"> <property name="text">
<string>Enable Actor Shadows</string> <string>Windowed Fullscreen</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="objectShadowsCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable shadows for primarily inanimate objects. May have a significant performance impact.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
</item>
<item>
<property name="text"> <property name="text">
<string>Enable Object Shadows</string> <string>Windowed</string>
</property> </property>
</widget> </item>
</item> </widget>
<item row="3" column="0"> </item>
<widget class="QCheckBox" name="terrainShadowsCheckBox"> <item row="6" column="0">
<property name="toolTip"> <widget class="QLabel" name="resolutionLabel">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable shadows for the terrain including distant terrain. May have a significant performance and shadow quality impact.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <property name="text">
</property> <string>Resolution</string>
<property name="text"> </property>
<string>Enable Terrain Shadows</string> <property name="alignment">
</property> <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</widget> </property>
</item> </widget>
<item row="4" column="0"> </item>
<widget class="QCheckBox" name="indoorShadowsCheckBox"> <item row="1" column="1">
<property name="toolTip"> <widget class="QDoubleSpinBox" name="framerateLimitSpinBox">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting.&lt;/p&gt;&lt;p&gt;Has no effect if actor/player shadows are not enabled.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <property name="enabled">
</property> <bool>false</bool>
<property name="text"> </property>
<string>Enable Indoor Shadows</string> <property name="suffix">
</property> <string> FPS</string>
</widget> </property>
</item> <property name="decimals">
<item row="5" column="0"> <number>1</number>
<widget class="QLabel" name="shadowComputeSceneBoundsLabel"> </property>
<property name="toolTip"> <property name="minimum">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Type of &quot;compute scene bounds&quot; computation method to be used. Bounds (default) for good balance between performance and shadow quality, primitives for better looking shadows or none for no computation.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <double>1.000000000000000</double>
</property> </property>
<property name="text"> <property name="maximum">
<string>Shadow Near Far Computation Method:</string> <double>1000.000000000000000</double>
</property> </property>
</widget> <property name="singleStep">
</item> <double>15.000000000000000</double>
<item row="5" column="1"> </property>
<widget class="QComboBox" name="shadowComputeSceneBoundsComboBox"> <property name="value">
<item> <double>300.000000000000000</double>
<property name="text"> </property>
<string>bounds</string> </widget>
</property> </item>
</item> <item row="4" column="0">
<item> <widget class="QLabel" name="antiAliasingLabel">
<property name="text"> <property name="text">
<string>primitives</string> <string>Anti-aliasing</string>
</property> </property>
</item> </widget>
<item> </item>
<property name="text"> <item row="2" column="0">
<string>none</string> <widget class="QLabel" name="vSyncLabel">
</property> <property name="text">
</item> <string>Vertical synchronization</string>
</widget> </property>
</item> </widget>
<item row="6" column="0"> </item>
<widget class="QLabel" name="shadowResolutionLabel"> <item row="7" column="0">
<property name="toolTip"> <spacer name="verticalSpacer">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The resolution of each individual shadow map. Increasing it significantly improves shadow quality but may have a minor performance impact.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <property name="orientation">
</property> <enum>Qt::Vertical</enum>
<property name="text"> </property>
<string>Shadow Map Resolution:</string> <property name="sizeHint" stdset="0">
</property> <size>
</widget> <width>20</width>
</item> <height>40</height>
<item row="6" column="1"> </size>
<widget class="QComboBox" name="shadowResolutionComboBox"> </property>
<item> </spacer>
<property name="text"> </item>
<string>512</string> </layout>
</property> </item>
</item> </layout>
<item>
<property name="text">
<string>1024</string>
</property>
</item>
<item>
<property name="text">
<string>2048</string>
</property>
</item>
<item>
<property name="text">
<string>4096</string>
</property>
</item>
</widget>
</item>
<item row="7" column="0">
<widget class="QCheckBox" name="shadowDistanceCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The distance from the camera at which shadows completely disappear.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Shadow Distance Limit:</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QSpinBox" name="shadowDistanceSpinBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;64 game units is 1 real life yard or about 0.9 m&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="suffix">
<string> unit(s)</string>
</property>
<property name="minimum">
<number>512</number>
</property>
<property name="maximum">
<number>81920</number>
</property>
<property name="value">
<number>8192</number>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="fadeStartLabel">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The fraction of the limit above at which shadows begin to gradually fade away.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Fade Start Multiplier:</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QDoubleSpinBox" name="fadeStartSpinBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="decimals">
<number>2</number>
</property>
<property name="minimum">
<double>0.000000000000000</double>
</property>
<property name="maximum">
<double>1.000000000000000</double>
</property>
<property name="value">
<double>0.900000000000000</double>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget> </widget>
</item> </item>
</layout> </layout>

View file

@ -6,7 +6,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>514</width> <width>515</width>
<height>397</height> <height>397</height>
</rect> </rect>
</property> </property>
@ -129,16 +129,22 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov
</property> </property>
</widget> </widget>
</item> </item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</spacer>
</item>
</layout> </layout>
</widget> </widget>
<resources/> <resources/>

View file

@ -7,20 +7,20 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>720</width> <width>720</width>
<height>565</height> <height>635</height>
</rect> </rect>
</property> </property>
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>720</width> <width>720</width>
<height>565</height> <height>635</height>
</size> </size>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>OpenMW Launcher</string> <string>OpenMW Launcher</string>
</property> </property>
<property name="windowIcon"> <property name="windowIcon">
<iconset resource="../launcher/launcher.qrc"> <iconset resource="../../../files/launcher/launcher.qrc">
<normaloff>:/images/openmw.png</normaloff>:/images/openmw.png</iconset> <normaloff>:/images/openmw.png</normaloff>:/images/openmw.png</iconset>
</property> </property>
<widget class="QWidget" name="centralwidget"> <widget class="QWidget" name="centralwidget">
@ -120,7 +120,7 @@ QToolButton {
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="icon"> <property name="icon">
<iconset resource="../launcher/launcher.qrc"> <iconset resource="../../../files/launcher/launcher.qrc">
<normaloff>:/images/openmw-plugin.png</normaloff>:/images/openmw-plugin.png</iconset> <normaloff>:/images/openmw-plugin.png</normaloff>:/images/openmw-plugin.png</iconset>
</property> </property>
<property name="text"> <property name="text">
@ -141,11 +141,11 @@ QToolButton {
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="icon"> <property name="icon">
<iconset resource="../launcher/launcher.qrc"> <iconset resource="../../../files/launcher/launcher.qrc">
<normaloff>:/images/preferences-video.png</normaloff>:/images/preferences-video.png</iconset> <normaloff>:/images/preferences-video.png</normaloff>:/images/preferences-video.png</iconset>
</property> </property>
<property name="text"> <property name="text">
<string>Graphics</string> <string>Display</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Allows to change graphics settings</string> <string>Allows to change graphics settings</string>
@ -156,7 +156,7 @@ QToolButton {
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="icon"> <property name="icon">
<iconset resource="../launcher/launcher.qrc"> <iconset resource="../../../files/launcher/launcher.qrc">
<normaloff>:/images/preferences.png</normaloff>:/images/preferences.png</iconset> <normaloff>:/images/preferences.png</normaloff>:/images/preferences.png</iconset>
</property> </property>
<property name="text"> <property name="text">
@ -171,7 +171,7 @@ QToolButton {
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="icon"> <property name="icon">
<iconset resource="../launcher/launcher.qrc"> <iconset resource="../../../files/launcher/launcher.qrc">
<normaloff>:/images/preferences-advanced.png</normaloff>:/images/preferences-advanced.png</iconset> <normaloff>:/images/preferences-advanced.png</normaloff>:/images/preferences-advanced.png</iconset>
</property> </property>
<property name="text"> <property name="text">
@ -183,7 +183,7 @@ QToolButton {
</action> </action>
</widget> </widget>
<resources> <resources>
<include location="../launcher/launcher.qrc"/> <include location="../../../files/launcher/launcher.qrc"/>
</resources> </resources>
<connections/> <connections/>
</ui> </ui>

File diff suppressed because it is too large Load diff

View file

@ -221,7 +221,7 @@ namespace NavMeshTool
constexpr double expiryDelay = 0; constexpr double expiryDelay = 0;
Resource::ImageManager imageManager(&vfs, expiryDelay); Resource::ImageManager imageManager(&vfs, expiryDelay);
Resource::NifFileManager nifFileManager(&vfs); Resource::NifFileManager nifFileManager(&vfs, &encoder.getStatelessEncoder());
Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, expiryDelay); Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, expiryDelay);
Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay); Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay);
DetourNavigator::RecastGlobalAllocator::init(); DetourNavigator::RecastGlobalAllocator::init();

View file

@ -17,6 +17,7 @@
#include <components/vfs/bsaarchive.hpp> #include <components/vfs/bsaarchive.hpp>
#include <components/vfs/filesystemarchive.hpp> #include <components/vfs/filesystemarchive.hpp>
#include <components/vfs/manager.hpp> #include <components/vfs/manager.hpp>
#include <components/vfs/recursivedirectoryiterator.hpp>
#include <boost/program_options.hpp> #include <boost/program_options.hpp>
@ -84,7 +85,7 @@ void readNIF(
try try
{ {
Nif::NIFFile file(fullPath); Nif::NIFFile file(fullPath);
Nif::Reader reader(file); Nif::Reader reader(file, nullptr);
if (vfs != nullptr) if (vfs != nullptr)
reader.parse(vfs->get(pathStr)); reader.parse(vfs->get(pathStr));
else else

View file

@ -624,7 +624,7 @@ bool CSMFilter::Parser::parse(const std::string& filter, bool allowPredefined)
} }
if (node) if (node)
mFilter = node; mFilter = std::move(node);
else else
{ {
// Empty filter string equals to filter "true". // Empty filter string equals to filter "true".

View file

@ -171,7 +171,7 @@ void CSMTools::ReportModel::flagAsReplaced(int index)
hint[0] = 'r'; hint[0] = 'r';
line.mHint = hint; line.mHint = std::move(hint);
emit dataChanged(this->index(index, 0), this->index(index, columnCount())); emit dataChanged(this->index(index, 0), this->index(index, columnCount()));
} }

View file

@ -468,13 +468,13 @@ namespace CSMWorld
if (type == UniversalId::Type_Creature) if (type == UniversalId::Type_Creature)
{ {
// Valid creature record // Valid creature record
setupCreature(id, data); setupCreature(id, std::move(data));
emit actorChanged(id); emit actorChanged(id);
} }
else if (type == UniversalId::Type_Npc) else if (type == UniversalId::Type_Npc)
{ {
// Valid npc record // Valid npc record
setupNpc(id, data); setupNpc(id, std::move(data));
emit actorChanged(id); emit actorChanged(id);
} }
else else
@ -665,7 +665,7 @@ namespace CSMWorld
RaceDataPtr data = mCachedRaces.get(race); RaceDataPtr data = mCachedRaces.get(race);
if (data) if (data)
{ {
setupRace(race, data); setupRace(race, std::move(data));
// Race was changed. Need to mark actor dependencies as dirty. // Race was changed. Need to mark actor dependencies as dirty.
// Cannot use markDirtyDependency because that would invalidate // Cannot use markDirtyDependency because that would invalidate
// the current iterator. // the current iterator.
@ -683,7 +683,7 @@ namespace CSMWorld
ActorDataPtr data = mCachedActors.get(actor); ActorDataPtr data = mCachedActors.get(actor);
if (data) if (data)
{ {
setupActor(actor, data); setupActor(actor, std::move(data));
} }
} }
mDirtyActors.clear(); mDirtyActors.clear();

View file

@ -504,7 +504,7 @@ namespace CSMWorld
auto record2 = std::make_unique<Record<ESXRecordT>>(); auto record2 = std::make_unique<Record<ESXRecordT>>();
record2->mState = Record<ESXRecordT>::State_ModifiedOnly; record2->mState = Record<ESXRecordT>::State_ModifiedOnly;
record2->mModified = record; record2->mModified = std::move(record);
insertRecord(std::move(record2), getAppendIndex(id, type), type); insertRecord(std::move(record2), getAppendIndex(id, type), type);
} }

View file

@ -36,7 +36,7 @@ CSMWorld::TouchCommand::TouchCommand(IdTable& table, const std::string& id, QUnd
void CSMWorld::TouchCommand::redo() void CSMWorld::TouchCommand::redo()
{ {
mOld.reset(mTable.getRecord(mId).clone().get()); mOld = mTable.getRecord(mId).clone();
mChanged = mTable.touchRecord(mId); mChanged = mTable.touchRecord(mId);
} }
@ -181,9 +181,8 @@ const std::string& CSMWorld::TouchLandCommand::getDestinationId() const
void CSMWorld::TouchLandCommand::onRedo() void CSMWorld::TouchLandCommand::onRedo()
{ {
mOld = mLands.getRecord(mId).clone();
mChanged = mLands.touchRecord(mId); mChanged = mLands.touchRecord(mId);
if (mChanged)
mOld.reset(mLands.getRecord(mId).clone().get());
} }
void CSMWorld::TouchLandCommand::onUndo() void CSMWorld::TouchLandCommand::onUndo()

View file

@ -149,7 +149,8 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data
mResourcesManager.setVFS(mVFS.get()); mResourcesManager.setVFS(mVFS.get());
constexpr double expiryDelay = 0; constexpr double expiryDelay = 0;
mResourceSystem = std::make_unique<Resource::ResourceSystem>(mVFS.get(), expiryDelay); mResourceSystem
= std::make_unique<Resource::ResourceSystem>(mVFS.get(), expiryDelay, &mEncoder.getStatelessEncoder());
Shader::ShaderManager::DefineMap defines Shader::ShaderManager::DefineMap defines
= mResourceSystem->getSceneManager()->getShaderManager().getGlobalDefines(); = mResourceSystem->getSceneManager()->getShaderManager().getGlobalDefines();
@ -1107,7 +1108,7 @@ void CSMWorld::Data::loadFallbackEntries()
newMarker.mModel = model; newMarker.mModel = model;
newMarker.mRecordFlags = 0; newMarker.mRecordFlags = 0;
auto record = std::make_unique<CSMWorld::Record<ESM::Static>>(); auto record = std::make_unique<CSMWorld::Record<ESM::Static>>();
record->mBase = newMarker; record->mBase = std::move(newMarker);
record->mState = CSMWorld::RecordBase::State_BaseOnly; record->mState = CSMWorld::RecordBase::State_BaseOnly;
mReferenceables.appendRecord(std::move(record), CSMWorld::UniversalId::Type_Static); mReferenceables.appendRecord(std::move(record), CSMWorld::UniversalId::Type_Static);
} }
@ -1123,7 +1124,7 @@ void CSMWorld::Data::loadFallbackEntries()
newMarker.mModel = model; newMarker.mModel = model;
newMarker.mRecordFlags = 0; newMarker.mRecordFlags = 0;
auto record = std::make_unique<CSMWorld::Record<ESM::Door>>(); auto record = std::make_unique<CSMWorld::Record<ESM::Door>>();
record->mBase = newMarker; record->mBase = std::move(newMarker);
record->mState = CSMWorld::RecordBase::State_BaseOnly; record->mState = CSMWorld::RecordBase::State_BaseOnly;
mReferenceables.appendRecord(std::move(record), CSMWorld::UniversalId::Type_Door); mReferenceables.appendRecord(std::move(record), CSMWorld::UniversalId::Type_Door);
} }

View file

@ -117,7 +117,7 @@ void CSMWorld::IdCompletionManager::generateCompleters(CSMWorld::Data& data)
completer->setPopup(popup); // The completer takes ownership of the popup completer->setPopup(popup); // The completer takes ownership of the popup
completer->setMaxVisibleItems(10); completer->setMaxVisibleItems(10);
mCompleters[current->first] = completer; mCompleters[current->first] = std::move(completer);
} }
} }
} }

View file

@ -11,6 +11,7 @@
#include <components/misc/strings/lower.hpp> #include <components/misc/strings/lower.hpp>
#include <components/vfs/manager.hpp> #include <components/vfs/manager.hpp>
#include <components/vfs/recursivedirectoryiterator.hpp>
CSMWorld::Resources::Resources( CSMWorld::Resources::Resources(
const VFS::Manager* vfs, const std::string& baseDirectory, UniversalId::Type type, const char* const* extensions) const VFS::Manager* vfs, const std::string& baseDirectory, UniversalId::Type type, const char* const* extensions)

View file

@ -91,7 +91,7 @@ void CSVDoc::AdjusterWidget::setName(const QString& name, bool addon)
{ {
// path already points to the local data directory // path already points to the local data directory
message = "Will be saved as: " + Files::pathToQString(path); message = "Will be saved as: " + Files::pathToQString(path);
mResultPath = path; mResultPath = std::move(path);
} }
// in all other cases, ensure the path points to data-local and do an existing file check // in all other cases, ensure the path points to data-local and do an existing file check
else else

View file

@ -410,7 +410,7 @@ bool CSVDoc::ViewManager::removeDocument(CSVDoc::View* view)
remainingViews.push_back(*iter); remainingViews.push_back(*iter);
} }
mDocumentManager.removeDocument(document); mDocumentManager.removeDocument(document);
mViews = remainingViews; mViews = std::move(remainingViews);
} }
return true; return true;
} }

View file

@ -514,7 +514,7 @@ void CSVRender::PagedWorldspaceWidget::moveCellSelection(int x, int y)
addCellToScene(*iter); addCellToScene(*iter);
} }
mSelection = newSelection; mSelection = std::move(newSelection);
} }
void CSVRender::PagedWorldspaceWidget::addCellToSceneFromCamera(int offsetX, int offsetY) void CSVRender::PagedWorldspaceWidget::addCellToSceneFromCamera(int offsetX, int offsetY)

View file

@ -541,7 +541,7 @@ void CSVRender::TerrainTextureMode::editTerrainTextureGrid(const WorldspaceHitRe
= landTable.data(landTable.getModelIndex(cellId, textureColumn)) = landTable.data(landTable.getModelIndex(cellId, textureColumn))
.value<CSMWorld::LandTexturesColumn::DataType>(); .value<CSMWorld::LandTexturesColumn::DataType>();
newTerrainOtherCell[yInOtherCell * landTextureSize + xInOtherCell] = brushInt; newTerrainOtherCell[yInOtherCell * landTextureSize + xInOtherCell] = brushInt;
pushEditToCommand(newTerrainOtherCell, document, landTable, cellId); pushEditToCommand(newTerrainOtherCell, document, landTable, std::move(cellId));
} }
} }
} }
@ -702,7 +702,7 @@ void CSVRender::TerrainTextureMode::createTexture(const std::string& textureFile
QModelIndex index(ltexTable.getModelIndex(newId, ltexTable.findColumnIndex(CSMWorld::Columns::ColumnId_Texture))); QModelIndex index(ltexTable.getModelIndex(newId, ltexTable.findColumnIndex(CSMWorld::Columns::ColumnId_Texture)));
undoStack.push(new CSMWorld::ModifyCommand(ltexTable, index, textureFileNameVariant)); undoStack.push(new CSMWorld::ModifyCommand(ltexTable, index, textureFileNameVariant));
undoStack.endMacro(); undoStack.endMacro();
mBrushTexture = newId; mBrushTexture = std::move(newId);
} }
bool CSVRender::TerrainTextureMode::allowLandTextureEditing(const std::string& cellId) bool CSVRender::TerrainTextureMode::allowLandTextureEditing(const std::string& cellId)

View file

@ -184,11 +184,11 @@ void CSVRender::WorldspaceWidget::selectDefaultNavigationMode()
void CSVRender::WorldspaceWidget::centerOrbitCameraOnSelection() void CSVRender::WorldspaceWidget::centerOrbitCameraOnSelection()
{ {
std::vector<osg::ref_ptr<TagBase>> selection = getSelection(~0u); std::vector<osg::ref_ptr<TagBase>> selection = getSelection(Mask_Reference);
for (std::vector<osg::ref_ptr<TagBase>>::iterator it = selection.begin(); it != selection.end(); ++it) for (std::vector<osg::ref_ptr<TagBase>>::iterator it = selection.begin(); it != selection.end(); ++it)
{ {
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(it->get())) if (CSVRender::ObjectTag* objectTag = static_cast<CSVRender::ObjectTag*>(it->get()))
{ {
mOrbitCamControl->setCenter(objectTag->mObject->getPosition().asVec3()); mOrbitCamControl->setCenter(objectTag->mObject->getPosition().asVec3());
} }
@ -440,7 +440,7 @@ CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick(
osg::Node* node = *nodeIter; osg::Node* node = *nodeIter;
if (osg::ref_ptr<CSVRender::TagBase> tag = dynamic_cast<CSVRender::TagBase*>(node->getUserData())) if (osg::ref_ptr<CSVRender::TagBase> tag = dynamic_cast<CSVRender::TagBase*>(node->getUserData()))
{ {
WorldspaceHitResult hit = { true, tag, 0, 0, 0, intersection.getWorldIntersectPoint() }; WorldspaceHitResult hit = { true, std::move(tag), 0, 0, 0, intersection.getWorldIntersectPoint() };
if (intersection.indexList.size() >= 3) if (intersection.indexList.size() >= 3)
{ {
hit.index0 = intersection.indexList[0]; hit.index0 = intersection.indexList[0];
@ -757,13 +757,14 @@ void CSVRender::WorldspaceWidget::toggleHiddenInstances()
if (selection.empty()) if (selection.empty())
return; return;
const CSVRender::ObjectTag* firstSelection = dynamic_cast<CSVRender::ObjectTag*>(selection.begin()->get()); const CSVRender::ObjectTag* firstSelection = static_cast<CSVRender::ObjectTag*>(selection.begin()->get());
assert(firstSelection != nullptr);
const CSVRender::Mask firstMask const CSVRender::Mask firstMask
= firstSelection->mObject->getRootNode()->getNodeMask() == Mask_Hidden ? Mask_Reference : Mask_Hidden; = firstSelection->mObject->getRootNode()->getNodeMask() == Mask_Hidden ? Mask_Reference : Mask_Hidden;
for (const auto& object : selection) for (const auto& object : selection)
if (const auto objectTag = dynamic_cast<CSVRender::ObjectTag*>(object.get())) if (const auto objectTag = static_cast<CSVRender::ObjectTag*>(object.get()))
objectTag->mObject->getRootNode()->setNodeMask(firstMask); objectTag->mObject->getRootNode()->setNodeMask(firstMask);
} }

View file

@ -213,7 +213,7 @@ void CSVWidget::TextureBrushWindow::setBrushTexture(std::string brushTexture)
mSelectedBrush->setText(QString::fromStdString(mBrushTextureLabel)); mSelectedBrush->setText(QString::fromStdString(mBrushTextureLabel));
} }
mBrushTexture = newBrushTextureId; mBrushTexture = std::move(newBrushTextureId);
emit passTextureId(mBrushTexture); emit passTextureId(mBrushTexture);
emit passBrushShape(mBrushShape); // updates the icon tooltip emit passBrushShape(mBrushShape); // updates the icon tooltip

View file

@ -146,7 +146,7 @@ void CSVWorld::ExtendedCommandConfigurator::setupCheckBoxes(const std::vector<CS
CSMWorld::UniversalId type = types[counter]; CSMWorld::UniversalId type = types[counter];
current->first->setText(QString::fromUtf8(type.getTypeName().c_str())); current->first->setText(QString::fromUtf8(type.getTypeName().c_str()));
current->first->setChecked(true); current->first->setChecked(true);
current->second = type; current->second = std::move(type);
++counter; ++counter;
} }
else else

View file

@ -169,7 +169,7 @@ void CSVWorld::TableSubView::createFilterRequest(std::vector<CSMWorld::Universal
{ {
CSVFilter::FilterData filterData; CSVFilter::FilterData filterData;
filterData.searchData = it->getId(); filterData.searchData = it->getId();
filterData.columns = col; filterData.columns = std::move(col);
sourceFilter.emplace_back(filterData); sourceFilter.emplace_back(filterData);
} }
@ -195,7 +195,7 @@ void CSVWorld::TableSubView::createFilterRequest(std::vector<CSMWorld::Universal
CSVFilter::FilterData filterData; CSVFilter::FilterData filterData;
filterData.searchData = qData; filterData.searchData = qData;
filterData.columns = searchColumns; filterData.columns = std::move(searchColumns);
sourceFilterByValue.emplace_back(filterData); sourceFilterByValue.emplace_back(filterData);

View file

@ -321,11 +321,7 @@ bool OMW::Engine::frame(float frametime)
// update GUI by world data // update GUI by world data
{ {
ScopedProfile<UserStatsType::WindowManager> profile(frameStart, frameNumber, *timer, *stats); ScopedProfile<UserStatsType::WindowManager> profile(frameStart, frameNumber, *timer, *stats);
mWorld->updateWindowManager();
if (mStateManager->getState() != MWBase::StateManager::State_NoGame)
{
mWorld->updateWindowManager();
}
} }
mLuaWorker->allowUpdate(); // if there is a separate Lua thread, it starts the update now mLuaWorker->allowUpdate(); // if there is a separate Lua thread, it starts the update now
@ -706,7 +702,8 @@ void OMW::Engine::prepareEngine()
VFS::registerArchives(mVFS.get(), mFileCollections, mArchives, true); VFS::registerArchives(mVFS.get(), mFileCollections, mArchives, true);
mResourceSystem = std::make_unique<Resource::ResourceSystem>(mVFS.get(), Settings::cells().mCacheExpiryDelay); mResourceSystem = std::make_unique<Resource::ResourceSystem>(
mVFS.get(), Settings::cells().mCacheExpiryDelay, &mEncoder.get()->getStatelessEncoder());
mResourceSystem->getSceneManager()->getShaderManager().setMaxTextureUnits(mGlMaxTextureImageUnits); mResourceSystem->getSceneManager()->getShaderManager().setMaxTextureUnits(mGlMaxTextureImageUnits);
mResourceSystem->getSceneManager()->setUnRefImageDataAfterApply( mResourceSystem->getSceneManager()->setUnRefImageDataAfterApply(
false); // keep to Off for now to allow better state sharing false); // keep to Off for now to allow better state sharing
@ -826,7 +823,7 @@ void OMW::Engine::prepareEngine()
} }
listener->loadingOff(); listener->loadingOff();
mWorld->init(mViewer, rootNode, mWorkQueue.get(), *mUnrefQueue); mWorld->init(mViewer, std::move(rootNode), mWorkQueue.get(), *mUnrefQueue);
mEnvironment.setWorldScene(mWorld->getWorldScene()); mEnvironment.setWorldScene(mWorld->getWorldScene());
mWorld->setupPlayer(); mWorld->setupPlayer();
mWorld->setRandomSeed(mRandomSeed); mWorld->setRandomSeed(mRandomSeed);

View file

@ -187,6 +187,8 @@ namespace MWBase
virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) = 0; virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) = 0;
virtual bool checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const = 0;
/// Save the current animation state of managed references to their RefData. /// Save the current animation state of managed references to their RefData.
virtual void persistAnimationStates() = 0; virtual void persistAnimationStates() = 0;

View file

@ -37,23 +37,6 @@ namespace MWClass
return true; return true;
} }
void Actor::block(const MWWorld::Ptr& ptr) const
{
const MWWorld::InventoryStore& inv = getInventoryStore(ptr);
MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
if (shield == inv.end())
return;
MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager();
const ESM::RefId skill = shield->getClass().getEquipmentSkill(*shield);
if (skill == ESM::Skill::LightArmor)
sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Light Armor Hit"), 1.0f, 1.0f);
else if (skill == ESM::Skill::MediumArmor)
sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Medium Armor Hit"), 1.0f, 1.0f);
else if (skill == ESM::Skill::HeavyArmor)
sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Heavy Armor Hit"), 1.0f, 1.0f);
}
osg::Vec3f Actor::getRotationVector(const MWWorld::Ptr& ptr) const osg::Vec3f Actor::getRotationVector(const MWWorld::Ptr& ptr) const
{ {
MWMechanics::Movement& movement = getMovementSettings(ptr); MWMechanics::Movement& movement = getMovementSettings(ptr);

View file

@ -45,8 +45,6 @@ namespace MWClass
bool useAnim() const override; bool useAnim() const override;
void block(const MWWorld::Ptr& ptr) const override;
osg::Vec3f getRotationVector(const MWWorld::Ptr& ptr) const override; osg::Vec3f getRotationVector(const MWWorld::Ptr& ptr) const override;
///< Return desired rotations, as euler angles. Sets getMovementSettings(ptr).mRotation to zero. ///< Return desired rotations, as euler angles. Sets getMovementSettings(ptr).mRotation to zero.

View file

@ -183,16 +183,17 @@ namespace MWClass
return getClassModel<ESM::Creature>(ptr); return getClassModel<ESM::Creature>(ptr);
} }
void Creature::getModelsToPreload(const MWWorld::Ptr& ptr, std::vector<std::string>& models) const void Creature::getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector<std::string>& models) const
{ {
std::string model = getModel(ptr); std::string model = getModel(ptr);
if (!model.empty()) if (!model.empty())
models.push_back(model); models.push_back(model);
// FIXME: use const version of InventoryStore functions once they are available const MWWorld::CustomData* customData = ptr.getRefData().getCustomData();
if (hasInventoryStore(ptr)) if (customData && hasInventoryStore(ptr))
{ {
const MWWorld::InventoryStore& invStore = getInventoryStore(ptr); const auto& invStore
= static_cast<const MWWorld::InventoryStore&>(*customData->asCreatureCustomData().mContainerStore);
for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
{ {
MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot); MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot);
@ -339,10 +340,7 @@ namespace MWClass
MWMechanics::applyElementalShields(ptr, victim); MWMechanics::applyElementalShields(ptr, victim);
if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage, attackStrength)) if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage, attackStrength))
{
damage = 0; damage = 0;
victim.getClass().block(victim);
}
MWMechanics::diseaseContact(victim, ptr); MWMechanics::diseaseContact(victim, ptr);
@ -513,7 +511,7 @@ namespace MWClass
throw std::runtime_error("this creature has no inventory store"); throw std::runtime_error("this creature has no inventory store");
} }
bool Creature::hasInventoryStore(const MWWorld::Ptr& ptr) const bool Creature::hasInventoryStore(const MWWorld::ConstPtr& ptr) const
{ {
return isFlagBitSet(ptr, ESM::Creature::Weapon); return isFlagBitSet(ptr, ESM::Creature::Weapon);
} }

View file

@ -79,7 +79,7 @@ namespace MWClass
MWWorld::InventoryStore& getInventoryStore(const MWWorld::Ptr& ptr) const override; MWWorld::InventoryStore& getInventoryStore(const MWWorld::Ptr& ptr) const override;
///< Return inventory store ///< Return inventory store
bool hasInventoryStore(const MWWorld::Ptr& ptr) const override; bool hasInventoryStore(const MWWorld::ConstPtr& ptr) const override;
ESM::RefId getScript(const MWWorld::ConstPtr& ptr) const override; ESM::RefId getScript(const MWWorld::ConstPtr& ptr) const override;
///< Return name of the script attached to ptr ///< Return name of the script attached to ptr
@ -107,7 +107,7 @@ namespace MWClass
std::string getModel(const MWWorld::ConstPtr& ptr) const override; std::string getModel(const MWWorld::ConstPtr& ptr) const override;
void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector<std::string>& models) const override; void getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector<std::string>& models) const override;
///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation:
///< list getModel(). ///< list getModel().

View file

@ -99,25 +99,6 @@ namespace MWClass
customData.mSpawn = true; customData.mSpawn = true;
} }
void CreatureLevList::getModelsToPreload(const MWWorld::Ptr& ptr, std::vector<std::string>& models) const
{
// disable for now, too many false positives
/*
const MWWorld::LiveCellRef<ESM::CreatureLevList> *ref = ptr.get<ESM::CreatureLevList>();
for (std::vector<ESM::LevelledListBase::LevelItem>::const_iterator it = ref->mBase->mList.begin(); it !=
ref->mBase->mList.end(); ++it)
{
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
if (it->mLevel > player.getClass().getCreatureStats(player).getLevel())
continue;
const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore();
MWWorld::ManualRef ref(store, it->mId);
ref.getPtr().getClass().getModelsToPreload(ref.getPtr(), models);
}
*/
}
void CreatureLevList::insertObjectRendering( void CreatureLevList::insertObjectRendering(
const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const
{ {

View file

@ -20,10 +20,6 @@ namespace MWClass
bool hasToolTip(const MWWorld::ConstPtr& ptr) const override; bool hasToolTip(const MWWorld::ConstPtr& ptr) const override;
///< @return true if this object has a tooltip when focused (default implementation: true) ///< @return true if this object has a tooltip when focused (default implementation: true)
void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector<std::string>& models) const override;
///< Get a list of models to preload that this object may use (directly or indirectly). default implementation:
///< list getModel().
void insertObjectRendering(const MWWorld::Ptr& ptr, const std::string& model, void insertObjectRendering(const MWWorld::Ptr& ptr, const std::string& model,
MWRender::RenderingInterface& renderingInterface) const override; MWRender::RenderingInterface& renderingInterface) const override;
///< Add reference into a cell for rendering ///< Add reference into a cell for rendering

View file

@ -436,10 +436,11 @@ namespace MWClass
return model; return model;
} }
void Npc::getModelsToPreload(const MWWorld::Ptr& ptr, std::vector<std::string>& models) const void Npc::getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector<std::string>& models) const
{ {
const MWWorld::LiveCellRef<ESM::NPC>* npc = ptr.get<ESM::NPC>(); const MWWorld::LiveCellRef<ESM::NPC>* npc = ptr.get<ESM::NPC>();
const ESM::Race* race = MWBase::Environment::get().getESMStore()->get<ESM::Race>().search(npc->mBase->mRace); const auto& esmStore = MWBase::Environment::get().getESMStore();
const ESM::Race* race = esmStore->get<ESM::Race>().search(npc->mBase->mRace);
if (race && race->mData.mFlags & ESM::Race::Beast) if (race && race->mData.mFlags & ESM::Race::Beast)
models.push_back(Settings::models().mBaseanimkna); models.push_back(Settings::models().mBaseanimkna);
@ -453,56 +454,57 @@ namespace MWClass
if (!npc->mBase->mHead.empty()) if (!npc->mBase->mHead.empty())
{ {
const ESM::BodyPart* head const ESM::BodyPart* head = esmStore->get<ESM::BodyPart>().search(npc->mBase->mHead);
= MWBase::Environment::get().getESMStore()->get<ESM::BodyPart>().search(npc->mBase->mHead);
if (head) if (head)
models.push_back(Misc::ResourceHelpers::correctMeshPath(head->mModel)); models.push_back(Misc::ResourceHelpers::correctMeshPath(head->mModel));
} }
if (!npc->mBase->mHair.empty()) if (!npc->mBase->mHair.empty())
{ {
const ESM::BodyPart* hair const ESM::BodyPart* hair = esmStore->get<ESM::BodyPart>().search(npc->mBase->mHair);
= MWBase::Environment::get().getESMStore()->get<ESM::BodyPart>().search(npc->mBase->mHair);
if (hair) if (hair)
models.push_back(Misc::ResourceHelpers::correctMeshPath(hair->mModel)); models.push_back(Misc::ResourceHelpers::correctMeshPath(hair->mModel));
} }
bool female = (npc->mBase->mFlags & ESM::NPC::Female); bool female = (npc->mBase->mFlags & ESM::NPC::Female);
// FIXME: use const version of InventoryStore functions once they are available const MWWorld::CustomData* customData = ptr.getRefData().getCustomData();
// preload equipped items if (customData)
const MWWorld::InventoryStore& invStore = getInventoryStore(ptr);
for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
{ {
MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot); const MWWorld::InventoryStore& invStore = customData->asNpcCustomData().mInventoryStore;
if (equipped != invStore.end()) for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
{ {
std::vector<ESM::PartReference> parts; MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot);
if (equipped->getType() == ESM::Clothing::sRecordId) if (equipped != invStore.end())
{ {
const ESM::Clothing* clothes = equipped->get<ESM::Clothing>()->mBase; const auto addParts = [&](const std::vector<ESM::PartReference>& parts) {
parts = clothes->mParts.mParts; for (const ESM::PartReference& partRef : parts)
} {
else if (equipped->getType() == ESM::Armor::sRecordId) const ESM::RefId& partname
{ = (female && !partRef.mFemale.empty()) || (!female && partRef.mMale.empty())
const ESM::Armor* armor = equipped->get<ESM::Armor>()->mBase; ? partRef.mFemale
parts = armor->mParts.mParts; : partRef.mMale;
}
else
{
std::string model = equipped->getClass().getModel(*equipped);
if (!model.empty())
models.push_back(model);
}
for (std::vector<ESM::PartReference>::const_iterator it = parts.begin(); it != parts.end(); ++it) const ESM::BodyPart* part = esmStore->get<ESM::BodyPart>().search(partname);
{ if (part && !part->mModel.empty())
const ESM::RefId& partname models.push_back(Misc::ResourceHelpers::correctMeshPath(part->mModel));
= (female && !it->mFemale.empty()) || (!female && it->mMale.empty()) ? it->mFemale : it->mMale; }
};
const ESM::BodyPart* part if (equipped->getType() == ESM::Clothing::sRecordId)
= MWBase::Environment::get().getESMStore()->get<ESM::BodyPart>().search(partname); {
if (part && !part->mModel.empty()) const ESM::Clothing* clothes = equipped->get<ESM::Clothing>()->mBase;
models.push_back(Misc::ResourceHelpers::correctMeshPath(part->mModel)); addParts(clothes->mParts.mParts);
}
else if (equipped->getType() == ESM::Armor::sRecordId)
{
const ESM::Armor* armor = equipped->get<ESM::Armor>()->mBase;
addParts(armor->mParts.mParts);
}
else
{
std::string model = equipped->getClass().getModel(*equipped);
if (!model.empty())
models.push_back(model);
}
} }
} }
} }
@ -512,9 +514,8 @@ namespace MWClass
{ {
const std::vector<const ESM::BodyPart*>& parts const std::vector<const ESM::BodyPart*>& parts
= MWRender::NpcAnimation::getBodyParts(race->mId, female, false, false); = MWRender::NpcAnimation::getBodyParts(race->mId, female, false, false);
for (std::vector<const ESM::BodyPart*>::const_iterator it = parts.begin(); it != parts.end(); ++it) for (const ESM::BodyPart* part : parts)
{ {
const ESM::BodyPart* part = *it;
if (part && !part->mModel.empty()) if (part && !part->mModel.empty())
models.push_back(Misc::ResourceHelpers::correctMeshPath(part->mModel)); models.push_back(Misc::ResourceHelpers::correctMeshPath(part->mModel));
} }
@ -678,10 +679,7 @@ namespace MWClass
MWMechanics::applyElementalShields(ptr, victim); MWMechanics::applyElementalShields(ptr, victim);
if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage, attackStrength)) if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage, attackStrength))
{
damage = 0; damage = 0;
victim.getClass().block(victim);
}
if (victim == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState()) if (victim == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState())
damage = 0; damage = 0;

View file

@ -74,7 +74,7 @@ namespace MWClass
MWWorld::InventoryStore& getInventoryStore(const MWWorld::Ptr& ptr) const override; MWWorld::InventoryStore& getInventoryStore(const MWWorld::Ptr& ptr) const override;
///< Return inventory store ///< Return inventory store
bool hasInventoryStore(const MWWorld::Ptr& ptr) const override { return true; } bool hasInventoryStore(const MWWorld::ConstPtr& ptr) const override { return true; }
bool evaluateHit(const MWWorld::Ptr& ptr, MWWorld::Ptr& victim, osg::Vec3f& hitPosition) const override; bool evaluateHit(const MWWorld::Ptr& ptr, MWWorld::Ptr& victim, osg::Vec3f& hitPosition) const override;
@ -85,7 +85,7 @@ namespace MWClass
const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful, const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful,
const MWMechanics::DamageSourceType sourceType) const override; const MWMechanics::DamageSourceType sourceType) const override;
void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector<std::string>& models) const override; void getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector<std::string>& models) const override;
///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation:
///< list getModel(). ///< list getModel().

View file

@ -364,9 +364,8 @@ namespace MWGui
if (mCurrentWindowSize == _sender->getSize()) if (mCurrentWindowSize == _sender->getSize())
return; return;
mTopicsList->adjustSize(); redrawTopicsList();
updateHistory(); updateHistory();
updateTopicFormat();
mCurrentWindowSize = _sender->getSize(); mCurrentWindowSize = _sender->getSize();
} }
@ -534,6 +533,14 @@ namespace MWGui
return true; return true;
} }
void DialogueWindow::redrawTopicsList()
{
mTopicsList->adjustSize();
// The topics list has been regenerated so topic formatting needs to be updated
updateTopicFormat();
}
void DialogueWindow::updateTopicsPane() void DialogueWindow::updateTopicsPane()
{ {
mTopicsList->clear(); mTopicsList->clear();
@ -591,11 +598,9 @@ namespace MWGui
t->eventTopicActivated += MyGUI::newDelegate(this, &DialogueWindow::onTopicActivated); t->eventTopicActivated += MyGUI::newDelegate(this, &DialogueWindow::onTopicActivated);
mTopicLinks[topicId] = std::move(t); mTopicLinks[topicId] = std::move(t);
} }
mTopicsList->adjustSize();
redrawTopicsList();
updateHistory(); updateHistory();
// The topics list has been regenerated so topic formatting needs to be updated
updateTopicFormat();
} }
void DialogueWindow::updateHistory(bool scrollbar) void DialogueWindow::updateHistory(bool scrollbar)
@ -756,21 +761,12 @@ namespace MWGui
+ std::string("/100")); + std::string("/100"));
} }
bool dispositionWasVisible = mDispositionBar->getVisible(); if (mDispositionBar->getVisible() != dispositionVisible)
if (dispositionVisible && !dispositionWasVisible)
{ {
mDispositionBar->setVisible(true); mDispositionBar->setVisible(dispositionVisible);
int offset = mDispositionBar->getHeight() + 5; const int offset = (mDispositionBar->getHeight() + 5) * (dispositionVisible ? 1 : -1);
mTopicsList->setCoord(mTopicsList->getCoord() + MyGUI::IntCoord(0, offset, 0, -offset)); mTopicsList->setCoord(mTopicsList->getCoord() + MyGUI::IntCoord(0, offset, 0, -offset));
mTopicsList->adjustSize(); redrawTopicsList();
}
else if (!dispositionVisible && dispositionWasVisible)
{
mDispositionBar->setVisible(false);
int offset = mDispositionBar->getHeight() + 5;
mTopicsList->setCoord(mTopicsList->getCoord() - MyGUI::IntCoord(0, offset, 0, -offset));
mTopicsList->adjustSize();
} }
} }

View file

@ -190,6 +190,7 @@ namespace MWGui
void updateDisposition(); void updateDisposition();
void restock(); void restock();
void deleteLater(); void deleteLater();
void redrawTopicsList();
bool mIsCompanion; bool mIsCompanion;
std::list<std::string> mKeywords; std::list<std::string> mKeywords;

View file

@ -18,6 +18,7 @@
#include <components/resource/resourcesystem.hpp> #include <components/resource/resourcesystem.hpp>
#include <components/settings/values.hpp> #include <components/settings/values.hpp>
#include <components/vfs/manager.hpp> #include <components/vfs/manager.hpp>
#include <components/vfs/recursivedirectoryiterator.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/inputmanager.hpp" #include "../mwbase/inputmanager.hpp"

View file

@ -29,6 +29,7 @@
#include <components/sceneutil/lightmanager.hpp> #include <components/sceneutil/lightmanager.hpp>
#include <components/settings/values.hpp> #include <components/settings/values.hpp>
#include <components/vfs/manager.hpp> #include <components/vfs/manager.hpp>
#include <components/vfs/recursivedirectoryiterator.hpp>
#include <components/widgets/sharedstatebutton.hpp> #include <components/widgets/sharedstatebutton.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
@ -131,7 +132,7 @@ namespace
void updateMaxLightsComboBox(MyGUI::ComboBox* box) void updateMaxLightsComboBox(MyGUI::ComboBox* box)
{ {
constexpr int min = 8; constexpr int min = 8;
constexpr int max = 32; constexpr int max = 64;
constexpr int increment = 8; constexpr int increment = 8;
const int maxLights = Settings::shaders().mMaxLights; const int maxLights = Settings::shaders().mMaxLights;
// show increments of 8 in dropdown // show increments of 8 in dropdown

View file

@ -23,6 +23,11 @@ namespace
float mTimeOffset = 0.f; float mTimeOffset = 0.f;
}; };
struct StreamMusicArgs
{
float mFade = 1.f;
};
PlaySoundArgs getPlaySoundArgs(const sol::optional<sol::table>& options) PlaySoundArgs getPlaySoundArgs(const sol::optional<sol::table>& options)
{ {
PlaySoundArgs args; PlaySoundArgs args;
@ -55,6 +60,17 @@ namespace
return MWSound::PlayMode::NoEnvNoScaling; return MWSound::PlayMode::NoEnvNoScaling;
return MWSound::PlayMode::NoEnv; return MWSound::PlayMode::NoEnv;
} }
StreamMusicArgs getStreamMusicArgs(const sol::optional<sol::table>& options)
{
StreamMusicArgs args;
if (options.has_value())
{
args.mFade = options->get_or("fadeOut", 1.f);
}
return args;
}
} }
namespace MWLua namespace MWLua
@ -95,9 +111,10 @@ namespace MWLua
return MWBase::Environment::get().getSoundManager()->getSoundPlaying(MWWorld::Ptr(), fileName); return MWBase::Environment::get().getSoundManager()->getSoundPlaying(MWWorld::Ptr(), fileName);
}; };
api["streamMusic"] = [](std::string_view fileName) { api["streamMusic"] = [](std::string_view fileName, const sol::optional<sol::table>& options) {
auto args = getStreamMusicArgs(options);
MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager();
sndMgr->streamMusic(std::string(fileName), MWSound::MusicType::Scripted); sndMgr->streamMusic(std::string(fileName), MWSound::MusicType::Scripted, args.mFade);
}; };
api["isMusicPlaying"] = []() { return MWBase::Environment::get().getSoundManager()->isMusicPlaying(); }; api["isMusicPlaying"] = []() { return MWBase::Environment::get().getSoundManager()->isMusicPlaying(); };

View file

@ -1,3 +1,4 @@
#include "../stats.hpp"
#include "actor.hpp" #include "actor.hpp"
#include "types.hpp" #include "types.hpp"
@ -42,6 +43,20 @@ namespace MWLua
record["soulValue"] = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mSoul; }); record["soulValue"] = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mSoul; });
record["type"] = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mType; }); record["type"] = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mType; });
record["baseGold"] = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mGold; }); record["baseGold"] = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mGold; });
record["combatSkill"]
= sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mCombat; });
record["magicSkill"] = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mMagic; });
record["stealthSkill"]
= sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mStealth; });
record["attack"] = sol::readonly_property([context](const ESM::Creature& rec) -> sol::table {
sol::state_view& lua = context.mLua->sol();
sol::table res(lua, sol::create);
int index = 1;
for (auto attack : rec.mData.mAttack)
res[index++] = attack;
return LuaUtil::makeReadOnly(res);
});
addActorServicesBindings<ESM::Creature>(record, context); addActorServicesBindings<ESM::Creature>(record, context);
} }
} }

View file

@ -5,6 +5,7 @@
#include <components/settings/values.hpp> #include <components/settings/values.hpp>
#include <components/vfs/manager.hpp> #include <components/vfs/manager.hpp>
#include <components/vfs/pathutil.hpp> #include <components/vfs/pathutil.hpp>
#include <components/vfs/recursivedirectoryiterator.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"

View file

@ -2034,6 +2034,14 @@ namespace MWMechanics
return false; return false;
} }
bool Actors::checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const
{
const auto iter = mIndex.find(ptr.mRef);
if (iter != mIndex.end())
return iter->second->getCharacterController().isScriptedAnimPlaying();
return false;
}
void Actors::persistAnimationStates() const void Actors::persistAnimationStates() const
{ {
for (const Actor& actor : mActors) for (const Actor& actor : mActors)

View file

@ -116,6 +116,7 @@ namespace MWMechanics
const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false) const; const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false) const;
void skipAnimation(const MWWorld::Ptr& ptr) const; void skipAnimation(const MWWorld::Ptr& ptr) const;
bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) const; bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) const;
bool checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const;
void persistAnimationStates() const; void persistAnimationStates() const;
void getObjectsInRange(const osg::Vec3f& position, float radius, std::vector<MWWorld::Ptr>& out) const; void getObjectsInRange(const osg::Vec3f& position, float radius, std::vector<MWWorld::Ptr>& out) const;

View file

@ -9,6 +9,7 @@
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/luamanager.hpp" #include "../mwbase/luamanager.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwworld/cellstore.hpp" #include "../mwworld/cellstore.hpp"
@ -120,12 +121,12 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
MWBase::World* world = MWBase::Environment::get().getWorld(); MWBase::World* world = MWBase::Environment::get().getWorld();
const DetourNavigator::AgentBounds agentBounds = world->getPathfindingAgentBounds(actor); const DetourNavigator::AgentBounds agentBounds = world->getPathfindingAgentBounds(actor);
/// Stops the actor when it gets too close to a unloaded cell /// Stops the actor when it gets too close to a unloaded cell or when the actor is playing a scripted animation
//... At current time, this test is unnecessary. AI shuts down when actor is more than "actors processing range" //... At current time, the first test is unnecessary. AI shuts down when actor is more than
// setting value //... "actors processing range" setting value units from player, and exterior cells are 8192 units long and wide.
//... units from player, and exterior cells are 8192 units long and wide.
//... But AI processing distance may increase in the future. //... But AI processing distance may increase in the future.
if (isNearInactiveCell(position)) if (isNearInactiveCell(position)
|| MWBase::Environment::get().getMechanicsManager()->checkScriptedAnimationPlaying(actor))
{ {
actor.getClass().getMovementSettings(actor).mPosition[0] = 0; actor.getClass().getMovementSettings(actor).mPosition[0] = 0;
actor.getClass().getMovementSettings(actor).mPosition[1] = 0; actor.getClass().getMovementSettings(actor).mPosition[1] = 0;

View file

@ -217,7 +217,6 @@ namespace MWMechanics
std::string chooseRandomAttackAnimation() const; std::string chooseRandomAttackAnimation() const;
static bool isRandomAttackAnimation(std::string_view group); static bool isRandomAttackAnimation(std::string_view group);
bool isScriptedAnimPlaying() const;
bool isMovementAnimationControlled() const; bool isMovementAnimationControlled() const;
void updateAnimQueue(); void updateAnimQueue();
@ -278,6 +277,7 @@ namespace MWMechanics
bool playGroup(std::string_view groupname, int mode, int count, bool scripted = false); bool playGroup(std::string_view groupname, int mode, int count, bool scripted = false);
void skipAnim(); void skipAnim();
bool isAnimPlaying(std::string_view groupName) const; bool isAnimPlaying(std::string_view groupName) const;
bool isScriptedAnimPlaying() const;
enum KillResult enum KillResult
{ {

View file

@ -135,6 +135,15 @@ namespace MWMechanics
auto& prng = MWBase::Environment::get().getWorld()->getPrng(); auto& prng = MWBase::Environment::get().getWorld()->getPrng();
if (Misc::Rng::roll0to99(prng) < x) if (Misc::Rng::roll0to99(prng) < x)
{ {
MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager();
const ESM::RefId skill = shield->getClass().getEquipmentSkill(*shield);
if (skill == ESM::Skill::LightArmor)
sndMgr->playSound3D(blocker, ESM::RefId::stringRefId("Light Armor Hit"), 1.0f, 1.0f);
else if (skill == ESM::Skill::MediumArmor)
sndMgr->playSound3D(blocker, ESM::RefId::stringRefId("Medium Armor Hit"), 1.0f, 1.0f);
else if (skill == ESM::Skill::HeavyArmor)
sndMgr->playSound3D(blocker, ESM::RefId::stringRefId("Heavy Armor Hit"), 1.0f, 1.0f);
// Reduce shield durability by incoming damage // Reduce shield durability by incoming damage
int shieldhealth = shield->getClass().getItemHealth(*shield); int shieldhealth = shield->getClass().getItemHealth(*shield);

View file

@ -771,6 +771,14 @@ namespace MWMechanics
return false; return false;
} }
bool MechanicsManager::checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const
{
if (ptr.getClass().isActor())
return mActors.checkScriptedAnimationPlaying(ptr);
return false;
}
bool MechanicsManager::onOpen(const MWWorld::Ptr& ptr) bool MechanicsManager::onOpen(const MWWorld::Ptr& ptr)
{ {
if (ptr.getClass().isActor()) if (ptr.getClass().isActor())

View file

@ -145,6 +145,7 @@ namespace MWMechanics
const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false) override; const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false) override;
void skipAnimation(const MWWorld::Ptr& ptr) override; void skipAnimation(const MWWorld::Ptr& ptr) override;
bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) override; bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) override;
bool checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const override;
void persistAnimationStates() override; void persistAnimationStates() override;
/// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently

View file

@ -101,7 +101,7 @@ namespace MWRender
templateNode, mObjectRoot, bonefilter, found->second, mResourceSystem->getSceneManager(), &rotation); templateNode, mObjectRoot, bonefilter, found->second, mResourceSystem->getSceneManager(), &rotation);
} }
return SceneUtil::attach( return SceneUtil::attach(
templateNode, mObjectRoot, bonefilter, found->second, mResourceSystem->getSceneManager()); std::move(templateNode), mObjectRoot, bonefilter, found->second, mResourceSystem->getSceneManager());
} }
std::string ActorAnimation::getShieldMesh(const MWWorld::ConstPtr& shield, bool female) const std::string ActorAnimation::getShieldMesh(const MWWorld::ConstPtr& shield, bool female) const

View file

@ -33,6 +33,7 @@
#include <components/sceneutil/keyframe.hpp> #include <components/sceneutil/keyframe.hpp>
#include <components/vfs/manager.hpp> #include <components/vfs/manager.hpp>
#include <components/vfs/recursivedirectoryiterator.hpp>
#include <components/sceneutil/lightmanager.hpp> #include <components/sceneutil/lightmanager.hpp>
#include <components/sceneutil/lightutil.hpp> #include <components/sceneutil/lightutil.hpp>

View file

@ -422,7 +422,8 @@ namespace MWRender
if (cellX > mMaxX || cellX < mMinX || cellY > mMaxY || cellY < mMinY) if (cellX > mMaxX || cellX < mMinX || cellY > mMaxY || cellY < mMinY)
return; return;
requestOverlayTextureUpdate(originX, mHeight - originY, cellSize, cellSize, localMapTexture, false, true); requestOverlayTextureUpdate(
originX, mHeight - originY, cellSize, cellSize, std::move(localMapTexture), false, true);
} }
void GlobalMap::clear() void GlobalMap::clear()
@ -554,7 +555,7 @@ namespace MWRender
{ {
mOverlayImage = image; mOverlayImage = image;
requestOverlayTextureUpdate(0, 0, mWidth, mHeight, texture, true, false); requestOverlayTextureUpdate(0, 0, mWidth, mHeight, std::move(texture), true, false);
} }
else else
{ {
@ -562,7 +563,7 @@ namespace MWRender
// In the latter case, we'll want filtering. // In the latter case, we'll want filtering.
// Create a RTT Camera and draw the image onto mOverlayImage in the next frame. // Create a RTT Camera and draw the image onto mOverlayImage in the next frame.
requestOverlayTextureUpdate(destBox.mLeft, destBox.mTop, destBox.mRight - destBox.mLeft, requestOverlayTextureUpdate(destBox.mLeft, destBox.mTop, destBox.mRight - destBox.mLeft,
destBox.mBottom - destBox.mTop, texture, true, true, srcBox.mLeft / float(imageWidth), destBox.mBottom - destBox.mTop, std::move(texture), true, true, srcBox.mLeft / float(imageWidth),
srcBox.mTop / float(imageHeight), srcBox.mRight / float(imageWidth), srcBox.mTop / float(imageHeight), srcBox.mRight / float(imageWidth),
srcBox.mBottom / float(imageHeight)); srcBox.mBottom / float(imageHeight));
} }

View file

@ -19,7 +19,7 @@ namespace MWRender
auto resolveFragment = shaderManager.getShader("luminance/resolve.frag", defines); auto resolveFragment = shaderManager.getShader("luminance/resolve.frag", defines);
mResolveProgram = shaderManager.getProgram(vertex, std::move(resolveFragment)); mResolveProgram = shaderManager.getProgram(vertex, std::move(resolveFragment));
mLuminanceProgram = shaderManager.getProgram(vertex, std::move(luminanceFragment)); mLuminanceProgram = shaderManager.getProgram(std::move(vertex), std::move(luminanceFragment));
for (auto& buffer : mBuffers) for (auto& buffer : mBuffers)
{ {

View file

@ -68,7 +68,7 @@ namespace MWRender
ptr.getClass().adjustScale(ptr, scaleVec, true); ptr.getClass().adjustScale(ptr, scaleVec, true);
insert->setScale(scaleVec); insert->setScale(scaleVec);
ptr.getRefData().setBaseNode(insert); ptr.getRefData().setBaseNode(std::move(insert));
} }
void Objects::insertModel(const MWWorld::Ptr& ptr, const std::string& mesh, bool allowLight) void Objects::insertModel(const MWWorld::Ptr& ptr, const std::string& mesh, bool allowLight)

View file

@ -288,6 +288,8 @@ namespace MWRender
pass.mRenderTarget->getAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0) pass.mRenderTarget->getAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0)
.getTexture())); .getTexture()));
assert(texture != nullptr);
texture->setTextureSize(w, h); texture->setTextureSize(w, h);
texture->setNumMipmapLevels(pass.mRenderTexture->getNumMipmapLevels()); texture->setNumMipmapLevels(pass.mRenderTexture->getNumMipmapLevels());
texture->dirtyTextureObject(); texture->dirtyTextureObject();

View file

@ -23,6 +23,7 @@
#include <components/stereo/multiview.hpp> #include <components/stereo/multiview.hpp>
#include <components/stereo/stereomanager.hpp> #include <components/stereo/stereomanager.hpp>
#include <components/vfs/manager.hpp> #include <components/vfs/manager.hpp>
#include <components/vfs/recursivedirectoryiterator.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
@ -662,6 +663,11 @@ namespace MWRender
for (const auto& name : pass->getRenderTargets()) for (const auto& name : pass->getRenderTargets())
{ {
if (name.empty())
{
continue;
}
auto& renderTarget = technique->getRenderTargetsMap()[name]; auto& renderTarget = technique->getRenderTargetsMap()[name];
subPass.mStateSet->setTextureAttribute(subTexUnit, renderTarget.mTarget); subPass.mStateSet->setTextureAttribute(subTexUnit, renderTarget.mTarget);
subPass.mStateSet->addUniform(new osg::Uniform(name.c_str(), subTexUnit)); subPass.mStateSet->addUniform(new osg::Uniform(name.c_str(), subTexUnit));

View file

@ -1,5 +1,7 @@
#include "precipitationocclusion.hpp" #include "precipitationocclusion.hpp"
#include <cassert>
#include <osgUtil/CullVisitor> #include <osgUtil/CullVisitor>
#include <components/misc/constants.hpp> #include <components/misc/constants.hpp>
@ -120,16 +122,19 @@ namespace MWRender
void PrecipitationOccluder::update() void PrecipitationOccluder::update()
{ {
if (!mRange.has_value())
return;
const osg::Vec3 pos = mSceneCamera->getInverseViewMatrix().getTrans(); const osg::Vec3 pos = mSceneCamera->getInverseViewMatrix().getTrans();
const float zmin = pos.z() - mRange.z() - Constants::CellSizeInUnits; const float zmin = pos.z() - mRange->z() - Constants::CellSizeInUnits;
const float zmax = pos.z() + mRange.z() + Constants::CellSizeInUnits; const float zmax = pos.z() + mRange->z() + Constants::CellSizeInUnits;
const float near = 0; const float near = 0;
const float far = zmax - zmin; const float far = zmax - zmin;
const float left = -mRange.x() / 2; const float left = -mRange->x() / 2;
const float right = -left; const float right = -left;
const float top = mRange.y() / 2; const float top = mRange->y() / 2;
const float bottom = -top; const float bottom = -top;
if (SceneUtil::AutoDepth::isReversed()) if (SceneUtil::AutoDepth::isReversed())
@ -163,10 +168,14 @@ namespace MWRender
mSkyCullCallback = nullptr; mSkyCullCallback = nullptr;
mRootNode->removeChild(mCamera); mRootNode->removeChild(mCamera);
mRange = std::nullopt;
} }
void PrecipitationOccluder::updateRange(const osg::Vec3f range) void PrecipitationOccluder::updateRange(const osg::Vec3f range)
{ {
assert(range.x() != 0);
assert(range.y() != 0);
assert(range.z() != 0);
const osg::Vec3f margin = { -50, -50, 0 }; const osg::Vec3f margin = { -50, -50, 0 };
mRange = range - margin; mRange = range - margin;
} }

View file

@ -4,6 +4,8 @@
#include <osg/Camera> #include <osg/Camera>
#include <osg/Texture2D> #include <osg/Texture2D>
#include <optional>
namespace MWRender namespace MWRender
{ {
class PrecipitationOccluder class PrecipitationOccluder
@ -27,7 +29,7 @@ namespace MWRender
osg::ref_ptr<osg::Camera> mCamera; osg::ref_ptr<osg::Camera> mCamera;
osg::ref_ptr<osg::Camera> mSceneCamera; osg::ref_ptr<osg::Camera> mSceneCamera;
osg::ref_ptr<osg::Texture2D> mDepthTexture; osg::ref_ptr<osg::Texture2D> mDepthTexture;
osg::Vec3f mRange; std::optional<osg::Vec3f> mRange;
}; };
} }

View file

@ -716,9 +716,12 @@ namespace MWRender
// need to wrap this in a StateUpdater? // need to wrap this in a StateUpdater?
mSunLight->setPosition(osg::Vec4(position.x(), position.y(), position.z(), 0)); mSunLight->setPosition(osg::Vec4(position.x(), position.y(), position.z(), 0));
// The sun is not synchronized with the sunlight because sunlight origin can't reach the horizon
// This is based on exterior sun orbit and won't make sense for interiors, see WeatherManager::update
position.z() = 400.f - std::abs(position.x());
mSky->setSunDirection(position); mSky->setSunDirection(position);
mPostProcessor->getStateUpdater()->setSunPos(mSunLight->getPosition(), mNight); mPostProcessor->getStateUpdater()->setSunPos(osg::Vec4f(position, 0.f), mNight);
} }
void RenderingManager::addCell(const MWWorld::CellStore* store) void RenderingManager::addCell(const MWWorld::CellStore* store)

View file

@ -106,7 +106,7 @@ namespace MWRender
mProgramBlobber = shaderManager.getProgram( mProgramBlobber = shaderManager.getProgram(
vertex, shaderManager.getShader("ripples_blobber.frag", defineMap, osg::Shader::FRAGMENT)); vertex, shaderManager.getShader("ripples_blobber.frag", defineMap, osg::Shader::FRAGMENT));
mProgramSimulation = shaderManager.getProgram( mProgramSimulation = shaderManager.getProgram(
vertex, shaderManager.getShader("ripples_simulate.frag", defineMap, osg::Shader::FRAGMENT)); std::move(vertex), shaderManager.getShader("ripples_simulate.frag", defineMap, osg::Shader::FRAGMENT));
} }
void RipplesSurface::setupComputePipeline() void RipplesSurface::setupComputePipeline()

View file

@ -764,7 +764,7 @@ namespace MWRender
cloudTex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); cloudTex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
cloudTex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); cloudTex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
mCloudUpdater->setTexture(cloudTex); mCloudUpdater->setTexture(std::move(cloudTex));
} }
if (mStormDirection != weather.mStormDirection) if (mStormDirection != weather.mStormDirection)
@ -786,7 +786,7 @@ namespace MWRender
cloudTex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); cloudTex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
cloudTex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); cloudTex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
mNextCloudUpdater->setTexture(cloudTex); mNextCloudUpdater->setTexture(std::move(cloudTex));
mNextStormDirection = weather.mStormDirection; mNextStormDirection = weather.mStormDirection;
} }
} }

View file

@ -722,8 +722,8 @@ namespace MWRender
mRainSettingsUpdater = new RainSettingsUpdater(); mRainSettingsUpdater = new RainSettingsUpdater();
node->setUpdateCallback(mRainSettingsUpdater); node->setUpdateCallback(mRainSettingsUpdater);
mShaderWaterStateSetUpdater mShaderWaterStateSetUpdater = new ShaderWaterStateSetUpdater(
= new ShaderWaterStateSetUpdater(this, mReflection, mRefraction, mRipples, std::move(program), normalMap); this, mReflection, mRefraction, mRipples, std::move(program), std::move(normalMap));
node->addCullCallback(mShaderWaterStateSetUpdater); node->addCullCallback(mShaderWaterStateSetUpdater);
} }

View file

@ -13,6 +13,7 @@
#include <components/settings/values.hpp> #include <components/settings/values.hpp>
#include <components/vfs/manager.hpp> #include <components/vfs/manager.hpp>
#include <components/vfs/pathutil.hpp> #include <components/vfs/pathutil.hpp>
#include <components/vfs/recursivedirectoryiterator.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/mechanicsmanager.hpp"

View file

@ -16,6 +16,7 @@
#include <components/loadinglistener/loadinglistener.hpp> #include <components/loadinglistener/loadinglistener.hpp>
#include <components/files/conversion.hpp> #include <components/files/conversion.hpp>
#include <components/misc/algorithm.hpp>
#include <components/settings/values.hpp> #include <components/settings/values.hpp>
#include <osg/Image> #include <osg/Image>
@ -81,10 +82,8 @@ std::map<int, int> MWState::StateManager::buildContentFileIndexMap(const ESM::ES
for (int iPrev = 0; iPrev < static_cast<int>(prev.size()); ++iPrev) for (int iPrev = 0; iPrev < static_cast<int>(prev.size()); ++iPrev)
{ {
std::string id = Misc::StringUtils::lowerCase(prev[iPrev].name);
for (int iCurrent = 0; iCurrent < static_cast<int>(current.size()); ++iCurrent) for (int iCurrent = 0; iCurrent < static_cast<int>(current.size()); ++iCurrent)
if (id == Misc::StringUtils::lowerCase(current[iCurrent])) if (Misc::StringUtils::ciEqual(prev[iPrev].name, current[iCurrent]))
{ {
map.insert(std::make_pair(iPrev, iCurrent)); map.insert(std::make_pair(iPrev, iCurrent));
break; break;
@ -410,9 +409,38 @@ void MWState::StateManager::loadGame(const std::filesystem::path& filepath)
loadGame(character, filepath); loadGame(character, filepath);
} }
struct VersionMismatchError : public std::runtime_error struct SaveFormatVersionError : public std::exception
{ {
using std::runtime_error::runtime_error; using std::exception::exception;
SaveFormatVersionError(ESM::FormatVersion savegameFormat, const std::string& message)
: mSavegameFormat(savegameFormat)
, mErrorMessage(message)
{
}
const char* what() const noexcept override { return mErrorMessage.c_str(); }
ESM::FormatVersion getFormatVersion() const { return mSavegameFormat; }
protected:
ESM::FormatVersion mSavegameFormat = ESM::DefaultFormatVersion;
std::string mErrorMessage;
};
struct SaveVersionTooOldError : SaveFormatVersionError
{
SaveVersionTooOldError(ESM::FormatVersion savegameFormat)
: SaveFormatVersionError(savegameFormat, "format version " + std::to_string(savegameFormat) + " is too old")
{
}
};
struct SaveVersionTooNewError : SaveFormatVersionError
{
SaveVersionTooNewError(ESM::FormatVersion savegameFormat)
: SaveFormatVersionError(savegameFormat, "format version " + std::to_string(savegameFormat) + " is too new")
{
}
}; };
void MWState::StateManager::loadGame(const Character* character, const std::filesystem::path& filepath) void MWState::StateManager::loadGame(const Character* character, const std::filesystem::path& filepath)
@ -428,23 +456,9 @@ void MWState::StateManager::loadGame(const Character* character, const std::file
ESM::FormatVersion version = reader.getFormatVersion(); ESM::FormatVersion version = reader.getFormatVersion();
if (version > ESM::CurrentSaveGameFormatVersion) if (version > ESM::CurrentSaveGameFormatVersion)
throw VersionMismatchError("#{OMWEngine:LoadingRequiresNewVersionError}"); throw SaveVersionTooNewError(version);
else if (version < ESM::MinSupportedSaveGameFormatVersion) else if (version < ESM::MinSupportedSaveGameFormatVersion)
{ throw SaveVersionTooOldError(version);
const char* release;
// Report the last version still capable of reading this save
if (version <= ESM::OpenMW0_48SaveGameFormatVersion)
release = "OpenMW 0.48.0";
else
{
// Insert additional else if statements above to cover future releases
static_assert(ESM::MinSupportedSaveGameFormatVersion <= ESM::OpenMW0_49SaveGameFormatVersion);
release = "OpenMW 0.49.0";
}
auto l10n = MWBase::Environment::get().getL10nManager()->getContext("OMWEngine");
std::string message = l10n->formatMessage("LoadingRequiresOldVersionError", { "version" }, { release });
throw VersionMismatchError(message);
}
std::map<int, int> contentFileMap = buildContentFileIndexMap(reader); std::map<int, int> contentFileMap = buildContentFileIndexMap(reader);
reader.setContentFileMapping(&contentFileMap); reader.setContentFileMapping(&contentFileMap);
@ -626,23 +640,49 @@ void MWState::StateManager::loadGame(const Character* character, const std::file
MWBase::Environment::get().getLuaManager()->gameLoaded(); MWBase::Environment::get().getLuaManager()->gameLoaded();
} }
catch (const SaveVersionTooNewError& e)
{
std::string error = "#{OMWEngine:LoadingRequiresNewVersionError}";
printSavegameFormatError(e.what(), error);
}
catch (const SaveVersionTooOldError& e)
{
const char* release;
// Report the last version still capable of reading this save
if (e.getFormatVersion() <= ESM::OpenMW0_48SaveGameFormatVersion)
release = "OpenMW 0.48.0";
else
{
// Insert additional else if statements above to cover future releases
static_assert(ESM::MinSupportedSaveGameFormatVersion <= ESM::OpenMW0_49SaveGameFormatVersion);
release = "OpenMW 0.49.0";
}
auto l10n = MWBase::Environment::get().getL10nManager()->getContext("OMWEngine");
std::string error = l10n->formatMessage("LoadingRequiresOldVersionError", { "version" }, { release });
printSavegameFormatError(e.what(), error);
}
catch (const std::exception& e) catch (const std::exception& e)
{ {
Log(Debug::Error) << "Failed to load saved game: " << e.what();
cleanup(true);
MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_MainMenu);
std::vector<std::string> buttons;
buttons.emplace_back("#{Interface:OK}");
std::string error = "#{OMWEngine:LoadingFailed}: " + std::string(e.what()); std::string error = "#{OMWEngine:LoadingFailed}: " + std::string(e.what());
printSavegameFormatError(e.what(), error);
MWBase::Environment::get().getWindowManager()->interactiveMessageBox(error, buttons);
} }
} }
void MWState::StateManager::printSavegameFormatError(
const std::string& exceptionText, const std::string& messageBoxText)
{
Log(Debug::Error) << "Failed to load saved game: " << exceptionText;
cleanup(true);
MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_MainMenu);
std::vector<std::string> buttons;
buttons.emplace_back("#{Interface:OK}");
MWBase::Environment::get().getWindowManager()->interactiveMessageBox(messageBoxText, buttons);
}
void MWState::StateManager::quickLoad() void MWState::StateManager::quickLoad()
{ {
if (Character* currentCharacter = getCurrentCharacter()) if (Character* currentCharacter = getCurrentCharacter())

View file

@ -22,6 +22,8 @@ namespace MWState
private: private:
void cleanup(bool force = false); void cleanup(bool force = false);
void printSavegameFormatError(const std::string& exceptionText, const std::string& messageBoxText);
bool confirmLoading(const std::vector<std::string_view>& missingFiles) const; bool confirmLoading(const std::vector<std::string_view>& missingFiles) const;
void writeScreenshot(std::vector<char>& imageData) const; void writeScreenshot(std::vector<char>& imageData) const;

View file

@ -52,20 +52,13 @@ namespace MWWorld
struct ListModelsVisitor struct ListModelsVisitor
{ {
ListModelsVisitor(std::vector<std::string>& out) bool operator()(const MWWorld::ConstPtr& ptr)
: mOut(out)
{
}
virtual bool operator()(const MWWorld::Ptr& ptr)
{ {
ptr.getClass().getModelsToPreload(ptr, mOut); ptr.getClass().getModelsToPreload(ptr, mOut);
return true; return true;
} }
virtual ~ListModelsVisitor() = default;
std::vector<std::string>& mOut; std::vector<std::string>& mOut;
}; };
@ -90,8 +83,8 @@ namespace MWWorld
{ {
mTerrainView = mTerrain->createView(); mTerrainView = mTerrain->createView();
ListModelsVisitor visitor(mMeshes); ListModelsVisitor visitor{ mMeshes };
cell->forEach(visitor); cell->forEachConst(visitor);
} }
void abort() override { mAbort = true; } void abort() override { mAbort = true; }

View file

@ -209,8 +209,8 @@ namespace MWWorld
/// false will abort the iteration. /// false will abort the iteration.
/// \note Prefer using forEachConst when possible. /// \note Prefer using forEachConst when possible.
/// \note Do not modify this cell (i.e. remove/add objects) during the forEach, doing this may result in /// \note Do not modify this cell (i.e. remove/add objects) during the forEach, doing this may result in
/// unintended behaviour. \attention This function also lists deleted (count 0) objects! \return Iteration /// unintended behaviour. \attention This function also lists deleted (count 0) objects!
/// completed? /// \return Iteration completed?
template <class Visitor> template <class Visitor>
bool forEach(Visitor&& visitor) bool forEach(Visitor&& visitor)
{ {
@ -224,12 +224,12 @@ namespace MWWorld
mHasState = true; mHasState = true;
for (unsigned int i = 0; i < mMergedRefs.size(); ++i) for (LiveCellRefBase* mergedRef : mMergedRefs)
{ {
if (!isAccessible(mMergedRefs[i]->mData, mMergedRefs[i]->mRef)) if (!isAccessible(mergedRef->mData, mergedRef->mRef))
continue; continue;
if (!visitor(MWWorld::Ptr(mMergedRefs[i], this))) if (!visitor(MWWorld::Ptr(mergedRef, this)))
return false; return false;
} }
return true; return true;
@ -238,8 +238,8 @@ namespace MWWorld
/// Call visitor (MWWorld::ConstPtr) for each reference. visitor must return a bool. Returning /// Call visitor (MWWorld::ConstPtr) for each reference. visitor must return a bool. Returning
/// false will abort the iteration. /// false will abort the iteration.
/// \note Do not modify this cell (i.e. remove/add objects) during the forEach, doing this may result in /// \note Do not modify this cell (i.e. remove/add objects) during the forEach, doing this may result in
/// unintended behaviour. \attention This function also lists deleted (count 0) objects! \return Iteration /// unintended behaviour. \attention This function also lists deleted (count 0) objects!
/// completed? /// \return Iteration completed?
template <class Visitor> template <class Visitor>
bool forEachConst(Visitor&& visitor) const bool forEachConst(Visitor&& visitor) const
{ {
@ -249,12 +249,12 @@ namespace MWWorld
if (mMergedRefsNeedsUpdate) if (mMergedRefsNeedsUpdate)
updateMergedRefs(); updateMergedRefs();
for (unsigned int i = 0; i < mMergedRefs.size(); ++i) for (const LiveCellRefBase* mergedRef : mMergedRefs)
{ {
if (!isAccessible(mMergedRefs[i]->mData, mMergedRefs[i]->mRef)) if (!isAccessible(mergedRef->mData, mergedRef->mRef))
continue; continue;
if (!visitor(MWWorld::ConstPtr(mMergedRefs[i], this))) if (!visitor(MWWorld::ConstPtr(mergedRef, this)))
return false; return false;
} }
return true; return true;
@ -263,8 +263,8 @@ namespace MWWorld
/// Call visitor (ref) for each reference of given type. visitor must return a bool. Returning /// Call visitor (ref) for each reference of given type. visitor must return a bool. Returning
/// false will abort the iteration. /// false will abort the iteration.
/// \note Do not modify this cell (i.e. remove/add objects) during the forEach, doing this may result in /// \note Do not modify this cell (i.e. remove/add objects) during the forEach, doing this may result in
/// unintended behaviour. \attention This function also lists deleted (count 0) objects! \return Iteration /// unintended behaviour. \attention This function also lists deleted (count 0) objects!
/// completed? /// \return Iteration completed?
template <class T, class Visitor> template <class T, class Visitor>
bool forEachType(Visitor&& visitor) bool forEachType(Visitor&& visitor)
{ {

View file

@ -118,11 +118,6 @@ namespace MWWorld
throw std::runtime_error("class cannot hit"); throw std::runtime_error("class cannot hit");
} }
void Class::block(const Ptr& ptr) const
{
throw std::runtime_error("class cannot block");
}
void Class::onHit(const Ptr& ptr, float damage, bool ishealth, const Ptr& object, const Ptr& attacker, void Class::onHit(const Ptr& ptr, float damage, bool ishealth, const Ptr& object, const Ptr& attacker,
const osg::Vec3f& hitPosition, bool successful, const MWMechanics::DamageSourceType sourceType) const const osg::Vec3f& hitPosition, bool successful, const MWMechanics::DamageSourceType sourceType) const
{ {
@ -149,7 +144,7 @@ namespace MWWorld
throw std::runtime_error("class does not have an inventory store"); throw std::runtime_error("class does not have an inventory store");
} }
bool Class::hasInventoryStore(const Ptr& ptr) const bool Class::hasInventoryStore(const ConstPtr& ptr) const
{ {
return false; return false;
} }
@ -320,7 +315,7 @@ namespace MWWorld
return false; return false;
} }
void Class::getModelsToPreload(const Ptr& ptr, std::vector<std::string>& models) const void Class::getModelsToPreload(const ConstPtr& ptr, std::vector<std::string>& models) const
{ {
std::string model = getModel(ptr); std::string model = getModel(ptr);
if (!model.empty()) if (!model.empty())

View file

@ -151,10 +151,6 @@ namespace MWWorld
/// actor responsible for the attack. \a successful specifies if the hit is /// actor responsible for the attack. \a successful specifies if the hit is
/// successful or not. \a sourceType classifies the damage source. /// successful or not. \a sourceType classifies the damage source.
virtual void block(const Ptr& ptr) const;
///< Play the appropriate sound for a blocked attack, depending on the currently equipped shield
/// (default implementation: throw an exception)
virtual std::unique_ptr<Action> activate(const Ptr& ptr, const Ptr& actor) const; virtual std::unique_ptr<Action> activate(const Ptr& ptr, const Ptr& actor) const;
///< Generate action for activation (default implementation: return a null action). ///< Generate action for activation (default implementation: return a null action).
@ -170,7 +166,7 @@ namespace MWWorld
///< Return inventory store or throw an exception, if class does not have a ///< Return inventory store or throw an exception, if class does not have a
/// inventory store (default implementation: throw an exception) /// inventory store (default implementation: throw an exception)
virtual bool hasInventoryStore(const Ptr& ptr) const; virtual bool hasInventoryStore(const ConstPtr& ptr) const;
///< Does this object have an inventory store, i.e. equipment slots? (default implementation: false) ///< Does this object have an inventory store, i.e. equipment slots? (default implementation: false)
virtual bool canLock(const ConstPtr& ptr) const; virtual bool canLock(const ConstPtr& ptr) const;
@ -284,7 +280,7 @@ namespace MWWorld
virtual bool useAnim() const; virtual bool useAnim() const;
///< Whether or not to use animated variant of model (default false) ///< Whether or not to use animated variant of model (default false)
virtual void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector<std::string>& models) const; virtual void getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector<std::string>& models) const;
///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation:
///< list getModel(). ///< list getModel().

View file

@ -747,21 +747,21 @@ namespace MWWorld
const float dayDuration = adjustedNightStart - mSunriseTime; const float dayDuration = adjustedNightStart - mSunriseTime;
const float nightDuration = 24.f - dayDuration; const float nightDuration = 24.f - dayDuration;
double theta; float orbit;
if (!is_night) if (!is_night)
{ {
theta = static_cast<float>(osg::PI) * (adjustedHour - mSunriseTime) / dayDuration; float t = (adjustedHour - mSunriseTime) / dayDuration;
orbit = 1.f - 2.f * t;
} }
else else
{ {
theta = static_cast<float>(osg::PI) float t = (adjustedHour - adjustedNightStart) / nightDuration;
- static_cast<float>(osg::PI) * (adjustedHour - adjustedNightStart) / nightDuration; orbit = 2.f * t - 1.f;
} }
osg::Vec3f final(static_cast<float>(cos(theta)), // Hardcoded constant from Morrowind
-0.268f, // approx tan( -15 degrees ) const osg::Vec3f sunDir(-400.f * orbit, 75.f, -100.f);
static_cast<float>(sin(theta))); mRendering.setSunDirection(sunDir);
mRendering.setSunDirection(final * -1);
mRendering.setNight(is_night); mRendering.setNight(is_night);
} }

View file

@ -60,6 +60,7 @@
#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/scriptmanager.hpp" #include "../mwbase/scriptmanager.hpp"
#include "../mwbase/soundmanager.hpp" #include "../mwbase/soundmanager.hpp"
#include "../mwbase/statemanager.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
#include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/actorutil.hpp"
@ -997,6 +998,9 @@ namespace MWWorld
{ {
MWWorld::Ptr facedObject; MWWorld::Ptr facedObject;
if (MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame)
return facedObject;
if (MWBase::Environment::get().getWindowManager()->isGuiMode() if (MWBase::Environment::get().getWindowManager()->isGuiMode()
&& MWBase::Environment::get().getWindowManager()->isConsoleMode()) && MWBase::Environment::get().getWindowManager()->isConsoleMode())
facedObject = getFacedObject(getMaxActivationDistance() * 50, false); facedObject = getFacedObject(getMaxActivationDistance() * 50, false);

View file

@ -1,3 +1,4 @@
#include <components/detournavigator/debug.hpp>
#include <components/detournavigator/gettilespositions.hpp> #include <components/detournavigator/gettilespositions.hpp>
#include <components/detournavigator/settings.hpp> #include <components/detournavigator/settings.hpp>
@ -86,4 +87,79 @@ namespace
EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0))); EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0)));
} }
struct TilesPositionsRangeParams
{
TilesPositionsRange mA;
TilesPositionsRange mB;
TilesPositionsRange mResult;
};
struct DetourNavigatorGetIntersectionTest : TestWithParam<TilesPositionsRangeParams>
{
};
TEST_P(DetourNavigatorGetIntersectionTest, should_return_expected_result)
{
EXPECT_EQ(getIntersection(GetParam().mA, GetParam().mB), GetParam().mResult);
EXPECT_EQ(getIntersection(GetParam().mB, GetParam().mA), GetParam().mResult);
}
const TilesPositionsRangeParams getIntersectionParams[] = {
{ .mA = TilesPositionsRange{}, .mB = TilesPositionsRange{}, .mResult = TilesPositionsRange{} },
{
.mA = TilesPositionsRange{ .mBegin = TilePosition{ 0, 0 }, .mEnd = TilePosition{ 2, 2 } },
.mB = TilesPositionsRange{ .mBegin = TilePosition{ 1, 1 }, .mEnd = TilePosition{ 3, 3 } },
.mResult = TilesPositionsRange{ .mBegin = TilePosition{ 1, 1 }, .mEnd = TilePosition{ 2, 2 } },
},
{
.mA = TilesPositionsRange{ .mBegin = TilePosition{ 0, 0 }, .mEnd = TilePosition{ 1, 1 } },
.mB = TilesPositionsRange{ .mBegin = TilePosition{ 2, 2 }, .mEnd = TilePosition{ 3, 3 } },
.mResult = TilesPositionsRange{},
},
{
.mA = TilesPositionsRange{ .mBegin = TilePosition{ 0, 0 }, .mEnd = TilePosition{ 1, 1 } },
.mB = TilesPositionsRange{ .mBegin = TilePosition{ 1, 1 }, .mEnd = TilePosition{ 2, 2 } },
.mResult = TilesPositionsRange{ .mBegin = TilePosition{ 1, 1 }, .mEnd = TilePosition{ 1, 1 } },
},
{
.mA = TilesPositionsRange{ .mBegin = TilePosition{ 0, 0 }, .mEnd = TilePosition{ 1, 1 } },
.mB = TilesPositionsRange{ .mBegin = TilePosition{ 0, 2 }, .mEnd = TilePosition{ 3, 3 } },
.mResult = TilesPositionsRange{},
},
{
.mA = TilesPositionsRange{ .mBegin = TilePosition{ 0, 0 }, .mEnd = TilePosition{ 1, 1 } },
.mB = TilesPositionsRange{ .mBegin = TilePosition{ 2, 0 }, .mEnd = TilePosition{ 3, 3 } },
.mResult = TilesPositionsRange{},
},
};
INSTANTIATE_TEST_SUITE_P(
GetIntersectionParams, DetourNavigatorGetIntersectionTest, ValuesIn(getIntersectionParams));
struct DetourNavigatorGetUnionTest : TestWithParam<TilesPositionsRangeParams>
{
};
TEST_P(DetourNavigatorGetUnionTest, should_return_expected_result)
{
EXPECT_EQ(getUnion(GetParam().mA, GetParam().mB), GetParam().mResult);
EXPECT_EQ(getUnion(GetParam().mB, GetParam().mA), GetParam().mResult);
}
const TilesPositionsRangeParams getUnionParams[] = {
{ .mA = TilesPositionsRange{}, .mB = TilesPositionsRange{}, .mResult = TilesPositionsRange{} },
{
.mA = TilesPositionsRange{ .mBegin = TilePosition{ 0, 0 }, .mEnd = TilePosition{ 2, 2 } },
.mB = TilesPositionsRange{ .mBegin = TilePosition{ 1, 1 }, .mEnd = TilePosition{ 3, 3 } },
.mResult = TilesPositionsRange{ .mBegin = TilePosition{ 0, 0 }, .mEnd = TilePosition{ 3, 3 } },
},
{
.mA = TilesPositionsRange{ .mBegin = TilePosition{ 0, 0 }, .mEnd = TilePosition{ 1, 1 } },
.mB = TilesPositionsRange{ .mBegin = TilePosition{ 1, 1 }, .mEnd = TilePosition{ 2, 2 } },
.mResult = TilesPositionsRange{ .mBegin = TilePosition{ 0, 0 }, .mEnd = TilePosition{ 2, 2 } },
},
};
INSTANTIATE_TEST_SUITE_P(GetUnionParams, DetourNavigatorGetUnionTest, ValuesIn(getUnionParams));
} }

View file

@ -39,6 +39,8 @@ namespace
using namespace DetourNavigator; using namespace DetourNavigator;
using namespace DetourNavigator::Tests; using namespace DetourNavigator::Tests;
constexpr int heightfieldTileSize = ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1);
struct DetourNavigatorNavigatorTest : Test struct DetourNavigatorNavigatorTest : Test
{ {
Settings mSettings = makeSettings(); Settings mSettings = makeSettings();
@ -53,7 +55,6 @@ namespace
AreaCosts mAreaCosts; AreaCosts mAreaCosts;
Loading::Listener mListener; Loading::Listener mListener;
const osg::Vec2i mCellPosition{ 0, 0 }; const osg::Vec2i mCellPosition{ 0, 0 };
const int mHeightfieldTileSize = ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1);
const float mEndTolerance = 0; const float mEndTolerance = 0;
const btTransform mTransform{ btMatrix3x3::getIdentity(), btVector3(256, 256, 0) }; const btTransform mTransform{ btMatrix3x3::getIdentity(), btVector3(256, 256, 0) };
const ObjectTransform mObjectTransform{ ESM::Position{ { 256, 256, 0 }, { 0, 0, 0 } }, 0.0f }; const ObjectTransform mObjectTransform{ ESM::Position{ { 256, 256, 0 }, { 0, 0, 0 } }, 0.0f };
@ -129,7 +130,7 @@ namespace
{ {
} }
T& shape() { return static_cast<T&>(*mInstance->mCollisionShape); } T& shape() const { return static_cast<T&>(*mInstance->mCollisionShape); }
const osg::ref_ptr<const Resource::BulletShapeInstance>& instance() const { return mInstance; } const osg::ref_ptr<const Resource::BulletShapeInstance>& instance() const { return mInstance; }
private: private:
@ -167,7 +168,7 @@ namespace
TEST_F(DetourNavigatorNavigatorTest, update_then_find_path_should_return_path) TEST_F(DetourNavigatorNavigatorTest, update_then_find_path_should_return_path)
{ {
const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData);
const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); const int cellSize = heightfieldTileSize * static_cast<int>(surface.mSize - 1);
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
auto updateGuard = mNavigator->makeUpdateGuard(); auto updateGuard = mNavigator->makeUpdateGuard();
@ -189,7 +190,7 @@ namespace
TEST_F(DetourNavigatorNavigatorTest, find_path_to_the_start_position_should_contain_single_point) TEST_F(DetourNavigatorNavigatorTest, find_path_to_the_start_position_should_contain_single_point)
{ {
const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData);
const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); const int cellSize = heightfieldTileSize * static_cast<int>(surface.mSize - 1);
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
auto updateGuard = mNavigator->makeUpdateGuard(); auto updateGuard = mNavigator->makeUpdateGuard();
@ -211,7 +212,7 @@ namespace
mSettings, std::make_unique<NavMeshDb>(":memory:", std::numeric_limits<std::uint64_t>::max()))); mSettings, std::make_unique<NavMeshDb>(":memory:", std::numeric_limits<std::uint64_t>::max())));
const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData);
const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); const int cellSize = heightfieldTileSize * static_cast<int>(surface.mSize - 1);
CollisionShapeInstance compound(std::make_unique<btCompoundShape>()); CollisionShapeInstance compound(std::make_unique<btCompoundShape>());
compound.shape().addChildShape( compound.shape().addChildShape(
@ -256,7 +257,7 @@ namespace
TEST_F(DetourNavigatorNavigatorTest, update_changed_object_should_change_navmesh) TEST_F(DetourNavigatorNavigatorTest, update_changed_object_should_change_navmesh)
{ {
const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData);
const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); const int cellSize = heightfieldTileSize * static_cast<int>(surface.mSize - 1);
CollisionShapeInstance compound(std::make_unique<btCompoundShape>()); CollisionShapeInstance compound(std::make_unique<btCompoundShape>());
compound.shape().addChildShape( compound.shape().addChildShape(
@ -335,7 +336,7 @@ namespace
TEST_F(DetourNavigatorNavigatorTest, only_one_heightfield_per_cell_is_allowed) TEST_F(DetourNavigatorNavigatorTest, only_one_heightfield_per_cell_is_allowed)
{ {
const HeightfieldSurface surface1 = makeSquareHeightfieldSurface(defaultHeightfieldData); const HeightfieldSurface surface1 = makeSquareHeightfieldSurface(defaultHeightfieldData);
const int cellSize1 = mHeightfieldTileSize * (surface1.mSize - 1); const int cellSize1 = heightfieldTileSize * (surface1.mSize - 1);
const std::array<float, 5 * 5> heightfieldData2{ { const std::array<float, 5 * 5> heightfieldData2{ {
-25, -25, -25, -25, -25, // row 0 -25, -25, -25, -25, -25, // row 0
@ -345,7 +346,7 @@ namespace
-25, -25, -25, -25, -25, // row 4 -25, -25, -25, -25, -25, // row 4
} }; } };
const HeightfieldSurface surface2 = makeSquareHeightfieldSurface(heightfieldData2); const HeightfieldSurface surface2 = makeSquareHeightfieldSurface(heightfieldData2);
const int cellSize2 = mHeightfieldTileSize * (surface2.mSize - 1); const int cellSize2 = heightfieldTileSize * (surface2.mSize - 1);
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
mNavigator->addHeightfield(mCellPosition, cellSize1, surface1, nullptr); mNavigator->addHeightfield(mCellPosition, cellSize1, surface1, nullptr);
@ -412,7 +413,7 @@ namespace
0, -50, -100, -100, -100, // row 4 0, -50, -100, -100, -100, // row 4
} }; } };
const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData);
const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); const int cellSize = heightfieldTileSize * static_cast<int>(surface.mSize - 1);
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
mNavigator->addWater(mCellPosition, cellSize, 300, nullptr); mNavigator->addWater(mCellPosition, cellSize, 300, nullptr);
@ -446,7 +447,7 @@ namespace
0, 0, 0, 0, 0, 0, 0, // row 6 0, 0, 0, 0, 0, 0, 0, // row 6
} }; } };
const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData);
const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); const int cellSize = heightfieldTileSize * static_cast<int>(surface.mSize - 1);
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
mNavigator->addWater(mCellPosition, cellSize, -25, nullptr); mNavigator->addWater(mCellPosition, cellSize, -25, nullptr);
@ -480,7 +481,7 @@ namespace
0, 0, 0, 0, 0, 0, 0, // row 6 0, 0, 0, 0, 0, 0, 0, // row 6
} }; } };
const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData);
const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); const int cellSize = heightfieldTileSize * static_cast<int>(surface.mSize - 1);
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr); mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr);
@ -513,7 +514,7 @@ namespace
0, 0, 0, 0, 0, 0, 0, // row 6 0, 0, 0, 0, 0, 0, 0, // row 6
} }; } };
const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData);
const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); const int cellSize = heightfieldTileSize * static_cast<int>(surface.mSize - 1);
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
mNavigator->addWater(mCellPosition, cellSize, -25, nullptr); mNavigator->addWater(mCellPosition, cellSize, -25, nullptr);
@ -566,7 +567,7 @@ namespace
TEST_F(DetourNavigatorNavigatorTest, update_heightfield_remove_and_update_then_find_path_should_return_path) TEST_F(DetourNavigatorNavigatorTest, update_heightfield_remove_and_update_then_find_path_should_return_path)
{ {
const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData);
const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); const int cellSize = heightfieldTileSize * static_cast<int>(surface.mSize - 1);
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr); mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr);
@ -602,7 +603,7 @@ namespace
0, -25, -100, -100, -100, -100, // row 5 0, -25, -100, -100, -100, -100, // row 5
} }; } };
const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData);
const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); const int cellSize = heightfieldTileSize * static_cast<int>(surface.mSize - 1);
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr); mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr);
@ -629,7 +630,7 @@ namespace
mSettings, std::make_unique<NavMeshDb>(":memory:", std::numeric_limits<std::uint64_t>::max()))); mSettings, std::make_unique<NavMeshDb>(":memory:", std::numeric_limits<std::uint64_t>::max())));
const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData);
const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); const int cellSize = heightfieldTileSize * static_cast<int>(surface.mSize - 1);
const btVector3 shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); const btVector3 shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight);
std::vector<CollisionShapeInstance<btBoxShape>> boxes; std::vector<CollisionShapeInstance<btBoxShape>> boxes;
@ -718,7 +719,7 @@ namespace
TEST_F(DetourNavigatorNavigatorTest, update_then_raycast_should_return_position) TEST_F(DetourNavigatorNavigatorTest, update_then_raycast_should_return_position)
{ {
const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData);
const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); const int cellSize = heightfieldTileSize * static_cast<int>(surface.mSize - 1);
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr); mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr);
@ -737,7 +738,7 @@ namespace
update_for_oscillating_object_that_does_not_change_navmesh_should_not_trigger_navmesh_update) update_for_oscillating_object_that_does_not_change_navmesh_should_not_trigger_navmesh_update)
{ {
const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData);
const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); const int cellSize = heightfieldTileSize * static_cast<int>(surface.mSize - 1);
CollisionShapeInstance oscillatingBox(std::make_unique<btBoxShape>(btVector3(20, 20, 20))); CollisionShapeInstance oscillatingBox(std::make_unique<btBoxShape>(btVector3(20, 20, 20)));
const btVector3 oscillatingBoxShapePosition(288, 288, 400); const btVector3 oscillatingBoxShapePosition(288, 288, 400);
@ -777,7 +778,7 @@ namespace
TEST_F(DetourNavigatorNavigatorTest, should_provide_path_over_flat_heightfield) TEST_F(DetourNavigatorNavigatorTest, should_provide_path_over_flat_heightfield)
{ {
const HeightfieldPlane plane{ 100 }; const HeightfieldPlane plane{ 100 };
const int cellSize = mHeightfieldTileSize * 4; const int cellSize = heightfieldTileSize * 4;
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
mNavigator->addHeightfield(mCellPosition, cellSize, plane, nullptr); mNavigator->addHeightfield(mCellPosition, cellSize, plane, nullptr);
@ -796,7 +797,7 @@ namespace
TEST_F(DetourNavigatorNavigatorTest, for_not_reachable_destination_find_path_should_provide_partial_path) TEST_F(DetourNavigatorNavigatorTest, for_not_reachable_destination_find_path_should_provide_partial_path)
{ {
const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData);
const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); const int cellSize = heightfieldTileSize * static_cast<int>(surface.mSize - 1);
CollisionShapeInstance compound(std::make_unique<btCompoundShape>()); CollisionShapeInstance compound(std::make_unique<btCompoundShape>());
compound.shape().addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(204, -204, 0)), compound.shape().addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(204, -204, 0)),
@ -822,7 +823,7 @@ namespace
TEST_F(DetourNavigatorNavigatorTest, end_tolerance_should_extent_available_destinations) TEST_F(DetourNavigatorNavigatorTest, end_tolerance_should_extent_available_destinations)
{ {
const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData);
const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); const int cellSize = heightfieldTileSize * static_cast<int>(surface.mSize - 1);
CollisionShapeInstance compound(std::make_unique<btCompoundShape>()); CollisionShapeInstance compound(std::make_unique<btCompoundShape>());
compound.shape().addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(204, -204, 0)), compound.shape().addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(204, -204, 0)),
@ -948,7 +949,7 @@ namespace
TEST_F(DetourNavigatorNavigatorTest, find_nearest_nav_mesh_position_should_return_nav_mesh_position) TEST_F(DetourNavigatorNavigatorTest, find_nearest_nav_mesh_position_should_return_nav_mesh_position)
{ {
const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData);
const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); const int cellSize = heightfieldTileSize * static_cast<int>(surface.mSize - 1);
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
auto updateGuard = mNavigator->makeUpdateGuard(); auto updateGuard = mNavigator->makeUpdateGuard();
@ -966,7 +967,7 @@ namespace
TEST_F(DetourNavigatorNavigatorTest, find_nearest_nav_mesh_position_should_return_nullopt_when_too_far) TEST_F(DetourNavigatorNavigatorTest, find_nearest_nav_mesh_position_should_return_nullopt_when_too_far)
{ {
const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData);
const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); const int cellSize = heightfieldTileSize * static_cast<int>(surface.mSize - 1);
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
auto updateGuard = mNavigator->makeUpdateGuard(); auto updateGuard = mNavigator->makeUpdateGuard();
@ -984,7 +985,7 @@ namespace
TEST_F(DetourNavigatorNavigatorTest, find_nearest_nav_mesh_position_should_return_nullopt_when_flags_do_not_match) TEST_F(DetourNavigatorNavigatorTest, find_nearest_nav_mesh_position_should_return_nullopt_when_flags_do_not_match)
{ {
const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData);
const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); const int cellSize = heightfieldTileSize * static_cast<int>(surface.mSize - 1);
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
auto updateGuard = mNavigator->makeUpdateGuard(); auto updateGuard = mNavigator->makeUpdateGuard();
@ -998,4 +999,142 @@ namespace
EXPECT_EQ(findNearestNavMeshPosition(*mNavigator, mAgentBounds, position, searchAreaHalfExtents, Flag_swim), EXPECT_EQ(findNearestNavMeshPosition(*mNavigator, mAgentBounds, position, searchAreaHalfExtents, Flag_swim),
std::nullopt); std::nullopt);
} }
struct DetourNavigatorUpdateTest : TestWithParam<std::function<void(Navigator&)>>
{
};
std::vector<TilePosition> getUsedTiles(const NavMeshCacheItem& navMesh)
{
std::vector<TilePosition> result;
navMesh.forEachUsedTile([&](const TilePosition& position, const auto&...) { result.push_back(position); });
return result;
}
TEST_P(DetourNavigatorUpdateTest, update_should_change_covered_area_when_player_moves)
{
Loading::Listener listener;
Settings settings = makeSettings();
settings.mMaxTilesNumber = 5;
NavigatorImpl navigator(settings, nullptr);
const AgentBounds agentBounds{ CollisionShapeType::Aabb, { 29, 29, 66 } };
ASSERT_TRUE(navigator.addAgent(agentBounds));
GetParam()(navigator);
{
auto updateGuard = navigator.makeUpdateGuard();
navigator.update(osg::Vec3f(3000, 3000, 0), updateGuard.get());
}
navigator.wait(WaitConditionType::allJobsDone, &listener);
{
const auto navMesh = navigator.getNavMesh(agentBounds);
ASSERT_NE(navMesh, nullptr);
const TilePosition expectedTiles[] = { { 3, 4 }, { 4, 3 }, { 4, 4 }, { 4, 5 }, { 5, 4 } };
const auto usedTiles = getUsedTiles(*navMesh->lockConst());
EXPECT_THAT(usedTiles, UnorderedElementsAreArray(expectedTiles)) << usedTiles;
}
{
auto updateGuard = navigator.makeUpdateGuard();
navigator.update(osg::Vec3f(4000, 3000, 0), updateGuard.get());
}
navigator.wait(WaitConditionType::allJobsDone, &listener);
{
const auto navMesh = navigator.getNavMesh(agentBounds);
ASSERT_NE(navMesh, nullptr);
const TilePosition expectedTiles[] = { { 4, 4 }, { 5, 3 }, { 5, 4 }, { 5, 5 }, { 6, 4 } };
const auto usedTiles = getUsedTiles(*navMesh->lockConst());
EXPECT_THAT(usedTiles, UnorderedElementsAreArray(expectedTiles)) << usedTiles;
}
}
struct AddHeightfieldSurface
{
static constexpr std::size_t sSize = 65;
static constexpr float sHeights[sSize * sSize]{};
void operator()(Navigator& navigator) const
{
const osg::Vec2i cellPosition(0, 0);
const HeightfieldSurface surface{
.mHeights = sHeights,
.mSize = sSize,
.mMinHeight = -1,
.mMaxHeight = 1,
};
const int cellSize = heightfieldTileSize * static_cast<int>(surface.mSize - 1);
navigator.addHeightfield(cellPosition, cellSize, surface, nullptr);
}
};
struct AddHeightfieldPlane
{
void operator()(Navigator& navigator) const
{
const osg::Vec2i cellPosition(0, 0);
const HeightfieldPlane plane{ .mHeight = 0 };
const int cellSize = 8192;
navigator.addHeightfield(cellPosition, cellSize, plane, nullptr);
}
};
struct AddWater
{
void operator()(Navigator& navigator) const
{
const osg::Vec2i cellPosition(0, 0);
const float level = 0;
const int cellSize = 8192;
navigator.addWater(cellPosition, cellSize, level, nullptr);
}
};
struct AddObject
{
const float mSize = 8192;
CollisionShapeInstance<btBoxShape> mBox{ std::make_unique<btBoxShape>(btVector3(mSize, mSize, 1)) };
const ObjectTransform mTransform{
.mPosition = ESM::Position{ .pos = { 0, 0, 0 }, .rot{ 0, 0, 0 } },
.mScale = 1.0f,
};
void operator()(Navigator& navigator) const
{
navigator.addObject(ObjectId(&mBox.shape()), ObjectShapes(mBox.instance(), mTransform),
btTransform::getIdentity(), nullptr);
}
};
struct AddAll
{
AddHeightfieldSurface mAddHeightfieldSurface;
AddHeightfieldPlane mAddHeightfieldPlane;
AddWater mAddWater;
AddObject mAddObject;
void operator()(Navigator& navigator) const
{
mAddHeightfieldSurface(navigator);
mAddHeightfieldPlane(navigator);
mAddWater(navigator);
mAddObject(navigator);
}
};
const std::function<void(Navigator&)> addNavMeshData[] = {
AddHeightfieldSurface{},
AddHeightfieldPlane{},
AddWater{},
AddObject{},
AddAll{},
};
INSTANTIATE_TEST_SUITE_P(DifferentNavMeshData, DetourNavigatorUpdateTest, ValuesIn(addNavMeshData));
} }

View file

@ -42,12 +42,24 @@ namespace testing
<< ", " << value.y() << ", " << value.z() << ')'; << ", " << value.y() << ", " << value.z() << ')';
} }
template <>
inline testing::Message& Message::operator<<(const osg::Vec2i& value)
{
return (*this) << "{" << value.x() << ", " << value.y() << '}';
}
template <> template <>
inline testing::Message& Message::operator<<(const Wrapper<osg::Vec3f>& value) inline testing::Message& Message::operator<<(const Wrapper<osg::Vec3f>& value)
{ {
return (*this) << value.mValue; return (*this) << value.mValue;
} }
template <>
inline testing::Message& Message::operator<<(const Wrapper<osg::Vec2i>& value)
{
return (*this) << value.mValue;
}
template <> template <>
inline testing::Message& Message::operator<<(const Wrapper<float>& value) inline testing::Message& Message::operator<<(const Wrapper<float>& value)
{ {
@ -72,6 +84,12 @@ namespace testing
return writeRange(*this, value, 1); return writeRange(*this, value, 1);
} }
template <>
inline testing::Message& Message::operator<<(const std::vector<osg::Vec2i>& value)
{
return writeRange(*this, value, 1);
}
template <> template <>
inline testing::Message& Message::operator<<(const std::vector<float>& value) inline testing::Message& Message::operator<<(const std::vector<float>& value)
{ {

View file

@ -1,4 +1,5 @@
#include <components/esm/fourcc.hpp> #include <components/esm/fourcc.hpp>
#include <components/esm3/aisequence.hpp>
#include <components/esm3/esmreader.hpp> #include <components/esm3/esmreader.hpp>
#include <components/esm3/esmwriter.hpp> #include <components/esm3/esmwriter.hpp>
#include <components/esm3/loadcont.hpp> #include <components/esm3/loadcont.hpp>
@ -410,6 +411,85 @@ namespace ESM
EXPECT_EQ(result.mStringId, record.mStringId); EXPECT_EQ(result.mStringId, record.mStringId);
} }
TEST_P(Esm3SaveLoadRecordTest, aiSequenceAiWanderShouldNotChange)
{
AiSequence::AiWander record;
record.mData.mDistance = 1;
record.mData.mDuration = 2;
record.mData.mTimeOfDay = 3;
constexpr std::uint8_t idle[8] = { 4, 5, 6, 7, 8, 9, 10, 11 };
static_assert(std::size(idle) == std::size(record.mData.mIdle));
std::copy(std::begin(idle), std::end(idle), record.mData.mIdle);
record.mData.mShouldRepeat = 12;
record.mDurationData.mRemainingDuration = 13;
record.mStoredInitialActorPosition = true;
constexpr float initialActorPosition[3] = { 15, 16, 17 };
static_assert(std::size(initialActorPosition) == std::size(record.mInitialActorPosition.mValues));
std::copy(
std::begin(initialActorPosition), std::end(initialActorPosition), record.mInitialActorPosition.mValues);
AiSequence::AiWander result;
saveAndLoadRecord(record, GetParam(), result);
EXPECT_EQ(result.mData.mDistance, record.mData.mDistance);
EXPECT_EQ(result.mData.mDuration, record.mData.mDuration);
EXPECT_EQ(result.mData.mTimeOfDay, record.mData.mTimeOfDay);
EXPECT_THAT(result.mData.mIdle, ElementsAreArray(record.mData.mIdle));
EXPECT_EQ(result.mData.mShouldRepeat, record.mData.mShouldRepeat);
EXPECT_EQ(result.mDurationData.mRemainingDuration, record.mDurationData.mRemainingDuration);
EXPECT_EQ(result.mStoredInitialActorPosition, record.mStoredInitialActorPosition);
EXPECT_THAT(result.mInitialActorPosition.mValues, ElementsAreArray(record.mInitialActorPosition.mValues));
}
TEST_P(Esm3SaveLoadRecordTest, aiSequenceAiTravelShouldNotChange)
{
AiSequence::AiTravel record;
record.mData.mX = 1;
record.mData.mY = 2;
record.mData.mZ = 3;
record.mHidden = true;
record.mRepeat = true;
AiSequence::AiTravel result;
saveAndLoadRecord(record, GetParam(), result);
EXPECT_EQ(result.mData.mX, record.mData.mX);
EXPECT_EQ(result.mData.mY, record.mData.mY);
EXPECT_EQ(result.mData.mZ, record.mData.mZ);
EXPECT_EQ(result.mHidden, record.mHidden);
EXPECT_EQ(result.mRepeat, record.mRepeat);
}
TEST_P(Esm3SaveLoadRecordTest, aiSequenceAiEscortShouldNotChange)
{
AiSequence::AiEscort record;
record.mData.mX = 1;
record.mData.mY = 2;
record.mData.mZ = 3;
record.mData.mDuration = 4;
record.mTargetActorId = 5;
record.mTargetId = generateRandomRefId(32);
record.mCellId = generateRandomString(257);
record.mRemainingDuration = 6;
record.mRepeat = true;
AiSequence::AiEscort result;
saveAndLoadRecord(record, GetParam(), result);
EXPECT_EQ(result.mData.mX, record.mData.mX);
EXPECT_EQ(result.mData.mY, record.mData.mY);
EXPECT_EQ(result.mData.mZ, record.mData.mZ);
if (GetParam() <= MaxOldAiPackageFormatVersion)
EXPECT_EQ(result.mData.mDuration, record.mRemainingDuration);
else
EXPECT_EQ(result.mData.mDuration, record.mData.mDuration);
EXPECT_EQ(result.mTargetActorId, record.mTargetActorId);
EXPECT_EQ(result.mTargetId, record.mTargetId);
EXPECT_EQ(result.mCellId, record.mCellId);
EXPECT_EQ(result.mRemainingDuration, record.mRemainingDuration);
EXPECT_EQ(result.mRepeat, record.mRepeat);
}
INSTANTIATE_TEST_SUITE_P(FormatVersions, Esm3SaveLoadRecordTest, ValuesIn(getFormats())); INSTANTIATE_TEST_SUITE_P(FormatVersions, Esm3SaveLoadRecordTest, ValuesIn(getFormats()));
} }
} }

View file

@ -6,6 +6,7 @@
#include <components/misc/strings/conversion.hpp> #include <components/misc/strings/conversion.hpp>
#include <components/vfs/archive.hpp> #include <components/vfs/archive.hpp>
#include <components/vfs/file.hpp>
#include <components/vfs/manager.hpp> #include <components/vfs/manager.hpp>
#include <components/vfs/pathutil.hpp> #include <components/vfs/pathutil.hpp>
@ -60,7 +61,7 @@ namespace TestingOpenMW
void listResources(VFS::FileMap& out) override void listResources(VFS::FileMap& out) override
{ {
for (const auto& [key, value] : mFiles) for (const auto& [key, value] : mFiles)
out.emplace(VFS::Path::normalizeFilename(key), value); out.emplace(key, value);
} }
bool contains(std::string_view file) const override { return mFiles.contains(file); } bool contains(std::string_view file) const override { return mFiles.contains(file); }

View file

@ -810,8 +810,7 @@ bool Wizard::UnshieldWorker::extractFile(
if (!dir.mkpath(path)) if (!dir.mkpath(path))
return false; return false;
QString fileName(path); path.append(QString::fromUtf8(unshield_file_name(unshield, index)));
fileName.append(QString::fromUtf8(unshield_file_name(unshield, index)));
// Calculate the percentage done // Calculate the percentage done
int progress = (((float)counter / (float)unshield_file_count(unshield)) * 100); int progress = (((float)counter / (float)unshield_file_count(unshield)) * 100);
@ -825,13 +824,13 @@ bool Wizard::UnshieldWorker::extractFile(
emit textChanged(tr("Extracting: %1").arg(QString::fromUtf8(unshield_file_name(unshield, index)))); emit textChanged(tr("Extracting: %1").arg(QString::fromUtf8(unshield_file_name(unshield, index))));
emit progressChanged(progress); emit progressChanged(progress);
QByteArray array(fileName.toUtf8()); QByteArray array(path.toUtf8());
success = unshield_file_save(unshield, index, array.constData()); success = unshield_file_save(unshield, index, array.constData());
if (!success) if (!success)
{ {
qDebug() << "error"; qDebug() << "error";
dir.remove(fileName); dir.remove(path);
} }
return success; return success;

View file

@ -76,7 +76,7 @@ namespace Bsa
fail("Corrupted BSA"); fail("Corrupted BSA");
} }
mFolders[dirHash][{ nameHash, extHash }] = file; mFolders[dirHash][{ nameHash, extHash }] = std::move(file);
FileStruct fileStruct{}; FileStruct fileStruct{};
mFiles.push_back(fileStruct); mFiles.push_back(fileStruct);
@ -177,7 +177,7 @@ namespace Bsa
return std::nullopt; // folder not found return std::nullopt; // folder not found
uint32_t fileHash = generateHash(fileName); uint32_t fileHash = generateHash(fileName);
uint32_t extHash = *reinterpret_cast<const uint32_t*>(ext.data() + 1); uint32_t extHash = generateExtensionHash(ext);
auto iter = it->second.find({ fileHash, extHash }); auto iter = it->second.find({ fileHash, extHash });
if (iter == it->second.end()) if (iter == it->second.end())
return std::nullopt; // file not found return std::nullopt; // file not found

View file

@ -46,4 +46,12 @@ namespace Bsa
return result; return result;
} }
uint32_t generateExtensionHash(std::string_view extension)
{
uint32_t result = 0;
for (size_t i = 0; i < 4 && i < extension.size() - 1; i++)
result |= static_cast<uint8_t>(extension[i + 1]) << (8 * i);
return result;
}
} // namespace Bsa } // namespace Bsa

View file

@ -7,6 +7,7 @@
namespace Bsa namespace Bsa
{ {
uint32_t generateHash(const std::string& name); uint32_t generateHash(const std::string& name);
uint32_t generateExtensionHash(std::string_view extension);
} }
#endif #endif

View file

@ -172,7 +172,7 @@ namespace Bsa
return FileRecord(); // folder not found, return default which has offset of sInvalidOffset return FileRecord(); // folder not found, return default which has offset of sInvalidOffset
uint32_t fileHash = generateHash(fileName); uint32_t fileHash = generateHash(fileName);
uint32_t extHash = *reinterpret_cast<const uint32_t*>(ext.data() + 1); uint32_t extHash = generateExtensionHash(ext);
auto iter = it->second.find({ fileHash, extHash }); auto iter = it->second.find({ fileHash, extHash });
if (iter == it->second.end()) if (iter == it->second.end())
return FileRecord(); // file not found, return default which has offset of sInvalidOffset return FileRecord(); // file not found, return default which has offset of sInvalidOffset

View file

@ -371,7 +371,7 @@ bool Config::GameSettings::writeFileWithComments(QFile& file)
{ {
if ((keyMatch.captured(1) + "=" + keyMatch.captured(2)) == keyVal) if ((keyMatch.captured(1) + "=" + keyMatch.captured(2)) == keyVal)
{ {
*iter = settingLine; *iter = std::move(settingLine);
break; break;
} }
} }

View file

@ -601,7 +601,7 @@ namespace DetourNavigator
if (mSettings.get().mEnableRecastMeshFileNameRevision) if (mSettings.get().mEnableRecastMeshFileNameRevision)
recastMeshRevision = revision; recastMeshRevision = revision;
if (mSettings.get().mEnableNavMeshFileNameRevision) if (mSettings.get().mEnableNavMeshFileNameRevision)
navMeshRevision = revision; navMeshRevision = std::move(revision);
} }
if (recastMesh && mSettings.get().mEnableWriteRecastMeshToFile) if (recastMesh && mSettings.get().mEnableWriteRecastMeshToFile)
writeToFile(*recastMesh, writeToFile(*recastMesh,

View file

@ -76,4 +76,13 @@ namespace DetourNavigator
return {}; return {};
return TilesPositionsRange{ TilePosition(beginX, beginY), TilePosition(endX, endY) }; return TilesPositionsRange{ TilePosition(beginX, beginY), TilePosition(endX, endY) };
} }
TilesPositionsRange getUnion(const TilesPositionsRange& a, const TilesPositionsRange& b) noexcept
{
const int beginX = std::min(a.mBegin.x(), b.mBegin.x());
const int endX = std::max(a.mEnd.x(), b.mEnd.x());
const int beginY = std::min(a.mBegin.y(), b.mBegin.y());
const int endY = std::max(a.mEnd.y(), b.mEnd.y());
return TilesPositionsRange{ .mBegin = TilePosition(beginX, beginY), .mEnd = TilePosition(endX, endY) };
}
} }

View file

@ -50,6 +50,8 @@ namespace DetourNavigator
} }
TilesPositionsRange getIntersection(const TilesPositionsRange& a, const TilesPositionsRange& b) noexcept; TilesPositionsRange getIntersection(const TilesPositionsRange& a, const TilesPositionsRange& b) noexcept;
TilesPositionsRange getUnion(const TilesPositionsRange& a, const TilesPositionsRange& b) noexcept;
} }
#endif #endif

View file

@ -166,6 +166,7 @@ namespace DetourNavigator
return; return;
mLastRecastMeshManagerRevision = mRecastMeshManager.getRevision(); mLastRecastMeshManagerRevision = mRecastMeshManager.getRevision();
mPlayerTile = playerTile; mPlayerTile = playerTile;
mRecastMeshManager.setRange(makeRange(playerTile, mSettings.mMaxTilesNumber), guard);
const auto changedTiles = mRecastMeshManager.takeChangedTiles(guard); const auto changedTiles = mRecastMeshManager.takeChangedTiles(guard);
const TilesPositionsRange range = mRecastMeshManager.getLimitedObjectsRange(); const TilesPositionsRange range = mRecastMeshManager.getLimitedObjectsRange();
for (const auto& [agentBounds, cached] : mCache) for (const auto& [agentBounds, cached] : mCache)

View file

@ -54,6 +54,15 @@ namespace DetourNavigator
private: private:
const std::optional<std::unique_lock<Mutex>> mImpl; const std::optional<std::unique_lock<Mutex>> mImpl;
}; };
TilesPositionsRange getIndexRange(const auto& index)
{
const auto bounds = index.bounds();
return TilesPositionsRange{
.mBegin = makeTilePosition(bounds.min_corner()),
.mEnd = makeTilePosition(bounds.max_corner()) + TilePosition(1, 1),
};
}
} }
TileCachedRecastMeshManager::TileCachedRecastMeshManager(const RecastSettings& settings) TileCachedRecastMeshManager::TileCachedRecastMeshManager(const RecastSettings& settings)
@ -104,14 +113,28 @@ namespace DetourNavigator
TilesPositionsRange TileCachedRecastMeshManager::getLimitedObjectsRange() const TilesPositionsRange TileCachedRecastMeshManager::getLimitedObjectsRange() const
{ {
if (mObjects.empty()) std::optional<TilesPositionsRange> result;
return {}; if (!mWater.empty())
const auto bounds = mObjectIndex.bounds(); result = getIndexRange(mWaterIndex);
const TilesPositionsRange objectsRange{ if (!mHeightfields.empty())
.mBegin = makeTilePosition(bounds.min_corner()), {
.mEnd = makeTilePosition(bounds.max_corner()) + TilePosition(1, 1), const TilesPositionsRange range = getIndexRange(mHeightfieldIndex);
}; if (result.has_value())
return getIntersection(mRange, objectsRange); result = getUnion(*result, range);
else
result = range;
}
if (!mObjects.empty())
{
const TilesPositionsRange range = getIndexRange(mObjectIndex);
if (result.has_value())
result = getUnion(*result, range);
else
result = range;
}
if (result.has_value())
return getIntersection(mRange, *result);
return {};
} }
void TileCachedRecastMeshManager::setWorldspace(std::string_view worldspace, const UpdateGuard* guard) void TileCachedRecastMeshManager::setWorldspace(std::string_view worldspace, const UpdateGuard* guard)

View file

@ -0,0 +1,10 @@
#ifndef OPENMW_COMPONENTS_ESM_DECOMPOSE_H
#define OPENMW_COMPONENTS_ESM_DECOMPOSE_H
namespace ESM
{
template <class T>
void decompose(T&& value, const auto& apply) = delete;
}
#endif

View file

@ -3,32 +3,58 @@
#include "esmreader.hpp" #include "esmreader.hpp"
#include "esmwriter.hpp" #include "esmwriter.hpp"
#include <components/misc/concepts.hpp>
#include <algorithm> #include <algorithm>
#include <memory> #include <memory>
namespace ESM namespace ESM
{ {
template <Misc::SameAsWithoutCvref<AiSequence::AiWanderData> T>
void decompose(T&& v, const auto& f)
{
f(v.mDistance, v.mDuration, v.mTimeOfDay, v.mIdle, v.mShouldRepeat);
}
template <Misc::SameAsWithoutCvref<AiSequence::AiWanderDuration> T>
void decompose(T&& v, const auto& f)
{
std::uint32_t unused = 0;
f(v.mRemainingDuration, unused);
}
template <Misc::SameAsWithoutCvref<AiSequence::AiTravelData> T>
void decompose(T&& v, const auto& f)
{
f(v.mX, v.mY, v.mZ);
}
template <Misc::SameAsWithoutCvref<AiSequence::AiEscortData> T>
void decompose(T&& v, const auto& f)
{
f(v.mX, v.mY, v.mZ, v.mDuration);
}
namespace AiSequence namespace AiSequence
{ {
void AiWander::load(ESMReader& esm) void AiWander::load(ESMReader& esm)
{ {
esm.getHNT("DATA", mData.mDistance, mData.mDuration, mData.mTimeOfDay, mData.mIdle, mData.mShouldRepeat); esm.getNamedComposite("DATA", mData);
esm.getHNT("STAR", mDurationData.mRemainingDuration, mDurationData.unused); // was mStartTime esm.getNamedComposite("STAR", mDurationData); // was mStartTime
mStoredInitialActorPosition = esm.getHNOT("POS_", mInitialActorPosition.mValues); mStoredInitialActorPosition = esm.getHNOT("POS_", mInitialActorPosition.mValues);
} }
void AiWander::save(ESMWriter& esm) const void AiWander::save(ESMWriter& esm) const
{ {
esm.writeHNT("DATA", mData); esm.writeNamedComposite("DATA", mData);
esm.writeHNT("STAR", mDurationData); esm.writeNamedComposite("STAR", mDurationData); // was mStartTime
if (mStoredInitialActorPosition) if (mStoredInitialActorPosition)
esm.writeHNT("POS_", mInitialActorPosition); esm.writeHNT("POS_", mInitialActorPosition.mValues);
} }
void AiTravel::load(ESMReader& esm) void AiTravel::load(ESMReader& esm)
{ {
esm.getHNT("DATA", mData.mX, mData.mY, mData.mZ); esm.getNamedComposite("DATA", mData);
esm.getHNT(mHidden, "HIDD"); esm.getHNT(mHidden, "HIDD");
mRepeat = false; mRepeat = false;
esm.getHNOT(mRepeat, "REPT"); esm.getHNOT(mRepeat, "REPT");
@ -36,7 +62,7 @@ namespace ESM
void AiTravel::save(ESMWriter& esm) const void AiTravel::save(ESMWriter& esm) const
{ {
esm.writeHNT("DATA", mData); esm.writeNamedComposite("DATA", mData);
esm.writeHNT("HIDD", mHidden); esm.writeHNT("HIDD", mHidden);
if (mRepeat) if (mRepeat)
esm.writeHNT("REPT", mRepeat); esm.writeHNT("REPT", mRepeat);
@ -44,7 +70,7 @@ namespace ESM
void AiEscort::load(ESMReader& esm) void AiEscort::load(ESMReader& esm)
{ {
esm.getHNT("DATA", mData.mX, mData.mY, mData.mZ, mData.mDuration); esm.getNamedComposite("DATA", mData);
mTargetId = esm.getHNRefId("TARG"); mTargetId = esm.getHNRefId("TARG");
mTargetActorId = -1; mTargetActorId = -1;
esm.getHNOT(mTargetActorId, "TAID"); esm.getHNOT(mTargetActorId, "TAID");
@ -64,7 +90,7 @@ namespace ESM
void AiEscort::save(ESMWriter& esm) const void AiEscort::save(ESMWriter& esm) const
{ {
esm.writeHNT("DATA", mData); esm.writeNamedComposite("DATA", mData);
esm.writeHNRefId("TARG", mTargetId); esm.writeHNRefId("TARG", mTargetId);
esm.writeHNT("TAID", mTargetActorId); esm.writeHNT("TAID", mTargetActorId);
esm.writeHNT("DURA", mRemainingDuration); esm.writeHNT("DURA", mRemainingDuration);
@ -76,7 +102,7 @@ namespace ESM
void AiFollow::load(ESMReader& esm) void AiFollow::load(ESMReader& esm)
{ {
esm.getHNT("DATA", mData.mX, mData.mY, mData.mZ, mData.mDuration); esm.getNamedComposite("DATA", mData);
mTargetId = esm.getHNRefId("TARG"); mTargetId = esm.getHNRefId("TARG");
mTargetActorId = -1; mTargetActorId = -1;
esm.getHNOT(mTargetActorId, "TAID"); esm.getHNOT(mTargetActorId, "TAID");
@ -101,7 +127,7 @@ namespace ESM
void AiFollow::save(ESMWriter& esm) const void AiFollow::save(ESMWriter& esm) const
{ {
esm.writeHNT("DATA", mData); esm.writeNamedComposite("DATA", mData);
esm.writeHNRefId("TARG", mTargetId); esm.writeHNRefId("TARG", mTargetId);
esm.writeHNT("TAID", mTargetActorId); esm.writeHNT("TAID", mTargetActorId);
esm.writeHNT("DURA", mRemainingDuration); esm.writeHNT("DURA", mRemainingDuration);

View file

@ -36,32 +36,31 @@ namespace ESM
virtual ~AiPackage() {} virtual ~AiPackage() {}
}; };
#pragma pack(push, 1)
struct AiWanderData struct AiWanderData
{ {
int16_t mDistance; int16_t mDistance;
int16_t mDuration; int16_t mDuration;
unsigned char mTimeOfDay; std::uint8_t mTimeOfDay;
unsigned char mIdle[8]; std::uint8_t mIdle[8];
unsigned char mShouldRepeat; std::uint8_t mShouldRepeat;
}; };
struct AiWanderDuration struct AiWanderDuration
{ {
float mRemainingDuration; float mRemainingDuration;
int32_t unused;
}; };
struct AiTravelData struct AiTravelData
{ {
float mX, mY, mZ; float mX, mY, mZ;
}; };
struct AiEscortData struct AiEscortData
{ {
float mX, mY, mZ; float mX, mY, mZ;
int16_t mDuration; int16_t mDuration;
}; };
#pragma pack(pop)
struct AiWander : AiPackage struct AiWander : AiPackage
{ {
AiWanderData mData; AiWanderData mData;

View file

@ -12,8 +12,10 @@
#include <components/to_utf8/to_utf8.hpp> #include <components/to_utf8/to_utf8.hpp>
#include "components/esm/decompose.hpp"
#include "components/esm/esmcommon.hpp" #include "components/esm/esmcommon.hpp"
#include "components/esm/refid.hpp" #include "components/esm/refid.hpp"
#include "loadtes3.hpp" #include "loadtes3.hpp"
namespace ESM namespace ESM
@ -177,6 +179,16 @@ namespace ESM
(getT(args), ...); (getT(args), ...);
} }
void getNamedComposite(NAME name, auto& value)
{
decompose(value, [&](auto&... args) { getHNT(name, args...); });
}
void getComposite(auto& value)
{
decompose(value, [&](auto&... args) { (getT(args), ...); });
}
template <typename T, typename = std::enable_if_t<IsReadable<T>>> template <typename T, typename = std::enable_if_t<IsReadable<T>>>
void skipHT() void skipHT()
{ {

View file

@ -5,6 +5,7 @@
#include <list> #include <list>
#include <type_traits> #include <type_traits>
#include "components/esm/decompose.hpp"
#include "components/esm/esmcommon.hpp" #include "components/esm/esmcommon.hpp"
#include "components/esm/refid.hpp" #include "components/esm/refid.hpp"
@ -121,6 +122,20 @@ namespace ESM
endRecord(name); endRecord(name);
} }
void writeNamedComposite(NAME name, const auto& value)
{
decompose(value, [&](const auto&... args) {
startSubRecord(name);
(writeT(args), ...);
endRecord(name);
});
}
void writeComposite(const auto& value)
{
decompose(value, [&](const auto&... args) { (writeT(args), ...); });
}
// Prevent using writeHNT with strings. This already happened by accident and results in // Prevent using writeHNT with strings. This already happened by accident and results in
// state being discarded without any error on writing or reading it. :( // state being discarded without any error on writing or reading it. :(
// writeHNString and friends must be used instead. // writeHNString and friends must be used instead.
@ -132,7 +147,7 @@ namespace ESM
void writeHNT(NAME name, const T (&data)[size], int) = delete; void writeHNT(NAME name, const T (&data)[size], int) = delete;
template <typename T> template <typename T>
void writeHNT(NAME name, const T& data, int size) void writeHNT(NAME name, const T& data, std::size_t size)
{ {
startSubRecord(name); startSubRecord(name);
writeT(data, size); writeT(data, size);

View file

@ -74,7 +74,7 @@ namespace ESM
esm.getHNT(multiplier, "MULT"); esm.getHNT(multiplier, "MULT");
params.emplace_back(rand, multiplier); params.emplace_back(rand, multiplier);
} }
mPermanentMagicEffectMagnitudes[id] = params; mPermanentMagicEffectMagnitudes[id] = std::move(params);
} }
while (esm.isNextSub("EQUI")) while (esm.isNextSub("EQUI"))

View file

@ -4,12 +4,19 @@
#include <sstream> #include <sstream>
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include <components/misc/concepts.hpp>
#include "esmreader.hpp" #include "esmreader.hpp"
#include "esmwriter.hpp" #include "esmwriter.hpp"
namespace ESM namespace ESM
{ {
template <Misc::SameAsWithoutCvref<Script::SCHDstruct> T>
void decompose(T&& v, const auto& f)
{
f(v.mNumShorts, v.mNumLongs, v.mNumFloats, v.mScriptDataSize, v.mStringTableSize);
}
void Script::loadSCVR(ESMReader& esm) void Script::loadSCVR(ESMReader& esm)
{ {
uint32_t s = mData.mStringTableSize; uint32_t s = mData.mStringTableSize;
@ -99,11 +106,7 @@ namespace ESM
{ {
esm.getSubHeader(); esm.getSubHeader();
mId = esm.getMaybeFixedRefIdSize(32); mId = esm.getMaybeFixedRefIdSize(32);
esm.getT(mData.mNumShorts); esm.getComposite(mData);
esm.getT(mData.mNumLongs);
esm.getT(mData.mNumFloats);
esm.getT(mData.mScriptDataSize);
esm.getT(mData.mStringTableSize);
hasHeader = true; hasHeader = true;
break; break;
@ -157,7 +160,7 @@ namespace ESM
esm.startSubRecord("SCHD"); esm.startSubRecord("SCHD");
esm.writeMaybeFixedSizeRefId(mId, 32); esm.writeMaybeFixedSizeRefId(mId, 32);
esm.writeT(mData, 20); esm.writeComposite(mData);
esm.endRecord("SCHD"); esm.endRecord("SCHD");
if (isDeleted) if (isDeleted)

View file

@ -3,6 +3,8 @@
#include "esmreader.hpp" #include "esmreader.hpp"
#include "esmwriter.hpp" #include "esmwriter.hpp"
#include "../misc/algorithm.hpp"
namespace ESM namespace ESM
{ {
void SavedGame::load(ESMReader& esm) void SavedGame::load(ESMReader& esm)
@ -67,7 +69,9 @@ namespace ESM
std::vector<std::string_view> missingFiles; std::vector<std::string_view> missingFiles;
for (const std::string& contentFile : mContentFiles) for (const std::string& contentFile : mContentFiles)
{ {
if (std::find(allContentFiles.begin(), allContentFiles.end(), contentFile) == allContentFiles.end()) auto it = std::find_if(allContentFiles.begin(), allContentFiles.end(),
[&](const std::string& file) { return Misc::StringUtils::ciEqual(file, contentFile); });
if (it == allContentFiles.end())
{ {
missingFiles.emplace_back(contentFile); missingFiles.emplace_back(contentFile);
} }

Some files were not shown because too many files have changed in this diff Show more