Merge branch 'master' into menuscripts

ini_importer_tests
uramer 4 months ago
commit 9cce2e39ba

@ -245,6 +245,7 @@ Programmers
xyzz
Yohaulticetl
Yuri Krupenin
Yury Stepovikov
zelurker
Documentation

@ -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
@ -32,6 +34,7 @@
Bug #6190: Unintuitive sun specularity time of day dependence
Bug #6222: global map cell size can crash openmw if set to too high a value
Bug #6313: Followers with high Fight can turn hostile
Bug #6402: The sound of a thunderstorm does not stop playing after entering the premises
Bug #6427: Enemy health bar disappears before damaging effect ends
Bug #6550: Cloned body parts don't inherit texture effects
Bug #6645: Enemy block sounds align with animation instead of blocked hits
@ -127,6 +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

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

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

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

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

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

@ -41,11 +41,6 @@ int runLauncher(int argc, char* argv[])
appTranslator.load(":/translations/" + locale + ".qm");
app.installTranslator(&appTranslator);
// Now we make sure the current dir is set to application path
QDir dir(QCoreApplication::applicationDirPath());
QDir::setCurrent(dir.absolutePath());
Launcher::MainDialog mainWin(configurationManager);
Launcher::FirstRunDialogResult result = mainWin.showFirstRunDialog();

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

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

@ -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">
<property name="text">
<string>Screen:</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">
<property name="text">
<string>Window Mode:</string>
</property>
</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="2" column="0">
<widget class="QLabel" name="vSyncLabel">
<property name="text">
<string>Vertical Sync:</string>
</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="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">
<property name="text">
<string>Window Border</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">
<property name="text">
<string>Framerate Limit:</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">
<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>
</property>
<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>
</property>
<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>
</property>
<property name="text">
<string>Enable Object Shadows</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 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>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="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>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="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="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>

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

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

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

@ -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>
@ -33,7 +34,7 @@ bool hasExtension(const std::filesystem::path& filename, const std::string& exte
/// See if the file has the "nif" extension.
bool isNIF(const std::filesystem::path& filename)
{
return hasExtension(filename, ".nif");
return hasExtension(filename, ".nif") || hasExtension(filename, ".kf");
}
/// See if the file has the "bsa" extension.
bool isBSA(const std::filesystem::path& filename)
@ -75,7 +76,10 @@ void readNIF(
const std::string pathStr = Files::pathToUnicodeString(path);
if (!quiet)
{
std::cout << "Reading NIF file '" << pathStr << "'";
if (hasExtension(path, ".kf"))
std::cout << "Reading KF file '" << pathStr << "'";
else
std::cout << "Reading NIF file '" << pathStr << "'";
if (!source.empty())
std::cout << " from '" << Files::pathToUnicodeString(isBSA(source) ? source.filename() : source) << "'";
std::cout << std::endl;
@ -84,7 +88,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
@ -138,10 +142,10 @@ void readVFS(std::unique_ptr<VFS::Archive>&& archive, const std::filesystem::pat
bool parseOptions(int argc, char** argv, Files::PathContainer& files, Files::PathContainer& archives,
bool& writeDebugLog, bool& quiet)
{
bpo::options_description desc(R"(Ensure that OpenMW can use the provided NIF and BSA files
bpo::options_description desc(R"(Ensure that OpenMW can use the provided NIF, KF and BSA/BA2 files
Usages:
niftest <nif files, BSA files, or directories>
niftest <nif files, kf files, BSA/BA2 files, or directories>
Scan the file or directories for NIF errors.
Allowed options)");
@ -240,7 +244,8 @@ int main(int argc, char** argv)
}
else
{
std::cerr << "Error: '" << pathStr << "' is not a NIF file, BSA/BA2 archive, or directory" << std::endl;
std::cerr << "Error: '" << pathStr << "' is not a NIF/KF file, BSA/BA2 archive, or directory"
<< std::endl;
}
}
catch (std::exception& e)

@ -81,11 +81,6 @@ int runApplication(int argc, char* argv[])
Application application(argc, argv);
#ifdef Q_OS_MAC
QDir dir(QCoreApplication::applicationDirPath());
QDir::setCurrent(dir.absolutePath());
#endif
application.setWindowIcon(QIcon(":./openmw-cs.png"));
CS::Editor editor(argc, argv);

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

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

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

@ -15,11 +15,13 @@
#include <QVariant>
#include <components/esm3/loaddial.hpp>
#include <components/esm3/loadmgef.hpp>
#include <components/esm3/loadskil.hpp>
#include <components/misc/strings/lower.hpp>
#include "collectionbase.hpp"
#include "columnbase.hpp"
#include "columnimp.hpp"
#include "info.hpp"
#include "land.hpp"
#include "landtexture.hpp"
@ -82,6 +84,17 @@ namespace CSMWorld
record.mIndex = index;
}
inline ESM::RefId getRecordId(const ESM::MagicEffect& record)
{
return ESM::RefId::stringRefId(CSMWorld::getStringId(record.mId));
}
inline void setRecordId(const ESM::RefId& id, ESM::MagicEffect& record)
{
int index = ESM::MagicEffect::indexNameToIndex(id.getRefIdString());
record.mId = ESM::RefId::index(ESM::REC_MGEF, static_cast<std::uint32_t>(index));
}
inline ESM::RefId getRecordId(const LandTexture& record)
{
return ESM::RefId::stringRefId(LandTexture::createUniqueRecordId(record.mPluginIndex, record.mIndex));
@ -504,7 +517,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);
}

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

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

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

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

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

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

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

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

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

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

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

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

@ -24,7 +24,7 @@ add_openmw_dir (mwrender
bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation
renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover
postprocessor pingpongcull luminancecalculator pingpongcanvas transparentpass precipitationocclusion ripples
actorutil distortion
actorutil distortion animationpriority bonegroup blendmask
)
add_openmw_dir (mwinput
@ -64,7 +64,7 @@ add_openmw_dir (mwlua
context menuscripts globalscripts localscripts playerscripts luabindings objectbindings cellbindings
mwscriptbindings camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings
postprocessingbindings stats debugbindings corebindings worldbindings worker magicbindings factionbindings
classbindings itemdata inputprocessor
classbindings itemdata inputprocessor animationbindings
types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc
types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus
types/potion types/ingredient types/misc types/repair types/armor types/light types/static
@ -113,7 +113,7 @@ add_openmw_dir (mwstate
add_openmw_dir (mwbase
environment world scriptmanager dialoguemanager journal soundmanager mechanicsmanager
inputmanager windowmanager statemanager
inputmanager windowmanager statemanager luamanager
)
# Main executable

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

@ -219,8 +219,6 @@ int runApplication(int argc, char* argv[])
Platform::init();
#ifdef __APPLE__
std::filesystem::path binary_path = std::filesystem::absolute(std::filesystem::path(argv[0]));
std::filesystem::current_path(binary_path.parent_path());
setenv("OSG_GL_TEXTURE_STORAGE", "OFF", 0);
#endif

@ -8,6 +8,7 @@
#include <SDL_events.h>
#include "../mwgui/mode.hpp"
#include "../mwrender/animationpriority.hpp"
#include <components/sdlutil/events.hpp>
namespace MWWorld
@ -61,10 +62,14 @@ namespace MWBase
virtual void itemConsumed(const MWWorld::Ptr& consumable, const MWWorld::Ptr& actor) = 0;
virtual void objectActivated(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0;
virtual void useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor, bool force) = 0;
virtual void animationTextKey(const MWWorld::Ptr& actor, const std::string& key) = 0;
virtual void playAnimation(const MWWorld::Ptr& object, const std::string& groupname,
const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult,
std::string_view start, std::string_view stop, float startpoint, size_t loops, bool loopfallback)
= 0;
virtual void exteriorCreated(MWWorld::CellStore& cell) = 0;
virtual void actorDied(const MWWorld::Ptr& actor) = 0;
virtual void questUpdated(const ESM::RefId& questId, int stage) = 0;
// `arg` is either forwarded from MWGui::pushGuiMode or empty
virtual void uiModeChanged(const MWWorld::Ptr& arg) = 0;

@ -9,6 +9,7 @@
#include <vector>
#include "../mwmechanics/greetingstate.hpp"
#include "../mwrender/animationpriority.hpp"
#include "../mwworld/ptr.hpp"
@ -170,16 +171,33 @@ namespace MWBase
///< Forces an object to refresh its animation state.
virtual bool playAnimationGroup(
const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number = 1, bool persist = false)
const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number = 1, bool scripted = false)
= 0;
///< Run animation for a MW-reference. Calls to this function for references that are currently not
/// in the scene should be ignored.
///
/// \param mode 0 normal, 1 immediate start, 2 immediate loop
/// \param count How many times the animation should be run
/// \param persist Whether the animation state should be stored in saved games
/// and persist after cell unload.
/// \param number How many times the animation should be run
/// \param scripted Whether the animation should be treated as a scripted animation.
/// \return Success or error
virtual bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, float speed,
std::string_view startKey, std::string_view stopKey, bool forceLoop)
= 0;
///< Lua variant of playAnimationGroup. The mode parameter is omitted
/// and forced to 0. modes 1 and 2 can be emulated by doing clearAnimationQueue() and
/// setting the startKey.
///
/// \param number How many times the animation should be run
/// \param speed How fast to play the animation, where 1.f = normal speed
/// \param startKey Which textkey to start the animation from
/// \param stopKey Which textkey to stop the animation on
/// \param forceLoop Force the animation to be looping, even if it's normally not looping.
/// \param blendMask See MWRender::Animation::BlendMask
/// \param scripted Whether the animation should be treated as as scripted animation
/// \return Success or error
///
virtual void enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable) = 0;
virtual void skipAnimation(const MWWorld::Ptr& ptr) = 0;
///< Skip the animation for the given MW-reference for one frame. Calls to this function for
@ -187,9 +205,14 @@ 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;
/// Clear out the animation queue, and cancel any animation currently playing from the queue
virtual void clearAnimationQueue(const MWWorld::Ptr& ptr, bool clearScripted) = 0;
/// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently
/// paused we may want to do it manually (after equipping permanent enchantment)
virtual void updateMagicEffects(const MWWorld::Ptr& ptr) = 0;

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

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

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

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

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

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

@ -143,7 +143,7 @@ namespace MWClass
list.push_back(params);
}
info.effects = list;
info.effects = std::move(list);
info.text = std::move(text);
info.isIngredient = true;

@ -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)
{
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
MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot);
if (equipped != invStore.end())
{
std::string model = equipped->getClass().getModel(*equipped);
if (!model.empty())
models.push_back(model);
}
for (std::vector<ESM::PartReference>::const_iterator it = parts.begin(); it != parts.end(); ++it)
{
const ESM::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 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;
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;

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

@ -63,17 +63,17 @@ namespace
switch (order)
{
case 0:
return { question, { r0, r1, r2 }, sound };
return { std::move(question), { std::move(r0), std::move(r1), std::move(r2) }, std::move(sound) };
case 1:
return { question, { r0, r2, r1 }, sound };
return { std::move(question), { std::move(r0), std::move(r2), std::move(r1) }, std::move(sound) };
case 2:
return { question, { r1, r0, r2 }, sound };
return { std::move(question), { std::move(r1), std::move(r0), std::move(r2) }, std::move(sound) };
case 3:
return { question, { r1, r2, r0 }, sound };
return { std::move(question), { std::move(r1), std::move(r2), std::move(r0) }, std::move(sound) };
case 4:
return { question, { r2, r0, r1 }, sound };
return { std::move(question), { std::move(r2), std::move(r0), std::move(r1) }, std::move(sound) };
default:
return { question, { r2, r1, r0 }, sound };
return { std::move(question), { std::move(r2), std::move(r1), std::move(r0) }, std::move(sound) };
}
}
}

