1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-03-17 19:20:49 +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 #4816: GetWeaponDrawn returns 1 before weapon is attached
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 #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 #5129: Stuttering animation on Centurion Archer
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 #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 #7765: OpenMW-CS: Touch Record option is broken
Bug #7770: Sword of the Perithia: Script execution failure
Bug #7780: Non-ASCII texture paths in NIF files don't work
Feature #2566: Handle NAM9 records for manual cell references
Feature #3537: Shader-based water ripples
Feature #5173: Support for NiFogProperty

View File

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

View File

@ -85,7 +85,7 @@ namespace ESSImport
Misc::StringUtils::lowerCaseInPlace(group);
ESM::AnimationState::ScriptedAnimation scriptedAnim;
scriptedAnim.mGroup = group;
scriptedAnim.mGroup = std::move(group);
scriptedAnim.mTime = anis.mTime;
scriptedAnim.mAbsolute = true;
// 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);
}
newcell.mRefs = cellrefs;
newcell.mRefs = std::move(cellrefs);
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
mIntCells[cell.mName] = newcell;
mIntCells[cell.mName] = std::move(newcell);
}
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(screenComboBox, qOverload<int>(&QComboBox::currentIndexChanged), this, &GraphicsPage::screenChanged);
connect(framerateLimitCheckBox, &QCheckBox::toggled, this, &GraphicsPage::slotFramerateLimitToggled);
connect(shadowDistanceCheckBox, &QCheckBox::toggled, this, &GraphicsPage::slotShadowDistLimitToggled);
}
bool Launcher::GraphicsPage::setupSDL()
@ -126,58 +125,6 @@ bool Launcher::GraphicsPage::loadSettings()
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;
}
@ -220,53 +167,6 @@ void Launcher::GraphicsPage::saveSettings()
{
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)
@ -377,9 +277,3 @@ void Launcher::GraphicsPage::slotFramerateLimitToggled(bool 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 slotStandardToggled(bool checked);
void slotFramerateLimitToggled(bool checked);
void slotShadowDistLimitToggled(bool checked);
private:
QVector<QStringList> mResolutionsPerScreen;

View File

@ -212,6 +212,65 @@ bool Launcher::SettingsPage::loadSettings()
loadSettingBool(Settings::fog().mExponentialFog, *exponentialFogCheckBox);
loadSettingBool(Settings::fog().mSkyBlending, *skyBlendingCheckBox);
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
@ -359,6 +418,58 @@ void Launcher::SettingsPage::saveSettings()
saveSettingBool(*exponentialFogCheckBox, Settings::fog().mExponentialFog);
saveSettingBool(*skyBlendingCheckBox, Settings::fog().mSkyBlending);
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
@ -461,3 +572,17 @@ void Launcher::SettingsPage::slotSkyBlendingToggled(bool checked)
skyBlendingStartComboBox->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 slotPostProcessToggled(bool checked);
void slotSkyBlendingToggled(bool checked);
void slotShadowDistLimitToggled(bool checked);
void slotLightTypeCurrentIndexChanged(int index);
private:
Config::GameSettings& mGameSettings;

View File

@ -11,459 +11,229 @@
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item alignment="Qt::AlignTop">
<widget class="QTabWidget" name="DisplayTabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="DisplayWrapper">
<attribute name="title">
<string>Display</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<layout class="QGridLayout" name="gridLayout_4" columnstretch="1,1">
<item row="5" column="0">
<widget class="QLabel" name="screenLabel">
<item>
<widget class="QGroupBox" name="groupBox">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QGridLayout" name="gridLayout_4" columnstretch="1,1">
<item row="5" column="0">
<widget class="QLabel" name="screenLabel">
<property name="text">
<string>Screen</string>
</property>
</widget>
</item>
<item row="3" column="0">
<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">
<string>Screen:</string>
<string>0</string>
</property>
</widget>
</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">
</item>
<item>
<property name="text">
<string>Window Mode:</string>
<string>2</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="resolutionLabel">
</item>
<item>
<property name="text">
<string>Resolution:</string>
<string>4</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="vSyncLabel">
</item>
<item>
<property name="text">
<string>Vertical Sync:</string>
<string>8</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="antiAliasingLabel">
</item>
<item>
<property name="text">
<string>Anti-aliasing:</string>
<string>16</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="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">
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="framerateLimitCheckBox">
<property name="text">
<string>Framerate limit</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="windowBorderCheckBox">
<property name="text">
<string>Window border</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QComboBox" name="screenComboBox"/>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="vSyncComboBox">
<property name="currentIndex">
<number>0</number>
</property>
<item>
<property name="text">
<string>Window Border</string>
<string>Disabled</string>
</property>
</widget>
</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">
</item>
<item>
<property name="text">
<string>Framerate Limit:</string>
<string>Enabled</string>
</property>
</widget>
</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">
</item>
<item>
<property name="text">
<string>Lighting Method:</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>
<string>Adaptive</string>
</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">
<string>Enable Player Shadows</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>
<string>Fullscreen</string>
</property>
</item>
<item>
<property name="text">
<string>Enable Actor Shadows</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>
<string>Windowed Fullscreen</string>
</property>
</item>
<item>
<property name="text">
<string>Enable Object Shadows</string>
<string>Windowed</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="terrainShadowsCheckBox">
<property name="toolTip">
<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>
<property name="text">
<string>Enable Terrain Shadows</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="indoorShadowsCheckBox">
<property name="toolTip">
<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>
<property name="text">
<string>Enable Indoor Shadows</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="shadowComputeSceneBoundsLabel">
<property name="toolTip">
<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>
</property>
<property name="text">
<string>Shadow Near Far Computation Method:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QComboBox" name="shadowComputeSceneBoundsComboBox">
<item>
<property name="text">
<string>bounds</string>
</property>
</item>
<item>
<property name="text">
<string>primitives</string>
</property>
</item>
<item>
<property name="text">
<string>none</string>
</property>
</item>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="shadowResolutionLabel">
<property name="toolTip">
<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>
<property name="text">
<string>Shadow Map Resolution:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QComboBox" name="shadowResolutionComboBox">
<item>
<property name="text">
<string>512</string>
</property>
</item>
<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>
</item>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="resolutionLabel">
<property name="text">
<string>Resolution</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
</widget>
</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>
<item row="4" column="0">
<widget class="QLabel" name="antiAliasingLabel">
<property name="text">
<string>Anti-aliasing</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="vSyncLabel">
<property name="text">
<string>Vertical synchronization</string>
</property>
</widget>
</item>
<item row="7" column="0">
<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>
</item>
</layout>
</widget>
</item>
</layout>

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -504,7 +504,7 @@ namespace CSMWorld
auto record2 = std::make_unique<Record<ESXRecordT>>();
record2->mState = Record<ESXRecordT>::State_ModifiedOnly;
record2->mModified = record;
record2->mModified = std::move(record);
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()
{
mOld.reset(mTable.getRecord(mId).clone().get());
mOld = mTable.getRecord(mId).clone();
mChanged = mTable.touchRecord(mId);
}
@ -181,9 +181,8 @@ const std::string& CSMWorld::TouchLandCommand::getDestinationId() const
void CSMWorld::TouchLandCommand::onRedo()
{
mOld = mLands.getRecord(mId).clone();
mChanged = mLands.touchRecord(mId);
if (mChanged)
mOld.reset(mLands.getRecord(mId).clone().get());
}
void CSMWorld::TouchLandCommand::onUndo()

View File

@ -149,7 +149,8 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data
mResourcesManager.setVFS(mVFS.get());
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
= mResourceSystem->getSceneManager()->getShaderManager().getGlobalDefines();
@ -1107,7 +1108,7 @@ void CSMWorld::Data::loadFallbackEntries()
newMarker.mModel = model;
newMarker.mRecordFlags = 0;
auto record = std::make_unique<CSMWorld::Record<ESM::Static>>();
record->mBase = newMarker;
record->mBase = std::move(newMarker);
record->mState = CSMWorld::RecordBase::State_BaseOnly;
mReferenceables.appendRecord(std::move(record), CSMWorld::UniversalId::Type_Static);
}
@ -1123,7 +1124,7 @@ void CSMWorld::Data::loadFallbackEntries()
newMarker.mModel = model;
newMarker.mRecordFlags = 0;
auto record = std::make_unique<CSMWorld::Record<ESM::Door>>();
record->mBase = newMarker;
record->mBase = std::move(newMarker);
record->mState = CSMWorld::RecordBase::State_BaseOnly;
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->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/vfs/manager.hpp>
#include <components/vfs/recursivedirectoryiterator.hpp>
CSMWorld::Resources::Resources(
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
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
else

View File

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

View File

@ -514,7 +514,7 @@ void CSVRender::PagedWorldspaceWidget::moveCellSelection(int x, int y)
addCellToScene(*iter);
}
mSelection = newSelection;
mSelection = std::move(newSelection);
}
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))
.value<CSMWorld::LandTexturesColumn::DataType>();
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)));
undoStack.push(new CSMWorld::ModifyCommand(ltexTable, index, textureFileNameVariant));
undoStack.endMacro();
mBrushTexture = newId;
mBrushTexture = std::move(newId);
}
bool CSVRender::TerrainTextureMode::allowLandTextureEditing(const std::string& cellId)