@ -196,7 +196,7 @@ namespace MWGui
std::string topicName
= Misc::StringUtils::lowerCase(windowManager->getTranslationDataStorage().topicStandardForm(link));
std::string displayName = link;
std::string displayName = std::move(link);
while (displayName[displayName.size() - 1] == '*')
displayName.erase(displayName.size() - 1, 1);
@ -248,7 +248,7 @@ namespace MWGui
i = match.mEnd;
}
if (i != text.end())
addTopicLink(typesetter, 0, i - text.begin(), text.size());
addTopicLink(std::move(typesetter), 0, i - text.begin(), text.size());
}
}
@ -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();
}
}

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

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

@ -136,9 +136,9 @@ namespace MWGui
return;
if (enabled)
processor->enableTechnique(technique);
processor->enableTechnique(std::move(technique));
else
processor->disableTechnique(technique);
processor->disableTechnique(std::move(technique));
processor->saveChain();
}
}
@ -171,7 +171,7 @@ namespace MWGui
if (technique->getDynamic())
return;
if (processor->enableTechnique(technique, index) != MWRender::PostProcessor::Status_Error)
if (processor->enableTechnique(std::move(technique), index) != MWRender::PostProcessor::Status_Error)
processor->saveChain();
}
}

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

@ -0,0 +1,365 @@
#include <components/esm3/loadmgef.hpp>
#include <components/esm3/loadstat.hpp>
#include <components/lua/asyncpackage.hpp>
#include <components/lua/luastate.hpp>
#include <components/lua/utilpackage.hpp>
#include <components/misc/resourcehelpers.hpp>
#include <components/settings/values.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/world.hpp"
#include "../mwmechanics/character.hpp"
#include "../mwworld/esmstore.hpp"
#include "context.hpp"
#include "luamanagerimp.hpp"
#include "objectvariant.hpp"
#include "animationbindings.hpp"
#include <array>
namespace MWLua
{
struct AnimationGroup;
struct TextKeyCallback;
}
namespace sol
{
template <>
struct is_automagical<MWLua::AnimationGroup> : std::false_type
{
};
template <>
struct is_automagical<std::shared_ptr<MWLua::TextKeyCallback>> : std::false_type
{
};
}
namespace MWLua
{
using BlendMask = MWRender::Animation::BlendMask;
using BoneGroup = MWRender::Animation::BoneGroup;
using Priority = MWMechanics::Priority;
using AnimationPriorities = MWRender::Animation::AnimPriority;
MWWorld::Ptr getMutablePtrOrThrow(const ObjectVariant& variant)
{
if (variant.isLObject())
throw std::runtime_error("Local scripts can only modify animations of the object they are attached to.");
MWWorld::Ptr ptr = variant.ptr();
if (ptr.isEmpty())
throw std::runtime_error("Invalid object");
if (!ptr.getRefData().isEnabled())
throw std::runtime_error("Can't use a disabled object");
return ptr;
}
MWWorld::Ptr getPtrOrThrow(const ObjectVariant& variant)
{
MWWorld::Ptr ptr = variant.ptr();
if (ptr.isEmpty())
throw std::runtime_error("Invalid object");
return ptr;
}
MWRender::Animation* getMutableAnimationOrThrow(const ObjectVariant& variant)
{
MWWorld::Ptr ptr = getMutablePtrOrThrow(variant);
auto world = MWBase::Environment::get().getWorld();
MWRender::Animation* anim = world->getAnimation(ptr);
if (!anim)
throw std::runtime_error("Object has no animation");
return anim;
}
const MWRender::Animation* getConstAnimationOrThrow(const ObjectVariant& variant)
{
MWWorld::Ptr ptr = getPtrOrThrow(variant);
auto world = MWBase::Environment::get().getWorld();
const MWRender::Animation* anim = world->getAnimation(ptr);
if (!anim)
throw std::runtime_error("Object has no animation");
return anim;
}
const ESM::Static* getStatic(const sol::object& staticOrID)
{
if (staticOrID.is<ESM::Static>())
return staticOrID.as<const ESM::Static*>();
else
{
ESM::RefId id = ESM::RefId::deserializeText(LuaUtil::cast<std::string_view>(staticOrID));
return MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find(id);
}
}
std::string getStaticModelOrThrow(const sol::object& staticOrID)
{
const ESM::Static* static_ = getStatic(staticOrID);
if (!static_)
throw std::runtime_error("Invalid static");
return Misc::ResourceHelpers::correctMeshPath(static_->mModel);
}
static AnimationPriorities getPriorityArgument(const sol::table& args)
{
auto asPriorityEnum = args.get<sol::optional<Priority>>("priority");
if (asPriorityEnum)
return asPriorityEnum.value();
auto asTable = args.get<sol::optional<sol::table>>("priority");
if (asTable)
{
AnimationPriorities priorities = AnimationPriorities(Priority::Priority_Default);
for (auto entry : asTable.value())
{
if (!entry.first.is<BoneGroup>() || !entry.second.is<Priority>())
throw std::runtime_error("Priority table must consist of BoneGroup-Priority pairs only");
auto group = entry.first.as<BoneGroup>();
auto priority = entry.second.as<Priority>();
if (group < 0 || group >= BoneGroup::Num_BoneGroups)
throw std::runtime_error("Invalid bonegroup: " + std::to_string(group));
priorities[group] = priority;
}
return priorities;
}
return Priority::Priority_Default;
}
sol::table initAnimationPackage(const Context& context)
{
auto* lua = context.mLua;
auto mechanics = MWBase::Environment::get().getMechanicsManager();
auto world = MWBase::Environment::get().getWorld();
sol::table api(lua->sol(), sol::create);
api["PRIORITY"]
= LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs<std::string_view, MWMechanics::Priority>({
{ "Default", MWMechanics::Priority::Priority_Default },
{ "WeaponLowerBody", MWMechanics::Priority::Priority_WeaponLowerBody },
{ "SneakIdleLowerBody", MWMechanics::Priority::Priority_SneakIdleLowerBody },
{ "SwimIdle", MWMechanics::Priority::Priority_SwimIdle },
{ "Jump", MWMechanics::Priority::Priority_Jump },
{ "Movement", MWMechanics::Priority::Priority_Movement },
{ "Hit", MWMechanics::Priority::Priority_Hit },
{ "Weapon", MWMechanics::Priority::Priority_Weapon },
{ "Block", MWMechanics::Priority::Priority_Block },
{ "Knockdown", MWMechanics::Priority::Priority_Knockdown },
{ "Torch", MWMechanics::Priority::Priority_Torch },
{ "Storm", MWMechanics::Priority::Priority_Storm },
{ "Death", MWMechanics::Priority::Priority_Death },
{ "Scripted", MWMechanics::Priority::Priority_Scripted },
}));
api["BLEND_MASK"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs<std::string_view, BlendMask>({
{ "LowerBody", BlendMask::BlendMask_LowerBody },
{ "Torso", BlendMask::BlendMask_Torso },
{ "LeftArm", BlendMask::BlendMask_LeftArm },
{ "RightArm", BlendMask::BlendMask_RightArm },
{ "UpperBody", BlendMask::BlendMask_UpperBody },
{ "All", BlendMask::BlendMask_All },
}));
api["BONE_GROUP"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs<std::string_view, BoneGroup>({
{ "LowerBody", BoneGroup::BoneGroup_LowerBody },
{ "Torso", BoneGroup::BoneGroup_Torso },
{ "LeftArm", BoneGroup::BoneGroup_LeftArm },
{ "RightArm", BoneGroup::BoneGroup_RightArm },
}));
api["hasAnimation"] = [world](const sol::object& object) -> bool {
return world->getAnimation(getPtrOrThrow(ObjectVariant(object))) != nullptr;
};
// equivalent to MWScript's SkipAnim
api["skipAnimationThisFrame"] = [mechanics](const sol::object& object) {
MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object));
// This sets a flag that is only used during the update pass, so
// there's no need to queue
mechanics->skipAnimation(ptr);
};
api["getTextKeyTime"] = [](const sol::object& object, std::string_view key) -> sol::optional<float> {
float time = getConstAnimationOrThrow(ObjectVariant(object))->getTextKeyTime(key);
if (time >= 0.f)
return time;
return sol::nullopt;
};
api["isPlaying"] = [](const sol::object& object, std::string_view groupname) {
return getConstAnimationOrThrow(ObjectVariant(object))->isPlaying(groupname);
};
api["getCurrentTime"] = [](const sol::object& object, std::string_view groupname) -> sol::optional<float> {
float time = getConstAnimationOrThrow(ObjectVariant(object))->getCurrentTime(groupname);
if (time >= 0.f)
return time;
return sol::nullopt;
};
api["isLoopingAnimation"] = [](const sol::object& object, std::string_view groupname) {
return getConstAnimationOrThrow(ObjectVariant(object))->isLoopingAnimation(groupname);
};
api["cancel"] = [](const sol::object& object, std::string_view groupname) {
return getMutableAnimationOrThrow(ObjectVariant(object))->disable(groupname);
};
api["setLoopingEnabled"] = [](const sol::object& object, std::string_view groupname, bool enabled) {
return getMutableAnimationOrThrow(ObjectVariant(object))->setLoopingEnabled(groupname, enabled);
};
// MWRender::Animation::getInfo can also return the current speed multiplier, but this is never used.
api["getCompletion"] = [](const sol::object& object, std::string_view groupname) -> sol::optional<float> {
float completion = 0.f;
if (getConstAnimationOrThrow(ObjectVariant(object))->getInfo(groupname, &completion))
return completion;
return sol::nullopt;
};
api["getLoopCount"] = [](const sol::object& object, std::string groupname) -> sol::optional<size_t> {
size_t loops = 0;
if (getConstAnimationOrThrow(ObjectVariant(object))->getInfo(groupname, nullptr, nullptr, &loops))
return loops;
return sol::nullopt;
};
api["getSpeed"] = [](const sol::object& object, std::string groupname) -> sol::optional<float> {
float speed = 0.f;
if (getConstAnimationOrThrow(ObjectVariant(object))->getInfo(groupname, nullptr, &speed, nullptr))
return speed;
return sol::nullopt;
};
api["setSpeed"] = [](const sol::object& object, std::string groupname, float speed) {
getMutableAnimationOrThrow(ObjectVariant(object))->adjustSpeedMult(groupname, speed);
};
api["getActiveGroup"] = [](const sol::object& object, MWRender::BoneGroup boneGroup) -> std::string_view {
return getConstAnimationOrThrow(ObjectVariant(object))->getActiveGroup(boneGroup);
};
// Clears out the animation queue, and cancel any animation currently playing from the queue
api["clearAnimationQueue"] = [mechanics](const sol::object& object, bool clearScripted) {
MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object));
mechanics->clearAnimationQueue(ptr, clearScripted);
};
// Extended variant of MWScript's PlayGroup and LoopGroup
api["playQueued"] = sol::overload(
[mechanics](const sol::object& object, const std::string& groupname, const sol::table& options) {
int numberOfLoops = options.get_or("loops", std::numeric_limits<int>::max());
float speed = options.get_or("speed", 1.f);
std::string startKey = options.get_or<std::string>("startkey", "start");
std::string stopKey = options.get_or<std::string>("stopkey", "stop");
bool forceLoop = options.get_or("forceloop", false);
MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object));
mechanics->playAnimationGroupLua(ptr, groupname, numberOfLoops, speed, startKey, stopKey, forceLoop);
},
[mechanics](const sol::object& object, const std::string& groupname) {
MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object));
mechanics->playAnimationGroupLua(
ptr, groupname, std::numeric_limits<int>::max(), 1, "start", "stop", false);
});
api["playBlended"] = [](const sol::object& object, std::string_view groupname, const sol::table& options) {
int loops = options.get_or("loops", 0);
MWRender::Animation::AnimPriority priority = getPriorityArgument(options);
BlendMask blendMask = options.get_or("blendmask", BlendMask::BlendMask_All);
bool autoDisable = options.get_or("autodisable", true);
float speed = options.get_or("speed", 1.0f);
std::string start = options.get_or<std::string>("startkey", "start");
std::string stop = options.get_or<std::string>("stopkey", "stop");
float startpoint = options.get_or("startpoint", 0.0f);
bool forceLoop = options.get_or("forceloop", false);
auto animation = getMutableAnimationOrThrow(ObjectVariant(object));
animation->play(groupname, priority, blendMask, autoDisable, speed, start, stop, startpoint, loops,
forceLoop || animation->isLoopingAnimation(groupname));
};
api["hasGroup"] = [](const sol::object& object, std::string_view groupname) -> bool {
const MWRender::Animation* anim = getConstAnimationOrThrow(ObjectVariant(object));
return anim->hasAnimation(groupname);
};
// Note: This checks the nodemap, and does not read the scene graph itself, and so should be thread safe.
api["hasBone"] = [](const sol::object& object, std::string_view bonename) -> bool {
const MWRender::Animation* anim = getConstAnimationOrThrow(ObjectVariant(object));
return anim->getNode(bonename) != nullptr;
};
api["addVfx"] = sol::overload(
[context](const sol::object& object, const sol::object& staticOrID) {
context.mLuaManager->addAction(
[object = ObjectVariant(object), model = getStaticModelOrThrow(staticOrID)] {
MWRender::Animation* anim = getMutableAnimationOrThrow(object);
anim->addEffect(model, "");
},
"addVfxAction");
},
[context](const sol::object& object, const sol::object& staticOrID, const sol::table& options) {
context.mLuaManager->addAction(
[object = ObjectVariant(object), model = getStaticModelOrThrow(staticOrID),
effectId = options.get_or<std::string>("vfxId", ""), loop = options.get_or("loop", false),
bonename = options.get_or<std::string>("bonename", ""),
particleTexture = options.get_or<std::string>("particleTextureOverride", "")] {
MWRender::Animation* anim = getMutableAnimationOrThrow(ObjectVariant(object));
anim->addEffect(model, effectId, loop, bonename, particleTexture);
},
"addVfxAction");
});
api["removeVfx"] = [context](const sol::object& object, std::string_view effectId) {
context.mLuaManager->addAction(
[object = ObjectVariant(object), effectId = std::string(effectId)] {
MWRender::Animation* anim = getMutableAnimationOrThrow(object);
anim->removeEffect(effectId);
},
"removeVfxAction");
};
api["removeAllVfx"] = [context](const sol::object& object) {
context.mLuaManager->addAction(
[object = ObjectVariant(object)] {
MWRender::Animation* anim = getMutableAnimationOrThrow(object);
anim->removeEffects();
},
"removeVfxAction");
};
return LuaUtil::makeReadOnly(api);
}
sol::table initCoreVfxBindings(const Context& context)
{
sol::state_view& lua = context.mLua->sol();
sol::table api(lua, sol::create);
auto world = MWBase::Environment::get().getWorld();
api["spawn"] = sol::overload(
[world, context](const sol::object& staticOrID, const osg::Vec3f& worldPos) {
auto model = getStaticModelOrThrow(staticOrID);
context.mLuaManager->addAction(
[world, model, worldPos]() { world->spawnEffect(model, "", worldPos); }, "openmw.vfx.spawn");
},
[world, context](const sol::object& staticOrID, const osg::Vec3f& worldPos, const sol::table& options) {
auto model = getStaticModelOrThrow(staticOrID);
bool magicVfx = options.get_or("mwMagicVfx", true);
std::string textureOverride = options.get_or<std::string>("particleTextureOverride", "");
float scale = options.get_or("scale", 1.f);
context.mLuaManager->addAction(
[world, model, textureOverride, worldPos, scale, magicVfx]() {
world->spawnEffect(model, textureOverride, worldPos, scale, magicVfx);
},
"openmw.vfx.spawn");
});
return api;
}
}

@ -0,0 +1,12 @@
#ifndef MWLUA_ANIMATIONBINDINGS_H
#define MWLUA_ANIMATIONBINDINGS_H
#include <sol/forward.hpp>
namespace MWLua
{
sol::table initAnimationPackage(const Context& context);
sol::table initCoreVfxBindings(const Context& context);
}
#endif // MWLUA_ANIMATIONBINDINGS_H

@ -115,6 +115,13 @@ namespace MWLua
return cell == c.mStore || (cell->getCell()->getWorldSpace() == c.mStore->getCell()->getWorldSpace());
};
cellT["waterLevel"] = sol::readonly_property([](const CellT& c) -> sol::optional<float> {
if (c.mStore->getCell()->hasWater())
return c.mStore->getWaterLevel();
else
return sol::nullopt;
});
if constexpr (std::is_same_v<CellT, GCell>)
{ // only for global scripts
cellT["getAll"] = [ids = getPackageToTypeTable(context.mLua->sol())](
@ -270,7 +277,7 @@ namespace MWLua
if (!ok)
throw std::runtime_error(
std::string("Incorrect type argument in cell:getAll: " + LuaUtil::toString(*type)));
return GObjectList{ res };
return GObjectList{ std::move(res) };
};
}
}

@ -17,6 +17,7 @@
#include "../mwworld/datetimemanager.hpp"
#include "../mwworld/esmstore.hpp"
#include "animationbindings.hpp"
#include "factionbindings.hpp"
#include "luaevents.hpp"
#include "magicbindings.hpp"
@ -83,6 +84,7 @@ namespace MWLua
};
api["contentFiles"] = initContentFilesBindings(lua->sol());
api["sound"] = initCoreSoundBindings(context);
api["vfx"] = initCoreVfxBindings(context);
api["getFormId"] = [](std::string_view contentFile, unsigned int index) -> std::string {
const std::vector<std::string>& contentList = MWBase::Environment::get().getWorld()->getContentFiles();
for (size_t i = 0; i < contentList.size(); ++i)
@ -133,6 +135,7 @@ namespace MWLua
api[k] = v;
api["sendGlobalEvent"] = sol::nil;
api["sound"] = sol::nil;
api["vfx"] = sol::nil;
return LuaUtil::makeReadOnly(api);
}
}

@ -86,6 +86,15 @@ namespace MWLua
void operator()(const OnNewExterior& event) const { mGlobalScripts.onNewExterior(GCell{ &event.mCell }); }
void operator()(const OnAnimationTextKey& event) const
{
MWWorld::Ptr actor = getPtr(event.mActor);
if (actor.isEmpty())
return;
if (auto* scripts = getLocalScripts(actor))
scripts->onAnimationTextKey(event.mGroupname, event.mKey);
}
private:
MWWorld::Ptr getPtr(ESM::RefNum id) const
{

@ -51,7 +51,14 @@ namespace MWLua
{
MWWorld::CellStore& mCell;
};
using Event = std::variant<OnActive, OnInactive, OnConsume, OnActivate, OnUseItem, OnNewExterior, OnTeleported>;
struct OnAnimationTextKey
{
ESM::RefNum mActor;
std::string mGroupname;
std::string mKey;
};
using Event = std::variant<OnActive, OnInactive, OnConsume, OnActivate, OnUseItem, OnNewExterior, OnTeleported,
OnAnimationTextKey>;
void clear() { mQueue.clear(); }
void addToQueue(Event e) { mQueue.push_back(std::move(e)); }

@ -82,13 +82,18 @@ namespace MWLua
inputActions[sol::meta_function::pairs] = pairs;
}
context.mLua->sol().new_usertype<LuaUtil::InputAction::Info>("ActionInfo", "key",
sol::property([](const LuaUtil::InputAction::Info& info) { return info.mKey; }), "name",
sol::property([](const LuaUtil::InputAction::Info& info) { return info.mName; }), "description",
sol::property([](const LuaUtil::InputAction::Info& info) { return info.mDescription; }), "type",
sol::property([](const LuaUtil::InputAction::Info& info) { return info.mType; }), "l10n",
sol::property([](const LuaUtil::InputAction::Info& info) { return info.mL10n; }), "defaultValue",
sol::property([](const LuaUtil::InputAction::Info& info) { return info.mDefaultValue; }));
auto actionInfo = context.mLua->sol().new_usertype<LuaUtil::InputAction::Info>("ActionInfo");
actionInfo["key"] = sol::readonly_property(
[](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mKey; });
actionInfo["name"] = sol::readonly_property(
[](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mName; });
actionInfo["description"] = sol::readonly_property(
[](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mDescription; });
actionInfo["l10n"] = sol::readonly_property(
[](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mL10n; });
actionInfo["type"] = sol::readonly_property([](const LuaUtil::InputAction::Info& info) { return info.mType; });
actionInfo["defaultValue"]
= sol::readonly_property([](const LuaUtil::InputAction::Info& info) { return info.mDefaultValue; });
auto inputTriggers = context.mLua->sol().new_usertype<LuaUtil::InputTrigger::Registry>("InputTriggers");
inputTriggers[sol::meta_function::index]
@ -108,11 +113,15 @@ namespace MWLua
inputTriggers[sol::meta_function::pairs] = pairs;
}
context.mLua->sol().new_usertype<LuaUtil::InputTrigger::Info>("TriggerInfo", "key",
sol::property([](const LuaUtil::InputTrigger::Info& info) { return info.mKey; }), "name",
sol::property([](const LuaUtil::InputTrigger::Info& info) { return info.mName; }), "description",
sol::property([](const LuaUtil::InputTrigger::Info& info) { return info.mDescription; }), "l10n",
sol::property([](const LuaUtil::InputTrigger::Info& info) { return info.mL10n; }));
auto triggerInfo = context.mLua->sol().new_usertype<LuaUtil::InputTrigger::Info>("TriggerInfo");
triggerInfo["key"] = sol::readonly_property(
[](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mKey; });
triggerInfo["name"] = sol::readonly_property(
[](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mName; });
triggerInfo["description"] = sol::readonly_property(
[](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mDescription; });
triggerInfo["l10n"] = sol::readonly_property(
[](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mL10n; });
MWBase::InputManager* input = MWBase::Environment::get().getInputManager();
sol::table api(context.mLua->sol(), sol::create);

@ -3,6 +3,8 @@
#include <components/esm3/loadcell.hpp>
#include <components/misc/strings/lower.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwmechanics/aicombat.hpp"
#include "../mwmechanics/aiescort.hpp"
#include "../mwmechanics/aifollow.hpp"
@ -162,6 +164,10 @@ namespace MWLua
MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence();
ai.stack(MWMechanics::AiTravel(target.x(), target.y(), target.z(), false), ptr, cancelOther);
};
selfAPI["_enableLuaAnimations"] = [](SelfObject& self, bool enable) {
const MWWorld::Ptr& ptr = self.ptr();
MWBase::Environment::get().getMechanicsManager()->enableLuaAnimations(ptr, enable);
};
}
LocalScripts::LocalScripts(LuaUtil::LuaState* lua, const LObject& obj)
@ -170,7 +176,7 @@ namespace MWLua
{
this->addPackage("openmw.self", sol::make_object(lua->sol(), &mData));
registerEngineHandlers({ &mOnActiveHandlers, &mOnInactiveHandlers, &mOnConsumeHandlers, &mOnActivatedHandlers,
&mOnTeleportedHandlers });
&mOnTeleportedHandlers, &mOnAnimationTextKeyHandlers, &mOnPlayAnimationHandlers });
}
void LocalScripts::setActive(bool active)