View File

@ -184,11 +184,11 @@ void CSVRender::WorldspaceWidget::selectDefaultNavigationMode()
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)
{
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());
}
@ -440,7 +440,7 @@ CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick(
osg::Node* node = *nodeIter;
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)
{
hit.index0 = intersection.indexList[0];
@ -757,13 +757,14 @@ void CSVRender::WorldspaceWidget::toggleHiddenInstances()
if (selection.empty())
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
= firstSelection->mObject->getRootNode()->getNodeMask() == Mask_Hidden ? Mask_Reference : Mask_Hidden;
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);
}

View File

@ -213,7 +213,7 @@ void CSVWidget::TextureBrushWindow::setBrushTexture(std::string brushTexture)
mSelectedBrush->setText(QString::fromStdString(mBrushTextureLabel));
}
mBrushTexture = newBrushTextureId;
mBrushTexture = std::move(newBrushTextureId);
emit passTextureId(mBrushTexture);
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];
current->first->setText(QString::fromUtf8(type.getTypeName().c_str()));
current->first->setChecked(true);
current->second = type;
current->second = std::move(type);
++counter;
}
else

View File

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

View File

@ -321,11 +321,7 @@ bool OMW::Engine::frame(float frametime)
// update GUI by world data
{
ScopedProfile<UserStatsType::WindowManager> profile(frameStart, frameNumber, *timer, *stats);
if (mStateManager->getState() != MWBase::StateManager::State_NoGame)
{
mWorld->updateWindowManager();
}
mWorld->updateWindowManager();
}
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);
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()->setUnRefImageDataAfterApply(
false); // keep to Off for now to allow better state sharing
@ -826,7 +823,7 @@ void OMW::Engine::prepareEngine()
}
listener->loadingOff();
mWorld->init(mViewer, rootNode, mWorkQueue.get(), *mUnrefQueue);
mWorld->init(mViewer, std::move(rootNode), mWorkQueue.get(), *mUnrefQueue);
mEnvironment.setWorldScene(mWorld->getWorldScene());
mWorld->setupPlayer();
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 checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const = 0;
/// Save the current animation state of managed references to their RefData.
virtual void persistAnimationStates() = 0;