@ -71,6 +71,14 @@ namespace MWLua
void onConsume(const LObject& consumable) { callEngineHandlers(mOnConsumeHandlers, consumable); }
void onActivated(const LObject& actor) { callEngineHandlers(mOnActivatedHandlers, actor); }
void onTeleported() { callEngineHandlers(mOnTeleportedHandlers); }
void onAnimationTextKey(std::string_view groupname, std::string_view key)
{
callEngineHandlers(mOnAnimationTextKeyHandlers, groupname, key);
}
void onPlayAnimation(std::string_view groupname, const sol::table& options)
{
callEngineHandlers(mOnPlayAnimationHandlers, groupname, options);
}
void applyStatsCache();
@ -83,6 +91,8 @@ namespace MWLua
EngineHandlerList mOnConsumeHandlers{ "onConsume" };
EngineHandlerList mOnActivatedHandlers{ "onActivated" };
EngineHandlerList mOnTeleportedHandlers{ "onTeleported" };
EngineHandlerList mOnAnimationTextKeyHandlers{ "_onAnimationTextKey" };
EngineHandlerList mOnPlayAnimationHandlers{ "_onPlayAnimation" };
};
}

@ -7,6 +7,7 @@
#include "../mwbase/world.hpp"
#include "../mwworld/datetimemanager.hpp"
#include "animationbindings.hpp"
#include "camerabindings.hpp"
#include "cellbindings.hpp"
#include "corebindings.hpp"
@ -30,6 +31,7 @@ namespace MWLua
sol::state_view lua = context.mLua->sol();
MWWorld::DateTimeManager* tm = MWBase::Environment::get().getWorld()->getTimeManager();
return {
{ "openmw.animation", initAnimationPackage(context) },
{ "openmw.async",
LuaUtil::getAsyncPackageInitializer(
lua, [tm] { return tm->getSimulationTime(); }, [tm] { return tm->getGameTime(); }) },

@ -24,6 +24,7 @@
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/world.hpp"
#include "../mwrender/bonegroup.hpp"
#include "../mwrender/postprocessor.hpp"
#include "../mwworld/datetimemanager.hpp"
@ -402,6 +403,49 @@ namespace MWLua
mEngineEvents.addToQueue(EngineEvents::OnUseItem{ getId(actor), getId(object), force });
}
void LuaManager::animationTextKey(const MWWorld::Ptr& actor, const std::string& key)
{
auto pos = key.find(": ");
if (pos != std::string::npos)
mEngineEvents.addToQueue(
EngineEvents::OnAnimationTextKey{ getId(actor), key.substr(0, pos), key.substr(pos + 2) });
}
void LuaManager::playAnimation(const MWWorld::Ptr& actor, const std::string& groupname,
const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult,
std::string_view start, std::string_view stop, float startpoint, size_t loops, bool loopfallback)
{
sol::table options = mLua.newTable();
options["blendmask"] = blendMask;
options["autodisable"] = autodisable;
options["speed"] = speedmult;
options["startkey"] = start;
options["stopkey"] = stop;
options["startpoint"] = startpoint;
options["loops"] = loops;
options["forceloop"] = loopfallback;
bool priorityAsTable = false;
for (uint32_t i = 1; i < MWRender::sNumBlendMasks; i++)
if (priority[static_cast<MWRender::BoneGroup>(i)] != priority[static_cast<MWRender::BoneGroup>(0)])
priorityAsTable = true;
if (priorityAsTable)
{
sol::table priorityTable = mLua.newTable();
for (uint32_t i = 0; i < MWRender::sNumBlendMasks; i++)
priorityTable[static_cast<MWRender::BoneGroup>(i)] = priority[static_cast<MWRender::BoneGroup>(i)];
options["priority"] = priorityTable;
}
else
options["priority"] = priority[MWRender::BoneGroup_LowerBody];
// mEngineEvents.addToQueue(event);
// Has to be called immediately, otherwise engine details that depend on animations playing immediately
// break.
if (auto* scripts = actor.getRefData().getLuaScripts())
scripts->onPlayAnimation(groupname, options);
}
void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr)
{
mObjectLists.objectAddedToScene(ptr); // assigns generated RefNum if it is not set yet.

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

@ -389,6 +389,17 @@ namespace MWLua
auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS();
return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs);
});
magicEffectT["particle"]
= sol::readonly_property([](const ESM::MagicEffect& rec) -> std::string_view { return rec.mParticle; });
magicEffectT["continuousVfx"] = sol::readonly_property([](const ESM::MagicEffect& rec) -> bool {
return (rec.mData.mFlags & ESM::MagicEffect::ContinuousVfx) != 0;
});
magicEffectT["castingStatic"] = sol::readonly_property(
[](const ESM::MagicEffect& rec) -> std::string { return rec.mCasting.serializeText(); });
magicEffectT["hitStatic"] = sol::readonly_property(
[](const ESM::MagicEffect& rec) -> std::string { return rec.mHit.serializeText(); });
magicEffectT["areaStatic"] = sol::readonly_property(
[](const ESM::MagicEffect& rec) -> std::string { return rec.mArea.serializeText(); });
magicEffectT["name"] = sol::readonly_property([](const ESM::MagicEffect& rec) -> std::string_view {
return MWBase::Environment::get()
.getWorld()
@ -730,7 +741,7 @@ namespace MWLua
auto id = sol::make_object(lua, self.mIterator->getId().serializeText());
auto params = sol::make_object(lua, ActiveSpell{ self.mActor, *self.mIterator });
self.advance();
return { params, params };
return { id, params };
}
else
{

@ -104,8 +104,12 @@ namespace MWLua
});
mwscript["player"] = sol::readonly_property(
[](const MWScriptRef&) { return GObject(MWBase::Environment::get().getWorld()->getPlayerPtr()); });
mwscriptVars[sol::meta_function::index] = [](MWScriptVariables& s, std::string_view var) {
return s.mRef.getLocals().getVarAsDouble(s.mRef.mId, Misc::StringUtils::lowerCase(var));
mwscriptVars[sol::meta_function::index]
= [](MWScriptVariables& s, std::string_view var) -> sol::optional<double> {
if (s.mRef.getLocals().hasVar(s.mRef.mId, var))
return s.mRef.getLocals().getVarAsDouble(s.mRef.mId, Misc::StringUtils::lowerCase(var));
else
return sol::nullopt;
};
mwscriptVars[sol::meta_function::new_index] = [](MWScriptVariables& s, std::string_view var, double val) {
MWScript::Locals& locals = s.mRef.getLocals();

@ -613,7 +613,7 @@ namespace MWLua
MWBase::Environment::get().getWorldModel()->registerPtr(item);
list->push_back(getId(item));
}
return ObjectList<ObjectT>{ list };
return ObjectList<ObjectT>{ std::move(list) };
};
inventoryT["countOf"] = [](const InventoryT& inventory, std::string_view recordId) {
@ -661,7 +661,7 @@ namespace MWLua
list->push_back(getId(item));
}
}
return ObjectList<ObjectT>{ list };
return ObjectList<ObjectT>{ std::move(list) };
};
}

@ -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
@ -99,9 +115,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(); };

@ -380,7 +380,7 @@ namespace MWLua
addProp(context, attributeStatT, "base", &MWMechanics::AttributeValue::getBase);
addProp(context, attributeStatT, "damage", &MWMechanics::AttributeValue::getDamage);
attributeStatT["modified"]
= sol::property([=](const AttributeStat& stat) { return stat.getModified(context); });
= sol::readonly_property([=](const AttributeStat& stat) { return stat.getModified(context); });
addProp(context, attributeStatT, "modifier", &MWMechanics::AttributeValue::getModifier);
sol::table attributes(context.mLua->sol(), sol::create);
stats["attributes"] = LuaUtil::makeReadOnly(attributes);
@ -399,7 +399,8 @@ namespace MWLua
auto skillStatT = context.mLua->sol().new_usertype<SkillStat>("SkillStat");
addProp(context, skillStatT, "base", &MWMechanics::SkillValue::getBase);
addProp(context, skillStatT, "damage", &MWMechanics::SkillValue::getDamage);
skillStatT["modified"] = sol::property([=](const SkillStat& stat) { return stat.getModified(context); });
skillStatT["modified"]
= sol::readonly_property([=](const SkillStat& stat) { return stat.getModified(context); });
addProp(context, skillStatT, "modifier", &MWMechanics::SkillValue::getModifier);
skillStatT["progress"] = sol::property([context](const SkillStat& stat) { return stat.getProgress(context); },
[context](const SkillStat& stat, const sol::object& value) { stat.cache(context, "progress", value); });

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

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

@ -324,7 +324,7 @@ namespace MWMechanics
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr);
if (animation && !reflectStatic->mModel.empty())
animation->addEffect(Misc::ResourceHelpers::correctMeshPath(reflectStatic->mModel),
ESM::MagicEffect::Reflect, false);
ESM::MagicEffect::indexToName(ESM::MagicEffect::Reflect), false);
caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell(*reflected);
}
if (removedSpell)

@ -2019,6 +2019,24 @@ namespace MWMechanics
return false;
}
}
bool Actors::playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, float speed,
std::string_view startKey, std::string_view stopKey, bool forceLoop)
{
const auto iter = mIndex.find(ptr.mRef);
if (iter != mIndex.end())
return iter->second->getCharacterController().playGroupLua(
groupName, speed, startKey, stopKey, loops, forceLoop);
return false;
}
void Actors::enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable)
{
const auto iter = mIndex.find(ptr.mRef);
if (iter != mIndex.end())
iter->second->getCharacterController().enableLuaAnimations(enable);
}
void Actors::skipAnimation(const MWWorld::Ptr& ptr) const
{
const auto iter = mIndex.find(ptr.mRef);
@ -2034,12 +2052,27 @@ 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)
actor.getCharacterController().persistAnimationState();
}
void Actors::clearAnimationQueue(const MWWorld::Ptr& ptr, bool clearScripted)
{
const auto iter = mIndex.find(ptr.mRef);
if (iter != mIndex.end())
iter->second->getCharacterController().clearAnimQueue(clearScripted);
}
void Actors::getObjectsInRange(const osg::Vec3f& position, float radius, std::vector<MWWorld::Ptr>& out) const
{
for (const Actor& actor : mActors)

@ -114,9 +114,14 @@ namespace MWMechanics
bool playAnimationGroup(
const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false) const;
bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, float speed,
std::string_view startKey, std::string_view stopKey, bool forceLoop);
void enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable);
void skipAnimation(const MWWorld::Ptr& ptr) const;
bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) const;
bool checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const;
void persistAnimationStates() const;
void clearAnimationQueue(const MWWorld::Ptr& ptr, bool clearScripted);
void getObjectsInRange(const osg::Vec3f& position, float radius, std::vector<MWWorld::Ptr>& out) const;

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

@ -36,6 +36,7 @@
#include "../mwrender/animation.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/luamanager.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/windowmanager.hpp"
@ -270,7 +271,7 @@ namespace
case CharState_IdleSwim:
return Priority_SwimIdle;
case CharState_IdleSneak:
priority[MWRender::Animation::BoneGroup_LowerBody] = Priority_SneakIdleLowerBody;
priority[MWRender::BoneGroup_LowerBody] = Priority_SneakIdleLowerBody;
[[fallthrough]];
default:
return priority;
@ -444,8 +445,8 @@ namespace MWMechanics
{
mHitState = CharState_Block;
priority = Priority_Hit;
priority[MWRender::Animation::BoneGroup_LeftArm] = Priority_Block;
priority[MWRender::Animation::BoneGroup_LowerBody] = Priority_WeaponLowerBody;
priority[MWRender::BoneGroup_LeftArm] = Priority_Block;
priority[MWRender::BoneGroup_LowerBody] = Priority_WeaponLowerBody;
startKey = "block start";
stopKey = "block stop";
}
@ -482,8 +483,7 @@ namespace MWMechanics
return;
}
mAnimation->play(
mCurrentHit, priority, MWRender::Animation::BlendMask_All, true, 1, startKey, stopKey, 0.0f, ~0ul);
playBlendedAnimation(mCurrentHit, priority, MWRender::BlendMask_All, true, 1, startKey, stopKey, 0.0f, ~0ul);
}
void CharacterController::refreshJumpAnims(JumpingState jump, bool force)
@ -502,7 +502,7 @@ namespace MWMechanics
std::string_view weapShortGroup = getWeaponShortGroup(mWeaponType);
std::string jumpAnimName = "jump";
jumpAnimName += weapShortGroup;
MWRender::Animation::BlendMask jumpmask = MWRender::Animation::BlendMask_All;
MWRender::Animation::BlendMask jumpmask = MWRender::BlendMask_All;
if (!weapShortGroup.empty() && !mAnimation->hasAnimation(jumpAnimName))
jumpAnimName = fallbackShortWeaponGroup("jump", &jumpmask);
@ -520,10 +520,10 @@ namespace MWMechanics
mCurrentJump = jumpAnimName;
if (mJumpState == JumpState_InAir)
mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, false, 1.0f, startAtLoop ? "loop start" : "start",
"stop", 0.f, ~0ul);
playBlendedAnimation(jumpAnimName, Priority_Jump, jumpmask, false, 1.0f,
startAtLoop ? "loop start" : "start", "stop", 0.f, ~0ul);
else if (mJumpState == JumpState_Landing)
mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, true, 1.0f, "loop stop", "stop", 0.0f, 0);
playBlendedAnimation(jumpAnimName, Priority_Jump, jumpmask, true, 1.0f, "loop stop", "stop", 0.0f, 0);
}
bool CharacterController::onOpen() const
@ -539,8 +539,8 @@ namespace MWMechanics
if (mAnimation->isPlaying("containerclose"))
return false;
mAnimation->play("containeropen", Priority_Scripted, MWRender::Animation::BlendMask_All, false, 1.0f,
"start", "stop", 0.f, 0);
mAnimation->play(
"containeropen", Priority_Scripted, MWRender::BlendMask_All, false, 1.0f, "start", "stop", 0.f, 0);
if (mAnimation->isPlaying("containeropen"))
return false;
}
@ -560,8 +560,8 @@ namespace MWMechanics
if (animPlaying)
startPoint = 1.f - complete;
mAnimation->play("containerclose", Priority_Scripted, MWRender::Animation::BlendMask_All, false, 1.0f,
"start", "stop", startPoint, 0);
mAnimation->play("containerclose", Priority_Scripted, MWRender::BlendMask_All, false, 1.0f, "start", "stop",
startPoint, 0);
}
}
@ -600,7 +600,7 @@ namespace MWMechanics
if (!isRealWeapon(mWeaponType))
{
if (blendMask != nullptr)
*blendMask = MWRender::Animation::BlendMask_LowerBody;
*blendMask = MWRender::BlendMask_LowerBody;
return baseGroupName;
}
@ -619,13 +619,13 @@ namespace MWMechanics
// Special case for crossbows - we should apply 1h animations a fallback only for lower body
if (mWeaponType == ESM::Weapon::MarksmanCrossbow && blendMask != nullptr)
*blendMask = MWRender::Animation::BlendMask_LowerBody;
*blendMask = MWRender::BlendMask_LowerBody;
if (!mAnimation->hasAnimation(groupName))
{
groupName = baseGroupName;
if (blendMask != nullptr)
*blendMask = MWRender::Animation::BlendMask_LowerBody;
*blendMask = MWRender::BlendMask_LowerBody;
}
return groupName;
@ -658,7 +658,7 @@ namespace MWMechanics
}
}
MWRender::Animation::BlendMask movemask = MWRender::Animation::BlendMask_All;
MWRender::Animation::BlendMask movemask = MWRender::BlendMask_All;
std::string_view weapShortGroup = getWeaponShortGroup(mWeaponType);
@ -684,7 +684,7 @@ namespace MWMechanics
if (!mAnimation->hasAnimation(weapMovementAnimName))
weapMovementAnimName = fallbackShortWeaponGroup(movementAnimName, &movemask);
movementAnimName = weapMovementAnimName;
movementAnimName = std::move(weapMovementAnimName);
}
if (!mAnimation->hasAnimation(movementAnimName))
@ -749,7 +749,7 @@ namespace MWMechanics
}
}
mAnimation->play(
playBlendedAnimation(
mCurrentMovement, Priority_Movement, movemask, false, 1.f, "start", "stop", startpoint, ~0ul, true);
}
@ -798,7 +798,7 @@ namespace MWMechanics
weapIdleGroup += weapShortGroup;
if (!mAnimation->hasAnimation(weapIdleGroup))
weapIdleGroup = fallbackShortWeaponGroup(idleGroup);
idleGroup = weapIdleGroup;
idleGroup = std::move(weapIdleGroup);
// play until the Loop Stop key 2 to 5 times, then play until the Stop key
// this replicates original engine behavior for the "Idle1h" 1st-person animation
@ -820,9 +820,9 @@ namespace MWMechanics
mAnimation->getInfo(mCurrentIdle, &startPoint);
clearStateAnimation(mCurrentIdle);
mCurrentIdle = idleGroup;
mAnimation->play(mCurrentIdle, priority, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop",
startPoint, numLoops, true);
mCurrentIdle = std::move(idleGroup);
playBlendedAnimation(
mCurrentIdle, priority, MWRender::BlendMask_All, false, 1.0f, "start", "stop", startPoint, numLoops, true);
}
void CharacterController::refreshCurrentAnims(
@ -855,8 +855,8 @@ namespace MWMechanics
resetCurrentIdleState();
resetCurrentJumpState();
mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::BlendMask_All, false, 1.0f, "start",
"stop", startpoint, 0);
playBlendedAnimation(
mCurrentDeath, Priority_Death, MWRender::BlendMask_All, false, 1.0f, "start", "stop", startpoint, 0);
}
CharacterState CharacterController::chooseRandomDeathState() const
@ -998,6 +998,8 @@ namespace MWMechanics
{
std::string_view evt = key->second;
MWBase::Environment::get().getLuaManager()->animationTextKey(mPtr, key->second);
if (evt.substr(0, 7) == "sound: ")
{
MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager();
@ -1189,8 +1191,9 @@ namespace MWMechanics
{
if (!animPlaying)
{
int mask = MWRender::Animation::BlendMask_Torso | MWRender::Animation::BlendMask_RightArm;
mAnimation->play("idlestorm", Priority_Storm, mask, true, 1.0f, "start", "stop", 0.0f, ~0ul, true);
int mask = MWRender::BlendMask_Torso | MWRender::BlendMask_RightArm;
playBlendedAnimation(
"idlestorm", Priority_Storm, mask, true, 1.0f, "start", "stop", 0.0f, ~0ul, true);
}
else
{
@ -1247,41 +1250,6 @@ namespace MWMechanics
}
}
bool CharacterController::isLoopingAnimation(std::string_view group) const
{
// In Morrowind, a some animation groups are always considered looping, regardless
// of loop start/stop keys.
// To be match vanilla behavior we probably only need to check this list, but we don't
// want to prevent modded animations with custom group names from looping either.
static const std::unordered_set<std::string_view> loopingAnimations = { "walkforward", "walkback", "walkleft",
"walkright", "swimwalkforward", "swimwalkback", "swimwalkleft", "swimwalkright", "runforward", "runback",
"runleft", "runright", "swimrunforward", "swimrunback", "swimrunleft", "swimrunright", "sneakforward",
"sneakback", "sneakleft", "sneakright", "turnleft", "turnright", "swimturnleft", "swimturnright",
"spellturnleft", "spellturnright", "torch", "idle", "idle2", "idle3", "idle4", "idle5", "idle6", "idle7",
"idle8", "idle9", "idlesneak", "idlestorm", "idleswim", "jump", "inventoryhandtohand",
"inventoryweapononehand", "inventoryweapontwohand", "inventoryweapontwowide" };
static const std::vector<std::string_view> shortGroups = getAllWeaponTypeShortGroups();
if (mAnimation && mAnimation->getTextKeyTime(std::string(group) + ": loop start") >= 0)
return true;
// Most looping animations have variants for each weapon type shortgroup.
// Just remove the shortgroup instead of enumerating all of the possible animation groupnames.
// Make sure we pick the longest shortgroup so e.g. "bow" doesn't get picked over "crossbow"
// when the shortgroup is crossbow.
std::size_t suffixLength = 0;
for (std::string_view suffix : shortGroups)
{
if (suffix.length() > suffixLength && group.ends_with(suffix))
{
suffixLength = suffix.length();
}
}
group.remove_suffix(suffixLength);
return loopingAnimations.count(group) > 0;
}
bool CharacterController::updateWeaponState()
{
// If the current animation is scripted, we can't do anything here.
@ -1357,8 +1325,8 @@ namespace MWMechanics
if (mAnimation->isPlaying("shield"))
mAnimation->disable("shield");
mAnimation->play("torch", Priority_Torch, MWRender::Animation::BlendMask_LeftArm, false, 1.0f, "start",
"stop", 0.0f, std::numeric_limits<size_t>::max(), true);
playBlendedAnimation("torch", Priority_Torch, MWRender::BlendMask_LeftArm, false, 1.0f, "start", "stop",
0.0f, std::numeric_limits<size_t>::max(), true);
}
else if (mAnimation->isPlaying("torch"))
{
@ -1369,7 +1337,7 @@ namespace MWMechanics
// For biped actors, blend weapon animations with lower body animations with higher priority
MWRender::Animation::AnimPriority priorityWeapon(Priority_Weapon);
if (cls.isBipedal(mPtr))
priorityWeapon[MWRender::Animation::BoneGroup_LowerBody] = Priority_WeaponLowerBody;
priorityWeapon[MWRender::BoneGroup_LowerBody] = Priority_WeaponLowerBody;
bool forcestateupdate = false;
@ -1400,19 +1368,19 @@ namespace MWMechanics
{
// Note: we do not disable unequipping animation automatically to avoid body desync
weapgroup = getWeaponAnimation(mWeaponType);
int unequipMask = MWRender::Animation::BlendMask_All;
int unequipMask = MWRender::BlendMask_All;
bool useShieldAnims = mAnimation->useShieldAnimations();
if (useShieldAnims && mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell
&& !(mWeaponType == ESM::Weapon::None && weaptype == ESM::Weapon::Spell))
{
unequipMask = unequipMask | ~MWRender::Animation::BlendMask_LeftArm;
mAnimation->play("shield", Priority_Block, MWRender::Animation::BlendMask_LeftArm, true, 1.0f,
unequipMask = unequipMask | ~MWRender::BlendMask_LeftArm;
playBlendedAnimation("shield", Priority_Block, MWRender::BlendMask_LeftArm, true, 1.0f,
"unequip start", "unequip stop", 0.0f, 0);
}
else if (mWeaponType == ESM::Weapon::HandToHand)
mAnimation->showCarriedLeft(false);
mAnimation->play(
playBlendedAnimation(
weapgroup, priorityWeapon, unequipMask, false, 1.0f, "unequip start", "unequip stop", 0.0f, 0);
mUpperBodyState = UpperBodyState::Unequipping;
@ -1458,15 +1426,15 @@ namespace MWMechanics
if (weaptype != ESM::Weapon::None)
{
mAnimation->showWeapons(false);
int equipMask = MWRender::Animation::BlendMask_All;
int equipMask = MWRender::BlendMask_All;
if (useShieldAnims && weaptype != ESM::Weapon::Spell)
{
equipMask = equipMask | ~MWRender::Animation::BlendMask_LeftArm;
mAnimation->play("shield", Priority_Block, MWRender::Animation::BlendMask_LeftArm, true,
1.0f, "equip start", "equip stop", 0.0f, 0);
equipMask = equipMask | ~MWRender::BlendMask_LeftArm;
playBlendedAnimation("shield", Priority_Block, MWRender::BlendMask_LeftArm, true, 1.0f,
"equip start", "equip stop", 0.0f, 0);
}
mAnimation->play(
playBlendedAnimation(
weapgroup, priorityWeapon, equipMask, true, 1.0f, "equip start", "equip stop", 0.0f, 0);
mUpperBodyState = UpperBodyState::Equipping;
@ -1617,11 +1585,11 @@ namespace MWMechanics
if (mAnimation->getNode("Bip01 L Hand"))
mAnimation->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel),
-1, false, "Bip01 L Hand", effect->mParticle);
"", false, "Bip01 L Hand", effect->mParticle);
if (mAnimation->getNode("Bip01 R Hand"))
mAnimation->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel),
-1, false, "Bip01 R Hand", effect->mParticle);
"", false, "Bip01 R Hand", effect->mParticle);
}
// first effect used for casting animation
const ESM::ENAMstruct& firstEffect = effects->front();
@ -1656,9 +1624,8 @@ namespace MWMechanics
startKey = mAttackType + " start";
stopKey = mAttackType + " stop";
}
mAnimation->play(mCurrentWeapon, priorityWeapon, MWRender::Animation::BlendMask_All, false,
1, startKey, stopKey, 0.0f, 0);
playBlendedAnimation(mCurrentWeapon, priorityWeapon, MWRender::BlendMask_All, false, 1,
startKey, stopKey, 0.0f, 0);
mUpperBodyState = UpperBodyState::Casting;
}
}
@ -1709,8 +1676,8 @@ namespace MWMechanics
mAttackVictim = MWWorld::Ptr();
mAttackHitPos = osg::Vec3f();
mAnimation->play(mCurrentWeapon, priorityWeapon, MWRender::Animation::BlendMask_All, false,
weapSpeed, startKey, stopKey, 0.0f, 0);
playBlendedAnimation(mCurrentWeapon, priorityWeapon, MWRender::BlendMask_All, false, weapSpeed,
startKey, stopKey, 0.0f, 0);
}
}
@ -1783,7 +1750,7 @@ namespace MWMechanics
}
mAnimation->disable(mCurrentWeapon);
mAnimation->play(mCurrentWeapon, priorityWeapon, MWRender::Animation::BlendMask_All, false, weapSpeed,
playBlendedAnimation(mCurrentWeapon, priorityWeapon, MWRender::BlendMask_All, false, weapSpeed,
mAttackType + " max attack", mAttackType + ' ' + hit, startPoint, 0);
}
@ -1813,7 +1780,7 @@ namespace MWMechanics
// Follow animations have lower priority than movement for non-biped creatures, logic be damned
if (!cls.isBipedal(mPtr))
priorityFollow = Priority_Default;
mAnimation->play(mCurrentWeapon, priorityFollow, MWRender::Animation::BlendMask_All, false, weapSpeed,
playBlendedAnimation(mCurrentWeapon, priorityFollow, MWRender::BlendMask_All, false, weapSpeed,
mAttackType + ' ' + start, mAttackType + ' ' + stop, 0.0f, 0);
mUpperBodyState = UpperBodyState::AttackEnd;
@ -1935,9 +1902,16 @@ namespace MWMechanics
mIdleState = CharState_SpecialIdle;
auto priority = mAnimQueue.front().mScripted ? Priority_Scripted : Priority_Default;
mAnimation->setPlayScriptedOnly(mAnimQueue.front().mScripted);
mAnimation->play(mAnimQueue.front().mGroup, priority, MWRender::Animation::BlendMask_All, false, 1.0f,
(loopStart ? "loop start" : "start"), "stop", mAnimQueue.front().mTime, mAnimQueue.front().mLoopCount,
mAnimQueue.front().mLooping);
if (mAnimQueue.front().mScripted)
mAnimation->play(mAnimQueue.front().mGroup, priority, MWRender::BlendMask_All, false,
mAnimQueue.front().mSpeed, (loopStart ? "loop start" : mAnimQueue.front().mStartKey),
mAnimQueue.front().mStopKey, mAnimQueue.front().mTime, mAnimQueue.front().mLoopCount,
mAnimQueue.front().mLooping);
else
playBlendedAnimation(mAnimQueue.front().mGroup, priority, MWRender::BlendMask_All, false,
mAnimQueue.front().mSpeed, (loopStart ? "loop start" : mAnimQueue.front().mStartKey),
mAnimQueue.front().mStopKey, mAnimQueue.front().mTime, mAnimQueue.front().mLoopCount,
mAnimQueue.front().mLooping);
}
}
@ -2504,6 +2478,7 @@ namespace MWMechanics
state.mScriptedAnims.clear();
for (AnimationQueue::const_iterator iter = mAnimQueue.begin(); iter != mAnimQueue.end(); ++iter)
{
// TODO: Probably want to presist lua animations too
if (!iter->mScripted)
continue;
@ -2541,8 +2516,10 @@ namespace MWMechanics
AnimationQueueEntry entry;
entry.mGroup = iter->mGroup;
entry.mLoopCount = iter->mLoopCount;
entry.mScripted = true;
entry.mLooping = isLoopingAnimation(entry.mGroup);
entry.mLooping = mAnimation->isLoopingAnimation(entry.mGroup);
entry.mStartKey = "start";
entry.mStopKey = "stop";
entry.mSpeed = 1.f;
entry.mTime = iter->mTime;
if (iter->mAbsolute)
{
@ -2559,6 +2536,18 @@ namespace MWMechanics
}
}
void CharacterController::playBlendedAnimation(const std::string& groupname, const MWRender::AnimPriority& priority,
int blendMask, bool autodisable, float speedmult, std::string_view start, std::string_view stop,
float startpoint, size_t loops, bool loopfallback) const
{
if (mLuaAnimations)
MWBase::Environment::get().getLuaManager()->playAnimation(mPtr, groupname, priority, blendMask, autodisable,
speedmult, start, stop, startpoint, loops, loopfallback);
else
mAnimation->play(
groupname, priority, blendMask, autodisable, speedmult, start, stop, startpoint, loops, loopfallback);
}
bool CharacterController::playGroup(std::string_view groupname, int mode, int count, bool scripted)
{
if (!mAnimation || !mAnimation->hasAnimation(groupname))
@ -2568,7 +2557,7 @@ namespace MWMechanics
if (isScriptedAnimPlaying() && !scripted)
return true;
bool looping = isLoopingAnimation(groupname);
bool looping = mAnimation->isLoopingAnimation(groupname);
// If this animation is a looped animation that is already playing
// and has not yet reached the end of the loop, allow it to continue animating with its existing loop count
@ -2602,8 +2591,12 @@ namespace MWMechanics
entry.mGroup = groupname;
entry.mLoopCount = count;
entry.mTime = 0.f;
entry.mScripted = scripted;
// "PlayGroup idle" is a special case, used to remove to stop scripted animations playing
entry.mScripted = (scripted && groupname != "idle");
entry.mLooping = looping;
entry.mSpeed = 1.f;
entry.mStartKey = ((mode == 2) ? "loop start" : "start");
entry.mStopKey = "stop";
bool playImmediately = false;
@ -2618,10 +2611,6 @@ namespace MWMechanics
mAnimQueue.resize(1);
}
// "PlayGroup idle" is a special case, used to stop and remove scripted animations playing
if (groupname == "idle")
entry.mScripted = false;
mAnimQueue.push_back(entry);
if (playImmediately)
@ -2630,6 +2619,42 @@ namespace MWMechanics
return true;
}
bool CharacterController::playGroupLua(std::string_view groupname, float speed, std::string_view startKey,
std::string_view stopKey, int loops, bool forceLoop)
{
// Note: In mwscript, "idle" is a special case used to clear the anim queue.
// In lua we offer an explicit clear method instead so this method does not treat "idle" special.
if (!mAnimation || !mAnimation->hasAnimation(groupname))
return false;
AnimationQueueEntry entry;
entry.mGroup = groupname;
// Note: MWScript gives one less loop to actors than non-actors.
// But this is the Lua version. We don't need to reproduce this weirdness here.
entry.mLoopCount = std::max(loops, 0);
entry.mStartKey = startKey;
entry.mStopKey = stopKey;
entry.mLooping = mAnimation->isLoopingAnimation(groupname) || forceLoop;
entry.mScripted = true;
entry.mSpeed = speed;
entry.mTime = 0;
if (mAnimQueue.size() > 1)
mAnimQueue.resize(1);
mAnimQueue.push_back(entry);
if (mAnimQueue.size() == 1)
playAnimQueue();
return true;
}
void CharacterController::enableLuaAnimations(bool enable)
{
mLuaAnimations = enable;
}
void CharacterController::skipAnim()
{
mSkipAnim = true;
@ -2745,18 +2770,20 @@ namespace MWMechanics
// as it's extremely spread out (ActiveSpells, Spells, InventoryStore effects, etc...) so we do it here.
// Stop any effects that are no longer active
std::vector<int> effects;
mAnimation->getLoopingEffects(effects);
for (int effectId : effects)
{
if (mPtr.getClass().getCreatureStats(mPtr).isDeathAnimationFinished()
|| mPtr.getClass()
.getCreatureStats(mPtr)
.getMagicEffects()
.getOrDefault(MWMechanics::EffectKey(effectId))
.getMagnitude()
<= 0)
std::vector<std::string_view> effects = mAnimation->getLoopingEffects();
for (std::string_view effectId : effects)
{
auto index = ESM::MagicEffect::indexNameToIndex(effectId);
if (index >= 0
&& (mPtr.getClass().getCreatureStats(mPtr).isDeathAnimationFinished()
|| mPtr.getClass()
.getCreatureStats(mPtr)
.getMagicEffects()
.getOrDefault(MWMechanics::EffectKey(index))
.getMagnitude()
<= 0))
mAnimation->removeEffect(effectId);
}
}

@ -138,9 +138,13 @@ namespace MWMechanics
float mTime;
bool mLooping;
bool mScripted;
std::string mStartKey;
std::string mStopKey;
float mSpeed;
};
typedef std::deque<AnimationQueueEntry> AnimationQueue;
AnimationQueue mAnimQueue;
bool mLuaAnimations{ false };
CharacterState mIdleState{ CharState_None };
std::string mCurrentIdle;
@ -209,15 +213,12 @@ namespace MWMechanics
void refreshMovementAnims(CharacterState movement, bool force = false);
void refreshIdleAnims(CharacterState idle, bool force = false);
void clearAnimQueue(bool clearScriptedAnims = false);
bool updateWeaponState();
void updateIdleStormState(bool inwater) const;
std::string chooseRandomAttackAnimation() const;
static bool isRandomAttackAnimation(std::string_view group);
bool isScriptedAnimPlaying() const;
bool isMovementAnimationControlled() const;
void updateAnimQueue();
@ -248,8 +249,6 @@ namespace MWMechanics
void prepareHit();
bool isLoopingAnimation(std::string_view group) const;
public:
CharacterController(const MWWorld::Ptr& ptr, MWRender::Animation* anim);
virtual ~CharacterController();
@ -275,9 +274,17 @@ namespace MWMechanics
void persistAnimationState() const;
void unpersistAnimationState();
void playBlendedAnimation(const std::string& groupname, const MWRender::AnimPriority& priority, int blendMask,
bool autodisable, float speedmult, std::string_view start, std::string_view stop, float startpoint,
size_t loops, bool loopfallback = false) const;
bool playGroup(std::string_view groupname, int mode, int count, bool scripted = false);
bool playGroupLua(std::string_view groupname, float speed, std::string_view startKey, std::string_view stopKey,
int loops, bool forceLoop);
void enableLuaAnimations(bool enable);
void skipAnim();
bool isAnimPlaying(std::string_view groupName) const;
bool isScriptedAnimPlaying() const;
void clearAnimQueue(bool clearScriptedAnims = false);
enum KillResult
{

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

@ -756,6 +756,21 @@ namespace MWMechanics
else
return mObjects.playAnimationGroup(ptr, groupName, mode, number, scripted);
}
bool MechanicsManager::playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops,
float speed, std::string_view startKey, std::string_view stopKey, bool forceLoop)
{
if (ptr.getClass().isActor())
return mActors.playAnimationGroupLua(ptr, groupName, loops, speed, startKey, stopKey, forceLoop);
else
return mObjects.playAnimationGroupLua(ptr, groupName, loops, speed, startKey, stopKey, forceLoop);
}
void MechanicsManager::enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable)
{
if (ptr.getClass().isActor())
mActors.enableLuaAnimations(ptr, enable);
else
mObjects.enableLuaAnimations(ptr, enable);
}
void MechanicsManager::skipAnimation(const MWWorld::Ptr& ptr)
{
if (ptr.getClass().isActor())
@ -771,6 +786,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())
@ -791,6 +814,14 @@ namespace MWMechanics
mObjects.persistAnimationStates();
}
void MechanicsManager::clearAnimationQueue(const MWWorld::Ptr& ptr, bool clearScripted)
{
if (ptr.getClass().isActor())
mActors.clearAnimationQueue(ptr, clearScripted);
else
mObjects.clearAnimationQueue(ptr, clearScripted);
}
void MechanicsManager::updateMagicEffects(const MWWorld::Ptr& ptr)
{
mActors.updateMagicEffects(ptr);

@ -143,9 +143,14 @@ namespace MWMechanics
/// @return Success or error
bool playAnimationGroup(
const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false) override;
bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, float speed,
std::string_view startKey, std::string_view stopKey, bool forceLoop) override;
void enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable) override;
void skipAnimation(const MWWorld::Ptr& ptr) override;
bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) override;
bool checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const override;
void persistAnimationStates() override;
void clearAnimationQueue(const MWWorld::Ptr& ptr, bool clearScripted) override;
/// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently
/// paused we may want to do it manually (after equipping permanent enchantment)