View File

@ -37,23 +37,6 @@ namespace MWClass
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
{
MWMechanics::Movement& movement = getMovementSettings(ptr);

View File

@ -45,8 +45,6 @@ namespace MWClass
bool useAnim() const override;
void block(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.

View File

@ -183,16 +183,17 @@ namespace MWClass
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);
if (!model.empty())
models.push_back(model);
// FIXME: use const version of InventoryStore functions once they are available
if (hasInventoryStore(ptr))
const MWWorld::CustomData* customData = ptr.getRefData().getCustomData();
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)
{
MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot);
@ -339,10 +340,7 @@ namespace MWClass
MWMechanics::applyElementalShields(ptr, victim);
if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage, attackStrength))
{
damage = 0;
victim.getClass().block(victim);
}
MWMechanics::diseaseContact(victim, ptr);
@ -513,7 +511,7 @@ namespace MWClass
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);
}

View File

@ -79,7 +79,7 @@ namespace MWClass
MWWorld::InventoryStore& getInventoryStore(const MWWorld::Ptr& ptr) const override;
///< 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;
///< Return name of the script attached to ptr
@ -107,7 +107,7 @@ namespace MWClass
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:
///< list getModel().

View File

@ -99,25 +99,6 @@ namespace MWClass
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(
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;
///< @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,
MWRender::RenderingInterface& renderingInterface) const override;
///< Add reference into a cell for rendering

View File

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

View File