@ -113,6 +113,23 @@ namespace MWMechanics
return false;
}
}
bool Objects::playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, float speed,
std::string_view startKey, std::string_view stopKey, bool forceLoop)
{
const auto iter = mIndex.find(ptr.mRef);
if (iter != mIndex.end())
return iter->second->playGroupLua(groupName, speed, startKey, stopKey, loops, forceLoop);
return false;
}
void Objects::enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable)
{
const auto iter = mIndex.find(ptr.mRef);
if (iter != mIndex.end())
iter->second->enableLuaAnimations(enable);
}
void Objects::skipAnimation(const MWWorld::Ptr& ptr)
{
const auto iter = mIndex.find(ptr.mRef);
@ -126,6 +143,13 @@ namespace MWMechanics
object.persistAnimationState();
}
void Objects::clearAnimationQueue(const MWWorld::Ptr& ptr, bool clearScripted)
{
const auto iter = mIndex.find(ptr.mRef);
if (iter != mIndex.end())
iter->second->clearAnimQueue(clearScripted);
}
void Objects::getObjectsInRange(const osg::Vec3f& position, float radius, std::vector<MWWorld::Ptr>& out) const
{
for (const CharacterController& object : mObjects)

@ -47,8 +47,12 @@ namespace MWMechanics
bool playAnimationGroup(
const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false);
bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, float speed,
std::string_view startKey, std::string_view stopKey, bool forceLoop);
void enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable);
void skipAnimation(const MWWorld::Ptr& ptr);
void persistAnimationStates();
void clearAnimationQueue(const MWWorld::Ptr& ptr, bool clearScripted);
void getObjectsInRange(const osg::Vec3f& position, float radius, std::vector<MWWorld::Ptr>& out) const;