@ -74,7 +74,7 @@ namespace MWClass
MWWorld::InventoryStore& getInventoryStore(const MWWorld::Ptr& ptr) const override;
///< 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;
@ -85,7 +85,7 @@ namespace MWClass
const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful,
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:
///< list getModel().

View File

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

View File

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

View File

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

View File

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

View File

@ -23,6 +23,11 @@ namespace
float mTimeOffset = 0.f;
};
struct StreamMusicArgs
{
float mFade = 1.f;
};
PlaySoundArgs getPlaySoundArgs(const sol::optional<sol::table>& options)
{
PlaySoundArgs args;
@ -55,6 +60,17 @@ namespace
return MWSound::PlayMode::NoEnvNoScaling;
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
@ -95,9 +111,10 @@ namespace MWLua
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();
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(); };

View File

@ -1,3 +1,4 @@
#include "../stats.hpp"
#include "actor.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["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["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);
}
}

View File

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

View File

@ -2034,6 +2034,14 @@ namespace MWMechanics
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
{
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;
void skipAnimation(const MWWorld::Ptr& ptr) const;
bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) const;
bool checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const;
void persistAnimationStates() 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/luamanager.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/world.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();
const DetourNavigator::AgentBounds agentBounds = world->getPathfindingAgentBounds(actor);
/// Stops the actor when it gets too close to a unloaded cell
//... At current time, this test is unnecessary. AI shuts down when actor is more than "actors processing range"
// setting value
//... units from player, and exterior cells are 8192 units long and wide.
/// Stops the actor when it gets too close to a unloaded cell or when the actor is playing a scripted animation
//... At current time, the first test is unnecessary. AI shuts down when actor is more than
//... "actors processing range" setting value units from player, and exterior cells are 8192 units long and wide.
//... 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[1] = 0;

View File

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

View File

@ -135,6 +135,15 @@ namespace MWMechanics
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
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
int shieldhealth = shield->getClass().getItemHealth(*shield);

View File

@ -771,6 +771,14 @@ namespace MWMechanics
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)
{
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;
void skipAnimation(const MWWorld::Ptr& ptr) override;
bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) override;
bool checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const override;
void persistAnimationStates() override;
/// 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);
}
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

View File

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

View File

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

View File

@ -19,7 +19,7 @@ namespace MWRender
auto resolveFragment = shaderManager.getShader("luminance/resolve.frag", defines);
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)
{

View File

@ -68,7 +68,7 @@ namespace MWRender
ptr.getClass().adjustScale(ptr, scaleVec, true);
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)

View File

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

View File

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

View File

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

View File

@ -4,6 +4,8 @@
#include <osg/Camera>
#include <osg/Texture2D>
#include <optional>
namespace MWRender
{
class PrecipitationOccluder
@ -27,7 +29,7 @@ namespace MWRender
osg::ref_ptr<osg::Camera> mCamera;
osg::ref_ptr<osg::Camera> mSceneCamera;
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?
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);
mPostProcessor->getStateUpdater()->setSunPos(mSunLight->getPosition(), mNight);
mPostProcessor->getStateUpdater()->setSunPos(osg::Vec4f(position, 0.f), mNight);
}
void RenderingManager::addCell(const MWWorld::CellStore* store)

View File

@ -106,7 +106,7 @@ namespace MWRender
mProgramBlobber = shaderManager.getProgram(
vertex, shaderManager.getShader("ripples_blobber.frag", defineMap, osg::Shader::FRAGMENT));
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()

View File

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

View File

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

View File

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

View File

@ -16,6 +16,7 @@
#include <components/loadinglistener/loadinglistener.hpp>
#include <components/files/conversion.hpp>
#include <components/misc/algorithm.hpp>
#include <components/settings/values.hpp>
#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)
{
std::string id = Misc::StringUtils::lowerCase(prev[iPrev].name);
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));
break;
@ -410,9 +409,38 @@ void MWState::StateManager::loadGame(const std::filesystem::path& 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)
@ -428,23 +456,9 @@ void MWState::StateManager::loadGame(const Character* character, const std::file
ESM::FormatVersion version = reader.getFormatVersion();
if (version > ESM::CurrentSaveGameFormatVersion)
throw VersionMismatchError("#{OMWEngine:LoadingRequiresNewVersionError}");
throw SaveVersionTooNewError(version);
else if (version < ESM::MinSupportedSaveGameFormatVersion)
{
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);
}
throw SaveVersionTooOldError(version);
std::map<int, int> contentFileMap = buildContentFileIndexMap(reader);
reader.setContentFileMapping(&contentFileMap);
@ -626,23 +640,49 @@ void MWState::StateManager::loadGame(const Character* character, const std::file
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)
{
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());
MWBase::Environment::get().getWindowManager()->interactiveMessageBox(error, buttons);
printSavegameFormatError(e.what(), error);
}
}
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()
{
if (Character* currentCharacter = getCurrentCharacter())

View File

@ -22,6 +22,8 @@ namespace MWState
private:
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;
void writeScreenshot(std::vector<char>& imageData) const;

View File

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

View File

@ -209,8 +209,8 @@ namespace MWWorld
/// false will abort the iteration.
/// \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
/// unintended behaviour. \attention This function also lists deleted (count 0) objects! \return Iteration
/// completed?
/// unintended behaviour. \attention This function also lists deleted (count 0) objects!
/// \return Iteration completed?
template <class Visitor>
bool forEach(Visitor&& visitor)
{
@ -224,12 +224,12 @@ namespace MWWorld
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;
if (!visitor(MWWorld::Ptr(mMergedRefs[i], this)))
if (!visitor(MWWorld::Ptr(mergedRef, this)))
return false;
}
return true;
@ -238,8 +238,8 @@ namespace MWWorld
/// Call visitor (MWWorld::ConstPtr) for each reference. visitor must return a bool. Returning
/// false will abort the iteration.
/// \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
/// completed?
/// unintended behaviour. \attention This function also lists deleted (count 0) objects!
/// \return Iteration completed?
template <class Visitor>
bool forEachConst(Visitor&& visitor) const
{
@ -249,12 +249,12 @@ namespace MWWorld
if (mMergedRefsNeedsUpdate)
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;
if (!visitor(MWWorld::ConstPtr(mMergedRefs[i], this)))
if (!visitor(MWWorld::ConstPtr(mergedRef, this)))
return false;
}
return true;
@ -263,8 +263,8 @@ namespace MWWorld
/// Call visitor (ref) for each reference of given type. visitor must return a bool. Returning
/// false will abort the iteration.
/// \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
/// completed?
/// unintended behaviour. \attention This function also lists deleted (count 0) objects!
/// \return Iteration completed?
template <class T, class Visitor>
bool forEachType(Visitor&& visitor)
{

View File

@ -118,11 +118,6 @@ namespace MWWorld
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,
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");
}
bool Class::hasInventoryStore(const Ptr& ptr) const
bool Class::hasInventoryStore(const ConstPtr& ptr) const
{
return false;
}
@ -320,7 +315,7 @@ namespace MWWorld
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);
if (!model.empty())

View File

@ -151,10 +151,6 @@ namespace MWWorld
/// actor responsible for the attack. \a successful specifies if the hit is
/// 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;
///< 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
/// 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)
virtual bool canLock(const ConstPtr& ptr) const;
@ -284,7 +280,7 @@ namespace MWWorld
virtual bool useAnim() const;
///< 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:
///< list getModel().

View File

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

View File

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

View File