@ -552,8 +552,8 @@ namespace MWMechanics
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(mCaster);
if (animation)
{
animation->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel), effect->mIndex, false,
{}, effect->mParticle);
animation->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel),
ESM::MagicEffect::indexToName(effect->mIndex), false, {}, effect->mParticle);
}
else
{
@ -626,8 +626,8 @@ namespace MWMechanics
{
// Don't play particle VFX unless the effect is new or it should be looping.
if (playNonLooping || loop)
anim->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel), magicEffect.mIndex, loop,
{}, magicEffect.mParticle);
anim->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel),
ESM::MagicEffect::indexToName(magicEffect.mIndex), loop, {}, magicEffect.mParticle);
}
}
}

@ -285,8 +285,8 @@ namespace
const ESM::Static* absorbStatic = esmStore.get<ESM::Static>().find(ESM::RefId::stringRefId("VFX_Absorb"));
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target);
if (animation && !absorbStatic->mModel.empty())
animation->addEffect(
Misc::ResourceHelpers::correctMeshPath(absorbStatic->mModel), ESM::MagicEffect::SpellAbsorption, false);
animation->addEffect(Misc::ResourceHelpers::correctMeshPath(absorbStatic->mModel),
ESM::MagicEffect::indexToName(ESM::MagicEffect::SpellAbsorption), false);
const ESM::Spell* spell = esmStore.get<ESM::Spell>().search(spellId);
int spellCost = 0;
if (spell)
@ -455,11 +455,11 @@ namespace MWMechanics
if (!caster.isEmpty())
{
MWRender::Animation* anim = world->getAnimation(caster);
anim->removeEffect(effect.mEffectId);
anim->removeEffect(ESM::MagicEffect::indexToName(effect.mEffectId));
const ESM::Static* fx
= world->getStore().get<ESM::Static>().search(ESM::RefId::stringRefId("VFX_Summon_end"));
if (fx)
anim->addEffect(Misc::ResourceHelpers::correctMeshPath(fx->mModel), -1);
anim->addEffect(Misc::ResourceHelpers::correctMeshPath(fx->mModel), "");
}
}
else if (caster == getPlayer())
@ -490,7 +490,7 @@ namespace MWMechanics
if (!caster.isEmpty())
{
MWRender::Animation* anim = world->getAnimation(caster);
anim->removeEffect(effect.mEffectId);
anim->removeEffect(ESM::MagicEffect::indexToName(effect.mEffectId));
}
}
}
@ -1045,7 +1045,7 @@ namespace MWMechanics
effect.mFlags |= ESM::ActiveEffect::Flag_Remove;
auto anim = world->getAnimation(target);
if (anim)
anim->removeEffect(effect.mEffectId);
anim->removeEffect(ESM::MagicEffect::indexToName(effect.mEffectId));
}
else
effect.mFlags |= ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Remove;
@ -1287,7 +1287,7 @@ namespace MWMechanics
{
auto anim = MWBase::Environment::get().getWorld()->getAnimation(target);
if (anim)
anim->removeEffect(effect.mEffectId);
anim->removeEffect(ESM::MagicEffect::indexToName(effect.mEffectId));
}
}

@ -105,7 +105,7 @@ namespace MWMechanics
const ESM::Static* fx
= world->getStore().get<ESM::Static>().search(ESM::RefId::stringRefId("VFX_Summon_Start"));
if (fx)
anim->addEffect(Misc::ResourceHelpers::correctMeshPath(fx->mModel), -1, false);
anim->addEffect(Misc::ResourceHelpers::correctMeshPath(fx->mModel), "", false);
}
}
catch (std::exception& e)

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