@ -1,3 +1,4 @@
#include <components/detournavigator/debug.hpp>
#include <components/detournavigator/gettilespositions.hpp>
#include <components/detournavigator/settings.hpp>
@ -86,4 +87,79 @@ namespace
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::Tests;
constexpr int heightfieldTileSize = ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1);
struct DetourNavigatorNavigatorTest : Test
{
Settings mSettings = makeSettings();
@ -53,7 +55,6 @@ namespace
AreaCosts mAreaCosts;
Loading::Listener mListener;
const osg::Vec2i mCellPosition{ 0, 0 };
const int mHeightfieldTileSize = ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1);
const float mEndTolerance = 0;
const btTransform mTransform{ btMatrix3x3::getIdentity(), btVector3(256, 256, 0) };
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; }
private:
@ -167,7 +168,7 @@ namespace
TEST_F(DetourNavigatorNavigatorTest, update_then_find_path_should_return_path)
{
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));
auto updateGuard = mNavigator->makeUpdateGuard();
@ -189,7 +190,7 @@ namespace
TEST_F(DetourNavigatorNavigatorTest, find_path_to_the_start_position_should_contain_single_point)
{
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));
auto updateGuard = mNavigator->makeUpdateGuard();
@ -211,7 +212,7 @@ namespace
mSettings, std::make_unique<NavMeshDb>(":memory:", std::numeric_limits<std::uint64_t>::max())));
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>());
compound.shape().addChildShape(
@ -256,7 +257,7 @@ namespace
TEST_F(DetourNavigatorNavigatorTest, update_changed_object_should_change_navmesh)
{
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>());
compound.shape().addChildShape(
@ -335,7 +336,7 @@ namespace
TEST_F(DetourNavigatorNavigatorTest, only_one_heightfield_per_cell_is_allowed)
{
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{ {
-25, -25, -25, -25, -25, // row 0
@ -345,7 +346,7 @@ namespace
-25, -25, -25, -25, -25, // row 4
} };
const HeightfieldSurface surface2 = makeSquareHeightfieldSurface(heightfieldData2);
const int cellSize2 = mHeightfieldTileSize * (surface2.mSize - 1);
const int cellSize2 = heightfieldTileSize * (surface2.mSize - 1);
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
mNavigator->addHeightfield(mCellPosition, cellSize1, surface1, nullptr);
@ -412,7 +413,7 @@ namespace
0, -50, -100, -100, -100, // row 4
} };
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));
mNavigator->addWater(mCellPosition, cellSize, 300, nullptr);
@ -446,7 +447,7 @@ namespace
0, 0, 0, 0, 0, 0, 0, // row 6
} };
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));
mNavigator->addWater(mCellPosition, cellSize, -25, nullptr);
@ -480,7 +481,7 @@ namespace
0, 0, 0, 0, 0, 0, 0, // row 6
} };
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));
mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr);
@ -513,7 +514,7 @@ namespace
0, 0, 0, 0, 0, 0, 0, // row 6
} };
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));
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)
{
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));
mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr);
@ -602,7 +603,7 @@ namespace
0, -25, -100, -100, -100, -100, // row 5
} };
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));
mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr);
@ -629,7 +630,7 @@ namespace
mSettings, std::make_unique<NavMeshDb>(":memory:", std::numeric_limits<std::uint64_t>::max())));
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);
std::vector<CollisionShapeInstance<btBoxShape>> boxes;
@ -718,7 +719,7 @@ namespace
TEST_F(DetourNavigatorNavigatorTest, update_then_raycast_should_return_position)
{
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));
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)
{
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)));
const btVector3 oscillatingBoxShapePosition(288, 288, 400);
@ -777,7 +778,7 @@ namespace
TEST_F(DetourNavigatorNavigatorTest, should_provide_path_over_flat_heightfield)
{
const HeightfieldPlane plane{ 100 };
const int cellSize = mHeightfieldTileSize * 4;
const int cellSize = heightfieldTileSize * 4;
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
mNavigator->addHeightfield(mCellPosition, cellSize, plane, nullptr);
@ -796,7 +797,7 @@ namespace
TEST_F(DetourNavigatorNavigatorTest, for_not_reachable_destination_find_path_should_provide_partial_path)
{
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>());
compound.shape().addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(204, -204, 0)),
@ -822,7 +823,7 @@ namespace
TEST_F(DetourNavigatorNavigatorTest, end_tolerance_should_extent_available_destinations)
{
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>());
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)
{
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));
auto updateGuard = mNavigator->makeUpdateGuard();
@ -966,7 +967,7 @@ namespace
TEST_F(DetourNavigatorNavigatorTest, find_nearest_nav_mesh_position_should_return_nullopt_when_too_far)
{
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));
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)
{
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));
auto updateGuard = mNavigator->makeUpdateGuard();
@ -998,4 +999,142 @@ namespace
EXPECT_EQ(findNearestNavMeshPosition(*mNavigator, mAgentBounds, position, searchAreaHalfExtents, Flag_swim),
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() << ')';
}
template <>
inline testing::Message& Message::operator<<(const osg::Vec2i& value)
{
return (*this) << "{" << value.x() << ", " << value.y() << '}';
}
template <>
inline testing::Message& Message::operator<<(const Wrapper<osg::Vec3f>& value)
{
return (*this) << value.mValue;
}
template <>
inline testing::Message& Message::operator<<(const Wrapper<osg::Vec2i>& value)
{
return (*this) << value.mValue;
}
template <>
inline testing::Message& Message::operator<<(const Wrapper<float>& value)
{
@ -72,6 +84,12 @@ namespace testing
return writeRange(*this, value, 1);
}
template <>
inline testing::Message& Message::operator<<(const std::vector<osg::Vec2i>& value)
{
return writeRange(*this, value, 1);
}
template <>
inline testing::Message& Message::operator<<(const std::vector<float>& value)
{

View File

@ -1,4 +1,5 @@
#include <components/esm/fourcc.hpp>
#include <components/esm3/aisequence.hpp>
#include <components/esm3/esmreader.hpp>
#include <components/esm3/esmwriter.hpp>
#include <components/esm3/loadcont.hpp>
@ -410,6 +411,85 @@ namespace ESM
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()));
}
}

View File

@ -6,6 +6,7 @@
#include <components/misc/strings/conversion.hpp>
#include <components/vfs/archive.hpp>
#include <components/vfs/file.hpp>
#include <components/vfs/manager.hpp>
#include <components/vfs/pathutil.hpp>
@ -60,7 +61,7 @@ namespace TestingOpenMW
void listResources(VFS::FileMap& out) override
{
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); }

View File

@ -810,8 +810,7 @@ bool Wizard::UnshieldWorker::extractFile(
if (!dir.mkpath(path))
return false;
QString fileName(path);
fileName.append(QString::fromUtf8(unshield_file_name(unshield, index)));
path.append(QString::fromUtf8(unshield_file_name(unshield, index)));
// Calculate the percentage done
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 progressChanged(progress);
QByteArray array(fileName.toUtf8());
QByteArray array(path.toUtf8());
success = unshield_file_save(unshield, index, array.constData());
if (!success)
{
qDebug() << "error";
dir.remove(fileName);
dir.remove(path);
}
return success;

View File

@ -76,7 +76,7 @@ namespace Bsa
fail("Corrupted BSA");
}
mFolders[dirHash][{ nameHash, extHash }] = file;
mFolders[dirHash][{ nameHash, extHash }] = std::move(file);
FileStruct fileStruct{};
mFiles.push_back(fileStruct);
@ -177,7 +177,7 @@ namespace Bsa
return std::nullopt; // folder not found
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 });
if (iter == it->second.end())
return std::nullopt; // file not found

View File

@ -46,4 +46,12 @@ namespace Bsa
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

View File

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

View File

@ -172,7 +172,7 @@ namespace Bsa
return FileRecord(); // folder not found, return default which has offset of sInvalidOffset
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 });
if (iter == it->second.end())
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)
{
*iter = settingLine;
*iter = std::move(settingLine);
break;
}
}

View File

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

View File

@ -76,4 +76,13 @@ namespace DetourNavigator
return {};
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 getUnion(const TilesPositionsRange& a, const TilesPositionsRange& b) noexcept;
}
#endif

View File

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

View File