@ -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>
@ -45,6 +46,7 @@
#include <components/settings/values.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/luamanager.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/cellstore.hpp"
#include "../mwworld/class.hpp"
@ -52,6 +54,7 @@
#include "../mwworld/esmstore.hpp"
#include "../mwmechanics/character.hpp" // FIXME: for MWMechanics::Priority
#include "../mwmechanics/weapontype.hpp"
#include "actorutil.hpp"
#include "rotatecontroller.hpp"
@ -300,11 +303,10 @@ namespace
RemoveCallbackVisitor()
: RemoveVisitor()
, mHasMagicEffects(false)
, mEffectId(-1)
{
}
RemoveCallbackVisitor(int effectId)
RemoveCallbackVisitor(std::string_view effectId)
: RemoveVisitor()
, mHasMagicEffects(false)
, mEffectId(effectId)
@ -323,7 +325,7 @@ namespace
MWRender::UpdateVfxCallback* vfxCallback = dynamic_cast<MWRender::UpdateVfxCallback*>(callback);
if (vfxCallback)
{
bool toRemove = mEffectId < 0 || vfxCallback->mParams.mEffectId == mEffectId;
bool toRemove = mEffectId == "" || vfxCallback->mParams.mEffectId == mEffectId;
if (toRemove)
mToRemove.emplace_back(group.asNode(), group.getParent(0));
else
@ -337,7 +339,7 @@ namespace
void apply(osg::Geometry&) override {}
private:
int mEffectId;
std::string_view mEffectId;
};
class FindVfxCallbacksVisitor : public osg::NodeVisitor
@ -347,11 +349,10 @@ namespace
FindVfxCallbacksVisitor()
: osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
, mEffectId(-1)
{
}
FindVfxCallbacksVisitor(int effectId)
FindVfxCallbacksVisitor(std::string_view effectId)
: osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
, mEffectId(effectId)
{
@ -367,7 +368,7 @@ namespace
MWRender::UpdateVfxCallback* vfxCallback = dynamic_cast<MWRender::UpdateVfxCallback*>(callback);
if (vfxCallback)
{
if (mEffectId < 0 || vfxCallback->mParams.mEffectId == mEffectId)
if (mEffectId == "" || vfxCallback->mParams.mEffectId == mEffectId)
{
mCallbacks.push_back(vfxCallback);
}
@ -381,7 +382,7 @@ namespace
void apply(osg::Geometry&) override {}
private:
int mEffectId;
std::string_view mEffectId;
};
osg::ref_ptr<osg::LightModel> getVFXLightModelInstance()
@ -446,7 +447,7 @@ namespace MWRender
typedef std::map<std::string, osg::ref_ptr<SceneUtil::KeyframeController>> ControllerMap;
ControllerMap mControllerMap[Animation::sNumBlendMasks];
ControllerMap mControllerMap[sNumBlendMasks];
const SceneUtil::TextKeyMap& getTextKeys() const;
};
@ -714,6 +715,41 @@ namespace MWRender
return mSupportedAnimations.find(anim) != mSupportedAnimations.end();
}
bool Animation::isLoopingAnimation(std::string_view group) const
{
// In Morrowind, a some animation groups are always considered looping, regardless
// of loop start/stop keys.
// To be match vanilla behavior we probably only need to check this list, but we don't
// want to prevent modded animations with custom group names from looping either.
static const std::unordered_set<std::string_view> loopingAnimations = { "walkforward", "walkback", "walkleft",
"walkright", "swimwalkforward", "swimwalkback", "swimwalkleft", "swimwalkright", "runforward", "runback",
"runleft", "runright", "swimrunforward", "swimrunback", "swimrunleft", "swimrunright", "sneakforward",
"sneakback", "sneakleft", "sneakright", "turnleft", "turnright", "swimturnleft", "swimturnright",
"spellturnleft", "spellturnright", "torch", "idle", "idle2", "idle3", "idle4", "idle5", "idle6", "idle7",
"idle8", "idle9", "idlesneak", "idlestorm", "idleswim", "jump", "inventoryhandtohand",
"inventoryweapononehand", "inventoryweapontwohand", "inventoryweapontwowide" };
static const std::vector<std::string_view> shortGroups = MWMechanics::getAllWeaponTypeShortGroups();
if (getTextKeyTime(std::string(group) + ": loop start") >= 0)
return true;
// Most looping animations have variants for each weapon type shortgroup.
// Just remove the shortgroup instead of enumerating all of the possible animation groupnames.
// Make sure we pick the longest shortgroup so e.g. "bow" doesn't get picked over "crossbow"
// when the shortgroup is crossbow.
std::size_t suffixLength = 0;
for (std::string_view suffix : shortGroups)
{
if (suffix.length() > suffixLength && group.ends_with(suffix))
{
suffixLength = suffix.length();
}
}
group.remove_suffix(suffixLength);
return loopingAnimations.count(group) > 0;
}
float Animation::getStartTime(const std::string& groupname) const
{
for (AnimSourceList::const_reverse_iterator iter(mAnimSources.rbegin()); iter != mAnimSources.rend(); ++iter)
@ -757,16 +793,14 @@ namespace MWRender
state.mLoopStopTime = key->first;
}
if (mTextKeyListener)
try
{
try
{
if (mTextKeyListener != nullptr)
mTextKeyListener->handleTextKey(groupname, key, map);
}
catch (std::exception& e)
{
Log(Debug::Error) << "Error handling text key " << evt << ": " << e.what();
}
}
catch (std::exception& e)
{
Log(Debug::Error) << "Error handling text key " << evt << ": " << e.what();
}
}
@ -922,7 +956,7 @@ namespace MWRender
return true;
}
void Animation::setTextKeyListener(Animation::TextKeyListener* listener)
void Animation::setTextKeyListener(TextKeyListener* listener)
{
mTextKeyListener = listener;
}
@ -1051,7 +1085,16 @@ namespace MWRender
return true;
}
float Animation::getCurrentTime(const std::string& groupname) const
std::string_view Animation::getActiveGroup(BoneGroup boneGroup) const
{
if (auto timePtr = mAnimationTimePtr[boneGroup]->getTimePtr())
for (auto& state : mStates)
if (state.second.mTime == timePtr)
return state.first;
return "";
}
float Animation::getCurrentTime(std::string_view groupname) const
{
AnimStateMap::const_iterator iter = mStates.find(groupname);
if (iter == mStates.end())
@ -1495,8 +1538,8 @@ namespace MWRender
mExtraLightSource->setActorFade(mAlpha);
}
void Animation::addEffect(
const std::string& model, int effectId, bool loop, std::string_view bonename, std::string_view texture)
void Animation::addEffect(std::string_view model, std::string_view effectId, bool loop, std::string_view bonename,
std::string_view texture)
{
if (!mObjectRoot.get())
return;
@ -1578,7 +1621,7 @@ namespace MWRender
overrideFirstRootTexture(texture, mResourceSystem, *node);
}
void Animation::removeEffect(int effectId)
void Animation::removeEffect(std::string_view effectId)
{
RemoveCallbackVisitor visitor(effectId);
mInsert->accept(visitor);
@ -1588,17 +1631,19 @@ namespace MWRender
void Animation::removeEffects()
{
removeEffect(-1);
removeEffect("");
}
void Animation::getLoopingEffects(std::vector<int>& out) const
std::vector<std::string_view> Animation::getLoopingEffects() const
{
if (!mHasMagicEffects)
return;
return {};
FindVfxCallbacksVisitor visitor;
mInsert->accept(visitor);
std::vector<std::string_view> out;
for (std::vector<UpdateVfxCallback*>::iterator it = visitor.mCallbacks.begin(); it != visitor.mCallbacks.end();
++it)
{
@ -1607,6 +1652,7 @@ namespace MWRender
if (callback->mParams.mLoop && !callback->mFinished)
out.push_back(callback->mParams.mEffectId);
}
return out;
}
void Animation::updateEffects()

@ -1,6 +1,10 @@
#ifndef GAME_RENDER_ANIMATION_H
#define GAME_RENDER_ANIMATION_H
#include "animationpriority.hpp"
#include "blendmask.hpp"
#include "bonegroup.hpp"
#include "../mwworld/movementdirection.hpp"
#include "../mwworld/ptr.hpp"
@ -84,7 +88,7 @@ namespace MWRender
std::string mModelName; // Just here so we don't add the same effect twice
std::shared_ptr<EffectAnimationTime> mAnimTime;
float mMaxControllerLength;
int mEffectId;
std::string mEffectId;
bool mLoop;
std::string mBoneName;
};
@ -92,60 +96,9 @@ namespace MWRender
class Animation : public osg::Referenced
{
public:
enum BoneGroup
{
BoneGroup_LowerBody = 0,
BoneGroup_Torso,
BoneGroup_LeftArm,
BoneGroup_RightArm
};
enum BlendMask
{
BlendMask_LowerBody = 1 << 0,
BlendMask_Torso = 1 << 1,
BlendMask_LeftArm = 1 << 2,
BlendMask_RightArm = 1 << 3,
BlendMask_UpperBody = BlendMask_Torso | BlendMask_LeftArm | BlendMask_RightArm,
BlendMask_All = BlendMask_LowerBody | BlendMask_UpperBody
};
/* This is the number of *discrete* blend masks. */
static constexpr size_t sNumBlendMasks = 4;
/// Holds an animation priority value for each BoneGroup.
struct AnimPriority
{
/// Convenience constructor, initialises all priorities to the same value.
AnimPriority(int priority)
{
for (unsigned int i = 0; i < sNumBlendMasks; ++i)
mPriority[i] = priority;
}
bool operator==(const AnimPriority& other) const
{
for (unsigned int i = 0; i < sNumBlendMasks; ++i)
if (other.mPriority[i] != mPriority[i])
return false;
return true;
}
int& operator[](BoneGroup n) { return mPriority[n]; }
const int& operator[](BoneGroup n) const { return mPriority[n]; }
bool contains(int priority) const
{
for (unsigned int i = 0; i < sNumBlendMasks; ++i)
if (priority == mPriority[i])
return true;
return false;
}
int mPriority[sNumBlendMasks];
};
using BlendMask = MWRender::BlendMask;
using BoneGroup = MWRender::BoneGroup;
using AnimPriority = MWRender::AnimPriority;
class TextKeyListener
{
@ -384,11 +337,11 @@ namespace MWRender
* @param texture override the texture specified in the model's materials - if empty, do not override
* @note Will not add an effect twice.
*/
void addEffect(const std::string& model, int effectId, bool loop = false, std::string_view bonename = {},
std::string_view texture = {});
void removeEffect(int effectId);
void addEffect(std::string_view model, std::string_view effectId, bool loop = false,
std::string_view bonename = {}, std::string_view texture = {});
void removeEffect(std::string_view effectId);
void removeEffects();
void getLoopingEffects(std::vector<int>& out) const;
std::vector<std::string_view> getLoopingEffects() const;
// Add a spell casting glow to an object. From measuring video taken from the original engine,
// the glow seems to be about 1.5 seconds except for telekinesis, which is 1 second.
@ -398,6 +351,8 @@ namespace MWRender
bool hasAnimation(std::string_view anim) const;
bool isLoopingAnimation(std::string_view group) const;
// Specifies the axis' to accumulate on. Non-accumulated axis will just
// move visually, but not affect the actual movement. Each x/y/z value
// should be on the scale of 0 to 1.
@ -446,6 +401,9 @@ namespace MWRender
bool getInfo(std::string_view groupname, float* complete = nullptr, float* speedmult = nullptr,
size_t* loopcount = nullptr) const;
/// Returns the group name of the animation currently active on that bone group.
std::string_view getActiveGroup(BoneGroup boneGroup) const;
/// Get the absolute position in the animation track of the first text key with the given group.
float getStartTime(const std::string& groupname) const;
@ -454,7 +412,7 @@ namespace MWRender
/// Get the current absolute position in the animation track for the animation that is currently playing from
/// the given group.
float getCurrentTime(const std::string& groupname) const;
float getCurrentTime(std::string_view groupname) const;
/** Disables the specified animation group;
* \param groupname Animation group to disable.

@ -0,0 +1,42 @@
#ifndef GAME_RENDER_ANIMATIONPRIORITY_H
#define GAME_RENDER_ANIMATIONPRIORITY_H
#include "blendmask.hpp"
#include "bonegroup.hpp"
namespace MWRender
{
/// Holds an animation priority value for each BoneGroup.
struct AnimPriority
{
/// Convenience constructor, initialises all priorities to the same value.
AnimPriority(int priority)
{
for (unsigned int i = 0; i < sNumBlendMasks; ++i)
mPriority[i] = priority;
}
bool operator==(const AnimPriority& other) const
{
for (unsigned int i = 0; i < sNumBlendMasks; ++i)
if (other.mPriority[i] != mPriority[i])
return false;
return true;
}
int& operator[](BoneGroup n) { return mPriority[n]; }
const int& operator[](BoneGroup n) const { return mPriority[n]; }
bool contains(int priority) const
{
for (unsigned int i = 0; i < sNumBlendMasks; ++i)
if (priority == mPriority[i])
return true;
return false;
}
int mPriority[sNumBlendMasks];
};
}
#endif

@ -0,0 +1,22 @@
#ifndef GAME_RENDER_BLENDMASK_H
#define GAME_RENDER_BLENDMASK_H
#include <cstddef>
namespace MWRender
{
enum BlendMask
{
BlendMask_LowerBody = 1 << 0,
BlendMask_Torso = 1 << 1,
BlendMask_LeftArm = 1 << 2,
BlendMask_RightArm = 1 << 3,
BlendMask_UpperBody = BlendMask_Torso | BlendMask_LeftArm | BlendMask_RightArm,
BlendMask_All = BlendMask_LowerBody | BlendMask_UpperBody
};
/* This is the number of *discrete* blend masks. */
static constexpr size_t sNumBlendMasks = 4;
}
#endif

@ -0,0 +1,16 @@
#ifndef GAME_RENDER_BONEGROUP_H
#define GAME_RENDER_BONEGROUP_H
namespace MWRender
{
enum BoneGroup
{
BoneGroup_LowerBody = 0,
BoneGroup_Torso,
BoneGroup_LeftArm,
BoneGroup_RightArm,
Num_BoneGroups
};
}
#endif

@ -436,7 +436,7 @@ namespace MWRender
// We still should use one-handed animation as fallback
if (mAnimation->hasAnimation(inventoryGroup))
groupname = inventoryGroup;
groupname = std::move(inventoryGroup);
else
{
static const std::string oneHandFallback
@ -456,15 +456,15 @@ namespace MWRender
mAnimation->showCarriedLeft(showCarriedLeft);
mCurrentAnimGroup = groupname;
mAnimation->play(mCurrentAnimGroup, 1, Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, 0);
mCurrentAnimGroup = std::move(groupname);
mAnimation->play(mCurrentAnimGroup, 1, BlendMask::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, 0);
MWWorld::ConstContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
if (torch != inv.end() && torch->getType() == ESM::Light::sRecordId && showCarriedLeft)
{
if (!mAnimation->getInfo("torch"))
mAnimation->play(
"torch", 2, Animation::BlendMask_LeftArm, false, 1.0f, "start", "stop", 0.0f, ~0ul, true);
"torch", 2, BlendMask::BlendMask_LeftArm, false, 1.0f, "start", "stop", 0.0f, ~0ul, true);
}
else if (mAnimation->getInfo("torch"))
mAnimation->disable("torch");
@ -591,7 +591,7 @@ namespace MWRender
void RaceSelectionPreview::onSetup()
{
CharacterPreview::onSetup();
mAnimation->play("idle", 1, Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, 0);
mAnimation->play("idle", 1, BlendMask::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, 0);
mAnimation->runAnimation(0.f);
// attach camera to follow the head node

@ -170,7 +170,7 @@ namespace MWRender
else
source = mAnimationTimePtr[0];
SceneUtil::AssignControllerSourcesVisitor assignVisitor(source);
SceneUtil::AssignControllerSourcesVisitor assignVisitor(std::move(source));
attached->accept(assignVisitor);
}
catch (std::exception& e)

@ -363,7 +363,7 @@ namespace MWRender
imageDest.mImage = image;
imageDest.mX = x;
imageDest.mY = y;
mPendingImageDest[camera] = imageDest;
mPendingImageDest[camera] = std::move(imageDest);
}
// Create a quad rendering the updated texture
@ -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));
}

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

@ -853,7 +853,7 @@ namespace MWRender
src = mWeaponAnimationTime;
else
src = mAnimationTimePtr[0];
SceneUtil::AssignControllerSourcesVisitor assignVisitor(src);
SceneUtil::AssignControllerSourcesVisitor assignVisitor(std::move(src));
node->accept(assignVisitor);
}
}

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

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

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

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

Loading…
Cancel
Save