@ -54,6 +54,15 @@ namespace DetourNavigator
private:
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)
@ -104,14 +113,28 @@ namespace DetourNavigator
TilesPositionsRange TileCachedRecastMeshManager::getLimitedObjectsRange() const
{
if (mObjects.empty())
return {};
const auto bounds = mObjectIndex.bounds();
const TilesPositionsRange objectsRange{
.mBegin = makeTilePosition(bounds.min_corner()),
.mEnd = makeTilePosition(bounds.max_corner()) + TilePosition(1, 1),
};
return getIntersection(mRange, objectsRange);
std::optional<TilesPositionsRange> result;
if (!mWater.empty())
result = getIndexRange(mWaterIndex);
if (!mHeightfields.empty())
{
const TilesPositionsRange range = getIndexRange(mHeightfieldIndex);
if (result.has_value())
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)

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 "esmwriter.hpp"
#include <components/misc/concepts.hpp>
#include <algorithm>
#include <memory>
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
{
void AiWander::load(ESMReader& esm)
{
esm.getHNT("DATA", mData.mDistance, mData.mDuration, mData.mTimeOfDay, mData.mIdle, mData.mShouldRepeat);
esm.getHNT("STAR", mDurationData.mRemainingDuration, mDurationData.unused); // was mStartTime
esm.getNamedComposite("DATA", mData);
esm.getNamedComposite("STAR", mDurationData); // was mStartTime
mStoredInitialActorPosition = esm.getHNOT("POS_", mInitialActorPosition.mValues);
}
void AiWander::save(ESMWriter& esm) const
{
esm.writeHNT("DATA", mData);
esm.writeHNT("STAR", mDurationData);
esm.writeNamedComposite("DATA", mData);
esm.writeNamedComposite("STAR", mDurationData); // was mStartTime
if (mStoredInitialActorPosition)
esm.writeHNT("POS_", mInitialActorPosition);
esm.writeHNT("POS_", mInitialActorPosition.mValues);
}
void AiTravel::load(ESMReader& esm)
{
esm.getHNT("DATA", mData.mX, mData.mY, mData.mZ);
esm.getNamedComposite("DATA", mData);
esm.getHNT(mHidden, "HIDD");
mRepeat = false;
esm.getHNOT(mRepeat, "REPT");
@ -36,7 +62,7 @@ namespace ESM
void AiTravel::save(ESMWriter& esm) const
{
esm.writeHNT("DATA", mData);
esm.writeNamedComposite("DATA", mData);
esm.writeHNT("HIDD", mHidden);
if (mRepeat)
esm.writeHNT("REPT", mRepeat);
@ -44,7 +70,7 @@ namespace 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");
mTargetActorId = -1;
esm.getHNOT(mTargetActorId, "TAID");
@ -64,7 +90,7 @@ namespace ESM
void AiEscort::save(ESMWriter& esm) const
{
esm.writeHNT("DATA", mData);
esm.writeNamedComposite("DATA", mData);
esm.writeHNRefId("TARG", mTargetId);
esm.writeHNT("TAID", mTargetActorId);
esm.writeHNT("DURA", mRemainingDuration);
@ -76,7 +102,7 @@ namespace 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");
mTargetActorId = -1;
esm.getHNOT(mTargetActorId, "TAID");
@ -101,7 +127,7 @@ namespace ESM
void AiFollow::save(ESMWriter& esm) const
{
esm.writeHNT("DATA", mData);
esm.writeNamedComposite("DATA", mData);
esm.writeHNRefId("TARG", mTargetId);
esm.writeHNT("TAID", mTargetActorId);
esm.writeHNT("DURA", mRemainingDuration);

View File

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

View File

@ -12,8 +12,10 @@
#include <components/to_utf8/to_utf8.hpp>
#include "components/esm/decompose.hpp"
#include "components/esm/esmcommon.hpp"
#include "components/esm/refid.hpp"
#include "loadtes3.hpp"
namespace ESM
@ -177,6 +179,16 @@ namespace ESM
(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>>>
void skipHT()
{

View File

@ -5,6 +5,7 @@
#include <list>
#include <type_traits>
#include "components/esm/decompose.hpp"
#include "components/esm/esmcommon.hpp"
#include "components/esm/refid.hpp"
@ -121,6 +122,20 @@ namespace ESM
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
// state being discarded without any error on writing or reading it. :(
// writeHNString and friends must be used instead.
@ -132,7 +147,7 @@ namespace ESM
void writeHNT(NAME name, const T (&data)[size], int) = delete;
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);
writeT(data, size);

View File

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

View File

@ -4,12 +4,19 @@
#include <sstream>
#include <components/debug/debuglog.hpp>
#include <components/misc/concepts.hpp>
#include "esmreader.hpp"
#include "esmwriter.hpp"
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)
{
uint32_t s = mData.mStringTableSize;
@ -99,11 +106,7 @@ namespace ESM
{
esm.getSubHeader();
mId = esm.getMaybeFixedRefIdSize(32);
esm.getT(mData.mNumShorts);
esm.getT(mData.mNumLongs);
esm.getT(mData.mNumFloats);
esm.getT(mData.mScriptDataSize);
esm.getT(mData.mStringTableSize);
esm.getComposite(mData);
hasHeader = true;
break;
@ -157,7 +160,7 @@ namespace ESM
esm.startSubRecord("SCHD");
esm.writeMaybeFixedSizeRefId(mId, 32);
esm.writeT(mData, 20);
esm.writeComposite(mData);
esm.endRecord("SCHD");
if (isDeleted)

View File

@ -3,6 +3,8 @@
#include "esmreader.hpp"
#include "esmwriter.hpp"
#include "../misc/algorithm.hpp"
namespace ESM
{
void SavedGame::load(ESMReader& esm)
@ -67,7 +69,9 @@ namespace ESM
std::vector<std::string_view> missingFiles;
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);
}

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